diff --git a/api/app/Http/Controllers/GameController.php b/api/app/Http/Controllers/GameController.php index 5590763..057306e 100644 --- a/api/app/Http/Controllers/GameController.php +++ b/api/app/Http/Controllers/GameController.php @@ -13,19 +13,16 @@ class GameController extends Controller public function index(Request $request) { $this->authorize('viewAny', Game::class); - + $user = Auth::user(); $query = Game::query()->with(["winner", "player1", "player2"]); - - // Filter games based on user type + if ($user->type !== 'A') { - // Players can only see games they participate in $query->where(function ($q) use ($user) { $q->where('player1_user_id', $user->id) ->orWhere('player2_user_id', $user->id); }); } - // Admins see all games (no filter needed) if ($request->has("type") && in_array($request->type, ["3", "9"])) { $query->where("type", $request->type); @@ -55,7 +52,7 @@ class GameController extends Controller public function store(Request $request) { $this->authorize('create', Game::class); - + $validated = $request->validate([ "type" => "required|in:3,9", ]); @@ -77,7 +74,7 @@ class GameController extends Controller "type" => $validated["type"], "status" => "Pending", "player1_user_id" => $user->id, - "player2_user_id" => self::GHOST_ID, + "player2_user_id" => self::GHOST_ID, "winner_user_id" => self::GHOST_ID, "loser_user_id" => self::GHOST_ID, "began_at" => now(), @@ -92,38 +89,30 @@ class GameController extends Controller ], 201); } - public function join(Game $game) - { - $this->authorize('join', $game); - - $user = Auth::user(); - - if ($game->player1_user_id == $user->id) { - return response()->json([ - "message" => "You are the host. Waiting for opponent...", - "game" => $game - ], 200); - } - - if ($game->player2_user_id !== self::GHOST_ID) { - return response()->json(["message" => "Game is full"], 400); - } - - $game->update([ - "status" => "Playing", - "player2_user_id" => $user->id, - ]); - - return response()->json([ - "message" => "Joined successfully!", - "game" => $game, - ], 200); +public function join(Request $request, Game $game) +{ + // Prevent joining if already full or started + if ($game->status !== 'Pending' || $game->player2_user_id !== 1) { + return response()->json(['message' => 'Game is not available'], 400); } + // Prevent joining your own game + if ($game->player1_user_id === $request->user()->id) { + return response()->json(['message' => 'Cannot join your own game'], 400); + } + + $game->update([ + 'player2_user_id' => $request->user()->id, + 'status' => 'Playing', + ]); + + return response()->json($game); +} + public function show(Game $game) { $this->authorize('view', $game); - + $game->load(["player1", "player2", "winner"]); return response()->json($game); } @@ -131,7 +120,7 @@ class GameController extends Controller public function update(Request $request, Game $game) { $this->authorize('update', $game); - + if ($game->status == "Ended") { return response()->json(["message" => "Game already ended"], 400); } @@ -158,13 +147,13 @@ class GameController extends Controller public function resign(Game $game) { $this->authorize('resign', $game); - + $user = Auth::user(); - + $game->update([ 'status' => 'Ended', - 'winner_user_id' => $game->player1_user_id === $user->id - ? $game->player2_user_id + 'winner_user_id' => $game->player1_user_id === $user->id + ? $game->player2_user_id : $game->player1_user_id, 'loser_user_id' => $user->id, 'ended_at' => now(), diff --git a/api/app/Http/Controllers/TransactionController.php b/api/app/Http/Controllers/TransactionController.php new file mode 100644 index 0000000..41b62c8 --- /dev/null +++ b/api/app/Http/Controllers/TransactionController.php @@ -0,0 +1,31 @@ +authorize('viewAny', CoinTransaction::class); + + $query = CoinTransaction::with('user:id,nickname,email', 'type:id,name'); + + if ($request->has('sort_coins')) { + $direction = $request->get('sort_coins') === 'asc' ? 'asc' : 'desc'; + $query->orderBy('coins', $direction); + } + + $dateDirection = $request->get('sort_datetime') === 'asc' ? 'asc' : 'desc'; + $query->orderBy('transaction_datetime', $dateDirection); + + + $transactions = $query->paginate(15); + + return response()->json($transactions); + } +} \ No newline at end of file diff --git a/api/app/Http/Controllers/UserController.php b/api/app/Http/Controllers/UserController.php index 2fa423c..98a063d 100644 --- a/api/app/Http/Controllers/UserController.php +++ b/api/app/Http/Controllers/UserController.php @@ -21,11 +21,14 @@ class UserController extends Controller $query = User::query(); - // Filtros úteis para o Backoffice if ($request->has('type')) { $query->where('type', $request->type); } + if ($request->has('blocked')) { + $query->where('blocked', $request->type); + } + if ($request->has('search')) { $query->where(function ($q) use ($request) { $q->where('name', 'like', '%'.$request->search.'%') @@ -47,7 +50,6 @@ class UserController extends Controller $validated = $request->validate([ 'name' => 'required|string|max:255', - 'nickname' => 'required|string|max:20|unique:users,nickname', 'email' => 'required|email|unique:users,email', 'password' => 'required|string|min:3', 'type' => 'required|in:A,U', diff --git a/api/app/Models/Game.php b/api/app/Models/Game.php index 13f2865..d37524f 100644 --- a/api/app/Models/Game.php +++ b/api/app/Models/Game.php @@ -29,11 +29,22 @@ class Game extends Model 'match_id' ]; + protected $casts = [ + 'began_at' => 'datetime', + 'ended_at' => 'datetime', + 'is_draw' => 'boolean', + ]; + public function winner() { return $this->belongsTo(User::class, "winner_user_id"); } + public function loser() + { + return $this->belongsTo(User::class, "loser_user_id"); + } + public function player1() { return $this->belongsTo(User::class, 'player1_user_id'); diff --git a/api/app/Policies/CoinTransactionPolicy.php b/api/app/Policies/CoinTransactionPolicy.php new file mode 100644 index 0000000..6b3ce71 --- /dev/null +++ b/api/app/Policies/CoinTransactionPolicy.php @@ -0,0 +1,18 @@ +type === 'A' ? true : false; + } +} diff --git a/api/routes/api.php b/api/routes/api.php index 8e60833..1809c01 100644 --- a/api/routes/api.php +++ b/api/routes/api.php @@ -7,6 +7,7 @@ use App\Http\Controllers\ProfileController; use App\Http\Controllers\StatisticsController; use App\Http\Controllers\UserController; use App\Http\Controllers\CoinPurchaseController; +use App\Http\Controllers\TransactionController; use Illuminate\Support\Facades\Route; Route::prefix('v1')->group(function () { @@ -46,24 +47,50 @@ Route::prefix('v1')->group(function () { Route::get('/{user}/transactions', [UserController::class, 'getTransactions']); }); - // Game Resources Route::prefix('games')->group(function () { - Route::apiResource('/', GameController::class)->parameters([ - '' => 'game', - ]); - Route::post('/{game}/join', [GameController::class, 'join']); + //Host a new multiplayer game - MUST come before resource routes + Route::post('host', function (\Illuminate\Http\Request $request) { + $request->validate([ + 'type' => 'required|in:3,9' + ]); + + $game = \App\Models\Game::create([ + 'type' => $request->type, + 'status' => 'Pending', + 'player1_user_id' => $request->user()->id, + 'player2_user_id' => 1, // Ghost player + 'began_at' => now(), + ]); + + return response()->json($game); + }); + + // List open games (available to join) + Route::get('open', function (\Illuminate\Http\Request $request) { + $type = $request->query('type', '9'); + + $games = \App\Models\Game::where('status', 'Pending') + ->where('player2_user_id', 1) // Only games waiting for a second player + ->where('type', $type) + ->with('player1') // Load player1 relationship + ->orderBy('began_at', 'asc') + ->paginate(10); + + return response()->json($games); + }); + + Route::get('/', [GameController::class, 'index']); + Route::get('/{game}', [GameController::class, 'show']); + Route::post('/', [GameController::class, 'store']); + Route::put('/{game}', [GameController::class, 'update']); + Route::delete('/{game}', [GameController::class, 'destroy']); Route::post('/{game}/resign', [GameController::class, 'resign']); + Route::post('/{game}/join', [GameController::class, 'join']); }); - // Match Resources Route::prefix('matches')->group(function () { - Route::apiResource('/', MatchController::class)->parameters([ - '' => 'match', - ]); - Route::post('/{match}/join', [ - MatchController::class, - 'join', - ]); + Route::apiResource('/', MatchController::class)->parameters(['' => 'match']); + Route::post('/{match}/join', [MatchController::class, 'join']); }); // Admin Routes @@ -71,6 +98,7 @@ Route::prefix('v1')->group(function () { ->prefix('admin') ->group(function () { Route::get('/statistics', [StatisticsController::class, 'getAdminStats']); + Route::get('/transactions', [TransactionController::class, 'index']); Route::prefix('users')->group(function () { Route::get('/', [UserController::class, 'index']); Route::post('/', [UserController::class, 'store']); diff --git a/api/sample-requests.http b/api/sample-requests.http index 1172b90..4311fae 100644 --- a/api/sample-requests.http +++ b/api/sample-requests.http @@ -5,7 +5,7 @@ Content-Type: application/json Accept: application/json { - "email": "pa@mail.pt", + "email": "a1@mail.pt", "password": "123" } @@ -14,7 +14,7 @@ Accept: application/json @id = {{login.response.body.user.id}} ### Get All matches -GET http://localhost:8000/api/v1/matches +GET http://localhost:8000/api/v1/games Content-Type: application/json Accept: application/json Authorization: Bearer {{token}} @@ -55,7 +55,7 @@ Authorization: Bearer {{token}} "payment_reference": "912345678" } -### Get public stats +### Get leaderboard GET http://localhost:8000/api/v1/leaderboard Content-Type: application/json Accept: application/json @@ -64,4 +64,21 @@ Accept: application/json GET http://localhost:8000/api/v1/statistics/me Content-Type: application/json Accept: application/json +Authorization: Bearer {{token}} + +### Get public stats +GET http://localhost:8000/api/v1/statistics/public +Content-Type: application/json +Accept: application/json + +### Get admin stats +GET http://localhost:8000/api/v1/admin/statistics +Content-Type: application/json +Accept: application/json +Authorization: Bearer {{token}} + +### Get admin stats +GET http://localhost:8000/api/v1/admin/users +Content-Type: application/json +Accept: application/json Authorization: Bearer {{token}} \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 562dc55..da16d8c 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -3,8 +3,7 @@