Files
scrum-poker-internal/app/Services/Rooms/RoomParticipationService.php
T
2026-09-02 09:37:11 +02:00

183 lines
5.4 KiB
PHP

<?php
namespace App\Services\Rooms;
use App\Enums\RoomStateChange;
use App\Events\RoomStateChanged;
use App\Models\Player;
use App\Models\Room;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
class RoomParticipationService
{
public function __construct(
private readonly RoomRealtimeStateService $realtimeState,
) {}
public function findByJoinCode(string $joinCode): ?Room
{
if ($joinCode === '') {
return null;
}
return Room::query()
->where('join_code', $joinCode)
->first();
}
public function findExistingPlayer(
Room $room,
?User $user,
mixed $sessionPlayerId,
): ?Player {
if ($user !== null) {
return $room->players()
->where('user_id', $user->id)
->first();
}
if (! is_numeric($sessionPlayerId)) {
return null;
}
return $room->players()->find((int) $sessionPlayerId);
}
/**
* @param array{join_code: string, player_name: string} $data
* @param array<string, mixed> $sessionPlayerIds
* @return array{Room, Player}
*/
public function join(array $data, ?User $user, array $sessionPlayerIds): array
{
[$room, $player] = DB::transaction(function () use ($data, $user, $sessionPlayerIds): array {
$room = Room::query()
->where('join_code', $data['join_code'])
->lockForUpdate()
->firstOrFail();
if ($room->isClosed()) {
throw ValidationException::withMessages([
'join_code' => 'Dieser Raum ist geschlossen.',
]);
}
$player = $this->findExistingPlayer(
$room,
$user,
$sessionPlayerIds[$room->id] ?? null,
);
if ($player === null) {
$player = $room->players()->create([
'user_id' => $user?->id,
'name' => $data['player_name'],
'is_moderator' => false,
'last_seen_at' => now(),
]);
} else {
$player->update([
'last_seen_at' => now(),
'inactive_at' => null,
]);
}
$room->update([
'last_activity_at' => now(),
'state_version' => $room->state_version + 1,
]);
return [$room, $player];
});
RoomStateChanged::dispatch(
$room->id,
$room->state_version,
RoomStateChange::PlayerJoined,
$this->realtimeState->for($room),
);
return [$room, $player];
}
public function joinEmbedded(Room $room, User $user, string $playerName): Player
{
[$player, $change, $stateVersion] = DB::transaction(function () use ($room, $user, $playerName): array {
$lockedRoom = Room::query()
->lockForUpdate()
->findOrFail($room->id);
if ($lockedRoom->isClosed()) {
throw ValidationException::withMessages([
'join_code' => 'Dieser Raum ist geschlossen.',
]);
}
$nameIsTaken = $lockedRoom->players()
->where('name', $playerName)
->where(function ($query) use ($user): void {
$query->whereNull('user_id')
->orWhere('user_id', '!=', $user->id);
})
->exists();
if ($nameIsTaken) {
throw ValidationException::withMessages([
'player_name' => 'Dieser Anzeigename ist in dem Raum bereits vorhanden.',
]);
}
$player = $lockedRoom->players()
->where('user_id', $user->id)
->first();
$change = null;
if ($player === null) {
$player = $lockedRoom->players()->create([
'user_id' => $user->id,
'name' => $playerName,
'is_moderator' => false,
'last_seen_at' => now(),
]);
$change = RoomStateChange::PlayerJoined;
} else {
if ($player->inactive_at !== null) {
$change = RoomStateChange::PlayerJoined;
} elseif ($player->name !== $playerName) {
$change = RoomStateChange::PlayerUpdated;
}
$player->update([
'name' => $playerName,
'last_seen_at' => now(),
'inactive_at' => null,
]);
}
if ($change === null) {
return [$player, null, null];
}
$lockedRoom->update([
'last_activity_at' => now(),
'state_version' => $lockedRoom->state_version + 1,
]);
return [$player, $change, $lockedRoom->state_version];
});
if ($change instanceof RoomStateChange && is_int($stateVersion)) {
RoomStateChanged::dispatch(
$room->id,
$stateVersion,
$change,
$this->realtimeState->for($room),
);
}
return $player;
}
}