user(); $decks = Deck::query() ->where(function ($query) use ($user) { $query->where('is_system', true); if ($user !== null) { $query->orWhere('user_id', $user->id); } }) ->orderBy('name') ->get([ 'id', 'name', 'cards', ]); return Inertia::render('rooms/Create', [ 'decks' => $decks, 'defaultPlayerName' => $user?->name, ]); } public function store(Request $request, StoreRoomRequest $roomStoreRequest): RedirectResponse { $validated = $roomStoreRequest->validated(); $user = $request->user(); $deck = Deck::query() ->whereKey($validated['deck_id']) ->where(function ($query) use ($user) { $query->where('is_system', true); if ($user !== null) { $query->orWhere('user_id', $user->id); } }) ->firstOrFail(); [$room, $player] = DB::transaction(function () use ( $validated, $deck, $user, ) { $room = Room::create([ 'owner_id' => $user?->id, 'title' => $validated['title'] ?? null, 'last_activity_at' => now(), 'deck_config' => [ 'id' => $deck->id, 'name' => $deck->name, 'cards' => $deck->cards, ], ]); $player = $room->players()->create([ 'user_id' => $user?->id, 'name' => $validated['player_name'], 'is_moderator' => true, 'last_seen_at' => now(), ]); return [$room, $player]; }); $request->session()->put( "room_players.{$room->id}", $player->id, ); $redirect = to_route('room.show', $room); if ($user === null) { $redirect->with('room_notice', [ 'guest_retention_days' => config('rooms.guest_retention_days'), 'account_retention_days' => config('rooms.account_retention_days'), ]); } return $redirect; } public function show(Room $room, Request $request): Response { $currentPlayer = $request->attributes->get('currentPlayer'); abort_unless($currentPlayer instanceof Player, 403); $room->load([ 'players' => fn ($query) => $query ->select(['id', 'room_id', 'name', 'is_moderator', 'created_at']) ->orderByDesc('is_moderator') ->orderBy('created_at'), 'players.votes' => fn ($query) => $query ->where('room_id', $room->id) ->where('round', $room->current_round), ]); $players = $room->players->map(function (Player $player) use ($room) { $vote = $player->votes->first(); return [ 'id' => $player->id, 'name' => $player->name, 'is_moderator' => $player->is_moderator, 'has_voted' => $vote !== null, 'vote' => $room->revealed_at !== null ? $vote?->value : null, ]; }); $currentVote = $room->players ->firstWhere('id', $currentPlayer->id) ?->votes ->first(); $statistics = $room->revealed_at !== null ? $this->currentRoundStatistics($room) : null; return Inertia::render('rooms/Show', [ 'room' => [ 'id' => $room->id, 'title' => $room->title, 'join_code' => $room->join_code, 'current_round' => $room->current_round, 'revealed' => $room->revealed_at !== null, 'state_version' => $room->state_version, 'statistics' => $statistics, 'deck' => [ 'name' => $room->deck_config['name'], 'cards' => $room->deck_config['cards'], ], 'players' => $players, ], 'currentPlayer' => [ 'id' => $currentPlayer->id, 'name' => $currentPlayer->name, 'is_moderator' => $currentPlayer->is_moderator, 'current_vote' => $currentVote?->value, ], ]); } public function join(Request $request): Response { $joinCode = strtoupper(trim((string) $request->query('join_code'))); $room = $joinCode !== '' ? Room::query()->where('join_code', $joinCode)->first() : null; $existingPlayer = $room !== null && $request->user() !== null ? $room->players()->where('user_id', $request->user()->id)->first() : null; return Inertia::render('rooms/Join', [ 'join_code' => $joinCode, 'defaultPlayerName' => $existingPlayer instanceof Player ? $existingPlayer->name : $request->user()?->name, 'closedRoom' => $room?->isClosed() ? [ 'title' => $room->title, 'join_code' => $room->join_code, ] : null, ]); } public function reopen(Room $room, Request $request): RedirectResponse { $user = $request->user(); abort_if($room->trashed(), 404); abort_unless($user !== null && $user->id === $room->owner_id, 403); $player = DB::transaction(function () use ($room, $user) { $lockedRoom = Room::query()->lockForUpdate()->findOrFail($room->id); abort_unless($user->id === $lockedRoom->owner_id, 403); if ($lockedRoom->isClosed()) { $lockedRoom->update([ 'current_round' => $lockedRoom->current_round + 1, 'revealed_at' => null, 'closed_at' => null, 'last_activity_at' => now(), 'state_version' => $lockedRoom->state_version + 1, ]); } return $lockedRoom->players() ->where('user_id', $user->id) ->firstOrFail(); }); $request->session()->put("room_players.{$room->id}", $player->id); return to_route('room.show', $room); } public function destroy(Room $room, Request $request): RedirectResponse { $user = $request->user(); abort_if($room->trashed(), 404); abort_unless($user !== null && $user->id === $room->owner_id, 403); DB::transaction(function () use ($room, $user): void { $lockedRoom = Room::withTrashed() ->lockForUpdate() ->findOrFail($room->id); abort_if($lockedRoom->trashed(), 404); abort_unless($user->id === $lockedRoom->owner_id, 403); $lockedRoom->update([ 'state_version' => $lockedRoom->state_version + 1, ]); $lockedRoom->delete(); }); $request->session()->forget("room_players.{$room->id}"); Inertia::flash('toast', [ 'type' => 'success', 'message' => 'Der Raum wurde gelöscht.', ]); return to_route('dashboard'); } /** * @return array{average: string|null, median: string|null} */ private function currentRoundStatistics(Room $room): array { $values = []; foreach ($room->players as $player) { $vote = $player->votes->first(); if ($vote !== null && is_numeric($vote->value)) { $values[] = (float) $vote->value; } } sort($values, SORT_NUMERIC); $count = count($values); if ($count === 0) { return ['average' => null, 'median' => null]; } $middle = intdiv($count, 2); $median = $count % 2 === 1 ? $values[$middle] : ($values[$middle - 1] + $values[$middle]) / 2; return [ 'average' => number_format(array_sum($values) / $count, 2, ',', ''), 'median' => number_format($median, 2, ',', ''), ]; } }