allow anyone controll room option

This commit is contained in:
2026-08-27 15:41:21 +02:00
parent b2c1d67283
commit 72152e2d45
16 changed files with 328 additions and 11 deletions
+1
View File
@@ -9,6 +9,7 @@ enum RoomStateChange: string
case CardsRevealed = 'cards_revealed';
case VotesReset = 'votes_reset';
case NextRound = 'next_round';
case PermissionsUpdated = 'permissions_updated';
case RoomReopened = 'room_reopened';
case RoomClosed = 'room_closed';
case RoomDeleted = 'room_deleted';
@@ -0,0 +1,25 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\UpdateRoomPermissionsRequest;
use App\Models\Room;
use App\Services\Rooms\RoomPermissionService;
use Illuminate\Http\Response;
class RoomPermissionController extends Controller
{
public function __construct(
private readonly RoomPermissionService $roomPermissions,
) {}
public function __invoke(UpdateRoomPermissionsRequest $request, Room $room): Response
{
$this->roomPermissions->updateParticipantControls(
$room,
(bool) $request->validated('allow_participant_controls'),
);
return response()->noContent();
}
}
+5 -5
View File
@@ -16,7 +16,7 @@ class RoundController extends Controller
public function reset(Request $request, Room $room): Response
{
$this->ensureModerator($request, $room);
$this->ensureCanControlRound($request, $room);
$this->rounds->reset($room);
return response()->noContent();
@@ -24,7 +24,7 @@ class RoundController extends Controller
public function reveal(Request $request, Room $room): Response
{
$this->ensureModerator($request, $room);
$this->ensureCanControlRound($request, $room);
$this->rounds->reveal($room);
return response()->noContent();
@@ -32,20 +32,20 @@ class RoundController extends Controller
public function nextRound(Request $request, Room $room): Response
{
$this->ensureModerator($request, $room);
$this->ensureCanControlRound($request, $room);
$this->rounds->startNext($room);
return response()->noContent();
}
private function ensureModerator(Request $request, Room $room): void
private function ensureCanControlRound(Request $request, Room $room): void
{
$player = $request->attributes->get('currentPlayer');
abort_unless(
$player instanceof Player
&& $player->room_id === $room->id
&& $player->is_moderator,
&& ($player->is_moderator || $room->allow_participant_controls),
403,
);
}
@@ -0,0 +1,37 @@
<?php
namespace App\Http\Requests;
use App\Models\Player;
use App\Models\Room;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class UpdateRoomPermissionsRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
$room = $this->route('room');
$player = $this->attributes->get('currentPlayer');
return $room instanceof Room
&& $player instanceof Player
&& $player->room_id === $room->id
&& $player->is_moderator;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'allow_participant_controls' => ['required', 'boolean:strict'],
];
}
}
+1
View File
@@ -26,6 +26,7 @@ class RoomResource extends JsonResource
'current_round' => $room->current_round,
'revealed' => $room->revealed_at !== null,
'state_version' => $room->state_version,
'allow_participant_controls' => $room->allow_participant_controls,
'statistics' => $pageData->statistics,
'deck' => (new RoomDeckResource($room->deck_config))->resolve($request),
'players' => $room->players
+7
View File
@@ -19,6 +19,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property int $current_round
* @property CarbonImmutable|null $revealed_at
* @property int $state_version
* @property bool $allow_participant_controls
* @property CarbonImmutable|null $last_activity_at
* @property CarbonImmutable|null $closed_at
* @property CarbonImmutable|null $deleted_at
@@ -35,6 +36,7 @@ class Room extends Model
'deck_config',
'revealed_at',
'state_version',
'allow_participant_controls',
'last_activity_at',
'closed_at',
];
@@ -44,6 +46,7 @@ class Room extends Model
return [
'current_round' => 'integer',
'state_version' => 'integer',
'allow_participant_controls' => 'boolean',
'revealed_at' => 'immutable_datetime',
'last_activity_at' => 'immutable_datetime',
'closed_at' => 'immutable_datetime',
@@ -51,6 +54,10 @@ class Room extends Model
];
}
protected $attributes = [
'allow_participant_controls' => false,
];
protected static function booted(): void
{
static::creating(function (Room $room) {
@@ -0,0 +1,47 @@
<?php
namespace App\Services\Rooms;
use App\Enums\RoomStateChange;
use App\Events\RoomStateChanged;
use App\Models\Room;
use Illuminate\Support\Facades\DB;
class RoomPermissionService
{
public function __construct(
private readonly RoomRealtimeStateService $realtimeState,
) {}
public function updateParticipantControls(Room $room, bool $allowParticipantControls): void
{
[$stateVersion, $wasChanged] = DB::transaction(function () use ($room, $allowParticipantControls): array {
$lockedRoom = Room::query()
->lockForUpdate()
->findOrFail($room->id);
if ($lockedRoom->allow_participant_controls === $allowParticipantControls) {
return [$lockedRoom->state_version, false];
}
$lockedRoom->update([
'allow_participant_controls' => $allowParticipantControls,
'last_activity_at' => now(),
'state_version' => $lockedRoom->state_version + 1,
]);
return [$lockedRoom->state_version, true];
});
if (! $wasChanged) {
return;
}
RoomStateChanged::dispatch(
$room->id,
$stateVersion,
RoomStateChange::PermissionsUpdated,
$this->realtimeState->for($room),
);
}
}
@@ -15,6 +15,7 @@ class RoomRealtimeStateService
* current_round: int,
* revealed: bool,
* state_version: int,
* allow_participant_controls: bool,
* statistics: array{average: string|null, median: string|null}|null,
* players: array<int, array{
* id: int,
@@ -44,6 +45,7 @@ class RoomRealtimeStateService
'current_round' => $room->current_round,
'revealed' => $room->revealed_at !== null,
'state_version' => $room->state_version,
'allow_participant_controls' => $room->allow_participant_controls,
'statistics' => $this->statistics->currentRound($room),
'players' => $room->players
->map(function ($player) use ($room): array {