mirror of
https://github.com/ericasch6/scrum-poker-internal.git
synced 2026-10-13 16:12:09 +00:00
Add room participant moderation
This commit is contained in:
@@ -6,6 +6,7 @@ enum RoomStateChange: string
|
||||
{
|
||||
case PlayerJoined = 'player_joined';
|
||||
case PlayerUpdated = 'player_updated';
|
||||
case PlayerKicked = 'player_kicked';
|
||||
case VoteChanged = 'vote_changed';
|
||||
case CardsRevealed = 'cards_revealed';
|
||||
case VotesReset = 'votes_reset';
|
||||
|
||||
@@ -21,6 +21,7 @@ class RoomStateChanged implements ShouldBroadcastNow
|
||||
public readonly int $version,
|
||||
public readonly RoomStateChange $change,
|
||||
public readonly ?array $state = null,
|
||||
public readonly ?int $affectedPlayerId = null,
|
||||
) {}
|
||||
|
||||
public function broadcastOn(): Channel
|
||||
@@ -34,14 +35,20 @@ class RoomStateChanged implements ShouldBroadcastNow
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{version: int, change: string, state: array<string, mixed>|null}
|
||||
* @return array{version: int, change: string, state: array<string, mixed>|null, affected_player_id?: int}
|
||||
*/
|
||||
public function broadcastWith(): array
|
||||
{
|
||||
return [
|
||||
$payload = [
|
||||
'version' => $this->version,
|
||||
'change' => $this->change->value,
|
||||
'state' => $this->state,
|
||||
];
|
||||
|
||||
if ($this->affectedPlayerId !== null) {
|
||||
$payload['affected_player_id'] = $this->affectedPlayerId;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\KickRoomPlayerRequest;
|
||||
use App\Models\Player;
|
||||
use App\Models\Room;
|
||||
use App\Models\User;
|
||||
use App\Services\Rooms\RoomParticipationService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class RoomPlayerKickController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly RoomParticipationService $roomParticipation,
|
||||
) {}
|
||||
|
||||
public function __invoke(KickRoomPlayerRequest $request, Room $room, Player $player): RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
abort_unless($user instanceof User, 403);
|
||||
|
||||
$this->roomParticipation->kick($room, $player, $user);
|
||||
|
||||
Inertia::flash('toast', [
|
||||
'type' => 'success',
|
||||
'message' => "{$player->name} wurde aus dem Raum entfernt.",
|
||||
]);
|
||||
|
||||
return back();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\Player;
|
||||
use App\Models\Room;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class KickRoomPlayerRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
$room = $this->route('room');
|
||||
$player = $this->route('player');
|
||||
|
||||
return $room instanceof Room
|
||||
&& $player instanceof Player
|
||||
&& $this->user()?->id === $room->owner_id
|
||||
&& $player->room_id === $room->id
|
||||
&& $player->user_id !== $room->owner_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<int, string>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,17 @@ class DashboardRoomResource extends JsonResource
|
||||
'current_round' => $this->resource->current_round,
|
||||
'players_count' => $this->resource->players_count,
|
||||
'active_player_count' => $this->resource->active_player_count,
|
||||
'players' => $this->resource->relationLoaded('activePlayers')
|
||||
? $this->resource->activePlayers
|
||||
->map(fn ($player): array => [
|
||||
'id' => $player->id,
|
||||
'name' => $player->name,
|
||||
'is_moderator' => $player->is_moderator,
|
||||
'is_owner' => $player->user_id === $this->resource->owner_id,
|
||||
])
|
||||
->values()
|
||||
->all()
|
||||
: [],
|
||||
'last_activity_at' => $this->resource->last_activity_at?->toIso8601String(),
|
||||
'closed_at' => $this->resource->closed_at?->toIso8601String(),
|
||||
];
|
||||
|
||||
@@ -15,6 +15,12 @@ class DashboardRoomService
|
||||
{
|
||||
$ownedRooms = $user
|
||||
->ownedRooms()
|
||||
->with([
|
||||
'activePlayers' => fn ($query) => $query
|
||||
->select(['id', 'room_id', 'user_id', 'name', 'is_moderator', 'created_at'])
|
||||
->orderByDesc('is_moderator')
|
||||
->orderBy('created_at'),
|
||||
])
|
||||
->withCount('players')
|
||||
->withCount(['activePlayers as active_player_count'])
|
||||
->orderByRaw('closed_at IS NOT NULL')
|
||||
|
||||
@@ -179,4 +179,47 @@ class RoomParticipationService
|
||||
|
||||
return $player;
|
||||
}
|
||||
|
||||
public function kick(Room $room, Player $player, User $user): void
|
||||
{
|
||||
$stateVersion = DB::transaction(function () use ($room, $player, $user): ?int {
|
||||
$lockedRoom = Room::query()
|
||||
->lockForUpdate()
|
||||
->findOrFail($room->id);
|
||||
|
||||
abort_unless($lockedRoom->owner_id === $user->id, 403);
|
||||
|
||||
$lockedPlayer = $lockedRoom->players()
|
||||
->lockForUpdate()
|
||||
->findOrFail($player->id);
|
||||
|
||||
abort_if($lockedPlayer->user_id === $lockedRoom->owner_id, 403);
|
||||
|
||||
if ($lockedPlayer->inactive_at !== null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$lockedPlayer->update([
|
||||
'inactive_at' => now(),
|
||||
]);
|
||||
$lockedRoom->update([
|
||||
'last_activity_at' => now(),
|
||||
'state_version' => $lockedRoom->state_version + 1,
|
||||
]);
|
||||
|
||||
return $lockedRoom->state_version;
|
||||
});
|
||||
|
||||
if ($stateVersion === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
RoomStateChanged::dispatch(
|
||||
$room->id,
|
||||
$stateVersion,
|
||||
RoomStateChange::PlayerKicked,
|
||||
$this->realtimeState->for($room),
|
||||
$player->id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user