diff --git a/.env.example b/.env.example index 768bba0..027016a 100644 --- a/.env.example +++ b/.env.example @@ -3,8 +3,8 @@ APP_ENV=local APP_KEY= APP_DEBUG=true APP_TIMEZONE=Asia/Taipei -APP_URL=https://shop_12_wireui.test -L5_SWAGGER_CONST_HOST=https://shop_12_wireui.test/ +APP_URL=https://KTVCentral.test +L5_SWAGGER_CONST_HOST=https://KTVCentral.test/ APP_LOCALE=zh-tw APP_FALLBACK_LOCALE=zh-tw @@ -22,12 +22,13 @@ LOG_STACK=single LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=debug -DB_CONNECTION=sqlite -# DB_HOST=127.0.0.1 -# DB_PORT=3306 -# DB_DATABASE=laravel -# DB_USERNAME=root -# DB_PASSWORD= +#DB_CONNECTION=sqlite +DB_CONNECTION=mariadb +DB_HOST=127.0.0.1 +DB_PORT=3307 +DB_DATABASE=Karaoke-Kingpin_Central +DB_USERNAME=Karaoke-Kingpin +DB_PASSWORD=ESM7yTPMnavFmbBH SESSION_DRIVER=database SESSION_LIFETIME=120 diff --git a/app/Console/Commands/ClearMachineStatuses.php b/app/Console/Commands/ClearMachineStatuses.php new file mode 100644 index 0000000..6645547 --- /dev/null +++ b/app/Console/Commands/ClearMachineStatuses.php @@ -0,0 +1,46 @@ +format('l'); // e.g. "Monday" + $targetTable = "machine_statuses_" . $day; + + DB::statement("CREATE TABLE IF NOT EXISTS _machine_statuses LIKE machine_statuses"); + + // 先刪除舊表(如存在) + DB::statement("DROP TABLE IF EXISTS {$targetTable}"); + + // 改名備份 + DB::statement("RENAME TABLE machine_statuses TO {$targetTable}"); + + // 空表回命名 + DB::statement("RENAME TABLE _machine_statuses TO machine_statuses"); + + $this->info("Machine statuses backed up to {$targetTable} and table cleared."); + } +} diff --git a/app/Console/Commands/TransferSqliteToMysql.php b/app/Console/Commands/TransferSqliteToMysql.php new file mode 100644 index 0000000..da4935f --- /dev/null +++ b/app/Console/Commands/TransferSqliteToMysql.php @@ -0,0 +1,50 @@ +argument('path'), '/'); + $fullPath = Storage::disk('local')->path($path); + + $this->info("[Transfer] 開始轉移 SQLite 資料:{$fullPath}"); + + if (!file_exists($fullPath)) { + $this->error("[Transfer] 找不到 SQLite 檔案:{$fullPath}"); + return 1; + } + + try { + if ($this->option('sync')) { + $this->warn('[Transfer] 使用同步模式執行...'); + (new TransferSqliteTableJob($fullPath))->handle(); + $this->info('[Transfer] 匯出完成(同步)'); + } else { + TransferSqliteTableJob::dispatch($fullPath); + $this->info('[Transfer] 匯出任務已派送至 queue'); + } + + $duration = now()->diffInSeconds($start); + $this->info("[Transfer] 執行完成,用時 {$duration} 秒"); + + } catch (\Throwable $e) { + $this->error('[Transfer] 發生錯誤:' . $e->getMessage()); + return 1; + } + + return 0; + } +} \ No newline at end of file diff --git a/app/Enums/RoomStatus.php b/app/Enums/RoomStatus.php new file mode 100644 index 0000000..72ae246 --- /dev/null +++ b/app/Enums/RoomStatus.php @@ -0,0 +1,33 @@ + __('enums.room.status.Active'), + self::Closed => __('enums.room.status.Closed'), + self::Fire => __('enums.room.status.Fire'), + self::Error => __('enums.room.status.Error'), + }; + } +} diff --git a/app/Enums/RoomType.php b/app/Enums/RoomType.php new file mode 100644 index 0000000..e311fea --- /dev/null +++ b/app/Enums/RoomType.php @@ -0,0 +1,31 @@ + __('enums.room.status.Unset'), + self::PC => "PC", + self::SVR => "SVR", + }; + } +} diff --git a/app/Enums/UserGender.php b/app/Enums/UserGender.php index 07e5058..fc3a238 100644 --- a/app/Enums/UserGender.php +++ b/app/Enums/UserGender.php @@ -4,6 +4,14 @@ namespace App\Enums; use App\Enums\Traits\HasLabels; +/** + * @OA\Schema( + * schema="UserGender", + * type="string", + * enum={"male", "female", "other", "unset"}, + * example="male" + * ) + */ enum UserGender: string { use HasLabels; diff --git a/app/Enums/UserStatus.php b/app/Enums/UserStatus.php index 36bea9f..9f2b8d8 100644 --- a/app/Enums/UserStatus.php +++ b/app/Enums/UserStatus.php @@ -4,6 +4,15 @@ namespace App\Enums; use App\Enums\Traits\HasLabels; +/** + * @OA\Schema( + * schema="UserStatus", + * type="string", + * enum={"0", "1", "2"}, + * description="User status: 0=Active, 1=Suspended, 2=Deleting", + * example="0" + * ) + */ enum UserStatus: int { use HasLabels; diff --git a/app/Http/Controllers/RoomControlController.php b/app/Http/Controllers/RoomControlController.php new file mode 100644 index 0000000..efdd6fa --- /dev/null +++ b/app/Http/Controllers/RoomControlController.php @@ -0,0 +1,305 @@ +only(['branch', 'room_name', 'room_ip', 'email']); // 不記錄密碼 + Log::info('Token Request Payload:', $data); + + // 1. 驗證帳密(登入用) + $credentials = $request->only('email', 'password'); + + if (!Auth::attempt($credentials)) { + return ApiResponse::unauthorized(); + } + + // 2. 取得登入使用者 + $user = Auth::user(); + + // 3. 產生或取得 Token + if (empty($user->api_plain_token)) { + $token = $user->createToken('pc-heartbeat')->plainTextToken; + $user->api_plain_token = $token; + $user->save(); + } else { + $token = $user->api_plain_token; + } + + // 4. 驗證其他註冊欄位 + $validated = $request->validated(); // branch_id, room_name, room_ip + + // 5. 找出對應包廂 + $roomType = null; + $roomName = null; + // 從 room_name(例如 PC101, SVR01)中擷取 type 與 name + if (preg_match('/^([A-Za-z]+)(\d+)$/', $validated['room_name'], $matches)) { + $roomType = strtolower($matches[1]); // 'PC' → 'pc' + $roomName = $matches[2]; // '101' + } + $branch=Branch::where('name',$validated['branch_name'])->first(); + $room = Room::where('branch_id', $branch->id) + ->where('name', $roomName) + ->where('type', $roomType) + ->first(); + + if (!$room) { + return ApiResponse::error('找不到對應包廂'); + } + + // 6. 更新包廂資訊 + $room->internal_ip = $validated['room_ip']; + $room->port = 1000; // 預設值 + $room->is_online =1; + $room->status = RoomStatus::Closed; + $room->touch(); // 更新 updated_at + $room->save(); + + // 7. 回傳 token 與包廂資料 + return ApiResponse::success([ + 'token' => $token, + 'room' => $room, + ]); + } + + /** + * @OA\Post( + * path="/api/room/heartbeat", + * summary="包廂心跳封包指令", + * description="記錄設備連線狀況", + * operationId="heartbeatRoomCommand", + * tags={"Room Control"}, + * security={{"Authorization":{}}}, + * @OA\RequestBody( + * required=true, + * @OA\JsonContent(ref="#/components/schemas/ReceiveRoomStatusDataRequest") + * ), + * @OA\Response( + * response=200, + * description="成功傳送指令並回傳 TCP 回應", + * @OA\JsonContent( + * allOf={ + * @OA\Schema(ref="#/components/schemas/ApiResponse"), + * @OA\Schema( + * @OA\Property(property="data", ref="#/components/schemas/MachineStatus") + * ) + * } + * ) + * ), + * @OA\Response( + * response=401, + * description="Unauthorized", + * @OA\JsonContent( + * allOf={ + * @OA\Schema(ref="#/components/schemas/ApiResponse"), + * @OA\Schema( + * @OA\Property(property="code", type="string", example="UNAUTHORIZED"), + * @OA\Property(property="message", type="string", example="Unauthorized"), + * @OA\Property(property="data", type="null") + * ) + * } + * ) + * ), + * @OA\Parameter( + * name="Accept", + * in="header", + * required=true, + * @OA\Schema(type="string", default="application/json") + * ) + * ) + */ + public function StatusReport(ReceiveRoomStatusDataRequest $request) + { + $validated = $request->validated(); + $roomType = null; + $roomName = null; + // 從 room_name(例如 PC101, SVR01)中擷取 type 與 name + if (preg_match('/^([A-Za-z]+)(\d+)$/', $validated['hostname'], $matches)) { + $roomType = strtolower($matches[1]); // 'PC' → 'pc' + $roomName = $matches[2]; // '101' + } + $branch=Branch::where('name',$validated['branch_name'])->first(); + $room = Room::where('branch_id', $branch->id) + ->where('name', $roomName) + ->where('type', $roomType) + ->first(); + // 決定 status 欄位值 + $validated['status']= 'error'; + if($room){ + $validated['status']= 'online'; + if($room->internal_ip != $validated['ip']){ + $room->internal_ip = $validated['ip']; + $validated['status']='error'; + } + $room->is_online=1; + $room->touch(); // 更新 updated_at + $room->save(); + } + + return ApiResponse::success([ + 'data' => MachineStatus::create($validated), + ]); + } + /** + * @OA\Post( + * path="/api/room/sendSwitch", + * summary="送出包廂控制指令", + * description="依據傳入的 room_id 與 command,透過 TCP 傳送對應指令給包廂電腦。", + * operationId="sendRoomSwitchCommand", + * tags={"Room Control"}, + * security={{"Authorization":{}}}, + * @OA\RequestBody( + * required=true, + * @OA\JsonContent(ref="#/components/schemas/SendRoomSwitchCommandRequest") + * ), + * @OA\Response( + * response=200, + * description="成功傳送指令並回傳 TCP 回應", + * @OA\JsonContent( + * allOf={ + * @OA\Schema(ref="#/components/schemas/ApiResponse"), + * @OA\Schema( + * @OA\Property(property="data", ref="#/components/schemas/Room") + * ) + * } + * ) + * ), + * @OA\Response( + * response=401, + * description="Unauthorized", + * @OA\JsonContent( + * allOf={ + * @OA\Schema(ref="#/components/schemas/ApiResponse"), + * @OA\Schema( + * @OA\Property(property="code", type="string", example="UNAUTHORIZED"), + * @OA\Property(property="message", type="string", example="Unauthorized"), + * @OA\Property(property="data", type="null") + * ) + * } + * ) + * ), + * @OA\Parameter( + * name="Accept", + * in="header", + * required=true, + * @OA\Schema(type="string", default="application/json") + * ) + * ) + */ + public function sendSwitch(SendRoomSwitchCommandRequest $request): JsonResponse + { + $validated = $request->validated(); + + $room = Room::where([ + ['branch_id', $validated['branch_id']], + ['name', $validated['room_name']], + ])->first(); + + if (!$room) { + return ApiResponse::error('房間不存在'); + } + + // 檢查必要欄位是否缺失或狀態為錯誤 + if (empty($room->internal_ip) || empty($room->port)) { + return ApiResponse::error('房間未設定 IP 或 Port'); + } + + if ($room->status === RoomStatus::Error) { + return ApiResponse::error('房間目前處於錯誤狀態,無法操作'); + } + + $suffix = substr($room->name, -3) ?: $room->name; + $signal = match ($validated['command']) { + 'active' => 'O', + 'closed' => 'X', + 'fire' => 'F', + default => 'X', // fallback 保險起見 + }; + $data = $suffix . "," . $signal; + + //dd($data); + $client = new TcpSocketClient($room->internal_ip, $room->port); + try { + $response = $client->send($data); + + $room->status=$validated['command']; + $room->started_at=$validated['started_at']; + $room->ended_at=$validated['ended_at']; + $room->save(); + + return ApiResponse::success($room); + } catch (\Throwable $e) { + $room->status=RoomStatus::Error; + $room->started_at=null; + $room->ended_at=null; + $room->save(); + return ApiResponse::error($e->getMessage()); + } + } +} diff --git a/app/Http/Controllers/SqliteUploadController.php b/app/Http/Controllers/SqliteUploadController.php new file mode 100644 index 0000000..4dbb3df --- /dev/null +++ b/app/Http/Controllers/SqliteUploadController.php @@ -0,0 +1,75 @@ +validate([ + 'file' => 'required|file', + ]); + + if ($request->file('file')->getClientOriginalExtension() !== 'sqlite') { + return response()->json(['message' => '只允許上傳 .sqlite 檔案'], 422); + } + + $filename = $request->file('file')->getClientOriginalName(); + $path = $request->file('file')->storeAs('sqlite', $filename, 'local'); + TransferSqliteTableJob::dispatch(Storage::disk('local')->path($path)); + return response()->json([ + 'message' => '上傳成功,已派送資料處理任務', + 'path' => $path, + ]); + } +} \ No newline at end of file diff --git a/app/Http/Requests/ApiRequest.php b/app/Http/Requests/ApiRequest.php new file mode 100644 index 0000000..a05875b --- /dev/null +++ b/app/Http/Requests/ApiRequest.php @@ -0,0 +1,31 @@ +|string> + */ + public function rules(): array + { + return [ + 'branch_name' =>'required|string|exists:branches,name', + 'room_name' => 'required|string', + 'room_ip' => 'required|string', + 'email' => 'required|email', + 'password' => 'required', + ]; + } +} diff --git a/app/Http/Requests/ReceiveRoomStatusDataRequest.php b/app/Http/Requests/ReceiveRoomStatusDataRequest.php new file mode 100644 index 0000000..ae90d82 --- /dev/null +++ b/app/Http/Requests/ReceiveRoomStatusDataRequest.php @@ -0,0 +1,37 @@ +|string> + */ + public function rules(): array + { + return [ + 'branch_name' =>'required|string|exists:branches,name', + 'hostname' => 'required|string', + 'ip' => 'required|string', + 'cpu' => 'nullable|numeric', + 'memory' => 'nullable|numeric', + 'disk' => 'nullable|numeric', + ]; + } +} diff --git a/app/Http/Requests/SendRoomSwitchCommandRequest.php b/app/Http/Requests/SendRoomSwitchCommandRequest.php new file mode 100644 index 0000000..ba5a533 --- /dev/null +++ b/app/Http/Requests/SendRoomSwitchCommandRequest.php @@ -0,0 +1,35 @@ +|string> + */ + public function rules(): array + { + return [ + 'branch_id' => 'required|integer', + 'room_name' => 'required|string', + 'command' => 'required|string', + 'started_at' => 'nullable|date_format:Y-m-d H:i:s', + 'ended_at' => 'nullable|date_format:Y-m-d H:i:s', + ]; + } +} diff --git a/app/Http/Requests/Traits/FailedValidationJsonResponse.php b/app/Http/Requests/Traits/FailedValidationJsonResponse.php new file mode 100644 index 0000000..bd6c0ac --- /dev/null +++ b/app/Http/Requests/Traits/FailedValidationJsonResponse.php @@ -0,0 +1,20 @@ +json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + 'code' => 'ERROR', + 'token' => '' + ], Response::HTTP_UNPROCESSABLE_ENTITY)); + } +} \ No newline at end of file diff --git a/app/Http/Responses/ApiResponse.php b/app/Http/Responses/ApiResponse.php new file mode 100644 index 0000000..0b06727 --- /dev/null +++ b/app/Http/Responses/ApiResponse.php @@ -0,0 +1,39 @@ +json(['code' => $code,'message' => $message,'data' => $data,], $status); + } +} \ No newline at end of file diff --git a/app/Imports/DataImport.php b/app/Imports/DataImport.php deleted file mode 100644 index 369dcb3..0000000 --- a/app/Imports/DataImport.php +++ /dev/null @@ -1,45 +0,0 @@ -modelName= $modelName; - } - public function collection(Collection $rows) - { - - Log::warning('匯入啟動', [ - 'model' => $this->modelName, - 'rows_id' =>++$this->con, - 'rows_count' => $rows->count() - ]); - if($this->modelName=='User'){ - ImportUserChunkJob::dispatch($rows,$this->con); - }else{ - Log::warning('未知的 modelName', ['model' => $this->modelName]); - } - } - public function chunkSize(): int - { - return 1000; - } - - public function headingRow(): int - { - return 1; - } -} \ No newline at end of file diff --git a/app/Jobs/ImportJob.php b/app/Jobs/ImportJob.php deleted file mode 100644 index 8264354..0000000 --- a/app/Jobs/ImportJob.php +++ /dev/null @@ -1,59 +0,0 @@ -filePath = $filePath; - $this->modelName= $modelName; - } - - /** - * Execute the job. - */ - public function handle(): void - { - ini_set('memory_limit', '512M'); // ✅ 增加記憶體限制 - Log::info('[ImportJob] 開始處理檔案:' . $this->filePath); - - try { - if (!Storage::exists($this->filePath)) { - Log::warning('[ImportJob] 檔案不存在:' . $this->filePath); - return; - } - - Excel::import(new DataImport($this->modelName), $this->filePath); - Log::info('[ImportJob] 已提交所有 chunk 匯入任務。'); - - Storage::delete($this->filePath); - Log::info('[ImportJob] 已刪除檔案:' . $this->filePath); - } catch (\Throwable $e) { - Log::error("[ImportJob] 匯入失敗:{$e->getMessage()}", [ - 'file' => $this->filePath, - 'trace' => $e->getTraceAsString(), - ]); - } - } -} \ No newline at end of file diff --git a/app/Jobs/ImportUserChunkJob.php b/app/Jobs/ImportUserChunkJob.php deleted file mode 100644 index d96461f..0000000 --- a/app/Jobs/ImportUserChunkJob.php +++ /dev/null @@ -1,67 +0,0 @@ -rows = $rows; - $this->id = $id; - } - - public function handle(): void - { - Log::warning('匯入啟動', [ - 'model' => "ImportUserChunkJob", - 'rows_id' =>$this->id, - ]); - $now = now(); - foreach ($this->rows as $index => $row) { - try { - $name = $this->normalizeName($row['歌手姓名'] ?? ''); - - if (empty($name) || User::where('name', $name)->exists()) { - continue; - } - // 準備 song 資料 - $toInsert[] = [ - 'name' => $name, - 'category' => ArtistCategory::tryFrom(trim($row['歌手分類'] ?? '未定義')) ?? ArtistCategory::Unset, - 'simplified' => $simplified, - 'phonetic_abbr' => $phoneticAbbr, - 'pinyin_abbr' => $pinyinAbbr, - 'strokes_abbr' => $strokesAbbr, - 'enable' =>trim($row['狀態'] ?? 1), - 'created_at' => $now, - 'updated_at' => $now, - ]; - } catch (\Throwable $e) { - \Log::error("Row {$index} failed: {$e->getMessage()}", [ - 'row' => $row, - 'trace' => $e->getTraceAsString() - ]); - } - } - User::insert($toInsert); - } - - public function normalizeName(?string $str): string - { - return strtoupper(mb_convert_kana(trim($str ?? ''), 'as')); - } -} \ No newline at end of file diff --git a/app/Jobs/TransferSqliteTableJob.php b/app/Jobs/TransferSqliteTableJob.php new file mode 100644 index 0000000..dbf304d --- /dev/null +++ b/app/Jobs/TransferSqliteTableJob.php @@ -0,0 +1,101 @@ +sqlitePath=$sqlitePath; + } + + public function handle(): void + { + if (!file_exists($this->sqlitePath)) { + logger()->error("❌ SQLite file not found: {$this->sqlitePath}"); + return; + } + // ✅ 動態產生唯一 connection 名稱 + $connectionName = 'tempsqlite_' . md5($this->sqlitePath . microtime()); + config(["database.connections.{$connectionName}" => [ + 'driver' => 'sqlite', + 'database' => $this->sqlitePath, + 'prefix' => '', + ]]); + + $mysqlConnection = config('database.default'); + + $sqliteTables = DB::connection($connectionName)->select(" + SELECT name FROM sqlite_master + WHERE type='table' AND name NOT LIKE 'sqlite_%'; + "); + + if (empty($sqliteTables)) { + logger()->error("❌ No tables found in SQLite database."); + return; + } + + foreach ($sqliteTables as $tableObj) { + $table = $tableObj->name; + + if ($table === 'migrations') { + continue; + } + + try { + DB::connection($mysqlConnection)->statement('SET FOREIGN_KEY_CHECKS=0;'); + + DB::statement("CREATE TABLE IF NOT EXISTS _{$table} LIKE {$table}"); + + $rows = DB::connection($connectionName)->table($table)->cursor(); + + $buffer = []; + $count = 0; + + foreach ($rows as $row) { + $buffer[] = (array) $row; + $count++; + + if ($count % 500 === 0) { + DB::connection($mysqlConnection)->table("_" . $table)->insert($buffer); + $buffer = []; + } + } + + if (!empty($buffer)) { + DB::connection($mysqlConnection)->table("_" . $table)->insert($buffer); + } + + DB::statement("RENAME TABLE {$table} TO {$table}_"); + DB::statement("RENAME TABLE _{$table} TO {$table}"); + DB::statement("DROP TABLE IF EXISTS {$table}_"); + + logger()->info("✅ Done: {$table} ({$count} records)"); + } catch (\Exception $e) { + logger()->error("❌ Failed to transfer {$table}: " . $e->getMessage()); + } finally { + DB::connection($mysqlConnection)->statement('SET FOREIGN_KEY_CHECKS=1;'); + } + } + + // 🔥 結束後刪檔與釋放 connection + DB::purge($connectionName); + if (file_exists($this->sqlitePath)) { + sleep(1); + unlink($this->sqlitePath); + logger()->info("🧹 Temp SQLite file deleted: {$this->sqlitePath}"); + } + + logger()->info("🎉 All tables transferred."); + } +} \ No newline at end of file diff --git a/app/Livewire/Admin/BranchTable.php b/app/Livewire/Admin/BranchTable.php new file mode 100644 index 0000000..c60ee8d --- /dev/null +++ b/app/Livewire/Admin/BranchTable.php @@ -0,0 +1,89 @@ + 'outside']); + } + + public function setUp(): array + { + $actions = []; + $header = PowerGrid::header() + ->withoutLoading() + ->showToggleColumns(); + $header->includeViewOnTop('livewire.admin.branch-header') ; + + $actions[]=$header; + $actions[]=PowerGrid::footer()->showPerPage()->showRecordCount(); + return $actions; + } + + public function datasource(): Builder + { + return Branch::query(); + } + + public function relationSearch(): array + { + return []; + } + + public function fields(): PowerGridFields + { + return PowerGrid::fields() + ->add('id') + ->add('name') + ->add('external_ip') + ->add('enable') + ->add('created_at_formatted', fn (Branch $model) => Carbon::parse($model->created_at)->format('d/m/Y H:i:s')); + } + + public function columns(): array + { + $column=[]; + $column[]=Column::make(__('branches.no'), 'id'); + $column[]=Column::make(__('branches.name'), 'name')->sortable()->searchable(); + $column[]=Column::make(__('branches.external_ip'), 'external_ip')->sortable()->searchable(); + $column[]=Column::make(__('branches.enable'), 'enable'); + $column[]=Column::make('Created at', 'created_at_formatted', 'created_at')->sortable()->hidden(true, false); + return $column; + } + + public function filters(): array + { + return [ + Filter::inputText('name')->placeholder(__('branches.name')), + Filter::inputText('external_ip')->placeholder(__('branches.external_ip')), + Filter::boolean('enable')->label('✅', '❌'), + Filter::datetimepicker('created_at'), + ]; + } +} diff --git a/app/Livewire/Admin/RoleForm.php b/app/Livewire/Admin/RoleForm.php deleted file mode 100644 index d954564..0000000 --- a/app/Livewire/Admin/RoleForm.php +++ /dev/null @@ -1,114 +0,0 @@ -permissions = Permission::all(); - $this->canCreate = Auth::user()?->can('role-edit') ?? false; - $this->canEdit = Auth::user()?->can('role-edit') ?? false; - $this->canDelect = Auth::user()?->can('role-delete') ?? false; - } - - public function openCreateRoleModal() - { - $this->resetFields(); - $this->showCreateModal = true; - } - - public function openEditRoleModal($id) - { - $role = Role::findOrFail($id); - $this->roleId = $role->id; - $this->name = $role->name; - $this->selectedPermissions = $role->permissions()->pluck('id')->toArray(); - $this->showCreateModal = true; - } - - public function save() - { - $this->validate([ - 'name' => 'required|string|max:255', - 'selectedPermissions' => 'array', - ]); - - if ($this->roleId) { - if ($this->canEdit) { - $role = Role::findOrFail($this->roleId); - $role->update(['name' => $this->name]); - $role->syncPermissions($this->selectedPermissions); - $this->notification()->send([ - 'icon' => 'success', - 'title' => '成功', - 'description' => '角色已更新', - ]); - } - } else { - if ($this->canCreate) { - $role = Role::create(['name' => $this->name]); - $role->syncPermissions($this->selectedPermissions); - $this->notification()->send([ - 'icon' => 'success', - 'title' => '成功', - 'description' => '角色已新增', - ]); - } - } - - $this->resetFields(); - $this->showCreateModal = false; - $this->dispatch('pg:eventRefresh-role-table'); - } - - public function deleteRole($id) - { - if ($this->canDelect) { - Role::findOrFail($id)->delete(); - $this->notification()->send([ - 'icon' => 'success', - 'title' => '成功', - 'description' => '角色已刪除', - ]); - $this->dispatch('pg:eventRefresh-role-table'); - } - } - - public function resetFields() - { - $this->name = ''; - $this->selectedPermissions = []; - $this->roleId = null; - } - - public function render() - { - return view('livewire.admin.role-form'); - } -} diff --git a/app/Livewire/Admin/RoleTable.php b/app/Livewire/Admin/RoleTable.php deleted file mode 100644 index b575225..0000000 --- a/app/Livewire/Admin/RoleTable.php +++ /dev/null @@ -1,181 +0,0 @@ - 'outside']); - //權限設定 - $this->canCreate = Auth::user()?->can('role-edit') ?? false; - $this->canEdit = Auth::user()?->can('role-edit') ?? false; - $this->canDelect = Auth::user()?->can('role-delete') ?? false; - } - - public function setUp(): array - { - if($this->canDelect){ - $this->showCheckBox(); - } - $actions = []; - $header =PowerGrid::header(); - if($this->canCreate){ - $header->includeViewOnTop('livewire.admin.role-header'); - } - $actions[]=$header; - $actions[]=PowerGrid::footer() - ->showPerPage() - ->showRecordCount(); - return $actions; - } - public function header(): array - { - $actions = []; - if ($this->canDelect) { - $actions[]=Button::add('bulk-delete') - ->slot('Bulk delete ()') - ->icon('solid-trash',['id' => 'my-custom-icon-id', 'class' => 'font-bold']) - ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') - ->dispatch('bulkDelete.' . $this->tableName, []); - } - return $actions; - } - - public function datasource(): Builder - { - //dd(Role::with('permissions')); - return Role::with('permissions'); - } - - public function relationSearch(): array - { - return []; - } - - public function fields(): PowerGridFields - { - $allPermissions = Permission::pluck('name')->sort()->values(); - return PowerGrid::fields() - ->add('id') - ->add('name') - ->add('permissions_list', function (Role $model) use ($allPermissions) { - $rolePermissions = $model->permissions->pluck('name')->sort()->values(); - - if ($rolePermissions->count() === $allPermissions->count() && $rolePermissions->values()->all() === $allPermissions->values()->all()) { - return 'all'; - } - - return $rolePermissions->implode(', '); - }) - ->add('created_at_formatted', fn (Role $model) => Carbon::parse($model->created_at)->format('Y-m-d H:i:s')); - } - - public function columns(): array - { - $column=[]; - $column[]=Column::make(__('roles.no'), 'id')->sortable()->searchable(); - $column[]=Column::make(__('roles.name'), 'name')->sortable()->searchable() - ->editOnClick( - hasPermission: $this->canEdit, - dataField: 'name', - fallback: 'N/A', - saveOnMouseOut: true - ); - $column[]=Column::make(__('roles.permissions'), 'permissions_list'); - $column[]=Column::make('Created at', 'created_at_formatted', 'created_at')->sortable(); - $column[]=Column::action('Action'); - return $column; - } - #[On('bulkDelete.{tableName}')] - public function bulkDelete(): void - { - if ($this->canDelect) { - $this->js('alert(window.pgBulkActions.get(\'' . $this->tableName . '\'))'); - if($this->checkboxValues){ - Role::destroy($this->checkboxValues); - $this->js('window.pgBulkActions.clearAll()'); // clear the count on the interface. - } - } - } - #[On('onUpdatedEditable')] - public function onUpdatedEditable($id, $field, $value): void - { - if ($field === 'name' && $this->canEdit) { - $this->noUpdated($id,$field,$value); - } - } - private function noUpdated($id,$field,$value){ - $role = Role::find($id); - if ($role) { - $role->{$field} = $value; - $role->save(); // 明確觸發 saving - } - $this->notification()->send([ - 'icon' => 'success', - 'title' => $id.'.'.__('roles.'.$field).':'.$value, - 'description' => '已經寫入', - ]); - } - public function filters(): array - { - return [ - Filter::inputText('name')->placeholder(__('roles.name')), - Filter::datetimepicker('created_at'), - ]; - } - - - public function actions(Role $row): array - { - $actions = []; - if ($this->canEdit) { - $actions[] =Button::add('edit') - ->slot(__('roles.edit')) - ->icon('solid-pencil-square') - ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') - ->dispatchTo('admin.role-form', 'openEditRoleModal', ['id' => $row->id]); - } - if($this->canDelect){ - $actions[] =Button::add('delete') - ->slot(__('roles.delete')) - ->icon('solid-trash') - ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') - ->dispatchTo('admin.role-form', 'deleteRole', ['id' => $row->id]); - } - return $actions; - } - - /* - public function actionRules($row): array - { - return [ - // Hide button edit for ID 1 - Rule::button('edit') - ->when(fn($row) => $row->id === 1) - ->hide(), - ]; - } - */ -} diff --git a/app/Livewire/Admin/RoomDetailModal.php b/app/Livewire/Admin/RoomDetailModal.php new file mode 100644 index 0000000..5883175 --- /dev/null +++ b/app/Livewire/Admin/RoomDetailModal.php @@ -0,0 +1,114 @@ +room = Room::find($roomId); + $this->showModal = true; + } + public function closeModal() + { + $this->showModal = false; + } + public function startNotify() + { + $data = $this->buildNotifyData('active', now(), null); + $this->send($data); + } + + public function stopNotify() + { + $data = $this->buildNotifyData('closed', null, null); + $chk =$this->send($data); + + } + + public function fireNotify() + { + $data = $this->buildNotifyData('fire', null, null); + $this->send($data); + } + + public function openAccountNotify() + { + $data = $this->buildNotifyData('active', now(), null); + $this->send($data); + } + + public function closeAccountNotify() + { + $data = $this->buildNotifyData('closed', now(), null); + $this->send($data); + } + protected function buildNotifyData(string $command, $startedAt = null, $endedAt = null): array + { + return [ + 'branch_id' => $this->room->branch_id ?? 0, + 'room_name' => $this->room->name ?? '', + 'command' => $command, + 'started_at' => $startedAt ? $startedAt->toDateTimeString() : null, + 'ended_at' => $endedAt ? $endedAt->toDateTimeString() : null, + ]; + } + + function send(array $data){ + + $user = Auth::user(); + $token = $user->api_plain_token ?? null; + + if (!$token) { + $this->sendErrorNotification('api', 'API token is missing.'); + return false; + } + + $apiClient = new ApiClient(); + $response = $apiClient->setToken($token)->post('/room/sendSwitch', $data); + if ($response->failed()) { + $this->sendErrorNotification('api', 'API request failed: ' . $response->body()); + return false; + } + // ✅ 成功提示 + $this->notification()->send([ + 'icon' => 'success', + 'title' => '成功', + 'description' => '命令已成功發送:' . $data['command'], + ]); + // ✅ 關閉 modal + $this->showModal = false; + return true; + } + public function sendErrorNotification(string $title = '錯誤', string $description = '發生未知錯誤') + { + $this->notification()->send([ + 'icon' => 'error', + 'title' => $title, + 'description' =>$description, + ]); + } + + public function render() + { + return view('livewire.admin.room-detail-modal'); + } +} diff --git a/app/Livewire/Admin/RoomGrid.php b/app/Livewire/Admin/RoomGrid.php new file mode 100644 index 0000000..93331d8 --- /dev/null +++ b/app/Livewire/Admin/RoomGrid.php @@ -0,0 +1,43 @@ +roomTypes = ['all' => '全部'] + collect(RoomType::cases())->mapWithKeys(fn($e) => [$e->value => $e->labels()])->toArray(); + } + + public function render() + { + $branch = Branch::first(); + $this->branchName = $branch->name ?? ''; + + $rooms = collect(); // 預設為空集合 + $floors = []; + + if ($branch) { + $rooms = Room::where('branch_id', $branch->id)->get(); + $floors = $rooms->pluck('floor')->unique()->sort()->values()->toArray(); + } + + return view('livewire.admin.room-grid', [ + 'rooms' => $rooms, + 'floors' => $floors, + ]); + } +} diff --git a/app/Livewire/Admin/UserForm.php b/app/Livewire/Admin/UserForm.php deleted file mode 100644 index 8094c9e..0000000 --- a/app/Livewire/Admin/UserForm.php +++ /dev/null @@ -1,153 +0,0 @@ -'', - 'email' => '', - 'phone' => '', - 'birthday' => '', - 'gender' => 'unset', - 'status' => 0, - ]; - - - protected $rules = [ - 'fields.name' => 'required|string|max:255', - 'fields.email' => 'required|string|email|max:255', - 'fields.phone' => 'nullable|regex:/^09\d{8}$/', - 'fields.birthday' =>'nullable|date', - 'fields.gender' => 'required|in:male,female,other,unset', - 'fields.status' => 'required|integer|in:0,1,2', - ]; - - public function mount() - { - $this->fields['birthday'] = now()->toDateString(); - $this->genderOptions = collect(UserGender::cases())->map(fn ($gender) => [ - 'name' => $gender->labels(), - 'value' => $gender->value, - ])->toArray(); - $this->statusOptions = collect(UserStatus::cases())->map(fn ($status) => [ - 'name' => $status->labels(), - 'value' => $status->value, - ])->toArray(); - $this->rolesOptions = Role::all(); - $this->canCreate = Auth::user()?->can('user-edit') ?? false; - $this->canEdit = Auth::user()?->can('user-edit') ?? false; - $this->canDelect = Auth::user()?->can('user-delete') ?? false; - } - - public function openModal($id = null) - { - $this->resetFields(); - if($id){ - $obj = User::findOrFail($id); - $this->userId = $obj->id; - $this->fields = $obj->only(array_keys($this->fields)); - $this->selectedRoles = $obj->roles()->pluck('id')->toArray(); - } - $this->showModal = true; - } - - public function closeModal() - { - $this->resetFields(); - $this->showModal = false; - } - - public function save() - { - //$this->validate(); - - if ($this->userId) { - if ($this->canEdit) { - $obj = User::findOrFail($this->userId); - $obj->update($this->fields); - $obj->syncRoles($this->selectedRoles); - $this->notification()->send([ - 'icon' => 'success', - 'title' => '成功', - 'description' => '使用者已更新', - ]); - } - } else { - if ($this->canCreate) { - $obj = User::create($this->fields); - $obj->syncRoles($this->selectedRoles); - $this->notification()->send([ - 'icon' => 'success', - 'title' => '成功', - 'description' => '使用者已新增', - ]); - } - } - - $this->resetFields(); - $this->showModal = false; - $this->dispatch('pg:eventRefresh-user-table'); - } - - public function deleteUser($id) - { - if ($this->canDelect) { - User::findOrFail($id)->delete(); - $this->notification()->send([ - 'icon' => 'success', - 'title' => '成功', - 'description' => '使用者已刪除', - ]); - $this->dispatch('pg:eventRefresh-user-table'); - } - } - - public function resetFields() - { - foreach ($this->fields as $key => $value) { - if ($key == 'gender') { - $this->fields[$key] = 'unset'; - } elseif ($key == 'status') { - $this->fields[$key] = 0; - } elseif ($key == 'birthday') { - $this->fields[$key] = now()->toDateString(); - } else { - $this->fields[$key] = ''; - } - } - $this->userId = null; - $this->selectedRoles = []; - } - - public function render() - { - return view('livewire.admin.user-form'); - } -} diff --git a/app/Livewire/Admin/UserImportData.php b/app/Livewire/Admin/UserImportData.php deleted file mode 100644 index c3d3997..0000000 --- a/app/Livewire/Admin/UserImportData.php +++ /dev/null @@ -1,114 +0,0 @@ -canCreate = Auth::user()?->can('user-edit') ?? false; - $this->maxUploadSize = $this->getMaxUploadSize(); - } - - public function openModal() - { - $this->showModal = true; - } - - public function closeModal() - { - $this->deleteTmpFile(); // 關閉 modal 時刪除暫存檔案 - $this->reset(['file']); - $this->showModal = false; - } - - public function import() - { - // 檢查檔案是否有上傳 - $this->validate([ - 'file' => 'required|file|mimes:csv,xlsx,xls' - ]); - if ($this->canCreate) { - // 儲存檔案至 storage - $path = $this->file->storeAs('imports', uniqid() . '_' . $this->file->getClientOriginalName()); - - // 丟到 queue 執行 - ImportJob::dispatch($path,'User'); - - $this->notification()->send([ - 'icon' => 'info', - 'title' => $this->file->getClientOriginalName(), - 'description' => '已排入背景匯入作業,請稍候查看結果', - ]); - $this->deleteTmpFile(); // 匯入後也順便刪除 tmp 檔 - $this->reset(['file']); - $this->showModal = false; - } - } - protected function deleteTmpFile() - { - if($this->file!=null){ - $Path = $this->file->getRealPath(); - if ($Path && File::exists($Path)) { - File::delete($Path); - } - } - } - - private function getMaxUploadSize(): string - { - $uploadMax = $this->convertPHPSizeToBytes(ini_get('upload_max_filesize')); - $postMax = $this->convertPHPSizeToBytes(ini_get('post_max_size')); - $max = min($uploadMax, $postMax); - return $this->humanFileSize($max); - } - - private function convertPHPSizeToBytes(string $s): int - { - $s = trim($s); - $unit = strtolower($s[strlen($s) - 1]); - $bytes = (int) $s; - switch ($unit) { - case 'g': - $bytes *= 1024; - case 'm': - $bytes *= 1024; - case 'k': - $bytes *= 1024; - } - return $bytes; - } - - private function humanFileSize(int $bytes, int $decimals = 2): string - { - $sizes = ['B', 'KB', 'MB', 'GB']; - $factor = floor((strlen((string) $bytes) - 1) / 3); - return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . $sizes[$factor]; - } - - public function render() - { - return view('livewire.admin.user-import-data'); - } - -} \ No newline at end of file diff --git a/app/Livewire/Admin/UserTable.php b/app/Livewire/Admin/UserTable.php index 8d86f8d..821f241 100644 --- a/app/Livewire/Admin/UserTable.php +++ b/app/Livewire/Admin/UserTable.php @@ -6,73 +6,35 @@ use App\Models\User; use App\Enums\UserGender; use App\Enums\UserStatus; use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Blade; use Illuminate\Database\Eloquent\Builder; -use PowerComponents\LivewirePowerGrid\Button; use PowerComponents\LivewirePowerGrid\Column; use PowerComponents\LivewirePowerGrid\Facades\Filter; use PowerComponents\LivewirePowerGrid\Facades\PowerGrid; use PowerComponents\LivewirePowerGrid\PowerGridFields; use PowerComponents\LivewirePowerGrid\PowerGridComponent; -use PowerComponents\LivewirePowerGrid\Traits\WithExport; -use PowerComponents\LivewirePowerGrid\Components\SetUp\Exportable; -use PowerComponents\LivewirePowerGrid\Facades\Rule; -use Livewire\Attributes\On; -use WireUi\Traits\WireUiActions; final class UserTable extends PowerGridComponent { - use WithExport, WireUiActions; - public string $tableName = 'user-table'; public bool $showFilters = false; - public bool $canCreate; - public bool $canEdit; - public bool $canDownload; - public bool $canDelect; public function boot(): void { config(['livewire-powergrid.filter' => 'outside']); - //權限設定 - $this->canCreate = Auth::user()?->can('user-edit') ?? false; - $this->canEdit = Auth::user()?->can('user-edit') ?? false; - $this->canDownload=Auth::user()?->can('user-delete') ?? false; - $this->canDelect = Auth::user()?->can('user-delete') ?? false; } public function setUp(): array { - if($this->canDownload || $this->canDelect){ - $this->showCheckBox(); - } $actions = []; - $actions[] =PowerGrid::exportable(fileName: $this->tableName.'-file') - ->type(Exportable::TYPE_XLS, Exportable::TYPE_CSV); $header = PowerGrid::header() ->showToggleColumns(); - if($this->canCreate){ - $header->includeViewOnTop('livewire.admin.user-header'); - } + $header->includeViewOnTop('livewire.admin.user-header'); $actions[]=$header; $actions[]=PowerGrid::footer()->showPerPage()->showRecordCount(); return $actions; } - public function header(): array - { - $actions = []; - if ($this->canDelect) { - $actions[]=Button::add('bulk-delete') - ->slot('Bulk delete ()') - ->icon('solid-trash',['id' => 'my-custom-icon-id', 'class' => 'font-bold']) - ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') - ->dispatch('bulkDelete.' . $this->tableName, []); - } - return $actions; - } public function datasource(): Builder { @@ -93,36 +55,10 @@ final class UserTable extends PowerGridComponent ->add('phone') ->add('birthday_formatted',fn (User $model) => Carbon::parse($model->birthday)->format('Y-m-d')) ->add('gender_str', function (User $model) { - if ($this->canEdit) { - return Blade::render( - '', - [ - 'options' => UserGender::options(), - 'modelId' => intval($model->id), - 'fieldName'=>'gender', - 'selected' => $model->gender->value - ] - ); - } - // 沒有權限就顯示對應的文字 - - return $model->gender->labelPowergridFilter(); // 假設 label() 會回傳顯示文字 + return $model->gender->labelPowergridFilter(); } ) ->add('status_str', function (User $model) { - if ($this->canEdit) { - return Blade::render( - '', - [ - 'options' => UserStatus::options(), - 'modelId' => intval($model->id), - 'fieldName'=>'status', - 'selected' => $model->status->value - ] - ); - } - // 沒有權限就顯示對應的文字 - - return $model->status->labelPowergridFilter(); // 假設 label() 會回傳顯示文字 + return $model->status->labelPowergridFilter(); } ) ->add('roles' ,fn(User $model)=> $model->roles->pluck('name')->implode(', ')) ->add('created_at_formatted', fn (User $model) => Carbon::parse($model->created_at)->format('Y-m-d H:i:s')); @@ -134,85 +70,22 @@ final class UserTable extends PowerGridComponent Column::make('ID', 'id'), Column::make(__('users.name'), 'name') ->sortable() - ->searchable() - ->editOnClick( - hasPermission: $this->canEdit, - dataField: 'name', - fallback: 'N/A', - saveOnMouseOut: true - ), + ->searchable(), Column::make('Email', 'email') ->sortable() - ->searchable() - ->editOnClick( - hasPermission: $this->canEdit, - dataField: 'email', - fallback: 'N/A', - saveOnMouseOut: true - ), + ->searchable(), Column::make(__('users.phone'), 'phone') ->sortable() - ->searchable() - ->editOnClick( - hasPermission: $this->canEdit, - dataField: 'phone', - fallback: 'N/A', - saveOnMouseOut: true - ), + ->searchable(), Column::make(__('users.gender'), 'gender_str','users.gender'), Column::make(__('users.birthday'), 'birthday_formatted')->sortable()->searchable(), Column::make(__('users.status'), 'status_str','users.status'), Column::make(__('users.role'), 'roles'), Column::make('建立時間', 'created_at_formatted', 'created_at')->sortable(), - Column::action('操作') + ]; } - #[On('bulkDelete.{tableName}')] - public function bulkDelete(): void - { - if ($this->canDelect) { - $this->js('alert(window.pgBulkActions.get(\'' . $this->tableName . '\'))'); - if($this->checkboxValues){ - User::destroy($this->checkboxValues); - $this->js('window.pgBulkActions.clearAll()'); // clear the count on the interface. - } - } - } - #[On('categoryChanged')] - public function categoryChanged($value,$fieldName, $modelId): void - { - // dd($value,$fieldName, $modelId); - if (in_array($fieldName,['gender','status']) && $this->canEdit) { - $this->noUpdated($modelId,$fieldName,$value); - } - } - #[On('onUpdatedEditable')] - public function onUpdatedEditable($id, $field, $value): void - { - if (in_array($field,['name','email','phone']) && $this->canEdit) { - $this->noUpdated($id,$field,$value); - } - } - #[On('onUpdatedToggleable')] - public function onUpdatedToggleable($id, $field, $value): void - { - if (in_array($field,[]) && $this->canEdit) { - $this->noUpdated($id,$field,$value); - } - } - private function noUpdated($id,$field,$value){ - $user = User::find($id); - if ($user) { - $user->{$field} = $value; - $user->save(); // 明確觸發 saving - } - $this->notification()->send([ - 'icon' => 'success', - 'title' => $id.'.'.__('users.'.$field).':'.$value, - 'description' => '已經寫入', - ]); - } public function filters(): array { @@ -230,36 +103,5 @@ final class UserTable extends PowerGridComponent Filter::datetimepicker('created_at'), ]; } - - public function actions(User $row): array - { - $actions = []; - if ($this->canEdit) { - $actions[]=Button::add('edit') - ->slot(__('users.edit')) - ->icon('solid-pencil-square') - ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') - ->dispatchTo('admin.user-form', 'openModal', ['id' => $row->id]); - } - if($this->canDelect){ - $actions[]=Button::add('delete') - ->slot(__('users.delete')) - ->icon('solid-trash') - ->class('inline-flex items-center gap-1 px-3 py-1 rounded ') - ->dispatchTo('admin.user-form', 'deleteUser', ['id' => $row->id]); - } - return $actions; - } - - - /* public function actionRules($row): array - { - return [ - // Hide button edit for ID 1 - Rule::button('edit') - ->when(fn($row) => $row->id === 1) - ->hide(), - ]; - } */ } diff --git a/app/Models/Artist.php b/app/Models/Artist.php new file mode 100644 index 0000000..ab46f99 --- /dev/null +++ b/app/Models/Artist.php @@ -0,0 +1,30 @@ + */ + use HasFactory, LogsModelActivity; + + protected $fillable = [ + 'category', + 'name', + 'simplified', + 'phonetic_abbr', + 'pinyin_abbr', + 'strokes_abbr', + 'enable', + ]; + + protected function casts(): array + { + return [ + 'category' => \App\Enums\ArtistCategory::class, + ]; + } +} diff --git a/app/Models/Branch.php b/app/Models/Branch.php new file mode 100644 index 0000000..59cc2d4 --- /dev/null +++ b/app/Models/Branch.php @@ -0,0 +1,34 @@ + */ + use HasFactory, LogsModelActivity; + + protected $fillable = [ + 'name', + 'external_ip', + 'enable', + ]; + + public function rooms() { + return $this->hasMany(Room::class); + } + public function songs(){ + return $this->belongsToMany(Song::class) + ->withPivot('counts') + ->withTimestamps(); + } + protected static function booted() + { + static::deleting(function (Branch $branch) { + $branch->rooms()->delete(); + }); + } +} diff --git a/app/Models/MachineStatus.php b/app/Models/MachineStatus.php new file mode 100644 index 0000000..fd9f19b --- /dev/null +++ b/app/Models/MachineStatus.php @@ -0,0 +1,31 @@ + */ + use HasFactory, LogsModelActivity; + + protected $fillable = [ + 'floor', + 'type', + 'name', + 'internal_ip', + 'port', + 'is_online', + 'status', + 'started_at', + 'ended_at', + ]; + + protected $hidden = [ + 'internal_ip', + 'port', + ]; + + protected $casts = [ + 'floor' => 'int', + 'type' => \App\Enums\RoomType::class, + 'name' => 'string', + 'internal_ip' =>'string', + 'port' => 'int', + 'is_online' => 'boolean', + 'status' => \App\Enums\RoomStatus::class, + 'started_at' => 'datetime', + 'ended_at' => 'datetime', + + ]; + + public function str_started_at(){ + $str ="Not Set"; + if($this->started_at !=null){ + $str = $this->started_at; + } + return $str; + } + + public function str_ended_at(){ + $str ="Not Set"; + if($this->ended_at !=null){ + $str = $this->ended_at; + } + return $str; + } + + public function branch() { + return $this->belongsTo(Branch::class); + } + + public function statusLogs() { + return $this->hasMany(RoomStatusLog::class); + } +} diff --git a/app/Models/RoomStatusLog.php b/app/Models/RoomStatusLog.php new file mode 100644 index 0000000..7080beb --- /dev/null +++ b/app/Models/RoomStatusLog.php @@ -0,0 +1,30 @@ + */ + use HasFactory; + + public $timestamps = true; + + protected $fillable = + [ + 'room_id', + 'user_id', + 'status', + 'message', + ]; + + protected $casts = [ + 'status' => \App\Enums\RoomStatus::class, + ]; + + public function room() { + return $this->belongsTo(Room::class); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 3346083..4324251 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -9,11 +9,25 @@ use Illuminate\Notifications\Notifiable; use Spatie\Permission\Traits\HasRoles; use App\Traits\LogsModelActivity; use Spatie\Activitylog\Traits\CausesActivity; +use Laravel\Sanctum\HasApiTokens; +/** + * @OA\Schema( + * schema="User", + * type="object", + * @OA\Property(property="id", type="integer", example=1), + * @OA\Property(property="name", type="string", example="John Doe"), + * @OA\Property(property="email", type="string", example="john@example.com"), + * @OA\Property(property="phone", type="string", example="0900000000"), + * @OA\Property(property="birthday", type="string", format="date-time", example="2025-05-11T16:00:00.000000Z"), + * @OA\Property(property="gender", ref="#/components/schemas/UserGender"), + * @OA\Property(property="status", ref="#/components/schemas/UserStatus"), + * ) + */ class User extends Authenticatable { /** @use HasFactory<\Database\Factories\UserFactory> */ - use HasFactory, Notifiable, HasRoles, LogsModelActivity,CausesActivity; + use HasApiTokens, HasFactory, Notifiable, HasRoles, LogsModelActivity,CausesActivity; /** * The attributes that are mass assignable. @@ -38,6 +52,7 @@ class User extends Authenticatable protected $hidden = [ 'password', 'remember_token', + 'api_plain_token', ]; /** diff --git a/app/Observers/RoomObserver.php b/app/Observers/RoomObserver.php new file mode 100644 index 0000000..d8a3649 --- /dev/null +++ b/app/Observers/RoomObserver.php @@ -0,0 +1,59 @@ +wasChanged('status')) { + RoomStatusLog::create([ + 'room_id' => $room->id, + 'user_id' => Auth::id(), // 若是 console 或系統自動操作可能為 null + 'status' => $room->status, + 'message' => 'started_at:'.$room->started_at.',ended_at:'.$room->ended_at, + ]); + } + } + + /** + * Handle the Room "deleted" event. + */ + public function deleted(Room $room): void + { + // + } + + /** + * Handle the Room "restored" event. + */ + public function restored(Room $room): void + { + // + } + + /** + * Handle the Room "force deleted" event. + */ + public function forceDeleted(Room $room): void + { + // + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 452e6b6..4828850 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -4,6 +4,9 @@ namespace App\Providers; use Illuminate\Support\ServiceProvider; +use App\Models\Room; +use App\Observers\RoomObserver; + class AppServiceProvider extends ServiceProvider { /** @@ -19,6 +22,6 @@ class AppServiceProvider extends ServiceProvider */ public function boot(): void { - // + Room::observe(RoomObserver::class); } } diff --git a/app/Services/ApiClient.php b/app/Services/ApiClient.php new file mode 100644 index 0000000..08de505 --- /dev/null +++ b/app/Services/ApiClient.php @@ -0,0 +1,41 @@ +baseUrl = config('services.room_api.base_url', 'https://ktv.test/api'); + $this->token = $token ?? config('services.room_api.token',''); + } + public function setToken(string $token): self + { + $this->token = $token; + return $this; + } + + public function withDefaultHeaders(): \Illuminate\Http\Client\PendingRequest + { + return Http::withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $this->token, + 'Content-Type' => 'application/json', + ]); + } + + public function post(string $endpoint, array $data = []) + { + return $this->withDefaultHeaders()->post($this->baseUrl . $endpoint, $data); + } + + public function get(string $endpoint, array $query = []) + { + return $this->withDefaultHeaders()->get($this->baseUrl . $endpoint, $query); + } + +} \ No newline at end of file diff --git a/app/Services/TcpSocketClient.php b/app/Services/TcpSocketClient.php new file mode 100644 index 0000000..45530b5 --- /dev/null +++ b/app/Services/TcpSocketClient.php @@ -0,0 +1,43 @@ +ip = $ip; + $this->port = $port; + $this->timeout = $timeout; + } + + public function send(string $data): string + { + $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + if ($socket === false) { + throw new \Exception("Socket create failed: " . socket_strerror(socket_last_error())); + } + + $result = socket_connect($socket, $this->ip, $this->port); + if ($result === false) { + throw new \Exception("Socket connect failed: " . socket_strerror(socket_last_error($socket))); + } + + socket_write($socket, $data, strlen($data)); + + $response = ''; + while ($out = socket_read($socket, 2048)) { + $response .= $out; + // 根據協議判斷是否結束接收,可以自行調整 + if (strpos($response, "\n") !== false) { + break; + } + } + + socket_close($socket); + return $response; + } +} \ No newline at end of file diff --git a/bootstrap/app.php b/bootstrap/app.php index 4455074..3c1ee1e 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -6,6 +6,7 @@ use Illuminate\Foundation\Configuration\Middleware; return Application::configure(basePath: dirname(__DIR__)) ->withRouting( + api: __DIR__.'/../routes/api.php', web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php', health: '/up', @@ -18,5 +19,11 @@ return Application::configure(basePath: dirname(__DIR__)) ]); }) ->withExceptions(function (Exceptions $exceptions) { - // + $exceptions->render(function (\Illuminate\Auth\AuthenticationException $e, $request) { + if ($request->expectsJson()) { + return \App\Http\Responses\ApiResponse::unauthorized(); + } + // 其他非 JSON 請求的處理方式(可選) + return redirect()->guest(route('login')); + }); })->create(); diff --git a/composer.lock b/composer.lock index d33cbfd..e07de42 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4c3aaf10ca75f00df741897c5295dc94", + "content-hash": "63663f4c74cb0801a13c702e3d004b50", "packages": [ { "name": "brick/math", @@ -1434,16 +1434,16 @@ }, { "name": "laravel/framework", - "version": "v12.9.2", + "version": "v12.15.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "3db59aa0f382c349c78a92f3e5b5522e00e3301b" + "reference": "2ef7fb183f18e547af4eb9f5a55b2ac1011f0b77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/3db59aa0f382c349c78a92f3e5b5522e00e3301b", - "reference": "3db59aa0f382c349c78a92f3e5b5522e00e3301b", + "url": "https://api.github.com/repos/laravel/framework/zipball/2ef7fb183f18e547af4eb9f5a55b2ac1011f0b77", + "reference": "2ef7fb183f18e547af4eb9f5a55b2ac1011f0b77", "shasum": "" }, "require": { @@ -1464,7 +1464,7 @@ "guzzlehttp/uri-template": "^1.0", "laravel/prompts": "^0.3.0", "laravel/serializable-closure": "^1.3|^2.0", - "league/commonmark": "^2.6", + "league/commonmark": "^2.7", "league/flysystem": "^3.25.1", "league/flysystem-local": "^3.25.1", "league/uri": "^7.5.1", @@ -1556,7 +1556,7 @@ "php-http/discovery": "^1.15", "phpstan/phpstan": "^2.0", "phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1", - "predis/predis": "^2.3", + "predis/predis": "^2.3|^3.0", "resend/resend-php": "^0.10.0", "symfony/cache": "^7.2.0", "symfony/http-client": "^7.2.0", @@ -1588,7 +1588,7 @@ "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).", "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.5.3|^12.0.1).", - "predis/predis": "Required to use the predis connector (^2.3).", + "predis/predis": "Required to use the predis connector (^2.3|^3.0).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", @@ -1645,7 +1645,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-04-16T15:44:19+00:00" + "time": "2025-05-20T15:10:44+00:00" }, { "name": "laravel/prompts", @@ -1899,16 +1899,16 @@ }, { "name": "league/commonmark", - "version": "2.6.2", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "06c3b0bf2540338094575612f4a1778d0d2d5e94" + "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/06c3b0bf2540338094575612f4a1778d0d2d5e94", - "reference": "06c3b0bf2540338094575612f4a1778d0d2d5e94", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", + "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", "shasum": "" }, "require": { @@ -1945,7 +1945,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.7-dev" + "dev-main": "2.8-dev" } }, "autoload": { @@ -2002,7 +2002,7 @@ "type": "tidelift" } ], - "time": "2025-04-18T21:09:27+00:00" + "time": "2025-05-05T12:20:28+00:00" }, { "name": "league/config", @@ -2967,16 +2967,16 @@ }, { "name": "nesbot/carbon", - "version": "3.9.0", + "version": "3.9.1", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "6d16a8a015166fe54e22c042e0805c5363aef50d" + "reference": "ced71f79398ece168e24f7f7710462f462310d4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/6d16a8a015166fe54e22c042e0805c5363aef50d", - "reference": "6d16a8a015166fe54e22c042e0805c5363aef50d", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ced71f79398ece168e24f7f7710462f462310d4d", + "reference": "ced71f79398ece168e24f7f7710462f462310d4d", "shasum": "" }, "require": { @@ -3069,7 +3069,7 @@ "type": "tidelift" } ], - "time": "2025-03-27T12:57:33+00:00" + "time": "2025-05-01T19:51:51+00:00" }, { "name": "nette/schema", @@ -3279,31 +3279,31 @@ }, { "name": "nunomaduro/termwind", - "version": "v2.3.0", + "version": "v2.3.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda" + "reference": "dfa08f390e509967a15c22493dc0bac5733d9123" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/52915afe6a1044e8b9cee1bcff836fb63acf9cda", - "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/dfa08f390e509967a15c22493dc0bac5733d9123", + "reference": "dfa08f390e509967a15c22493dc0bac5733d9123", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.1.8" + "symfony/console": "^7.2.6" }, "require-dev": { - "illuminate/console": "^11.33.2", - "laravel/pint": "^1.18.2", + "illuminate/console": "^11.44.7", + "laravel/pint": "^1.22.0", "mockery/mockery": "^1.6.12", - "pestphp/pest": "^2.36.0", - "phpstan/phpstan": "^1.12.11", - "phpstan/phpstan-strict-rules": "^1.6.1", - "symfony/var-dumper": "^7.1.8", + "pestphp/pest": "^2.36.0 || ^3.8.2", + "phpstan/phpstan": "^1.12.25", + "phpstan/phpstan-strict-rules": "^1.6.2", + "symfony/var-dumper": "^7.2.6", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -3346,7 +3346,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.3.0" + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.1" }, "funding": [ { @@ -3362,20 +3362,20 @@ "type": "github" } ], - "time": "2024-11-21T10:39:51+00:00" + "time": "2025-05-08T08:14:37+00:00" }, { "name": "openspout/openspout", - "version": "v4.29.1", + "version": "v4.30.0", "source": { "type": "git", "url": "https://github.com/openspout/openspout.git", - "reference": "ec83106bc3922fe94c9d37976ba6b7259511c4c5" + "reference": "df9b0f4d229c37c3caa5a9252a6ad8a94efb0fb5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/openspout/openspout/zipball/ec83106bc3922fe94c9d37976ba6b7259511c4c5", - "reference": "ec83106bc3922fe94c9d37976ba6b7259511c4c5", + "url": "https://api.github.com/repos/openspout/openspout/zipball/df9b0f4d229c37c3caa5a9252a6ad8a94efb0fb5", + "reference": "df9b0f4d229c37c3caa5a9252a6ad8a94efb0fb5", "shasum": "" }, "require": { @@ -3389,13 +3389,13 @@ }, "require-dev": { "ext-zlib": "*", - "friendsofphp/php-cs-fixer": "^3.71.0", + "friendsofphp/php-cs-fixer": "^3.75.0", "infection/infection": "^0.29.14", - "phpbench/phpbench": "^1.4.0", - "phpstan/phpstan": "^2.1.8", - "phpstan/phpstan-phpunit": "^2.0.4", - "phpstan/phpstan-strict-rules": "^2.0.3", - "phpunit/phpunit": "^12.0.7" + "phpbench/phpbench": "^1.4.1", + "phpstan/phpstan": "^2.1.16", + "phpstan/phpstan-phpunit": "^2.0.6", + "phpstan/phpstan-strict-rules": "^2.0.4", + "phpunit/phpunit": "^12.1.5" }, "suggest": { "ext-iconv": "To handle non UTF-8 CSV files (if \"php-mbstring\" is not already installed or is too limited)", @@ -3443,7 +3443,7 @@ ], "support": { "issues": "https://github.com/openspout/openspout/issues", - "source": "https://github.com/openspout/openspout/tree/v4.29.1" + "source": "https://github.com/openspout/openspout/tree/v4.30.0" }, "funding": [ { @@ -3455,7 +3455,7 @@ "type": "github" } ], - "time": "2025-03-11T14:40:46+00:00" + "time": "2025-05-20T12:33:06+00:00" }, { "name": "phpoffice/phpspreadsheet", @@ -3640,16 +3640,16 @@ }, { "name": "power-components/livewire-powergrid", - "version": "v6.3.1", + "version": "v6.3.2", "source": { "type": "git", "url": "https://github.com/Power-Components/livewire-powergrid.git", - "reference": "36192c917d7c62930a3099ee1cfe4373d05dfe6e" + "reference": "b2d0b39f5d4631c0d1150730ed325863a64bff89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Power-Components/livewire-powergrid/zipball/36192c917d7c62930a3099ee1cfe4373d05dfe6e", - "reference": "36192c917d7c62930a3099ee1cfe4373d05dfe6e", + "url": "https://api.github.com/repos/Power-Components/livewire-powergrid/zipball/b2d0b39f5d4631c0d1150730ed325863a64bff89", + "reference": "b2d0b39f5d4631c0d1150730ed325863a64bff89", "shasum": "" }, "require": { @@ -3660,7 +3660,7 @@ "require-dev": { "composer/composer": "^2.7.9", "laradumps/laradumps": "^3.2|^4.0", - "larastan/larastan": "^2.9.8", + "larastan/larastan": "^2.0|^3.0", "laravel/pint": "1.17", "laravel/scout": "^10.11.3", "openspout/openspout": "^4.24.5", @@ -3706,7 +3706,7 @@ "homepage": "https://github.com/power-components/livewire-powergrid", "support": { "issues": "https://github.com/Power-Components/livewire-powergrid/issues", - "source": "https://github.com/Power-Components/livewire-powergrid/tree/v6.3.1" + "source": "https://github.com/Power-Components/livewire-powergrid/tree/v6.3.2" }, "funding": [ { @@ -3714,7 +3714,7 @@ "type": "github" } ], - "time": "2025-03-30T13:29:33+00:00" + "time": "2025-04-28T19:24:40+00:00" }, { "name": "psr/cache", @@ -4622,16 +4622,16 @@ }, { "name": "spatie/laravel-permission", - "version": "6.17.0", + "version": "6.18.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-permission.git", - "reference": "02ada8f638b643713fa2fb543384738e27346ddb" + "reference": "3c05f04d12275dfbe462c8b4aae3290e586c2dde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/02ada8f638b643713fa2fb543384738e27346ddb", - "reference": "02ada8f638b643713fa2fb543384738e27346ddb", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/3c05f04d12275dfbe462c8b4aae3290e586c2dde", + "reference": "3c05f04d12275dfbe462c8b4aae3290e586c2dde", "shasum": "" }, "require": { @@ -4693,7 +4693,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-permission/issues", - "source": "https://github.com/spatie/laravel-permission/tree/6.17.0" + "source": "https://github.com/spatie/laravel-permission/tree/6.18.0" }, "funding": [ { @@ -4701,20 +4701,20 @@ "type": "github" } ], - "time": "2025-04-08T15:06:14+00:00" + "time": "2025-05-14T03:32:23+00:00" }, { "name": "swagger-api/swagger-ui", - "version": "v5.21.0", + "version": "v5.22.0", "source": { "type": "git", "url": "https://github.com/swagger-api/swagger-ui.git", - "reference": "fceaec605072fbc717a04895bd19814d9a1c8e6d" + "reference": "4b37bf2a25a8d82fb0092b25d8108d1bb4171181" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/fceaec605072fbc717a04895bd19814d9a1c8e6d", - "reference": "fceaec605072fbc717a04895bd19814d9a1c8e6d", + "url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/4b37bf2a25a8d82fb0092b25d8108d1bb4171181", + "reference": "4b37bf2a25a8d82fb0092b25d8108d1bb4171181", "shasum": "" }, "type": "library", @@ -4760,9 +4760,9 @@ ], "support": { "issues": "https://github.com/swagger-api/swagger-ui/issues", - "source": "https://github.com/swagger-api/swagger-ui/tree/v5.21.0" + "source": "https://github.com/swagger-api/swagger-ui/tree/v5.22.0" }, - "time": "2025-04-13T19:37:38+00:00" + "time": "2025-05-21T12:44:47+00:00" }, { "name": "symfony/clock", @@ -4840,16 +4840,16 @@ }, { "name": "symfony/console", - "version": "v7.2.5", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "e51498ea18570c062e7df29d05a7003585b19b88" + "reference": "0e2e3f38c192e93e622e41ec37f4ca70cfedf218" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e51498ea18570c062e7df29d05a7003585b19b88", - "reference": "e51498ea18570c062e7df29d05a7003585b19b88", + "url": "https://api.github.com/repos/symfony/console/zipball/0e2e3f38c192e93e622e41ec37f4ca70cfedf218", + "reference": "0e2e3f38c192e93e622e41ec37f4ca70cfedf218", "shasum": "" }, "require": { @@ -4913,7 +4913,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.5" + "source": "https://github.com/symfony/console/tree/v7.2.6" }, "funding": [ { @@ -4929,7 +4929,7 @@ "type": "tidelift" } ], - "time": "2025-03-12T08:11:12+00:00" + "time": "2025-04-07T19:09:28+00:00" }, { "name": "symfony/css-selector", @@ -4998,16 +4998,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -5020,7 +5020,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -5045,7 +5045,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -5061,7 +5061,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/error-handler", @@ -5220,16 +5220,16 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", "shasum": "" }, "require": { @@ -5243,7 +5243,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -5276,7 +5276,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" }, "funding": [ { @@ -5292,7 +5292,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/finder", @@ -5360,16 +5360,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.2.5", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "371272aeb6286f8135e028ca535f8e4d6f114126" + "reference": "6023ec7607254c87c5e69fb3558255aca440d72b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/371272aeb6286f8135e028ca535f8e4d6f114126", - "reference": "371272aeb6286f8135e028ca535f8e4d6f114126", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6023ec7607254c87c5e69fb3558255aca440d72b", + "reference": "6023ec7607254c87c5e69fb3558255aca440d72b", "shasum": "" }, "require": { @@ -5418,7 +5418,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.2.5" + "source": "https://github.com/symfony/http-foundation/tree/v7.2.6" }, "funding": [ { @@ -5434,20 +5434,20 @@ "type": "tidelift" } ], - "time": "2025-03-25T15:54:33+00:00" + "time": "2025-04-09T08:14:01+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.2.5", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "b1fe91bc1fa454a806d3f98db4ba826eb9941a54" + "reference": "f9dec01e6094a063e738f8945ef69c0cfcf792ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b1fe91bc1fa454a806d3f98db4ba826eb9941a54", - "reference": "b1fe91bc1fa454a806d3f98db4ba826eb9941a54", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f9dec01e6094a063e738f8945ef69c0cfcf792ec", + "reference": "f9dec01e6094a063e738f8945ef69c0cfcf792ec", "shasum": "" }, "require": { @@ -5532,7 +5532,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.2.5" + "source": "https://github.com/symfony/http-kernel/tree/v7.2.6" }, "funding": [ { @@ -5548,20 +5548,20 @@ "type": "tidelift" } ], - "time": "2025-03-28T13:32:50+00:00" + "time": "2025-05-02T09:04:03+00:00" }, { "name": "symfony/mailer", - "version": "v7.2.3", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "f3871b182c44997cf039f3b462af4a48fb85f9d3" + "reference": "998692469d6e698c6eadc7ef37a6530a9eabb356" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/f3871b182c44997cf039f3b462af4a48fb85f9d3", - "reference": "f3871b182c44997cf039f3b462af4a48fb85f9d3", + "url": "https://api.github.com/repos/symfony/mailer/zipball/998692469d6e698c6eadc7ef37a6530a9eabb356", + "reference": "998692469d6e698c6eadc7ef37a6530a9eabb356", "shasum": "" }, "require": { @@ -5612,7 +5612,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.2.3" + "source": "https://github.com/symfony/mailer/tree/v7.2.6" }, "funding": [ { @@ -5628,20 +5628,20 @@ "type": "tidelift" } ], - "time": "2025-01-27T11:08:17+00:00" + "time": "2025-04-04T09:50:51+00:00" }, { "name": "symfony/mime", - "version": "v7.2.4", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "87ca22046b78c3feaff04b337f33b38510fd686b" + "reference": "706e65c72d402539a072d0d6ad105fff6c161ef1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/87ca22046b78c3feaff04b337f33b38510fd686b", - "reference": "87ca22046b78c3feaff04b337f33b38510fd686b", + "url": "https://api.github.com/repos/symfony/mime/zipball/706e65c72d402539a072d0d6ad105fff6c161ef1", + "reference": "706e65c72d402539a072d0d6ad105fff6c161ef1", "shasum": "" }, "require": { @@ -5696,7 +5696,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.2.4" + "source": "https://github.com/symfony/mime/tree/v7.2.6" }, "funding": [ { @@ -5712,11 +5712,11 @@ "type": "tidelift" } ], - "time": "2025-02-19T08:51:20+00:00" + "time": "2025-04-27T13:34:41+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -5775,7 +5775,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" }, "funding": [ { @@ -5795,7 +5795,7 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", @@ -5853,7 +5853,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" }, "funding": [ { @@ -5873,16 +5873,16 @@ }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", "shasum": "" }, "require": { @@ -5936,7 +5936,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.0" }, "funding": [ { @@ -5952,11 +5952,11 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-09-10T14:38:51+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -6017,7 +6017,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" }, "funding": [ { @@ -6037,19 +6037,20 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, "provide": { @@ -6097,7 +6098,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" }, "funding": [ { @@ -6113,20 +6114,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { @@ -6177,7 +6178,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" }, "funding": [ { @@ -6193,11 +6194,11 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", @@ -6253,7 +6254,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" }, "funding": [ { @@ -6273,7 +6274,7 @@ }, { "name": "symfony/polyfill-uuid", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", @@ -6332,7 +6333,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.32.0" }, "funding": [ { @@ -6494,16 +6495,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", "shasum": "" }, "require": { @@ -6521,7 +6522,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -6557,7 +6558,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" }, "funding": [ { @@ -6573,20 +6574,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2025-04-25T09:37:31+00:00" }, { "name": "symfony/string", - "version": "v7.2.0", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" + "reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", + "url": "https://api.github.com/repos/symfony/string/zipball/a214fe7d62bd4df2a76447c67c6b26e1d5e74931", + "reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931", "shasum": "" }, "require": { @@ -6644,7 +6645,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.2.0" + "source": "https://github.com/symfony/string/tree/v7.2.6" }, "funding": [ { @@ -6660,20 +6661,20 @@ "type": "tidelift" } ], - "time": "2024-11-13T13:31:26+00:00" + "time": "2025-04-20T20:18:16+00:00" }, { "name": "symfony/translation", - "version": "v7.2.4", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "283856e6981286cc0d800b53bd5703e8e363f05a" + "reference": "e7fd8e2a4239b79a0fd9fb1fef3e0e7f969c6dc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/283856e6981286cc0d800b53bd5703e8e363f05a", - "reference": "283856e6981286cc0d800b53bd5703e8e363f05a", + "url": "https://api.github.com/repos/symfony/translation/zipball/e7fd8e2a4239b79a0fd9fb1fef3e0e7f969c6dc6", + "reference": "e7fd8e2a4239b79a0fd9fb1fef3e0e7f969c6dc6", "shasum": "" }, "require": { @@ -6739,7 +6740,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.2.4" + "source": "https://github.com/symfony/translation/tree/v7.2.6" }, "funding": [ { @@ -6755,20 +6756,20 @@ "type": "tidelift" } ], - "time": "2025-02-13T10:27:23+00:00" + "time": "2025-04-07T19:09:28+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", - "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", "shasum": "" }, "require": { @@ -6781,7 +6782,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -6817,7 +6818,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" }, "funding": [ { @@ -6833,7 +6834,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-27T08:32:26+00:00" }, { "name": "symfony/uid", @@ -6911,16 +6912,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.2.3", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "82b478c69745d8878eb60f9a049a4d584996f73a" + "reference": "9c46038cd4ed68952166cf7001b54eb539184ccb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/82b478c69745d8878eb60f9a049a4d584996f73a", - "reference": "82b478c69745d8878eb60f9a049a4d584996f73a", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9c46038cd4ed68952166cf7001b54eb539184ccb", + "reference": "9c46038cd4ed68952166cf7001b54eb539184ccb", "shasum": "" }, "require": { @@ -6974,7 +6975,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.2.3" + "source": "https://github.com/symfony/var-dumper/tree/v7.2.6" }, "funding": [ { @@ -6990,20 +6991,20 @@ "type": "tidelift" } ], - "time": "2025-01-17T11:39:41+00:00" + "time": "2025-04-09T08:14:01+00:00" }, { "name": "symfony/yaml", - "version": "v7.2.5", + "version": "v7.2.6", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "4c4b6f4cfcd7e52053f0c8bfad0f7f30fb924912" + "reference": "0feafffb843860624ddfd13478f481f4c3cd8b23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/4c4b6f4cfcd7e52053f0c8bfad0f7f30fb924912", - "reference": "4c4b6f4cfcd7e52053f0c8bfad0f7f30fb924912", + "url": "https://api.github.com/repos/symfony/yaml/zipball/0feafffb843860624ddfd13478f481f4c3cd8b23", + "reference": "0feafffb843860624ddfd13478f481f4c3cd8b23", "shasum": "" }, "require": { @@ -7046,7 +7047,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.2.5" + "source": "https://github.com/symfony/yaml/tree/v7.2.6" }, "funding": [ { @@ -7062,7 +7063,7 @@ "type": "tidelift" } ], - "time": "2025-03-03T07:12:39+00:00" + "time": "2025-04-04T10:10:11+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -7121,16 +7122,16 @@ }, { "name": "vlucas/phpdotenv", - "version": "v5.6.1", + "version": "v5.6.2", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", - "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af", "shasum": "" }, "require": { @@ -7189,7 +7190,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2" }, "funding": [ { @@ -7201,7 +7202,7 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:52:34+00:00" + "time": "2025-04-30T23:37:27+00:00" }, { "name": "voku/portable-ascii", @@ -7464,16 +7465,16 @@ }, { "name": "wireui/wireui", - "version": "v2.4.2", + "version": "v2.4.3", "source": { "type": "git", "url": "https://github.com/wireui/wireui.git", - "reference": "fad14ea639322432fa24bb7e61f644a17b503582" + "reference": "29f6b969f7c9c057379486fd6370915eaec5e32f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wireui/wireui/zipball/fad14ea639322432fa24bb7e61f644a17b503582", - "reference": "fad14ea639322432fa24bb7e61f644a17b503582", + "url": "https://api.github.com/repos/wireui/wireui/zipball/29f6b969f7c9c057379486fd6370915eaec5e32f", + "reference": "29f6b969f7c9c057379486fd6370915eaec5e32f", "shasum": "" }, "require": { @@ -7524,22 +7525,22 @@ ], "support": { "issues": "https://github.com/wireui/wireui/issues", - "source": "https://github.com/wireui/wireui/tree/v2.4.2" + "source": "https://github.com/wireui/wireui/tree/v2.4.3" }, - "time": "2025-04-16T17:33:35+00:00" + "time": "2025-04-29T22:34:44+00:00" }, { "name": "zircote/swagger-php", - "version": "5.1.1", + "version": "5.1.3", "source": { "type": "git", "url": "https://github.com/zircote/swagger-php.git", - "reference": "7a6544c60441ddb5959b91266b3a290dc28537ba" + "reference": "b8ba6bd99805c0ae09a38d1b26c1c92820509bd0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/7a6544c60441ddb5959b91266b3a290dc28537ba", - "reference": "7a6544c60441ddb5959b91266b3a290dc28537ba", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/b8ba6bd99805c0ae09a38d1b26c1c92820509bd0", + "reference": "b8ba6bd99805c0ae09a38d1b26c1c92820509bd0", "shasum": "" }, "require": { @@ -7610,9 +7611,9 @@ ], "support": { "issues": "https://github.com/zircote/swagger-php/issues", - "source": "https://github.com/zircote/swagger-php/tree/5.1.1" + "source": "https://github.com/zircote/swagger-php/tree/5.1.3" }, - "time": "2025-04-27T10:02:08+00:00" + "time": "2025-05-20T03:35:10+00:00" } ], "packages-dev": [ @@ -7752,20 +7753,20 @@ }, { "name": "hamcrest/hamcrest-php", - "version": "v2.0.1", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", "shasum": "" }, "require": { - "php": "^5.3|^7.0|^8.0" + "php": "^7.4|^8.0" }, "replace": { "cordoval/hamcrest-php": "*", @@ -7773,8 +7774,8 @@ "kodova/hamcrest-php": "*" }, "require-dev": { - "phpunit/php-file-iterator": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { @@ -7797,9 +7798,9 @@ ], "support": { "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" }, - "time": "2020-07-09T08:09:16+00:00" + "time": "2025-04-30T06:54:44+00:00" }, { "name": "laravel/breeze", @@ -7942,16 +7943,16 @@ }, { "name": "laravel/pint", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "7ddfaa6523a675fae5c4123ee38fc6bfb8ee4f36" + "reference": "941d1927c5ca420c22710e98420287169c7bcaf7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/7ddfaa6523a675fae5c4123ee38fc6bfb8ee4f36", - "reference": "7ddfaa6523a675fae5c4123ee38fc6bfb8ee4f36", + "url": "https://api.github.com/repos/laravel/pint/zipball/941d1927c5ca420c22710e98420287169c7bcaf7", + "reference": "941d1927c5ca420c22710e98420287169c7bcaf7", "shasum": "" }, "require": { @@ -7963,11 +7964,11 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.75.0", - "illuminate/view": "^11.44.2", - "larastan/larastan": "^3.3.1", + "illuminate/view": "^11.44.7", + "larastan/larastan": "^3.4.0", "laravel-zero/framework": "^11.36.1", "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^2.3", + "nunomaduro/termwind": "^2.3.1", "pestphp/pest": "^2.36.0" }, "bin": [ @@ -8004,20 +8005,20 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-04-08T22:11:45+00:00" + "time": "2025-05-08T08:38:12+00:00" }, { "name": "laravel/sail", - "version": "v1.41.0", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "fe1a4ada0abb5e4bd99eb4e4b0d87906c00cdeec" + "reference": "71a509b14b2621ce58574274a74290f933c687f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/fe1a4ada0abb5e4bd99eb4e4b0d87906c00cdeec", - "reference": "fe1a4ada0abb5e4bd99eb4e4b0d87906c00cdeec", + "url": "https://api.github.com/repos/laravel/sail/zipball/71a509b14b2621ce58574274a74290f933c687f7", + "reference": "71a509b14b2621ce58574274a74290f933c687f7", "shasum": "" }, "require": { @@ -8067,7 +8068,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2025-01-24T15:45:36+00:00" + "time": "2025-05-13T13:34:34+00:00" }, { "name": "mockery/mockery", @@ -8154,16 +8155,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.0", + "version": "1.13.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414" + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", "shasum": "" }, "require": { @@ -8202,7 +8203,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" }, "funding": [ { @@ -8210,7 +8211,7 @@ "type": "tidelift" } ], - "time": "2025-02-12T12:17:51+00:00" + "time": "2025-04-29T12:36:36+00:00" }, { "name": "nunomaduro/collision", @@ -8754,16 +8755,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.17", + "version": "11.5.21", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "fd2e863a2995cdfd864fb514b5e0b28b09895b5c" + "reference": "d565e2cdc21a7db9dc6c399c1fc2083b8010f289" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fd2e863a2995cdfd864fb514b5e0b28b09895b5c", - "reference": "fd2e863a2995cdfd864fb514b5e0b28b09895b5c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d565e2cdc21a7db9dc6c399c1fc2083b8010f289", + "reference": "d565e2cdc21a7db9dc6c399c1fc2083b8010f289", "shasum": "" }, "require": { @@ -8773,7 +8774,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.0", + "myclabs/deep-copy": "^1.13.1", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", @@ -8786,7 +8787,7 @@ "sebastian/code-unit": "^3.0.3", "sebastian/comparator": "^6.3.1", "sebastian/diff": "^6.0.2", - "sebastian/environment": "^7.2.0", + "sebastian/environment": "^7.2.1", "sebastian/exporter": "^6.3.0", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", @@ -8835,7 +8836,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.17" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.21" }, "funding": [ { @@ -8846,12 +8847,20 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2025-04-08T07:59:11+00:00" + "time": "2025-05-21T12:35:00+00:00" }, { "name": "sebastian/cli-parser", @@ -9230,23 +9239,23 @@ }, { "name": "sebastian/environment", - "version": "7.2.0", + "version": "7.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5" + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", - "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.3" }, "suggest": { "ext-posix": "*" @@ -9282,15 +9291,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/7.2.0" + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" } ], - "time": "2024-07-03T04:54:44+00:00" + "time": "2025-05-21T11:55:47+00:00" }, { "name": "sebastian/exporter", @@ -9888,7 +9909,7 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^8.2" + "php": "^8.3" }, "platform-dev": [], "plugin-api-version": "2.6.0" diff --git a/config/services.php b/config/services.php index 27a3617..860cf31 100644 --- a/config/services.php +++ b/config/services.php @@ -13,7 +13,9 @@ return [ | a conventional file to locate the various service credentials. | */ - + 'room_api' => [ + 'base_url' => env('ROOM_API_BASE_URL', env('APP_URL') . '/api'), + ], 'postmark' => [ 'token' => env('POSTMARK_TOKEN'), ], diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php index a3937df..b74937d 100644 --- a/database/migrations/0001_01_01_000000_create_users_table.php +++ b/database/migrations/0001_01_01_000000_create_users_table.php @@ -22,6 +22,7 @@ return new class extends Migration $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); + $table->text('api_plain_token')->nullable(); $table->timestamps(); }); diff --git a/database/migrations/2025_05_23_031625_create_artists_table.php b/database/migrations/2025_05_23_031625_create_artists_table.php new file mode 100644 index 0000000..cd73351 --- /dev/null +++ b/database/migrations/2025_05_23_031625_create_artists_table.php @@ -0,0 +1,34 @@ +bigIncrements('id'); + $table->enum('category', ['未定義','男', '女','團','外', '其他'])->default('未定義')->index()->comment('歌星類別'); + $table->string('name')->unique()->comment('歌星名稱'); + $table->string('simplified')->index()->comment('歌星簡體'); + $table->string('phonetic_abbr')->index()->comment('歌星注音'); + $table->string('pinyin_abbr')->index()->comment('歌星拼音'); + $table->integer('strokes_abbr')->index()->comment('歌星筆劃'); + $table->tinyInteger('enable')->default(1)->comment('狀態'); // 1,可看,0,不可看 + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('artists'); + } +}; diff --git a/database/migrations/2025_05_23_031630_create_song_library_cache_table.php b/database/migrations/2025_05_23_031630_create_song_library_cache_table.php new file mode 100644 index 0000000..555799f --- /dev/null +++ b/database/migrations/2025_05_23_031630_create_song_library_cache_table.php @@ -0,0 +1,48 @@ +bigIncrements('song_id')->comment('歌曲編號'); + $table->string('song_name')->nullable()->index()->comment('歌曲檔名'); + $table->string('song_simplified')->nullable()->index()->comment('歌曲簡體'); + $table->string('phonetic_abbr')->nullable()->index()->comment('歌曲注音'); + $table->string('pinyin_abbr')->nullable()->index()->comment('歌曲拼音'); + $table->integer('strokes_abbr')->default(0)->index()->comment('歌曲筆劃'); + $table->integer('song_number')->default(0)->index()->comment('歌曲字數'); + $table->string('artistA')->nullable()->index()->comment('歌星名稱A'); + $table->string('artistB')->nullable()->index()->comment('歌星名稱B'); + $table->string('artistA_simplified')->nullable()->index()->comment('歌星簡體名稱A'); + $table->string('artistB_simplified')->nullable()->index()->comment('歌星簡體名稱B'); + $table->enum('artistA_category', ['未定義','男', '女','團','外', '其他'])->default('未定義')->index()->comment('歌星類別A'); + $table->enum('artistB_category', ['未定義','男', '女','團','外', '其他'])->default('未定義')->index()->comment('歌星類別B'); + $table->enum('artist_category', ['未定義','團'])->default('未定義')->index()->comment('歌星類別'); + $table->string('song_filename')->nullable()->comment('歌曲檔名'); + $table->string('song_category')->nullable()->comment('歌曲分類'); + $table->enum('language_name', ['未定義','國語','台語','英語','日語','粵語','韓語','越語','客語','其他'])->default('未定義')->index()->comment('語別'); + $table->date('add_date')->nullable()->index()->comment('新增日期'); + $table->enum('situation', ['未定義','浪漫', '柔和','動感','明亮'])->default('未定義')->index()->comment('情境'); + $table->tinyInteger('vocal')->index()->comment('人聲'); // 0,1 + $table->integer('db_change')->default(0)->index()->comment('DB加減'); + $table->integer('song_counts')->default(0)->index()->comment('點播次數'); + $table->dateTime('updated_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('song_library_cache'); + } +}; diff --git a/database/migrations/2025_05_23_052656_create_FavoriteSongs_table.php b/database/migrations/2025_05_23_052656_create_FavoriteSongs_table.php new file mode 100644 index 0000000..36a2570 --- /dev/null +++ b/database/migrations/2025_05_23_052656_create_FavoriteSongs_table.php @@ -0,0 +1,29 @@ +id(); + $table->string('songNumber',20); + $table->string('userPhone', 10); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('FavoriteSongs'); + } +}; diff --git a/database/migrations/2025_05_23_055303_create_branches_table.php b/database/migrations/2025_05_23_055303_create_branches_table.php new file mode 100644 index 0000000..c0c2523 --- /dev/null +++ b/database/migrations/2025_05_23_055303_create_branches_table.php @@ -0,0 +1,30 @@ +id(); + $table->string('name')->comment('店名'); + $table->ipAddress('external_ip')->comment('對外IP'); // 對外 IP + $table->tinyInteger('enable')->default(1)->comment('狀態'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('branches'); + } +}; diff --git a/database/migrations/2025_05_23_055307_create_rooms_table.php b/database/migrations/2025_05_23_055307_create_rooms_table.php new file mode 100644 index 0000000..d4468c7 --- /dev/null +++ b/database/migrations/2025_05_23_055307_create_rooms_table.php @@ -0,0 +1,37 @@ +id(); + $table->foreignId('branch_id')->constrained()->onDelete('cascade')->comment('關聯分店'); + $table->unsignedTinyInteger('floor')->default(1)->comment('樓層'); // 可根據實際狀況決定預設值 + $table->enum('type',['unset', 'pc','svr'])->default('unset')->comment('包廂類別'); + $table->string('name')->comment('包廂名稱'); + $table->string('internal_ip')->nullable()->comment('內部 IP'); + $table->unsignedSmallInteger('port')->nullable()->comment('通訊 Port'); + $table->tinyInteger('is_online')->default(0)->comment('連線狀態'); + $table->enum('status', ['active', 'closed','fire', 'error'])->default('error')->comment('狀態'); // :啟用中 / 已結束 + $table->dateTime('started_at')->nullable()->comment('開始時間'); // + $table->dateTime('ended_at')->nullable()->comment('結束時間'); // + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('rooms'); + } +}; diff --git a/database/migrations/2025_05_23_055312_create_room_status_logs_table.php b/database/migrations/2025_05_23_055312_create_room_status_logs_table.php new file mode 100644 index 0000000..d602ba8 --- /dev/null +++ b/database/migrations/2025_05_23_055312_create_room_status_logs_table.php @@ -0,0 +1,31 @@ +id(); + $table->foreignId('room_id')->nullable(); + $table->foreignId('user_id')->nullable(); // 操作者,可為 null(系統) + $table->enum('status', ['active', 'closed','fire', 'error', 'maintenance']); + $table->text('message')->nullable(); // 可填異常原因或操作說明 + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('room_status_logs'); + } +}; diff --git a/database/migrations/2025_05_23_170205_create_machine_statuses_table.php b/database/migrations/2025_05_23_170205_create_machine_statuses_table.php new file mode 100644 index 0000000..8833ab7 --- /dev/null +++ b/database/migrations/2025_05_23_170205_create_machine_statuses_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('branch_name'); + $table->string('hostname'); + $table->string('ip')->nullable(); + $table->decimal('cpu', 5, 2)->nullable(); + $table->unsignedInteger('memory')->nullable(); + $table->decimal('disk', 10, 2)->nullable(); + $table->string('status'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('machine_statuses'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index c89223e..9e5083a 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -16,6 +16,7 @@ class DatabaseSeeder extends Seeder $this->call([ PermissionTableSeeder::class, CreateAdminUserSeeder::class, + FavoriteSongsSeeder::class, ]); } } diff --git a/database/seeders/FavoriteSongsSeeder.php b/database/seeders/FavoriteSongsSeeder.php new file mode 100644 index 0000000..e370bce --- /dev/null +++ b/database/seeders/FavoriteSongsSeeder.php @@ -0,0 +1,23 @@ +insert([ + 'songNumber' => 999996, + 'userPhone' => '0912345678', + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]); + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9fc056a..131c4b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "shop_12_wireui", + "name": "KTVCentral", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/resources/lang/zh-tw/branches.php b/resources/lang/zh-tw/branches.php new file mode 100644 index 0000000..d84ef49 --- /dev/null +++ b/resources/lang/zh-tw/branches.php @@ -0,0 +1,23 @@ + '分店管理', + 'list' => '分店列表', + 'CreateNew' => '新增分店', + 'EditBranch' => '編輯分店', + 'ImportData' => '滙入分店', + 'create_edit' => '新增 / 編輯', + 'create' => '新增', + 'edit' => '編輯', + 'delete' => '刪除', + 'no' => '編號', + 'name' => '名稱', + 'external_ip' => '對外IP', + 'enable' => '狀態', + + + 'actions' => '操作', + 'view' => '查看', + 'submit' => '提交', + 'cancel' => '取消', +]; \ No newline at end of file diff --git a/resources/lang/zh-tw/enums.php b/resources/lang/zh-tw/enums.php index 348fee1..4d0af2f 100644 --- a/resources/lang/zh-tw/enums.php +++ b/resources/lang/zh-tw/enums.php @@ -8,4 +8,9 @@ return [ 'user.status.Active' => '正常', 'user.status.Suspended' => '停權', 'user.status.Deleting' => '刪除中', + + 'room.status.Active' => '已占用', + 'room.status.Closed' => '可用', + 'room.status.Fire' => '火災', + 'room.status.Error' => '維修', ]; \ No newline at end of file diff --git a/resources/views/components/room-card-svr.blade.php b/resources/views/components/room-card-svr.blade.php new file mode 100644 index 0000000..4b4711c --- /dev/null +++ b/resources/views/components/room-card-svr.blade.php @@ -0,0 +1,22 @@ +@php + use App\Enums\RoomStatus; + $statusColors = [ + RoomStatus::Active->value => 'green-600', + RoomStatus::Closed->value => 'gray-600', + RoomStatus::Error->value => 'red-600', + ]; +@endphp + + + {{-- 房間名稱 + 線上狀態圓點 --}} + + + + {{ $room->type->labels().".".$room->name }} + + + + {{ $room->status->labels() }} + + \ No newline at end of file diff --git a/resources/views/components/room-card.blade.php b/resources/views/components/room-card.blade.php new file mode 100644 index 0000000..f8a2290 --- /dev/null +++ b/resources/views/components/room-card.blade.php @@ -0,0 +1,26 @@ +@php + use App\Enums\RoomStatus; + $statusColors = [ + RoomStatus::Active->value => 'green-600', + RoomStatus::Closed->value => 'gray-600', + RoomStatus::Error->value => 'red-600', + ]; +@endphp + + + {{-- 房間名稱 + 線上狀態圓點 --}} + + + + {{ $room->type->labels().".".$room->name }} + + + + {{ $room->status->labels() }} + + {{ $room->str_started_at() }} + {{ $room->str_ended_at() }} + + \ No newline at end of file diff --git a/resources/views/livewire/admin/branch-header.blade.php b/resources/views/livewire/admin/branch-header.blade.php new file mode 100644 index 0000000..6d81f4b --- /dev/null +++ b/resources/views/livewire/admin/branch-header.blade.php @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/views/livewire/admin/branches.blade.php b/resources/views/livewire/admin/branches.blade.php new file mode 100644 index 0000000..39b1acd --- /dev/null +++ b/resources/views/livewire/admin/branches.blade.php @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/resources/views/livewire/admin/role-form.blade.php b/resources/views/livewire/admin/role-form.blade.php deleted file mode 100644 index 1d55768..0000000 --- a/resources/views/livewire/admin/role-form.blade.php +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/resources/views/livewire/admin/role-header.blade.php b/resources/views/livewire/admin/role-header.blade.php deleted file mode 100644 index 28df373..0000000 --- a/resources/views/livewire/admin/role-header.blade.php +++ /dev/null @@ -1,8 +0,0 @@ - - - \ No newline at end of file diff --git a/resources/views/livewire/admin/roles.blade.php b/resources/views/livewire/admin/roles.blade.php deleted file mode 100644 index 04ed419..0000000 --- a/resources/views/livewire/admin/roles.blade.php +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/views/livewire/admin/room-detail-modal.blade.php b/resources/views/livewire/admin/room-detail-modal.blade.php new file mode 100644 index 0000000..abda927 --- /dev/null +++ b/resources/views/livewire/admin/room-detail-modal.blade.php @@ -0,0 +1,29 @@ + + + + + + + + + {{ $room->name ?? '未選擇' }}包廂設定 + + + 開機 + 關機 + 火災 + + + 包廂開帳 + 包廂關帳 + + + + \ No newline at end of file diff --git a/resources/views/livewire/admin/room-grid.blade.php b/resources/views/livewire/admin/room-grid.blade.php new file mode 100644 index 0000000..a218376 --- /dev/null +++ b/resources/views/livewire/admin/room-grid.blade.php @@ -0,0 +1,50 @@ + + + {{-- 樓層 Tab --}} + + @foreach($floors as $fl) + + {{ $fl }}F + + @endforeach + + + {{-- 類別 Tab --}} + + @foreach(['all' => '全部', 'pc' => 'PC', 'svr' => 'SVR'] as $value => $label) + + {{ $label }} + + @endforeach + + + {{-- 房間卡片列表 --}} + + + @forelse($rooms as $room) + + + @if($room->type->value === \App\Enums\RoomType::SVR->value) + + @else + + @endif + + + @empty + 尚無包廂資料 + @endforelse + + + + + + \ No newline at end of file diff --git a/resources/views/livewire/admin/rooms.blade.php b/resources/views/livewire/admin/rooms.blade.php new file mode 100644 index 0000000..e4891df --- /dev/null +++ b/resources/views/livewire/admin/rooms.blade.php @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/resources/views/livewire/admin/user-form.blade.php b/resources/views/livewire/admin/user-form.blade.php deleted file mode 100644 index 1e69c56..0000000 --- a/resources/views/livewire/admin/user-form.blade.php +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/resources/views/livewire/admin/user-header.blade.php b/resources/views/livewire/admin/user-header.blade.php index 3343200..ee8c592 100644 --- a/resources/views/livewire/admin/user-header.blade.php +++ b/resources/views/livewire/admin/user-header.blade.php @@ -1,15 +1,2 @@ - - - \ No newline at end of file diff --git a/resources/views/livewire/admin/user-import-data.blade.php b/resources/views/livewire/admin/user-import-data.blade.php deleted file mode 100644 index 6415b7e..0000000 --- a/resources/views/livewire/admin/user-import-data.blade.php +++ /dev/null @@ -1,63 +0,0 @@ - - - {{-- 說明區塊 --}} - - 匯入格式說明 - 請依下列表格格式準備 Excel 或 CSV 檔案: - - - - - - 欄位名稱 - 說明 - 範例 - - - - - ??? - ??? - ??? - - - - - - - - - {{-- 檔案上傳 --}} - - - - - 系統限制:最大上傳 {{ $maxUploadSize }} - - - - - - - - - - 檔案上傳中,請稍候... - - - - - - - - - - - \ No newline at end of file diff --git a/resources/views/livewire/admin/users.blade.php b/resources/views/livewire/admin/users.blade.php index 6d3ebcb..6c5cc5f 100644 --- a/resources/views/livewire/admin/users.blade.php +++ b/resources/views/livewire/admin/users.blade.php @@ -1,6 +1,3 @@ - - - \ No newline at end of file diff --git a/resources/views/livewire/layout/admin/sidebar.blade.php b/resources/views/livewire/layout/admin/sidebar.blade.php index db1b96c..3bc6a60 100644 --- a/resources/views/livewire/layout/admin/sidebar.blade.php +++ b/resources/views/livewire/layout/admin/sidebar.blade.php @@ -8,8 +8,9 @@ new class extends Component public array $menus=[ ['label' => 'Dashboard', 'route' => 'admin.dashboard', 'icon' => 'home', 'permission' => null], ['label' => 'ActivityLog', 'route' => 'admin.activity-log', 'icon' => 'clock', 'permission' => null], - ['label' => 'Role', 'route' => 'admin.roles', 'icon' => 'user-circle', 'permission' => 'role-list'], ['label' => 'User', 'route' => 'admin.users', 'icon' => 'user-circle', 'permission' => 'user-list'], + ['label' => 'Branche', 'route' => 'admin.branches', 'icon' => 'building-library', 'permission' => 'room-list'], + ['label' => 'Room', 'route' => 'admin.rooms', 'icon' => 'film', 'permission' => 'room-list'], ]; /** @@ -29,25 +30,12 @@ new class extends Component 管理後台 - - - - - - - - - - - - - - {{ __('Profile') }} - - - + + diff --git a/routes/api.php b/routes/api.php new file mode 100644 index 0000000..daa9010 --- /dev/null +++ b/routes/api.php @@ -0,0 +1,16 @@ +group(function () { + Route::get('/profile', [AuthController::class, 'profile']); + Route::post('/room/sendSwitch', [RoomControlController::class, 'sendSwitch']); + Route::post('/room/heartbeat', [RoomControlController::class, 'StatusReport']); + Route::post('/upload-sqlite', [SqliteUploadController::class, 'upload']); +}); \ No newline at end of file diff --git a/routes/console.php b/routes/console.php index 3c9adf1..cbd4306 100644 --- a/routes/console.php +++ b/routes/console.php @@ -6,3 +6,9 @@ use Illuminate\Support\Facades\Artisan; Artisan::command('inspire', function () { $this->comment(Inspiring::quote()); })->purpose('Display an inspiring quote'); + +Schedule::command('machine_statuses:clear')->dailyAt('12:00'); // 每天凌晨 12:10 執行 +//首次部署或有新增命令時)建立或更新任務排程 Crontab +// 檢查是否已有下列 crontab 設定(crontab -e): +//分鐘 小時 日 月 星期 指令 +// * * * * * cd /Users/allen.yan/work/KTV && php artisan schedule:run >> /dev/null 2>&1 \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 1efc7ff..762acf0 100644 --- a/routes/web.php +++ b/routes/web.php @@ -21,7 +21,7 @@ require __DIR__.'/auth.php'; Route::middleware(['auth'])->prefix('admin')->name('admin.')->group(function () { Route::get('/dashboard', AdminDashboard::class)->name('dashboard'); Route::get('/activity-log', function () {return view('livewire.admin.activity-log');})->name('activity-log'); - Route::get('/roles', function () {return view('livewire.admin.roles');})->name('roles'); Route::get('/users', function () {return view('livewire.admin.users');})->name('users'); - + Route::get('/branches', function () {return view('livewire.admin.branches');})->name('branches'); + Route::get('/rooms', function () {return view('livewire.admin.rooms');})->name('rooms'); }); \ No newline at end of file diff --git a/更新後部署流程(建議步驟).ini b/更新後部署流程(建議步驟).ini new file mode 100644 index 0000000..c0b447c --- /dev/null +++ b/更新後部署流程(建議步驟).ini @@ -0,0 +1,52 @@ +✅ Laravel 更新後部署流程(建議步驟) + +1. 拉取新版程式碼 + git pull origin main + +2. 安裝依賴套件 + composer install --no-dev --optimize-autoloader + +cp .env.example .env +php artisan key:generate +npm install && npm run build + +3. 執行資料庫 migration(如有 schema 變更) + php artisan migrate + +4. 清除並重新快取設定與路由 + php artisan config:clear + php artisan config:cache + php artisan route:clear + php artisan route:cache + php artisan view:clear + php artisan view:cache + +5. (首次部署或有新增命令時)建立或更新任務排程 Crontab + 檢查是否已有下列 crontab 設定(crontab -e): + 分鐘 小時 日 月 星期 指令 + * * * * * cd /path/to/your/project && php artisan schedule:run >> /dev/null 2>&1 + 這樣 Laravel 才能自動執行你在 routes/console.php 中定義的排程任務。 + +6. (選擇性)部署完立即執行某些 Artisan 指令 + 例如你可能希望部署後立即重建一次機器狀態資料表,可以執行: + php artisan machine_statuses:clear + +7. 權限與快取設定(根據伺服器環境) + 確認 storage 和 bootstrap/cache 目錄權限正確: + chmod -R 775 storage bootstrap/cache + chown -R www-data:www-data storage bootstrap/cache + +✅ 完整部署腳本範例(可寫成 deploy.sh) + #!/bin/bash + + cd /var/www/your-project + + git pull origin main + composer install --no-dev --optimize-autoloader + php artisan migrate --force + php artisan config:cache + php artisan route:cache + php artisan view:cache + php artisan machine_statuses:clear + + echo "✅ Laravel 專案已更新並執行完成。" \ No newline at end of file diff --git a/開發手冊.ini b/開發手冊.ini index cb59e08..bf40894 100644 --- a/開發手冊.ini +++ b/開發手冊.ini @@ -79,4 +79,12 @@ composer require power-components/livewire-powergrid php artisan vendor:publish --tag=livewire-powergrid-config 建立分頁table -php artisan powergrid:create \ No newline at end of file +php artisan powergrid:create + + +php artisan make:job TransferSqliteTableJob + + +php artisan migrate:rollback +php artisan migrate +php artisan transfer:sqlite sqlite/tempUser.sqlite --sync
匯入格式說明
請依下列表格格式準備 Excel 或 CSV 檔案:
- 系統限制:最大上傳 {{ $maxUploadSize }} -