feature/api-admin-policies #78

Merged
KZix merged 23 commits from feature/api-admin-policies into develop 2025-12-29 17:11:15 +00:00
6 changed files with 114 additions and 62 deletions
Showing only changes of commit 25c819c6e4 - Show all commits
+11 -4
View File
@@ -2,11 +2,11 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Http\Requests\RegisterRequest;
use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use App\Models\User;
use App\Http\Requests\RegisterRequest;
class AuthController extends Controller class AuthController extends Controller
{ {
@@ -24,6 +24,13 @@ class AuthController extends Controller
} }
$user = Auth::user(); $user = Auth::user();
if ($user->blocked) {
Auth::logout();
return response()->json(['message' => 'Your account is blocked. Please contact support.'], 403);
}
$token = $user->createToken('auth-token')->plainTextToken; $token = $user->createToken('auth-token')->plainTextToken;
return response()->json([ return response()->json([
@@ -53,8 +60,8 @@ class AuthController extends Controller
'name' => $request->name, 'name' => $request->name,
'password' => bcrypt($request->password), 'password' => bcrypt($request->password),
'photo_avatar_filename' => $request->photo, 'photo_avatar_filename' => $request->photo,
'type' => 'P', 'type' => 'P',
'blocked' => false, 'blocked' => false,
'coins_balance' => 10, 'coins_balance' => 10,
]); ]);
@@ -14,7 +14,20 @@ class GameController extends Controller
public function index(Request $request) public function index(Request $request)
{ {
$this->authorize('viewAny', Game::class);
$user = Auth::user();
$query = Game::query()->with(["winner", "player1", "player2"]); $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"])) { if ($request->has("type") && in_array($request->type, ["3", "9"])) {
$query->where("type", $request->type); $query->where("type", $request->type);
@@ -43,6 +56,8 @@ class GameController extends Controller
public function store(Request $request) public function store(Request $request)
{ {
$this->authorize('create', Game::class);
$validated = $request->validate([ $validated = $request->validate([
"type" => "required|in:3,9", "type" => "required|in:3,9",
]); ]);
@@ -81,6 +96,8 @@ class GameController extends Controller
public function join(Game $game) public function join(Game $game)
{ {
$this->authorize('join', $game);
$user = Auth::user(); $user = Auth::user();
if ($game->player1_user_id == $user->id) { if ($game->player1_user_id == $user->id) {
@@ -107,12 +124,16 @@ class GameController extends Controller
public function show(Game $game) public function show(Game $game)
{ {
$this->authorize('view', $game);
$game->load(["player1", "player2", "winner"]); $game->load(["player1", "player2", "winner"]);
return response()->json($game); return response()->json($game);
} }
public function update(Request $request, Game $game) public function update(Request $request, Game $game)
{ {
$this->authorize('update', $game);
if ($game->status == "Ended") { if ($game->status == "Ended") {
return response()->json(["message" => "Game already ended"], 400); return response()->json(["message" => "Game already ended"], 400);
} }
@@ -135,4 +156,25 @@ class GameController extends Controller
return response()->json($game); return response()->json($game);
} }
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
: $game->player1_user_id,
'loser_user_id' => $user->id,
'ended_at' => now(),
]);
return response()->json([
'message' => 'You have resigned from the game',
'game' => $game->fresh(),
]);
}
} }
+34 -37
View File
@@ -17,6 +17,8 @@ class UserController extends Controller
*/ */
public function index(Request $request) public function index(Request $request)
{ {
$this->authorize('viewAny', User::class);
$query = User::query(); $query = User::query();
// Filtros úteis para o Backoffice // Filtros úteis para o Backoffice
@@ -41,6 +43,8 @@ class UserController extends Controller
*/ */
public function store(Request $request) public function store(Request $request)
{ {
$this->authorize('create', User::class);
$validated = $request->validate([ $validated = $request->validate([
'name' => 'required|string|max:255', 'name' => 'required|string|max:255',
'nickname' => 'required|string|max:20|unique:users,nickname', 'nickname' => 'required|string|max:20|unique:users,nickname',
@@ -73,7 +77,11 @@ class UserController extends Controller
public function show(User $user) public function show(User $user)
{ {
$currentUser = Auth::user(); $currentUser = Auth::user();
if ($currentUser->id !== $user->id && $currentUser->type !== 'A') {
// 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([ return response()->json([
'id' => $user->id, 'id' => $user->id,
'name' => $user->name, 'name' => $user->name,
@@ -82,8 +90,6 @@ class UserController extends Controller
'type' => $user->type, 'type' => $user->type,
]); ]);
} }
return response()->json($user);
} }
/** /**
@@ -92,12 +98,8 @@ class UserController extends Controller
*/ */
public function update(Request $request, User $user) public function update(Request $request, User $user)
{ {
$currentUser = Auth::user(); $this->authorize('update', $user);
if ($currentUser->id !== $user->id && $currentUser->type !== 'A') {
return response()->json(['message' => 'Unauthorized'], 403);
}
$validated = $request->validate([ $validated = $request->validate([
'name' => 'sometimes|string|max:255', 'name' => 'sometimes|string|max:255',
'nickname' => [ 'nickname' => [
@@ -112,16 +114,8 @@ class UserController extends Controller
Rule::unique('users')->ignore($user->id), Rule::unique('users')->ignore($user->id),
], ],
'password' => 'sometimes|string|min:3', 'password' => 'sometimes|string|min:3',
'blocked' => 'sometimes|boolean',
]); ]);
if (isset($validated['blocked']) && $currentUser->type !== 'A') {
return response()->json(
['message' => 'Only admins can block users'],
403,
);
}
// Lógica de Upload de Foto (Exemplo Básico) // Lógica de Upload de Foto (Exemplo Básico)
// if ($request->hasFile('photo_avatar')) { // if ($request->hasFile('photo_avatar')) {
// $path = $request->file('photo_avatar')->store('avatars', 'public'); // $path = $request->file('photo_avatar')->store('avatars', 'public');
@@ -139,24 +133,24 @@ class UserController extends Controller
*/ */
public function destroy(User $user) public function destroy(User $user)
{ {
$currentUser = Auth::user(); $this->authorize('delete', $user);
if ($currentUser->id !== $user->id && $currentUser->type !== 'A') { // Check if user has transactions or games - if so, soft delete
return response()->json(['message' => 'Unauthorized'], 403); $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';
} }
if ($currentUser->id === $user->id && $currentUser->type === 'A') { return response()->json(['message' => $message]);
if ($user->type === 'A') {
return response()->json(
['message' => 'Admins cannot delete their own account'],
403,
);
}
}
$user->delete();
return response()->json(['message' => 'User deleted successfully']);
} }
/** /**
@@ -165,11 +159,7 @@ class UserController extends Controller
*/ */
public function getMatches(Request $request, User $user) public function getMatches(Request $request, User $user)
{ {
$currentUser = Auth::user(); $this->authorize('view', $user);
if ($currentUser->id !== $user->id && $currentUser->type !== 'A') {
return response()->json(['message' => 'Unauthorized'], 403);
}
$matches = Game::query() $matches = Game::query()
->where(function ($q) use ($user) { ->where(function ($q) use ($user) {
@@ -187,14 +177,21 @@ class UserController extends Controller
public function block(User $user) public function block(User $user)
{ {
$this->authorize('block', $user);
$user->blocked = true; $user->blocked = true;
$user->save(); $user->save();
// Revoke all active tokens for immediate effect
$user->tokens()->delete();
return response()->json(['message' => 'User blocked successfully']); return response()->json(['message' => 'User blocked successfully']);
} }
public function unblock(User $user) public function unblock(User $user)
{ {
$this->authorize('unblock', $user);
$user->blocked = false; $user->blocked = false;
$user->save(); $user->save();
+10 -15
View File
@@ -12,9 +12,6 @@ class GamePolicy
*/ */
public function viewAny(User $user): bool public function viewAny(User $user): bool
{ {
if ($user->blocked === 1) {
return false;
}
return true; return true;
} }
@@ -23,12 +20,9 @@ class GamePolicy
*/ */
public function view(User $user, Game $game): bool public function view(User $user, Game $game): bool
{ {
if ($user->type === "A") { if ($user->type === 'A') {
return true; return true;
} }
if ($user->blocked === 1) {
return false;
}
if ( if (
$game->player1_user_id === $user->id || $game->player1_user_id === $user->id ||
@@ -45,7 +39,7 @@ class GamePolicy
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
if ($user->type === "A" || $user->blocked === 1) { if ($user->type === 'A') {
return false; return false;
} }
@@ -54,12 +48,12 @@ class GamePolicy
public function join(User $user, Game $game): bool public function join(User $user, Game $game): bool
{ {
if ($user->type === "A" || $user->blocked === 1) { if ($user->type === 'A') {
return false; return false;
} }
if ( if (
$game->status !== "waiting" || $game->status !== 'waiting' ||
$game->player1_user_id === $user->id $game->player1_user_id === $user->id
) { ) {
return false; return false;
@@ -70,12 +64,12 @@ class GamePolicy
public function resign(User $user, Game $game): bool public function resign(User $user, Game $game): bool
{ {
if ($user->type === "A" || $user->blocked === 1) { if ($user->type === 'A') {
return false; return false;
} }
if ( if (
$game->status !== "ongoing" || $game->status !== 'ongoing' ||
($game->player1_user_id !== $user->id && ($game->player1_user_id !== $user->id &&
$game->player2_user_id !== $user->id) $game->player2_user_id !== $user->id)
) { ) {
@@ -90,12 +84,12 @@ class GamePolicy
*/ */
public function update(User $user, Game $game): bool public function update(User $user, Game $game): bool
{ {
if ($user->type === "A" || $user->blocked === 1) { if ($user->type === 'A') {
return false; return false;
} }
if ( if (
$game->status !== "ongoing" || $game->status !== 'ongoing' ||
($game->player1_user_id !== $user->id && ($game->player1_user_id !== $user->id &&
$game->player2_user_id !== $user->id) $game->player2_user_id !== $user->id)
) { ) {
@@ -110,7 +104,8 @@ class GamePolicy
*/ */
public function delete(User $user, Game $game): bool public function delete(User $user, Game $game): bool
{ {
return false; // Only admins can delete games
return $user->type === 'A';
} }
/** /**
+15 -6
View File
@@ -3,7 +3,6 @@
namespace App\Policies; namespace App\Policies;
use App\Models\User; use App\Models\User;
use Illuminate\Auth\Access\Response;
class UserPolicy class UserPolicy
{ {
@@ -12,7 +11,7 @@ class UserPolicy
*/ */
public function viewAny(User $user): bool public function viewAny(User $user): bool
{ {
return false; return $user->type === 'A';
} }
/** /**
@@ -20,7 +19,7 @@ class UserPolicy
*/ */
public function view(User $user, User $model): bool public function view(User $user, User $model): bool
{ {
return false; return $user->id === $model->id || $user->type === 'A';
} }
/** /**
@@ -28,7 +27,7 @@ class UserPolicy
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
return false; return $user->type === 'A';
} }
/** /**
@@ -36,7 +35,7 @@ class UserPolicy
*/ */
public function update(User $user, User $model): bool public function update(User $user, User $model): bool
{ {
return false; return $user->id === $model->id;
} }
/** /**
@@ -44,7 +43,17 @@ class UserPolicy
*/ */
public function delete(User $user, User $model): bool public function delete(User $user, User $model): bool
{ {
return false; return ($user->type === 'A' && $user->id !== $model->id) || ($user->type === 'P' && $user->id === $model->id);
}
public function block(User $user, User $model): bool
{
return $user->type === 'A' && $user->id !== $model->id && $model->type === 'P';
}
public function unblock(User $user, User $model): bool
{
return $user->type === 'A' && $user->id !== $model->id && $model->type === 'P';
} }
/** /**
+2
View File
@@ -0,0 +1,2 @@
PORT=3000
API_URL=http://localhost:8000/api