authorize("viewAny", User::class); $query = User::query(); 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 . "%") ->orWhere("email", "like", "%" . $request->search . "%") ->orWhere("nickname", "like", "%" . $request->search . "%"); }); } return response()->json($query->paginate(15)); } /** * POST /api/users * Registar um novo utilizador (Admin only) */ public function store(Request $request) { $this->authorize("create", User::class); $validated = $request->validate([ "name" => "required|string|max:255", "email" => "required|email|unique:users,email", "password" => "required|string|min:3", "type" => "required|in:A,U", ]); $validated["password"] = Hash::make($validated["password"]); $user = User::create($validated); return response()->json($user, 201); } /** * GET /api/users/me * Retorna o perfil do utilizador autenticado atual. * Usado pelo VueJS para saber quem está logado. */ public function me(Request $request) { return response()->json($request->user()); } /** * GET /api/users/{user} * Mostra um perfil específico. */ public function show(User $user) { $currentUser = Auth::user(); // Admins and owners get full profile, others get limited view if ($currentUser->type === "A" || $currentUser->id === $user->id) { return response()->json($user); } else { return response()->json([ "id" => $user->id, "name" => $user->name, "nickname" => $user->nickname, "photo_avatar_filename" => $user->photo_avatar_filename, "type" => $user->type, ]); } } /** * PUT/PATCH /api/users/{user} * Atualizar perfil (Nome, Nickname, Password, Foto) */ public function update(Request $request, User $user) { $this->authorize("update", $user); $validated = $request->validate([ "name" => "sometimes|string|max:255", "nickname" => [ "sometimes", "string", "max:20", Rule::unique("users")->ignore($user->id), ], "email" => [ "sometimes", "email", Rule::unique("users")->ignore($user->id), ], "password" => "sometimes|string|min:3", ]); // Lógica de Upload de Foto (Exemplo Básico) // if ($request->hasFile('photo_avatar')) { // $path = $request->file('photo_avatar')->store('avatars', 'public'); // $validated['photo_avatar_filename'] = basename($path); // } $user->update($validated); return response()->json($user); } public function updatePassword(Request $request, User $user) { $this->authorize("update", $user); $validated = $request->validate([ "current_password" => "required|string", "new_password" => "required|string|min:3|confirmed", ]); // Verificar se a password atual está correta if (!Hash::check($validated["current_password"], $user->password)) { return response()->json( ["message" => "Current password is incorrect"], 403, ); } // Atualizar para a nova password $user->password = Hash::make($validated["new_password"]); $user->save(); return response()->json(["message" => "Password updated successfully"]); } /** * DELETE /api/users/{user} * Apagar conta (Soft Delete) */ public function destroy(User $user) { $this->authorize("delete", $user); // Check if user has transactions or games - if so, soft delete $hasTransactions = $user->transactions()->exists(); $hasGames = Game::where(function ($q) use ($user) { $q->where("player1_user_id", $user->id)->orWhere( "player2_user_id", $user->id, ); })->exists(); if ($hasTransactions || $hasGames) { $user->delete(); // Soft delete $message = "User account deactivated (soft-deleted due to transaction/game history)"; } else { $user->forceDelete(); // Hard delete $message = "User permanently deleted"; } return response()->json(["message" => $message]); } /** * GET /api/users/{user}/games * Histórico de Partidas Individuais (Cartas) */ public function getGames(Request $request, User $user) { $this->authorize("view", $user); // Define a query base para GAMES $query = \App\Models\Game::query() ->where(function ($q) use ($user) { $q->where("player1_user_id", $user->id)->orWhere( "player2_user_id", $user->id, ); }) ->with(["winner", "player1", "player2"]); if ($request->has("type")) { $query->where("type", $request->type); } if ($request->has("status")) { $query->where("status", $request->status); } // Date filtering if ($request->has("date_from")) { $query->whereDate("began_at", ">=", $request->date_from); } if ($request->has("date_to")) { $query->whereDate("began_at", "<=", $request->date_to); } // Flexible sorting $sortColumn = $request->get("sort_by", "began_at"); $sortDirection = $request->get("sort_direction", "desc"); // Whitelist sortable columns for security $allowedColumns = [ "began_at", "ended_at", "total_time", "total_points", ]; if (in_array($sortColumn, $allowedColumns)) { $query->orderBy($sortColumn, $sortDirection); } else { $query->orderBy("began_at", $sortDirection); } $games = $query->paginate(10); $games->getCollection()->transform(function ($game) use ($user) { if ($game->winner_user_id == $user->id) { $game->outcome = "win"; } elseif ($game->is_draw) { $game->outcome = "draw"; } else { $game->outcome = "loss"; } return $game; }); return response()->json($games); } /** * GET /api/users/{user}/matches * Histórico de Matches (Apostas / Marcas) */ public function getMatches(Request $request, User $user) { $this->authorize("view", $user); $query = MatchGame::query() ->where(function ($q) use ($user) { $q->where("player1_user_id", $user->id)->orWhere( "player2_user_id", $user->id, ); }) ->with(["winner", "player1", "player2"]); if ($request->has("type")) { $query->where("type", $request->type); } if ($request->has("status")) { $query->where("status", $request->status); } // Date filtering if ($request->has("date_from")) { $query->whereDate("began_at", ">=", $request->date_from); } if ($request->has("date_to")) { $query->whereDate("began_at", "<=", $request->date_to); } // Flexible sorting $sortColumn = $request->get("sort_by", "began_at"); $sortDirection = $request->get("sort_direction", "desc"); // Whitelist sortable columns for security $allowedColumns = ["began_at", "ended_at", "stake", "total_time"]; if (in_array($sortColumn, $allowedColumns)) { $query->orderBy($sortColumn, $sortDirection); } else { $query->orderBy("began_at", $sortDirection); } $matches = $query->paginate(10); $matches->getCollection()->transform(function ($match) use ($user) { if ($match->winner_user_id == $user->id) { $match->outcome = "win"; } elseif ( $match->winner_user_id && $match->winner_user_id != $user->id ) { $match->outcome = "loss"; } else { $match->outcome = "interrupted"; // Matches geralmente não empatam (alguém chega a 4 marcas) } return $match; }); return response()->json($matches); } public function block(User $user) { $this->authorize("block", $user); $user->blocked = true; $user->save(); // Revoke all active tokens for immediate effect $user->tokens()->delete(); return response()->json(["message" => "User blocked successfully"]); } public function unblock(User $user) { $this->authorize("unblock", $user); $user->blocked = false; $user->save(); 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("type", $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); } public function getMyTransactions(Request $request) { return $this->getTransactions($request, $request->user()); } }