mirror of
https://github.com/ericasch6/scrum-poker-internal.git
synced 2026-10-14 05:05:29 +00:00
build the backend structure and the fe for the Game
This commit is contained in:
@@ -1,11 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\StoreRoomRequest;
|
||||
use App\Models\Deck;
|
||||
use App\Models\Player;
|
||||
use App\Models\Room;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class RoomController extends Controller
|
||||
{
|
||||
public function create(){
|
||||
return Inertia::render('rooms/CreateRoom');
|
||||
public function create(Request $request)
|
||||
{
|
||||
$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,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request, StoreRoomRequest $roomStoreRequest)
|
||||
{
|
||||
$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 (
|
||||
$validated,
|
||||
$deck,
|
||||
$user,
|
||||
) {
|
||||
$room = Room::create([
|
||||
'title' => $validated['title'] ?? null,
|
||||
'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->session()->put(
|
||||
"room_players.{$room->id}",
|
||||
$player->id,
|
||||
);
|
||||
|
||||
return to_route('room.show', $room);
|
||||
}
|
||||
|
||||
public function show(Room $room, Request $request)
|
||||
{
|
||||
$currentPlayer = $request->attributes->get('currentPlayer');
|
||||
|
||||
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();
|
||||
|
||||
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,
|
||||
'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)
|
||||
{
|
||||
return Inertia::render('rooms/Join', [
|
||||
'join_code' => $request->query('join_code'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\JoinRoomRequest;
|
||||
use App\Models\Room;
|
||||
|
||||
class RoomPlayerController extends Controller
|
||||
{
|
||||
public function join(JoinRoomRequest $request)
|
||||
{
|
||||
$validated = $request->validated();
|
||||
$user = $request->user();
|
||||
|
||||
$room = Room::where('join_code', '=', $validated['join_code'])->firstOrFail();
|
||||
|
||||
$player = $room->players()->create([
|
||||
'user_id' => $user?->id,
|
||||
'name' => $validated['player_name'],
|
||||
'is_moderator' => false,
|
||||
'last_seen_at' => now(),
|
||||
]);
|
||||
|
||||
$request->session()->put(
|
||||
"room_players.{$room->id}",
|
||||
$player->id,
|
||||
);
|
||||
|
||||
return to_route('room.show', $room);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Player;
|
||||
use App\Models\Room;
|
||||
use App\Models\Vote;
|
||||
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');
|
||||
|
||||
abort_unless(
|
||||
$player instanceof Player
|
||||
&& $player->room_id === $room->id
|
||||
&& $player->is_moderator,
|
||||
403,
|
||||
);
|
||||
|
||||
DB::transaction(function () use ($room) {
|
||||
$lockedRoom = Room::query()
|
||||
->lockForUpdate()
|
||||
->findOrFail($room->id);
|
||||
|
||||
Vote::query()
|
||||
->where('room_id', $lockedRoom->id)
|
||||
->where('round', $lockedRoom->current_round)
|
||||
->delete();
|
||||
|
||||
$lockedRoom->update([
|
||||
'revealed_at' => null,
|
||||
'state_version' => $lockedRoom->state_version + 1,
|
||||
]);
|
||||
});
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
DB::transaction(function () use ($room) {
|
||||
$lockedRoom = Room::query()
|
||||
->lockForUpdate()
|
||||
->findOrFail($room->id);
|
||||
|
||||
if ($lockedRoom->revealed_at !== null) {
|
||||
throw ValidationException::withMessages([
|
||||
'room' => 'Die Karten sind bereits aufgedeckt.',
|
||||
]);
|
||||
}
|
||||
|
||||
$lockedRoom->update([
|
||||
'revealed_at' => now(),
|
||||
'state_version' => $lockedRoom->state_version + 1,
|
||||
]);
|
||||
});
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
DB::transaction(function () use ($room) {
|
||||
$lockedRoom = Room::query()
|
||||
->lockForUpdate()
|
||||
->findOrFail($room->id);
|
||||
|
||||
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,
|
||||
'state_version' => $lockedRoom->state_version + 1,
|
||||
]);
|
||||
});
|
||||
|
||||
return back();
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,55 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Requests\SubmitVoteRequest;
|
||||
use App\Models\Player;
|
||||
use App\Models\Room;
|
||||
use App\Models\Vote;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class VoteController extends Controller
|
||||
{
|
||||
//
|
||||
public function store(
|
||||
SubmitVoteRequest $request,
|
||||
Room $room,
|
||||
): RedirectResponse {
|
||||
$player = $request->attributes->get('currentPlayer');
|
||||
|
||||
abort_unless(
|
||||
$player instanceof Player
|
||||
&& $player->room_id === $room->id,
|
||||
403,
|
||||
);
|
||||
|
||||
$value = $request->validated('value');
|
||||
|
||||
DB::transaction(function () use ($room, $player, $value) {
|
||||
$lockedRoom = Room::query()
|
||||
->lockForUpdate()
|
||||
->findOrFail($room->id);
|
||||
|
||||
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->increment('state_version');
|
||||
});
|
||||
|
||||
return back();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\Room;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class EnsureRoomParticipant
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Closure(Request): (Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$room = $request->route('room');
|
||||
|
||||
if (! $room instanceof Room) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$sessionKey = "room_players.{$room->id}";
|
||||
$playerId = $request->session()->get($sessionKey);
|
||||
|
||||
if (! $playerId) {
|
||||
return to_route('room.join', [
|
||||
'join_code' => $room->join_code,
|
||||
])->withErrors([
|
||||
'join_code' => 'Du musst diesen Raum erst beitreten.',
|
||||
]);
|
||||
}
|
||||
|
||||
$player = $room->players()->find($playerId);
|
||||
|
||||
if (! $player) {
|
||||
$request->session()->forget($sessionKey);
|
||||
|
||||
return to_route('room.join', [
|
||||
'join_code' => $room->join_code,
|
||||
]);
|
||||
}
|
||||
|
||||
$request->attributes->set('currentPlayer', $player);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\Player;
|
||||
use App\Models\Room;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class JoinRoomRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'join_code' => strtoupper(trim((string) $this->input('join_code'))),
|
||||
'player_name' => trim((string) $this->input('player_name')),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$roomId = Room::query()
|
||||
->where('join_code', $this->input('join_code'))
|
||||
->value('id');
|
||||
|
||||
return [
|
||||
'join_code' => ['bail', 'string', 'required', 'size:6', Rule::exists(Room::class, 'join_code')],
|
||||
'player_name' => ['required', 'string', 'max:50', Rule::unique(Player::class, 'name')->where('room_id', $roomId)],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'join_code.exists' => 'Dieser Raumcode existiert nicht.',
|
||||
'join_code.size' => 'Der Raumcode muss aus sechs Zeichen bestehen.',
|
||||
'player_name.unique' => 'Dieser Name wird in dem Raum bereits verwendet.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreRoomRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'title' => ['nullable', 'string', 'max:100'],
|
||||
'player_name' => ['required', 'string', 'max:50'],
|
||||
'deck_id' => ['required', 'integer'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class SubmitVoteRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$room = $this->route('room');
|
||||
|
||||
return [
|
||||
'value' => ['bail', 'required', 'string', Rule::in(array_column($room->deck_config['cards'], 'key'))],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'value.required' => 'Du musst eine Karte auswählen.',
|
||||
'value.in' => 'Diese Karte gehört nicht zum verwendeten Deck.',
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user