From d2042fb95b0bec2cc119106b98eafb7fcce98d17 Mon Sep 17 00:00:00 2001 From: Twyco Date: Thu, 27 Aug 2026 15:38:12 +0200 Subject: [PATCH] removed throttle for mod --- app/Services/Rooms/RoomEmoteService.php | 16 +++--- resources/js/components/rooms/RoomTable.vue | 5 +- resources/js/composables/usePaperBallToss.ts | 21 +++++--- resources/js/types/emote.ts | 1 + tests/Feature/RoomEmoteTest.php | 52 ++++++++++++++++++++ 5 files changed, 80 insertions(+), 15 deletions(-) diff --git a/app/Services/Rooms/RoomEmoteService.php b/app/Services/Rooms/RoomEmoteService.php index d23818c..35c0281 100644 --- a/app/Services/Rooms/RoomEmoteService.php +++ b/app/Services/Rooms/RoomEmoteService.php @@ -29,15 +29,17 @@ class RoomEmoteService ]); } - $rateLimitKey = "room-emote:{$room->id}:{$player->id}"; + if (! $player->isModerator()) { + $rateLimitKey = "room-emote:{$room->id}:{$player->id}"; - abort_if( - RateLimiter::tooManyAttempts($rateLimitKey, 10), - 429, - 'Bitte warte kurz, bevor du erneut wirfst.', - ); + abort_if( + RateLimiter::tooManyAttempts($rateLimitKey, 10), + 429, + 'Bitte warte kurz, bevor du erneut wirfst.', + ); - RateLimiter::hit($rateLimitKey, 10); + RateLimiter::hit($rateLimitKey, 10); + } PaperBallThrown::dispatch( Str::uuid()->toString(), diff --git a/resources/js/components/rooms/RoomTable.vue b/resources/js/components/rooms/RoomTable.vue index 38067ad..c940434 100644 --- a/resources/js/components/rooms/RoomTable.vue +++ b/resources/js/components/rooms/RoomTable.vue @@ -21,6 +21,7 @@ const { activePaperBalls, hitPlayerIds, throwingAtPlayerId, throwPaperBall } = usePaperBallToss({ roomId: () => props.room.id, currentPlayerId: () => props.currentPlayer.id, + currentPlayerIsModerator: () => props.currentPlayer.is_moderator, board: gameBoard, }); @@ -115,7 +116,9 @@ const voteCount = computed( :revealed="room.revealed" :presenter-mode="presenterMode" :is-paper-ball-hit="hitPlayerIds.includes(player.id)" - :throw-disabled="throwingAtPlayerId !== null" + :throw-disabled=" + !currentPlayer.is_moderator && throwingAtPlayerId !== null + " @throw-paper-ball="throwPaperBall" /> diff --git a/resources/js/composables/usePaperBallToss.ts b/resources/js/composables/usePaperBallToss.ts index 21b71e6..cc1ddbc 100644 --- a/resources/js/composables/usePaperBallToss.ts +++ b/resources/js/composables/usePaperBallToss.ts @@ -17,6 +17,7 @@ const THROW_BUTTON_COOLDOWN = 550; export function usePaperBallToss({ roomId, currentPlayerId, + currentPlayerIsModerator, board, }: PaperBallTossOptions) { const activePaperBalls = ref([]); @@ -35,9 +36,11 @@ export function usePaperBallToss({ } async function throwPaperBall(targetPlayerId: number): Promise { + const shouldApplyCooldown = !currentPlayerIsModerator(); + if ( targetPlayerId === currentPlayerId() || - throwingAtPlayerId.value !== null + (shouldApplyCooldown && throwingAtPlayerId.value !== null) ) { return; } @@ -52,7 +55,9 @@ export function usePaperBallToss({ return; } - throwingAtPlayerId.value = targetPlayerId; + if (shouldApplyCooldown) { + throwingAtPlayerId.value = targetPlayerId; + } try { const response = await fetch(throwPaperBallRoute(roomId()).url, { @@ -91,11 +96,13 @@ export function usePaperBallToss({ } catch { toast.error('Der Papierknäuel konnte nicht geworfen werden.'); } finally { - schedule(() => { - if (throwingAtPlayerId.value === targetPlayerId) { - throwingAtPlayerId.value = null; - } - }, THROW_BUTTON_COOLDOWN); + if (shouldApplyCooldown) { + schedule(() => { + if (throwingAtPlayerId.value === targetPlayerId) { + throwingAtPlayerId.value = null; + } + }, THROW_BUTTON_COOLDOWN); + } } } diff --git a/resources/js/types/emote.ts b/resources/js/types/emote.ts index 6e3745a..4e8b60d 100644 --- a/resources/js/types/emote.ts +++ b/resources/js/types/emote.ts @@ -25,6 +25,7 @@ export type PaperBallFlight = { export type PaperBallTossOptions = { roomId: () => string; currentPlayerId: () => number; + currentPlayerIsModerator: () => boolean; board: Ref; }; diff --git a/tests/Feature/RoomEmoteTest.php b/tests/Feature/RoomEmoteTest.php index 9cf514b..769fd9a 100644 --- a/tests/Feature/RoomEmoteTest.php +++ b/tests/Feature/RoomEmoteTest.php @@ -59,6 +59,58 @@ test('a participant can throw a realtime paper ball at another player', function ->and($room->state_version)->toBe($stateVersion); }); +test('moderator paper ball throws are not rate limited', function () { + Event::fake([PaperBallThrown::class]); + $room = createEmoteRoom(); + $moderator = $room->players()->create([ + 'name' => 'Eric', + 'is_moderator' => true, + ]); + $target = $room->players()->create([ + 'name' => 'Anna', + 'is_moderator' => false, + ]); + + for ($throwCount = 0; $throwCount < 11; $throwCount++) { + $this->withSession(["room_players.{$room->id}" => $moderator->id]) + ->postJson(route('room.emote.paper-ball.store', $room), [ + 'target_player_id' => $target->id, + ]) + ->assertNoContent(); + } + + Event::assertDispatchedTimes(PaperBallThrown::class, 11); +}); + +test('participant paper ball throws remain rate limited', function () { + Event::fake([PaperBallThrown::class]); + $room = createEmoteRoom(); + $moderator = $room->players()->create([ + 'name' => 'Eric', + 'is_moderator' => true, + ]); + $participant = $room->players()->create([ + 'name' => 'Anna', + 'is_moderator' => false, + ]); + + for ($throwCount = 0; $throwCount < 10; $throwCount++) { + $this->withSession(["room_players.{$room->id}" => $participant->id]) + ->postJson(route('room.emote.paper-ball.store', $room), [ + 'target_player_id' => $moderator->id, + ]) + ->assertNoContent(); + } + + $this->withSession(["room_players.{$room->id}" => $participant->id]) + ->postJson(route('room.emote.paper-ball.store', $room), [ + 'target_player_id' => $moderator->id, + ]) + ->assertTooManyRequests(); + + Event::assertDispatchedTimes(PaperBallThrown::class, 10); +}); + test('a participant cannot throw a paper ball at themselves', function () { Event::fake([PaperBallThrown::class]); $room = createEmoteRoom();