From 5f845b2ba7ac4b1657aa69d6cd9fad5204ab7445 Mon Sep 17 00:00:00 2001 From: Tiago Ramos <2222055@my.ipleiria.pt> Date: Tue, 30 Dec 2025 16:08:53 +0000 Subject: [PATCH 1/4] Added routes, admin statistics to the controller and added models for coin transaction and coin transaction type --- .../Http/Controllers/StatisticsController.php | 61 +++++++++++++++++++ api/app/Models/CoinTransaction.php | 40 ++++++++++++ api/app/Models/CoinTransactionType.php | 20 ++++++ api/app/Models/Game.php | 1 - api/routes/api.php | 2 + 5 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 api/app/Models/CoinTransaction.php create mode 100644 api/app/Models/CoinTransactionType.php diff --git a/api/app/Http/Controllers/StatisticsController.php b/api/app/Http/Controllers/StatisticsController.php index faf7d6e..ac8a078 100644 --- a/api/app/Http/Controllers/StatisticsController.php +++ b/api/app/Http/Controllers/StatisticsController.php @@ -7,6 +7,8 @@ use App\Models\Game; use App\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; +use App\Models\CoinTransaction; +use App\Models\CoinTransactionType; class StatisticsController extends Controller { @@ -73,4 +75,63 @@ class StatisticsController extends Controller 'current_balance' => $user->coins_balance, // Extra info ]); } + + public function getAdminStats() + { + $totalPlayers = User::where('type', 'P')->count(); + $totalGames = Game::count(); + $totalBlockedUsers = User::where('blocked', true)->count(); + + $gamesByType = Game::select('type', DB::raw('count(*) as total')) + ->groupBy('type') + ->get(); + + // 3. Games Per Month (Line/Bar Chart) + $gamesPerMonth = Game::select( + DB::raw('DATE_FORMAT(began_at, "%Y-%m") as month'), + DB::raw('count(*) as total') + ) + ->whereNotNull('began_at') + ->groupBy('month') + ->orderBy('month', 'desc') + ->limit(12) + ->get(); + + // 4. Financial/Coin Stats (Using the new Tables) + // We calculate "Revenue" based on the volume of "Coin purchase" transactions + + // Calculate Total Coins Purchased (All time) + // We join with types table to filter by name "Coin purchase" + $totalCoinsPurchased = CoinTransaction::whereHas('type', function($q) { + $q->where('name', 'Coin purchase'); + }) + ->sum('coins'); + + // Calculate Purchases Per Month (Time Series) + // Groups by 'transaction_datetime' (YYYY-MM) + $purchasesPerMonth = \App\Models\CoinTransaction::join('coin_transaction_types', 'coin_transactions.coin_transaction_type_id', '=', 'coin_transaction_types.id') + ->where('coin_transaction_types.name', 'Coin purchase') + ->select( + DB::raw('DATE_FORMAT(coin_transactions.transaction_datetime, "%Y-%m") as month'), + DB::raw('SUM(coin_transactions.coins) as total_coins') + ) + ->groupBy('month') + ->orderBy('month', 'desc') + ->limit(12) + ->get(); + + return response()->json([ + 'global_counters' => [ + 'total_players' => $totalPlayers, + 'total_games' => $totalGames, + 'blocked_users' => $totalBlockedUsers, + 'total_coins_purchased' => (int) $totalCoinsPurchased, // Cast to int + ], + 'charts' => [ + 'games_by_type' => $gamesByType, + 'games_per_month' => $gamesPerMonth, + 'purchases_per_month' => $purchasesPerMonth, + ] + ]); + } } \ No newline at end of file diff --git a/api/app/Models/CoinTransaction.php b/api/app/Models/CoinTransaction.php new file mode 100644 index 0000000..024c9eb --- /dev/null +++ b/api/app/Models/CoinTransaction.php @@ -0,0 +1,40 @@ + 'datetime', + 'custom' => 'array', + ]; + + public function type() + { + return $this->belongsTo(CoinTransactionType::class, 'coin_transaction_type_id'); + } + + public function user() + { + return $this->belongsTo(User::class); + } +} \ No newline at end of file diff --git a/api/app/Models/CoinTransactionType.php b/api/app/Models/CoinTransactionType.php new file mode 100644 index 0000000..6caca4b --- /dev/null +++ b/api/app/Models/CoinTransactionType.php @@ -0,0 +1,20 @@ + 'array', + ]; +} \ No newline at end of file diff --git a/api/app/Models/Game.php b/api/app/Models/Game.php index dd65b7f..13f2865 100644 --- a/api/app/Models/Game.php +++ b/api/app/Models/Game.php @@ -3,7 +3,6 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; -use App\Http\Requests\StoreGameRequest; use Illuminate\Database\Eloquent\Factories\HasFactory; diff --git a/api/routes/api.php b/api/routes/api.php index b4991af..d15a631 100644 --- a/api/routes/api.php +++ b/api/routes/api.php @@ -39,6 +39,7 @@ Route::prefix('v1')->group(function () { UserController::class, 'getMatches', ]); + Route::get('/{user}/transactions', [UserController::class, 'getTransactions']); }); // Game Resources @@ -65,6 +66,7 @@ Route::prefix('v1')->group(function () { Route::middleware('user.type:A') ->prefix('admin') ->group(function () { + Route::get('/statistics', [StatisticsController::class, 'getAdminStats']); Route::prefix('users')->group(function () { Route::get('/', [UserController::class, 'index']); Route::post('/', [UserController::class, 'store']); From f38f08923f33c633d61cefad5e03e2de9e0be1ed Mon Sep 17 00:00:00 2001 From: Tiago Ramos <2222055@my.ipleiria.pt> Date: Tue, 30 Dec 2025 16:13:46 +0000 Subject: [PATCH 2/4] Added transaction details for the logged user --- .../Http/Controllers/StatisticsController.php | 12 ++------ api/app/Http/Controllers/UserController.php | 28 +++++++++++++++++++ api/app/Models/User.php | 5 ++++ 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/api/app/Http/Controllers/StatisticsController.php b/api/app/Http/Controllers/StatisticsController.php index ac8a078..e8019f4 100644 --- a/api/app/Http/Controllers/StatisticsController.php +++ b/api/app/Http/Controllers/StatisticsController.php @@ -8,7 +8,6 @@ use App\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use App\Models\CoinTransaction; -use App\Models\CoinTransactionType; class StatisticsController extends Controller { @@ -86,7 +85,6 @@ class StatisticsController extends Controller ->groupBy('type') ->get(); - // 3. Games Per Month (Line/Bar Chart) $gamesPerMonth = Game::select( DB::raw('DATE_FORMAT(began_at, "%Y-%m") as month'), DB::raw('count(*) as total') @@ -97,19 +95,13 @@ class StatisticsController extends Controller ->limit(12) ->get(); - // 4. Financial/Coin Stats (Using the new Tables) - // We calculate "Revenue" based on the volume of "Coin purchase" transactions - - // Calculate Total Coins Purchased (All time) - // We join with types table to filter by name "Coin purchase" + $totalCoinsPurchased = CoinTransaction::whereHas('type', function($q) { $q->where('name', 'Coin purchase'); }) ->sum('coins'); - // Calculate Purchases Per Month (Time Series) - // Groups by 'transaction_datetime' (YYYY-MM) - $purchasesPerMonth = \App\Models\CoinTransaction::join('coin_transaction_types', 'coin_transactions.coin_transaction_type_id', '=', 'coin_transaction_types.id') + $purchasesPerMonth = CoinTransaction::join('coin_transaction_types', 'coin_transactions.coin_transaction_type_id', '=', 'coin_transaction_types.id') ->where('coin_transaction_types.name', 'Coin purchase') ->select( DB::raw('DATE_FORMAT(coin_transactions.transaction_datetime, "%Y-%m") as month'), diff --git a/api/app/Http/Controllers/UserController.php b/api/app/Http/Controllers/UserController.php index 7825179..70911ee 100644 --- a/api/app/Http/Controllers/UserController.php +++ b/api/app/Http/Controllers/UserController.php @@ -197,4 +197,32 @@ class UserController extends Controller return response()->json(['message' => 'User unblocked successfully']); } + + public function getTransactions(Request $request, User $user) + { + if ($request->user()->id !== $user->id && $request->user()->type !== 'A') { + return response()->json(['message' => 'Forbidden'], 403); + } + + $query = $user->transactions()->with('type'); + + if ($request->has('type')) { + $query->whereHas('type', function ($q) use ($request) { + $q->where('name', $request->type); + }); + } + + if ($request->has('date_from')) { + $query->whereDate('transaction_datetime', '>=', $request->date_from); + } + + if ($request->has('date_to')) { + $query->whereDate('transaction_datetime', '<=', $request->date_to); + } + + $transactions = $query->orderBy('transaction_datetime', 'desc') + ->paginate(10); + + return response()->json($transactions); + } } diff --git a/api/app/Models/User.php b/api/app/Models/User.php index b33dc36..e0665ae 100644 --- a/api/app/Models/User.php +++ b/api/app/Models/User.php @@ -58,4 +58,9 @@ class User extends Authenticatable { return $this->hasMany(Game::class, 'player1_user_id')->orWhere('player2_user_id', $this->id); } + + public function transactions() + { + return $this->hasMany(CoinTransaction::class); + } } From 9bfa06b1cee8451bf13a2fa6fe1dffbf8e903a6c Mon Sep 17 00:00:00 2001 From: Tiago Ramos <2222055@my.ipleiria.pt> Date: Tue, 30 Dec 2025 19:01:02 +0000 Subject: [PATCH 3/4] Added endpoint to get authenticated user's transactions --- .../Http/Controllers/StatisticsController.php | 16 ++++++++++------ api/app/Http/Controllers/UserController.php | 5 +++++ api/routes/api.php | 1 + 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/api/app/Http/Controllers/StatisticsController.php b/api/app/Http/Controllers/StatisticsController.php index e8019f4..c43baad 100644 --- a/api/app/Http/Controllers/StatisticsController.php +++ b/api/app/Http/Controllers/StatisticsController.php @@ -81,12 +81,15 @@ class StatisticsController extends Controller $totalGames = Game::count(); $totalBlockedUsers = User::where('blocked', true)->count(); + // 2. Games Breakdown by Type $gamesByType = Game::select('type', DB::raw('count(*) as total')) ->groupBy('type') ->get(); + // 3. Games Per Month (CORRIGIDO PARA SQLITE) + // SQLite usa strftime('%Y-%m', coluna) em vez de DATE_FORMAT $gamesPerMonth = Game::select( - DB::raw('DATE_FORMAT(began_at, "%Y-%m") as month'), + DB::raw("strftime('%Y-%m', began_at) as month"), DB::raw('count(*) as total') ) ->whereNotNull('began_at') @@ -95,16 +98,17 @@ class StatisticsController extends Controller ->limit(12) ->get(); - - $totalCoinsPurchased = CoinTransaction::whereHas('type', function($q) { + // 4. Financial/Coin Stats + $totalCoinsPurchased = \App\Models\CoinTransaction::whereHas('type', function($q) { $q->where('name', 'Coin purchase'); }) ->sum('coins'); - $purchasesPerMonth = CoinTransaction::join('coin_transaction_types', 'coin_transactions.coin_transaction_type_id', '=', 'coin_transaction_types.id') + // Calculate Purchases Per Month (CORRIGIDO PARA SQLITE) + $purchasesPerMonth = \App\Models\CoinTransaction::join('coin_transaction_types', 'coin_transactions.coin_transaction_type_id', '=', 'coin_transaction_types.id') ->where('coin_transaction_types.name', 'Coin purchase') ->select( - DB::raw('DATE_FORMAT(coin_transactions.transaction_datetime, "%Y-%m") as month'), + DB::raw("strftime('%Y-%m', coin_transactions.transaction_datetime) as month"), DB::raw('SUM(coin_transactions.coins) as total_coins') ) ->groupBy('month') @@ -117,7 +121,7 @@ class StatisticsController extends Controller 'total_players' => $totalPlayers, 'total_games' => $totalGames, 'blocked_users' => $totalBlockedUsers, - 'total_coins_purchased' => (int) $totalCoinsPurchased, // Cast to int + 'total_coins_purchased' => (int) $totalCoinsPurchased, ], 'charts' => [ 'games_by_type' => $gamesByType, diff --git a/api/app/Http/Controllers/UserController.php b/api/app/Http/Controllers/UserController.php index 70911ee..01b511f 100644 --- a/api/app/Http/Controllers/UserController.php +++ b/api/app/Http/Controllers/UserController.php @@ -225,4 +225,9 @@ class UserController extends Controller return response()->json($transactions); } + + public function getMyTransactions(Request $request) + { + return $this->getTransactions($request, $request->user()); + } } diff --git a/api/routes/api.php b/api/routes/api.php index d15a631..87d8b69 100644 --- a/api/routes/api.php +++ b/api/routes/api.php @@ -30,6 +30,7 @@ Route::prefix('v1')->group(function () { 'updatePassword', ]); Route::post('/avatar', [ProfileController::class, 'uploadAvatar']); + Route::get('/transactions', [UserController::class, 'getMyTransactions']); }); // User Resources From cf3ca26382064eb5eddb970a75ab3455c746245e Mon Sep 17 00:00:00 2001 From: Tiago Ramos <2222055@my.ipleiria.pt> Date: Tue, 30 Dec 2025 19:17:49 +0000 Subject: [PATCH 4/4] removed comments on code --- api/app/Http/Controllers/StatisticsController.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/api/app/Http/Controllers/StatisticsController.php b/api/app/Http/Controllers/StatisticsController.php index c43baad..d0d529b 100644 --- a/api/app/Http/Controllers/StatisticsController.php +++ b/api/app/Http/Controllers/StatisticsController.php @@ -81,13 +81,11 @@ class StatisticsController extends Controller $totalGames = Game::count(); $totalBlockedUsers = User::where('blocked', true)->count(); - // 2. Games Breakdown by Type $gamesByType = Game::select('type', DB::raw('count(*) as total')) ->groupBy('type') ->get(); - // 3. Games Per Month (CORRIGIDO PARA SQLITE) - // SQLite usa strftime('%Y-%m', coluna) em vez de DATE_FORMAT + $gamesPerMonth = Game::select( DB::raw("strftime('%Y-%m', began_at) as month"), DB::raw('count(*) as total') @@ -98,13 +96,11 @@ class StatisticsController extends Controller ->limit(12) ->get(); - // 4. Financial/Coin Stats $totalCoinsPurchased = \App\Models\CoinTransaction::whereHas('type', function($q) { $q->where('name', 'Coin purchase'); }) ->sum('coins'); - // Calculate Purchases Per Month (CORRIGIDO PARA SQLITE) $purchasesPerMonth = \App\Models\CoinTransaction::join('coin_transaction_types', 'coin_transactions.coin_transaction_type_id', '=', 'coin_transaction_types.id') ->where('coin_transaction_types.name', 'Coin purchase') ->select(