fix: refactored the codebase

This commit is contained in:
2026-08-20 23:00:47 +02:00
parent 6f47ed0674
commit a77f74911d
64 changed files with 2864 additions and 1739 deletions
+16 -17
View File
@@ -2,33 +2,32 @@
namespace App\Http\Controllers;
use App\Models\Room;
use App\Http\Resources\DashboardRoomResource;
use App\Models\User;
use App\Services\Rooms\DashboardRoomService;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class DashboardController extends Controller
{
public function __construct(
private readonly DashboardRoomService $dashboardRooms,
) {}
public function __invoke(Request $request): Response
{
$rooms = $request->user()
->ownedRooms()
->withCount('players')
->orderByRaw('closed_at IS NOT NULL')
->orderByDesc('last_activity_at')
->get()
->map(fn (Room $room) => [
'id' => $room->id,
'title' => $room->title,
'join_code' => $room->join_code,
'current_round' => $room->current_round,
'players_count' => $room->players_count,
'last_activity_at' => $room->last_activity_at?->toIso8601String(),
'closed_at' => $room->closed_at?->toIso8601String(),
]);
$user = $request->user();
abort_unless($user instanceof User, 403);
$rooms = $this->dashboardRooms->forUser($user);
return Inertia::render('Dashboard', [
'rooms' => $rooms,
'rooms' => DashboardRoomResource::collection($rooms['owned'])
->resolve($request),
'favoriteRooms' => DashboardRoomResource::collection($rooms['favorites'])
->resolve($request),
]);
}
}
@@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers;
use App\Models\Room;
use App\Models\User;
use App\Services\Rooms\FavoriteRoomService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class FavoriteRoomController extends Controller
{
public function __construct(
private readonly FavoriteRoomService $favoriteRooms,
) {}
public function __invoke(Request $request, Room $room): RedirectResponse
{
abort_if($room->trashed(), 404);
$user = $request->user();
abort_unless($user instanceof User, 401);
$this->favoriteRooms->toggle($user, $room);
return back();
}
}
+28 -252
View File
@@ -2,86 +2,44 @@
namespace App\Http\Controllers;
use App\Enums\RoomStateChange;
use App\Events\RoomStateChanged;
use App\Http\Requests\StoreRoomRequest;
use App\Models\Deck;
use App\Http\Resources\CurrentPlayerResource;
use App\Http\Resources\DeckResource;
use App\Http\Resources\RoomResource;
use App\Models\Player;
use App\Models\Room;
use App\Services\Rooms\RoomCreationService;
use App\Services\Rooms\RoomPageService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
use Inertia\Response;
class RoomController extends Controller
{
public function __construct(
private readonly RoomCreationService $roomCreation,
private readonly RoomPageService $roomPage,
) {}
public function create(Request $request): Response
{
$user = $request->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,
'decks' => DeckResource::collection(
$this->roomCreation->availableDecks($request->user()),
)->resolve($request),
'defaultPlayerName' => $request->user()?->name,
]);
}
public function store(Request $request, StoreRoomRequest $roomStoreRequest): RedirectResponse
public function store(StoreRoomRequest $request): 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 (
/** @var array{deck_id: int, player_name: string, title?: string|null} $validated */
$validated = $request->validated();
[$room, $player] = $this->roomCreation->create(
$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->user(),
);
$request->session()->put(
"room_players.{$room->id}",
@@ -90,7 +48,7 @@ class RoomController extends Controller
$redirect = to_route('room.show', $room);
if ($user === null) {
if ($request->user() === null) {
$redirect->with('room_notice', [
'guest_retention_days' => config('rooms.guest_retention_days'),
'account_retention_days' => config('rooms.account_retention_days'),
@@ -106,197 +64,15 @@ class RoomController extends Controller
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, $stateVersion, $wasReopened] = DB::transaction(function () use ($room, $user): array {
$lockedRoom = Room::query()->lockForUpdate()->findOrFail($room->id);
abort_unless($user->id === $lockedRoom->owner_id, 403);
$wasReopened = $lockedRoom->isClosed();
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(),
$lockedRoom->state_version,
$wasReopened,
];
});
$request->session()->put("room_players.{$room->id}", $player->id);
if ($wasReopened) {
RoomStateChanged::dispatch(
$room->id,
$stateVersion,
RoomStateChange::RoomReopened,
);
}
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);
$stateVersion = DB::transaction(function () use ($room, $user): int {
$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();
return $lockedRoom->state_version;
});
$request->session()->forget("room_players.{$room->id}");
RoomStateChanged::dispatch(
$room->id,
$stateVersion,
RoomStateChange::RoomDeleted,
$pageData = $this->roomPage->prepare(
$room,
$currentPlayer,
$request->user(),
);
Inertia::flash('toast', [
'type' => 'success',
'message' => 'Der Raum wurde gelöscht.',
return Inertia::render('rooms/Show', [
'room' => (new RoomResource($pageData))->resolve($request),
'currentPlayer' => (new CurrentPlayerResource($pageData))->resolve($request),
]);
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, ',', ''),
];
}
}
+11 -42
View File
@@ -2,18 +2,19 @@
namespace App\Http\Controllers;
use App\Events\PaperBallThrown;
use App\Http\Requests\ThrowPaperBallRequest;
use App\Models\Player;
use App\Models\Room;
use Illuminate\Http\Request;
use App\Services\Rooms\RoomEmoteService;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class RoomEmoteController extends Controller
{
public function store(Request $request, Room $room): Response
public function __construct(
private readonly RoomEmoteService $roomEmotes,
) {}
public function store(ThrowPaperBallRequest $request, Room $room): Response
{
$player = $request->attributes->get('currentPlayer');
@@ -22,42 +23,10 @@ class RoomEmoteController extends Controller
403,
);
$validated = $request->validate([
'target_player_id' => ['required', 'integer'],
]);
$targetPlayerId = (int) $validated['target_player_id'];
if ($targetPlayerId === $player->id) {
throw ValidationException::withMessages([
'target_player_id' => 'Du kannst dich nicht selbst abwerfen.',
]);
}
$targetExists = $room->players()
->whereKey($targetPlayerId)
->exists();
if (! $targetExists) {
throw ValidationException::withMessages([
'target_player_id' => 'Dieser Spieler sitzt nicht an diesem Tisch.',
]);
}
$rateLimitKey = "room-emote:{$room->id}:{$player->id}";
abort_if(
RateLimiter::tooManyAttempts($rateLimitKey, 10),
429,
'Bitte warte kurz, bevor du erneut wirfst.',
);
RateLimiter::hit($rateLimitKey, 10);
PaperBallThrown::dispatch(
Str::uuid()->toString(),
$room->id,
$player->id,
$targetPlayerId,
$this->roomEmotes->throwPaperBall(
$room,
$player,
(int) $request->validated('target_player_id'),
);
return response()->noContent();
@@ -0,0 +1,50 @@
<?php
namespace App\Http\Controllers;
use App\Http\Resources\ClosedRoomResource;
use App\Models\Player;
use App\Services\Rooms\RoomParticipationService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class RoomJoinController extends Controller
{
public function __construct(
private readonly RoomParticipationService $roomParticipation,
) {}
public function __invoke(Request $request): Response|RedirectResponse
{
$joinCode = strtoupper(trim((string) $request->query('join_code')));
$room = $this->roomParticipation->findByJoinCode($joinCode);
$existingPlayer = $room !== null
? $this->roomParticipation->findExistingPlayer(
$room,
$request->user(),
$request->session()->get("room_players.{$room->id}"),
)
: null;
if (
$room !== null
&& ! $room->isClosed()
&& $existingPlayer instanceof Player
&& $existingPlayer->inactive_at === null
) {
return to_route('room.show', $room);
}
return Inertia::render('rooms/Join', [
'join_code' => $joinCode,
'defaultPlayerName' => $existingPlayer instanceof Player
? $existingPlayer->name
: $request->user()?->name,
'closedRoom' => $room?->isClosed()
? (new ClosedRoomResource($room))->resolve($request)
: null,
]);
}
}
@@ -0,0 +1,52 @@
<?php
namespace App\Http\Controllers;
use App\Models\Room;
use App\Models\User;
use App\Services\Rooms\RoomLifecycleService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
class RoomLifecycleController extends Controller
{
public function __construct(
private readonly RoomLifecycleService $roomLifecycle,
) {}
public function reopen(Room $room, Request $request): RedirectResponse
{
$user = $request->user();
abort_if($room->trashed(), 404);
abort_unless($user instanceof User && $user->id === $room->owner_id, 403);
$result = $this->roomLifecycle->reopen($room, $user);
$request->session()->put(
"room_players.{$room->id}",
$result['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 instanceof User && $user->id === $room->owner_id, 403);
$this->roomLifecycle->delete($room, $user);
$request->session()->forget("room_players.{$room->id}");
Inertia::flash('toast', [
'type' => 'success',
'message' => 'Der Raum wurde gelöscht.',
]);
return to_route('dashboard');
}
}
+12 -60
View File
@@ -2,81 +2,33 @@
namespace App\Http\Controllers;
use App\Enums\RoomStateChange;
use App\Events\RoomStateChanged;
use App\Http\Requests\JoinRoomRequest;
use App\Models\Player;
use App\Models\Room;
use App\Services\Rooms\RoomParticipationService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
class RoomPlayerController extends Controller
{
public function __construct(
private readonly RoomParticipationService $roomParticipation,
) {}
public function join(JoinRoomRequest $request): RedirectResponse
{
/** @var array{join_code: string, player_name: string} $validated */
$validated = $request->validated();
$user = $request->user();
$sessionPlayerIds = $request->session()->get('room_players', []);
[$room, $player] = DB::transaction(function () use ($request, $validated, $user): array {
$room = Room::query()
->where('join_code', $validated['join_code'])
->lockForUpdate()
->firstOrFail();
if ($room->isClosed()) {
throw ValidationException::withMessages([
'join_code' => 'Dieser Raum ist geschlossen.',
]);
}
$player = $this->existingPlayer($request, $room);
if ($player === null) {
$player = $room->players()->create([
'user_id' => $user?->id,
'name' => $validated['player_name'],
'is_moderator' => false,
'last_seen_at' => now(),
]);
} else {
$player->update(['last_seen_at' => now()]);
}
$room->update([
'last_activity_at' => now(),
'state_version' => $room->state_version + 1,
]);
return [$room, $player];
});
[$room, $player] = $this->roomParticipation->join(
$validated,
$request->user(),
is_array($sessionPlayerIds) ? $sessionPlayerIds : [],
);
$request->session()->put(
"room_players.{$room->id}",
$player->id,
);
RoomStateChanged::dispatch(
$room->id,
$room->state_version,
RoomStateChange::PlayerJoined,
);
return to_route('room.show', $room);
}
private function existingPlayer(JoinRoomRequest $request, Room $room): ?Player
{
if ($request->user() !== null) {
return $room->players()
->where('user_id', $request->user()->id)
->first();
}
$playerId = $request->session()->get("room_players.{$room->id}");
return is_numeric($playerId)
? $room->players()->find((int) $playerId)
: null;
}
}
+37 -142
View File
@@ -2,156 +2,51 @@
namespace App\Http\Controllers;
use App\Enums\RoomStateChange;
use App\Events\RoomStateChanged;
use App\Models\Player;
use App\Models\Room;
use App\Models\Vote;
use App\Services\Rooms\RoundService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
class RoundController extends Controller
{
public function reset(
Request $request,
Room $room,
): RedirectResponse {
$player = $request->attributes->get('currentPlayer');
public function __construct(
private readonly RoundService $rounds,
) {}
abort_unless(
$player instanceof Player
&& $player->room_id === $room->id
&& $player->is_moderator,
403,
);
$stateVersion = DB::transaction(function () use ($room): int {
$lockedRoom = Room::query()
->lockForUpdate()
->findOrFail($room->id);
$this->ensureRoomIsOpen($lockedRoom);
Vote::query()
->where('room_id', $lockedRoom->id)
->where('round', $lockedRoom->current_round)
->delete();
$lockedRoom->update([
'revealed_at' => null,
'last_activity_at' => now(),
'state_version' => $lockedRoom->state_version + 1,
]);
return $lockedRoom->state_version;
});
RoomStateChanged::dispatch(
$room->id,
$stateVersion,
RoomStateChange::VotesReset,
);
return back();
}
public function reveal(
Request $request,
Room $room,
): RedirectResponse {
$player = $request->attributes->get('currentPlayer');
abort_unless(
$player instanceof Player
&& $player->room_id === $room->id
&& $player->is_moderator,
403,
);
$stateVersion = DB::transaction(function () use ($room): int {
$lockedRoom = Room::query()
->lockForUpdate()
->findOrFail($room->id);
$this->ensureRoomIsOpen($lockedRoom);
if ($lockedRoom->revealed_at !== null) {
throw ValidationException::withMessages([
'room' => 'Die Karten sind bereits aufgedeckt.',
]);
}
$lockedRoom->update([
'revealed_at' => now(),
'last_activity_at' => now(),
'state_version' => $lockedRoom->state_version + 1,
]);
return $lockedRoom->state_version;
});
RoomStateChanged::dispatch(
$room->id,
$stateVersion,
RoomStateChange::CardsRevealed,
);
return back();
}
public function nextRound(
Request $request,
Room $room,
): RedirectResponse {
$player = $request->attributes->get('currentPlayer');
abort_unless(
$player instanceof Player
&& $player->room_id === $room->id
&& $player->is_moderator,
403,
);
$stateVersion = DB::transaction(function () use ($room): int {
$lockedRoom = Room::query()
->lockForUpdate()
->findOrFail($room->id);
$this->ensureRoomIsOpen($lockedRoom);
if ($lockedRoom->revealed_at === null) {
throw ValidationException::withMessages([
'room' => 'Die aktuelle Runde muss zuerst aufgedeckt werden.',
]);
}
$lockedRoom->update([
'current_round' => $lockedRoom->current_round + 1,
'revealed_at' => null,
'last_activity_at' => now(),
'state_version' => $lockedRoom->state_version + 1,
]);
return $lockedRoom->state_version;
});
RoomStateChanged::dispatch(
$room->id,
$stateVersion,
RoomStateChange::NextRound,
);
return back();
}
private function ensureRoomIsOpen(Room $room): void
public function reset(Request $request, Room $room): RedirectResponse
{
if ($room->isClosed()) {
throw ValidationException::withMessages([
'room' => 'Dieser Raum ist geschlossen.',
]);
}
$this->ensureModerator($request, $room);
$this->rounds->reset($room);
return back();
}
public function reveal(Request $request, Room $room): RedirectResponse
{
$this->ensureModerator($request, $room);
$this->rounds->reveal($room);
return back();
}
public function nextRound(Request $request, Room $room): RedirectResponse
{
$this->ensureModerator($request, $room);
$this->rounds->startNext($room);
return back();
}
private function ensureModerator(Request $request, Room $room): void
{
$player = $request->attributes->get('currentPlayer');
abort_unless(
$player instanceof Player
&& $player->room_id === $room->id
&& $player->is_moderator,
403,
);
}
}
@@ -5,6 +5,9 @@ namespace App\Http\Controllers\Settings;
use App\Http\Controllers\Controller;
use App\Http\Requests\Settings\PasswordUpdateRequest;
use App\Http\Requests\Settings\TwoFactorAuthenticationRequest;
use App\Http\Resources\PasskeyResource;
use App\Models\User;
use App\Services\Settings\SecuritySettingsService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Validation\Rules\Password;
use Inertia\Inertia;
@@ -13,37 +16,34 @@ use Laravel\Fortify\Features;
class SecurityController extends Controller
{
public function __construct(
private readonly SecuritySettingsService $securitySettings,
) {}
/**
* Show the user's security settings page.
*/
public function edit(TwoFactorAuthenticationRequest $request): Response
{
$user = $request->user();
abort_unless($user instanceof User, 403);
$canManageTwoFactor = Features::canManageTwoFactorAuthentication();
$canManagePasskeys = Features::canManagePasskeys();
$props = [
'canManageTwoFactor' => Features::canManageTwoFactorAuthentication(),
'canManagePasskeys' => Features::canManagePasskeys(),
'passkeys' => Features::canManagePasskeys()
? $request->user()
->passkeys()
->select(['id', 'name', 'credential', 'created_at', 'last_used_at'])
->latest()
->get()
->map(fn ($passkey) => [
'id' => $passkey->id,
'name' => $passkey->name,
'authenticator' => $passkey->authenticator,
'created_at_diff' => $passkey->created_at->diffForHumans(),
'last_used_at_diff' => $passkey->last_used_at?->diffForHumans(),
])
->values()
->all()
: [],
'canManageTwoFactor' => $canManageTwoFactor,
'canManagePasskeys' => $canManagePasskeys,
'passkeys' => PasskeyResource::collection(
$this->securitySettings->passkeys($user, $canManagePasskeys),
)->resolve($request),
'passwordRules' => Password::defaults()->toPasswordRulesString(),
];
if (Features::canManageTwoFactorAuthentication()) {
if ($canManageTwoFactor) {
$request->ensureStateIsValid();
$props['twoFactorEnabled'] = $request->user()->hasEnabledTwoFactorAuthentication();
$props['twoFactorEnabled'] = $user->hasEnabledTwoFactorAuthentication();
$props['requiresConfirmation'] = Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm');
}
+9 -47
View File
@@ -2,18 +2,18 @@
namespace App\Http\Controllers;
use App\Enums\RoomStateChange;
use App\Events\RoomStateChanged;
use App\Http\Requests\SubmitVoteRequest;
use App\Models\Player;
use App\Models\Room;
use App\Models\Vote;
use App\Services\Rooms\VoteService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
class VoteController extends Controller
{
public function __construct(
private readonly VoteService $votes,
) {}
public function store(
SubmitVoteRequest $request,
Room $room,
@@ -26,48 +26,10 @@ class VoteController extends Controller
403,
);
$value = $request->validated('value');
$stateVersion = DB::transaction(function () use ($room, $player, $value): int {
$lockedRoom = Room::query()
->lockForUpdate()
->findOrFail($room->id);
if ($lockedRoom->isClosed()) {
throw ValidationException::withMessages([
'value' => 'Dieser Raum ist geschlossen.',
]);
}
if ($lockedRoom->revealed_at !== null) {
throw ValidationException::withMessages([
'value' => 'Die Karten wurden bereits aufgedeckt.',
]);
}
Vote::updateOrCreate(
[
'room_id' => $lockedRoom->id,
'player_id' => $player->id,
'round' => $lockedRoom->current_round,
],
[
'value' => $value,
],
);
$lockedRoom->update([
'last_activity_at' => now(),
'state_version' => $lockedRoom->state_version + 1,
]);
return $lockedRoom->state_version;
});
RoomStateChanged::dispatch(
$room->id,
$stateVersion,
RoomStateChange::VoteChanged,
$this->votes->submit(
$room,
$player,
$request->validated('value'),
);
return back();