From 00c4225987fc9a11e88409d6b73cc29f4569dbd0 Mon Sep 17 00:00:00 2001 From: "allen.yan" Date: Wed, 20 Aug 2025 15:26:05 +0800 Subject: [PATCH] =?UTF-8?q?202508201522=20=E8=AA=BF=E6=95=B4=E5=8C=85?= =?UTF-8?q?=E5=B8=B3=E8=A8=98=E9=8C=84=20=E5=8C=85=E5=BB=82=E5=8A=A0?= =?UTF-8?q?=E5=85=A5=E6=AD=8C=E6=9B=B2=E9=BB=9E=E6=AD=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Enums/OrderedSongStatus.php | 35 ++ .../Controllers/Api/RoomSongController.php | 301 ++++++++++++++++++ .../Controllers/RoomControlController.php | 3 +- app/Http/Requests/OrderSongRequest.php | 33 ++ app/Http/Requests/RoomSongRequest.php | 43 +++ app/Livewire/Modals/RoomDetailModal.php | 8 +- app/Livewire/Tables/RoomStatusLogTable.php | 60 +++- app/Models/OrderedSong.php | 87 +++++ app/Models/Room.php | 21 +- app/Models/RoomSession.php | 42 +++ app/Models/RoomStatusLog.php | 12 + app/Observers/RoomObserver.php | 82 +++-- ..._055312_create_room_status_logs_table.php} | 13 +- ...8_18_103818_create_room_sessions_table.php | 34 ++ ...8_18_104152_create_ordered_songs_table.php | 44 +++ resources/lang/zh-tw/enums.php | 6 + resources/lang/zh-tw/room-status-log.php | 7 + routes/api.php | 6 + 18 files changed, 801 insertions(+), 36 deletions(-) create mode 100644 app/Enums/OrderedSongStatus.php create mode 100644 app/Http/Controllers/Api/RoomSongController.php create mode 100644 app/Http/Requests/OrderSongRequest.php create mode 100644 app/Http/Requests/RoomSongRequest.php create mode 100644 app/Models/OrderedSong.php create mode 100644 app/Models/RoomSession.php rename database/migrations/{2025_05_23_055312_create_room_status_logs_table.php => 2025_08_18_055312_create_room_status_logs_table.php} (55%) create mode 100644 database/migrations/2025_08_18_103818_create_room_sessions_table.php create mode 100644 database/migrations/2025_08_18_104152_create_ordered_songs_table.php diff --git a/app/Enums/OrderedSongStatus.php b/app/Enums/OrderedSongStatus.php new file mode 100644 index 0000000..4d05b3e --- /dev/null +++ b/app/Enums/OrderedSongStatus.php @@ -0,0 +1,35 @@ + __('enums.NotPlayed'), + self::Playing => __('enums.Playing'), + self::Played => __('enums.Played'), + self::NoFile => __('enums.NoFile'), + self::Skipped => __('enums.Skipped'), + self::InsertPlayback => __('enums.InsertPlayback'), + }; + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Api/RoomSongController.php b/app/Http/Controllers/Api/RoomSongController.php new file mode 100644 index 0000000..a626d03 --- /dev/null +++ b/app/Http/Controllers/Api/RoomSongController.php @@ -0,0 +1,301 @@ +getRoomSession($request->api_token) ; + + // 取得歌曲名稱 + $song = Song::findOrFail($request->song_id); + + // 建立 OrderedSong + $orderedSong = OrderedSong::create([ + 'room_session_id' => $roomSession->id, + 'from_by' => $request->from_by, + 'status' => $request->status, + 'song_id' => $request->song_id, + 'song_name' => $song->name, + 'artist_name' => $song->str_artists_plus(), + 'ordered_at' => now(), + ]); + + // 檢查這首歌在此 session 是否第一次點 + $countInSession = OrderedSong::where('room_session_id', $roomSession->id) + ->where('song_id', $request->song_id) + ->count(); + + if ($countInSession === 1) { // 第一次點才加 + $song->increment('song_counts'); + } + + return ApiResponse::success([ + 'ordered_song' => $orderedSong, + 'next_song_name' => $this->nextSongName($roomSession), + ]); + } + /** + * @OA\Get( + * path="/api/room/ordered-songs", + * summary="取得包廂點歌列表", + * description="取得指定包廂的已播、正在播放、待播/插播歌曲列表", + * tags={"Room Control Song"}, + * security={{"Authorization":{}}}, + * @OA\Parameter(ref="#/components/parameters/ApiTokenQuery"), + * @OA\Response( + * response=200, + * description="成功取得點歌列表", + * @OA\JsonContent( + * type="object", + * @OA\Property( + * property="played", + * type="array", + * description="已播放 (Played, Skipped, NoFile)", + * @OA\Items(ref="#/components/schemas/OrderedSong") + * ), + * @OA\Property( + * property="playing", + * type="array", + * description="正在播放中 (Playing)", + * @OA\Items(ref="#/components/schemas/OrderedSong") + * ), + * @OA\Property( + * property="not_played", + * type="array", + * description="未播放 (NotPlayed, InsertPlayback)", + * @OA\Items(ref="#/components/schemas/OrderedSong") + * ) + * ) + * ), + * @OA\Response( + * response=422, + * description="驗證失敗", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="message", type="string", example="The given data was invalid."), + * @OA\Property(property="errors", type="object") + * ) + * ) + * ) + */ + public function listOrderedSongs(RoomSongRequest $request) + { + $roomSession = $this->getRoomSession($request->api_token) ; + // 已結束 (finished + canceled) + $played = OrderedSong::where('room_session_id', $roomSession->id) + ->whereIn('status', ['Played', 'Skipped', 'NoFile']) + ->orderByDesc('finished_at') + ->get(); + $playing = OrderedSong::where('room_session_id', $roomSession->id) + ->whereIn('status', ['Playing']) + ->get(); + + // 正在播 + 插播 + 待播 + $not_played = OrderedSong::where('room_session_id', $roomSession->id) + ->whereIn('status', ['Playing', 'InsertPlayback', 'NotPlayed']) + ->orderByRaw("FIELD(status, 'Playing', 'InsertPlayback', 'NotPlayed')") // playing > InsertPlayback > NotPlayed + ->orderByRaw("CASE + WHEN status = 'InsertPlayback' THEN ordered_at END DESC") // InsertPlayback 越後排越前 + ->orderByRaw("CASE + WHEN status = 'NotPlayed' THEN ordered_at END ASC") // NotPlayed 越後排越後 + ->get(); + + return ApiResponse::success([ + 'played' => $played, + 'playing' => $playing, + 'not_played' => $not_played, + ]); + } + /** + * @OA\Post( + * path="/api/room/current-song", + * summary="取得目前播放中的歌曲", + * description="回傳當前房間正在播放的歌曲資訊 (包含部分 song 欄位)", + * tags={"Room Control Song"}, + * security={{"Authorization":{}}}, + * @OA\RequestBody( + * required=true, + * @OA\JsonContent(ref="#/components/schemas/RoomSongRequest") + * ), + * @OA\Response( + * response=200, + * description="成功回傳目前播放的歌曲", + * @OA\JsonContent( + * type="object", + * @OA\Property( + * property="success", + * type="boolean", + * example=true + * ), + * @OA\Property( + * property="data", + * type="object", + * @OA\Property( + * property="current", + * ref="#/components/schemas/OrderedSongWithPartialSong" + * ) + * ) + * ) + * ) + * ) + */ + public function currentSong(RoomSongRequest $request) + { + $roomSession = $this->getRoomSession($request->api_token) ; + $current = OrderedSong::where('room_session_id', $roomSession->id) + ->where('status', 'Playing') + ->withPartialSong() + ->first(); + return ApiResponse::success([ + 'current' => $current , + ]); + } + /** + * @OA\Post( + * path="/api/room/next-song", + * summary="播放下一首歌曲", + * description="將目前播放的歌標記為結束,並將下一首設為播放中,回傳下首與下下首資訊。", + * tags={"Room Control Song"}, + * security={{"Authorization":{}}}, + * @OA\RequestBody( + * required=true, + * @OA\JsonContent(ref="#/components/schemas/RoomSongRequest") + * ), + * @OA\Response( + * response=200, + * description="成功回傳歌曲資訊", + * @OA\JsonContent( + * type="object", + * @OA\Property( + * property="current", + * type="object", + * nullable=true, + * ref="#/components/schemas/OrderedSongWithPartialSong" + * ), + * @OA\Property( + * property="next", + * type="object", + * nullable=true, + * ref="#/components/schemas/OrderedSongWithPartialSong" + * ) + * ) + * ), + * @OA\Response( + * response=401, + * description="未授權或 Token 錯誤" + * ), + * @OA\Response( + * response=404, + * description="找不到房間或歌曲" + * ) + * ) + */ + public function nextSong(RoomSongRequest $request) + { + $roomSession = $this->getRoomSession($request->api_token) ; + // 找目前正在播 + $current = OrderedSong::where('room_session_id', $roomSession->id) + ->where('status', 'Playing') + ->first(); + if ($current) { + // 把當前設為 finished,並記錄結束時間 + $current->update([ + 'status' => 'Played', + 'finished_at' => now(), + ]); + } + // 撈出候播清單(下首 + 下下首) + $queue = OrderedSong::where('room_session_id', $roomSession->id) + ->whereIn('status', ['InsertPlayback', 'NotPlayed']) + ->withPartialSong() + ->orderByRaw("FIELD(status, 'InsertPlayback', 'NotPlayed')") // InsertPlayback > NotPlayed + ->orderByRaw("CASE WHEN status = 'InsertPlayback' THEN ordered_at END DESC") // InsertPlayback 後插播的先播 + ->orderByRaw("CASE WHEN status = 'NotPlayed' THEN ordered_at END ASC") // NotPlayed 先點的先播 + ->limit(2) + ->get(); + $current =$queue->get(0); + if ($current) { + $current->update([ + 'status' => 'Playing', + 'started_at' => now(), + ]); + } + return ApiResponse::success([ + 'current' => $current,// 下首 + 'next' => $queue->get(1) ?? null,// 下下首 + ]); + } + + private function nextSongName(RoomSession $roomSession) + { + // 找下首 + $next = OrderedSong::where('room_session_id', $roomSession->id) + ->whereIn('status', ['InsertPlayback', 'NotPlayed']) + ->with('song') + ->orderByRaw("FIELD(status, 'InsertPlayback', 'NotPlayed')") + ->orderByRaw("CASE WHEN status = 'InsertPlayback' THEN ordered_at END DESC") + ->orderByRaw("CASE WHEN status = 'NotPlayed' THEN ordered_at END ASC") + ->first(); + return $next?->song?->name; + + } + /** + * 取得對應的 RoomSession + */ + private function getRoomSession($api_token): RoomSession + { + return RoomSession::where('api_token', $api_token) + ->whereIn('status', ['active', 'maintain']) + ->firstOrFail(); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/RoomControlController.php b/app/Http/Controllers/RoomControlController.php index d89c12c..6cf1dc1 100644 --- a/app/Http/Controllers/RoomControlController.php +++ b/app/Http/Controllers/RoomControlController.php @@ -265,6 +265,8 @@ class RoomControlController extends Controller return ApiResponse::error('房間未設定 IP 或 Port'); } $room->status=$validated['command']; + $room->log_source='api'; + $room->log_message='sendSwitch'; $room->started_at=$validated['started_at']; $room->ended_at=$validated['ended_at']; $room->save(); @@ -279,7 +281,6 @@ class RoomControlController extends Controller }; $data = $suffix . "," . $signal; - //dd($data); $client = new TcpSocketClient($room->internal_ip, $room->port); try { $response = $client->send($data); diff --git a/app/Http/Requests/OrderSongRequest.php b/app/Http/Requests/OrderSongRequest.php new file mode 100644 index 0000000..7beb1bc --- /dev/null +++ b/app/Http/Requests/OrderSongRequest.php @@ -0,0 +1,33 @@ + [ + 'required', + Rule::exists('room_sessions', 'api_token') + ->where(fn ($q) => $q->whereIn('status', ['active', 'maintain'])) + ], + 'song_id' => 'required|exists:songs,id', + 'status' => 'required|in:NotPlayed,InsertPlayback,Skipped', + 'from_by' => 'nullable', + ]; + } +} \ No newline at end of file diff --git a/app/Http/Requests/RoomSongRequest.php b/app/Http/Requests/RoomSongRequest.php new file mode 100644 index 0000000..257afd1 --- /dev/null +++ b/app/Http/Requests/RoomSongRequest.php @@ -0,0 +1,43 @@ + [ + 'required', + Rule::exists('room_sessions', 'api_token') + ->where(fn ($q) => $q->whereIn('status', ['active', 'maintain'])) + ] + ]; + } +} \ No newline at end of file diff --git a/app/Livewire/Modals/RoomDetailModal.php b/app/Livewire/Modals/RoomDetailModal.php index cb73f06..95dfb65 100644 --- a/app/Livewire/Modals/RoomDetailModal.php +++ b/app/Livewire/Modals/RoomDetailModal.php @@ -4,7 +4,7 @@ namespace App\Livewire\Modals; use App\Models\Room; use App\Models\Branch; - +use Illuminate\Support\Carbon; use Livewire\Component; use App\Services\ApiClient; use Illuminate\Support\Facades\Auth; @@ -36,20 +36,20 @@ class RoomDetailModal extends Component } public function startNotify() { - $data = $this->buildNotifyData('maintain', null, null); + $data = $this->buildNotifyData('maintain', Carbon::now(), null); $this->send($data); } public function stopNotify() { - $data = $this->buildNotifyData('closed', null, null); + $data = $this->buildNotifyData('closed', null, Carbon::now()); $chk =$this->send($data); } public function fireNotify() { - $data = $this->buildNotifyData('fire', null, null); + $data = $this->buildNotifyData('fire', null, Carbon::now()); $this->send($data); } diff --git a/app/Livewire/Tables/RoomStatusLogTable.php b/app/Livewire/Tables/RoomStatusLogTable.php index 6fb4d13..a4afa4a 100644 --- a/app/Livewire/Tables/RoomStatusLogTable.php +++ b/app/Livewire/Tables/RoomStatusLogTable.php @@ -2,7 +2,9 @@ namespace App\Livewire\Tables; +use App\Models\Branch; use App\Models\RoomStatusLog; +use App\Enums\RoomType; use Illuminate\Support\Carbon; use Illuminate\Database\Eloquent\Builder; use PowerComponents\LivewirePowerGrid\Button; @@ -37,28 +39,47 @@ final class RoomStatusLogTable extends PowerGridComponent public function datasource(): Builder { - return RoomStatusLog::query()->latest();; + return RoomStatusLog::with(['room', 'branch'])->latest(); } public function relationSearch(): array { - return []; + return [ + 'branch' => ['name'], + 'room' => ['name'], + 'user' => ['name'], + ]; } public function fields(): PowerGridFields { return PowerGrid::fields() ->add('id') + ->add('branch_name', function (RoomStatusLog $model) { + return $model->branch?->name; + }) + ->add('room_type', function (RoomStatusLog $model) { + return $model->room?->type->labelPowergridFilter(); + }) ->add('room_name', function (RoomStatusLog $model) { - return $model->room?->type->labelPowergridFilter().$model->room?->name; + return $model->room?->name; }) ->add('user_name', function (RoomStatusLog $model){ return $model->user?->name; }) + ->add('is_online_img', fn ($model) => + [ + $model->is_online ? 'check-circle' : 'x-circle' => [ + 'text-color' => $model->is_online ? 'text-green-600' : 'text-red-600', + ], + ]) ->add('status_str',function (RoomStatusLog $model){ return $model->status->labelPowergridFilter(); }) + ->add('started_at') + ->add('ended_at') ->add('message') + ->add('source') ->add('created_at'); } @@ -66,17 +87,48 @@ final class RoomStatusLogTable extends PowerGridComponent { $column=[]; $column[]=Column::make(__('room-status-log.id'), 'id'); - $column[]=Column::make(__('room-status-log.room'), 'room_name'); + $column[]=Column::make(__('room-status-log.branch'), 'branch_name'); + $column[]=Column::make(__('room-status-log.room_type'), 'room_type'); + $column[]=Column::make(__('room-status-log.room_name'), 'room_name'); $column[]=Column::make(__('room-status-log.user'), 'user_name'); + $column[]=Column::make(__('room-status-log.is_online'), 'is_online_img')->template(); $column[]=Column::make(__('room-status-log.status'), 'status_str'); + $column[]=Column::make(__('room-status-log.started_at'), 'started_at'); + $column[]=Column::make(__('room-status-log.ended_at'), 'ended_at'); $column[]=Column::make(__('room-status-log.message'), 'message'); + $column[]=Column::make(__('room-status-log.source'), 'source'); $column[]=Column::make(__('room-status-log.created_at'), 'created_at'); return $column; } + public function rowTemplates(): array + { + return [ + 'check-circle' => ' + + ', + 'x-circle' => ' + + ', + ]; + } public function filters(): array { + $branches = Branch::query()->orderBy('name')->get()->map(fn($branch) => (object)[ + 'value' => $branch->id, + 'label' => $branch->name, + ]); + //dd($branches); return [ + Filter::enumSelect('branch_name','branch_id') + ->datasource($branches) + ->optionLabel('label'), + Filter::inputText('room_type') + ->placeholder('輸入"pc","svr"') + ->filterRelation('room','type'), + Filter::inputText('room_name') + ->placeholder('輸入包廂名稱') + ->filterRelation('room','name') ]; } } diff --git a/app/Models/OrderedSong.php b/app/Models/OrderedSong.php new file mode 100644 index 0000000..fb21747 --- /dev/null +++ b/app/Models/OrderedSong.php @@ -0,0 +1,87 @@ + 'datetime', + 'started_at' => 'datetime', + 'finished_at' => 'datetime', + 'status' => \App\Enums\OrderedSongStatus::class, + ]; + + + public function session() + { + return $this->belongsTo(RoomSession::class, 'room_session_id'); + } + + public function song() + { + return $this->belongsTo(Song::class); + } + public function scopeWithPartialSong($query) + { + return $query->with([ + 'song' => function ($q) { + $q->select('id', 'name','filename','db_change','vocal','situation'); // 精簡版 + } + ]); + } +} diff --git a/app/Models/Room.php b/app/Models/Room.php index 4721b71..6acbb83 100644 --- a/app/Models/Room.php +++ b/app/Models/Room.php @@ -2,7 +2,6 @@ namespace App\Models; -use Illuminate\Support\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use App\Traits\LogsModelActivity; @@ -22,11 +21,15 @@ use App\Traits\LogsModelActivity; * @OA\Property(property="ended_at", type="string", format="date-time", example=null), * ) */ + class Room extends Model { /** @use HasFactory<\Database\Factories\ArtistFactory> */ use HasFactory, LogsModelActivity; + public string $log_message = 'BranchForm-add'; + public string $log_source = 'manual'; + protected $fillable = [ 'branch_id', 'floor', @@ -39,6 +42,12 @@ class Room extends Model 'started_at', 'ended_at', ]; + protected $attributes = [ + 'type' => \App\Enums\RoomType::Unset, + 'floor' => 1, + 'is_online' => false, + 'status' => \App\Enums\RoomStatus::Error, + ]; //protected $hidden = [ // 'internal_ip', @@ -55,13 +64,12 @@ class Room extends Model '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; + $str = $this->started_at; } return $str; } @@ -77,8 +85,15 @@ class Room extends Model public function branch() { return $this->belongsTo(Branch::class); } + public function full_name(){ + return $this->type->labelPowergridFilter().$this->name; + } public function statusLogs() { return $this->hasMany(RoomStatusLog::class); } + public function sessions() + { + return $this->hasMany(RoomSession::class); + } } diff --git a/app/Models/RoomSession.php b/app/Models/RoomSession.php new file mode 100644 index 0000000..7b17096 --- /dev/null +++ b/app/Models/RoomSession.php @@ -0,0 +1,42 @@ + 'datetime', + 'ended_at' => 'datetime', + ]; + + // 狀態常數 + public const STATUS_ACTIVE = 'active'; + public const STATUS_CLOSED = 'closed'; + public const STATUS_FORCE_CLOSED = 'force_closed'; + public const STATUS_FIRE_CLOSED = 'fire_closed'; + + // 模式常數 + public const MODE_NORMAL = 'normal'; + public const MODE_VIP = 'vip'; + public const MODE_TEST = 'test'; + + public function room() + { + return $this->belongsTo(Room::class); + } +} diff --git a/app/Models/RoomStatusLog.php b/app/Models/RoomStatusLog.php index 1c7dca5..7c7f7b2 100644 --- a/app/Models/RoomStatusLog.php +++ b/app/Models/RoomStatusLog.php @@ -13,18 +13,30 @@ class RoomStatusLog extends Model protected $fillable = [ + 'branch_id', 'room_id', 'user_id', + 'is_online', 'status', + 'started_at', + 'ended_at', 'message', + 'source', ]; protected $casts = [ + 'is_online' => 'boolean', 'status' => \App\Enums\RoomStatus::class, + 'started_at' => 'datetime', + 'ended_at' => 'datetime', + ]; public function user(){ return $this->belongsTo(User::class); } + public function branch() { + return $this->belongsTo(Branch::class); + } public function room() { return $this->belongsTo(Room::class); } diff --git a/app/Observers/RoomObserver.php b/app/Observers/RoomObserver.php index d8a3649..e7bd0b9 100644 --- a/app/Observers/RoomObserver.php +++ b/app/Observers/RoomObserver.php @@ -3,6 +3,7 @@ namespace App\Observers; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Arr; use App\Models\Room; use App\Models\RoomStatusLog; @@ -14,7 +15,8 @@ class RoomObserver */ public function created(Room $room): void { - // + // 建立初始 log + $this->createStatusLog($room); } /** @@ -23,13 +25,33 @@ class RoomObserver public function updated(Room $room): void { // 檢查是否有變更狀態 - if ($room->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, - ]); + if ($room->wasChanged()) { + $this->createStatusLog($room); + } + if ($room->isDirty('status')) { + $now = now(); + + // 找到最後一筆未結束 session + $lastSession = $room->sessions()->whereNull('ended_at')->latest('started_at')->first(); + + if ($lastSession) { + // 結束上一筆 session + $lastSession->update([ + 'status' => $room->status->value, + 'ended_at' => $now, + ]); + } + + // 如果狀態是 active 或 maintain,開新 session + if (in_array($room->status->value, ['active', 'maintain'])) { + $mode = $room->status->value === 'active' ? 'normal' : 'test'; + $room->sessions()->create([ + 'mode' => $mode, + 'status' => $room->status->value, + 'started_at' => $now, + 'api_token' => bin2hex(random_bytes(32)), + ]); + } } } @@ -38,22 +60,38 @@ class RoomObserver */ public function deleted(Room $room): void { - // + $message = sprintf( + "%s:%s%s (%s:%s) 已刪除", + $room->branch->name, + $room->type->value, + $room->name, + $room->internal_ip, + $room->port + ); + $this->createStatusLog($room,$message); + } + + /** + * 建立 RoomStatusLog + */ + private function createStatusLog(Room $room,$log_message =null): void + { + $message=($log_message !=null)?$log_message:$room->log_message ?? ''; + RoomStatusLog::create([ + 'branch_id' => $room->branch->id, + 'room_id' => $room->id, + 'user_id' => Auth::id() ?? 0, + 'is_online' => $room->is_online, + 'status' => $room->status, + 'started_at' => $room->started_at, + 'ended_at' => $room->ended_at, + 'message' => $message, + 'source' => $this->getSource($room), + ]); } - /** - * Handle the Room "restored" event. - */ - public function restored(Room $room): void + private function getSource(Room $room): string { - // - } - - /** - * Handle the Room "force deleted" event. - */ - public function forceDeleted(Room $room): void - { - // + return app()->runningInConsole() ? 'system' : ($room->log_source ?? 'manual'); } } diff --git a/database/migrations/2025_05_23_055312_create_room_status_logs_table.php b/database/migrations/2025_08_18_055312_create_room_status_logs_table.php similarity index 55% rename from database/migrations/2025_05_23_055312_create_room_status_logs_table.php rename to database/migrations/2025_08_18_055312_create_room_status_logs_table.php index a9f91b3..37bb00a 100644 --- a/database/migrations/2025_05_23_055312_create_room_status_logs_table.php +++ b/database/migrations/2025_08_18_055312_create_room_status_logs_table.php @@ -13,10 +13,19 @@ return new class extends Migration { Schema::create('room_status_logs', function (Blueprint $table) { $table->id(); - $table->foreignId('room_id')->nullable(); - $table->foreignId('user_id')->nullable(); // 操作者,可為 null(系統) + $table->unsignedBigInteger('branch_id'); + $table->unsignedBigInteger('room_id'); + $table->unsignedBigInteger('user_id'); + $table->tinyInteger('is_online')->default(0); $table->enum('status', ['active', 'closed','fire', 'error', 'maintain']); + $table->timestamp('started_at')->nullable(); + $table->timestamp('ended_at')->nullable(); $table->text('message')->nullable(); // 可填異常原因或操作說明 + $table->enum('source', ['system', 'manual', 'api'])->default('manual'); + $table->index('branch_id'); + $table->index('room_id'); + $table->index('user_id'); + $table->index('started_at'); $table->timestamps(); }); } diff --git a/database/migrations/2025_08_18_103818_create_room_sessions_table.php b/database/migrations/2025_08_18_103818_create_room_sessions_table.php new file mode 100644 index 0000000..664ca15 --- /dev/null +++ b/database/migrations/2025_08_18_103818_create_room_sessions_table.php @@ -0,0 +1,34 @@ +id(); + $table->foreignId('room_id')->constrained()->cascadeOnDelete(); + $table->enum('status', ['active', 'closed','fire', 'error', 'maintain'])->default('error'); + $table->timestamp('started_at')->nullable(); + $table->timestamp('ended_at')->nullable(); + $table->enum('mode', ['normal', 'test'])->default('normal'); + $table->string('close_reason')->nullable(); + $table->string('api_token', 64)->unique()->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('room_sessions'); + } +}; diff --git a/database/migrations/2025_08_18_104152_create_ordered_songs_table.php b/database/migrations/2025_08_18_104152_create_ordered_songs_table.php new file mode 100644 index 0000000..eb3cf77 --- /dev/null +++ b/database/migrations/2025_08_18_104152_create_ordered_songs_table.php @@ -0,0 +1,44 @@ +id(); + $table->foreignId('room_session_id')->constrained('room_sessions')->cascadeOnDelete(); + $table->string('from_by')->nullable(); + + // 歌曲資訊 + $table->unsignedBigInteger('song_id'); + $table->string('song_name')->nullable(); + $table->string('artist_name')->nullable(); + + + // 狀態:未播 / 播放中 / 已播 / 刪除 + $table->enum('status', ['NotPlayed', 'Playing', 'Played', 'NoFile', 'Skipped' , 'InsertPlayback'])->default('NotPlayed'); + + // 播放流程 + $table->timestamp('ordered_at')->useCurrent(); // 點歌時間 + $table->timestamp('started_at')->nullable(); // 開始播放時間 + $table->timestamp('finished_at')->nullable(); // 播放結束時間 + + + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('ordered_songs'); + } +}; diff --git a/resources/lang/zh-tw/enums.php b/resources/lang/zh-tw/enums.php index 2e06d66..9266a12 100644 --- a/resources/lang/zh-tw/enums.php +++ b/resources/lang/zh-tw/enums.php @@ -2,6 +2,12 @@ return [ 'Unset' => '未定義', + 'NotPlayed' =>'未播放', + 'Playing' =>'播放中', + 'Played' =>'播畢', + 'NoFile' =>'無文件', + 'Skipped' =>'刪除', + 'InsertPlayback' =>'插播', 'user.gender.Male' =>'男', 'user.gender.Female' =>'女', 'user.gender.Other' =>'其他', diff --git a/resources/lang/zh-tw/room-status-log.php b/resources/lang/zh-tw/room-status-log.php index 40ae803..b875073 100644 --- a/resources/lang/zh-tw/room-status-log.php +++ b/resources/lang/zh-tw/room-status-log.php @@ -4,10 +4,17 @@ return [ 'list' => '包廂狀態紀錄', 'id' => '編號', + 'branch' => '分店', 'room' => '包廂', + 'room_type' => '包廂類別', + 'room_name' => '包廂名稱', 'user' => '操成者', + 'is_online' => '在線?', 'status' => '狀態', + 'started_at' => '開始於', + 'ended_at' => '結束於', 'message' => '紀錄', + 'source' => '來源', 'created_at' => '建立於' ]; \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index daa9010..bc8c465 100644 --- a/routes/api.php +++ b/routes/api.php @@ -12,5 +12,11 @@ Route::middleware('auth:sanctum')->group(function () { Route::get('/profile', [AuthController::class, 'profile']); Route::post('/room/sendSwitch', [RoomControlController::class, 'sendSwitch']); Route::post('/room/heartbeat', [RoomControlController::class, 'StatusReport']); + + Route::post('/room/order-song', [RoomSongController::class, 'orderSong']); + Route::get ('/room/ordered-songs', [RoomSongController::class, 'listOrderedSongs']); + Route::post ('/room/current-song', [RoomSongController::class, 'currentSong']); + Route::post ('/room/next-song', [RoomSongController::class, 'nextSong']); + Route::post('/upload-sqlite', [SqliteUploadController::class, 'upload']); }); \ No newline at end of file