user(); if (!$user || !$user->roles()->where('name', 'ROLE_ADMIN')->exists()) { return response()->json(['message' => 'Forbidden'], 403); } return null; } public function index(Request $request): JsonResponse { if ($error = $this->ensureAdmin($request)) { return $error; } $roles = Role::query() ->orderBy('name') ->get() ->map(fn (Role $role) => [ 'id' => $role->id, 'name' => $role->name, 'color' => $role->color, ]); return response()->json($roles); } public function store(Request $request): JsonResponse { if ($error = $this->ensureAdmin($request)) { return $error; } $data = $request->validate([ 'name' => ['required', 'string', 'max:100', 'unique:roles,name'], 'color' => ['nullable', 'string', 'max:20', 'regex:/^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/'], ]); $normalizedName = $this->normalizeRoleName($data['name']); if (Role::query()->where('name', $normalizedName)->exists()) { return response()->json(['message' => 'Role already exists.'], 422); } $role = Role::create([ 'name' => $normalizedName, 'color' => $data['color'] ?? null, ]); return response()->json([ 'id' => $role->id, 'name' => $role->name, 'color' => $role->color, ], 201); } public function update(Request $request, Role $role): JsonResponse { if ($error = $this->ensureAdmin($request)) { return $error; } $data = $request->validate([ 'name' => ['required', 'string', 'max:100', "unique:roles,name,{$role->id}"], 'color' => ['nullable', 'string', 'max:20', 'regex:/^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/'], ]); $normalizedName = $this->normalizeRoleName($data['name']); if (Role::query() ->where('id', '!=', $role->id) ->where('name', $normalizedName) ->exists() ) { return response()->json(['message' => 'Role already exists.'], 422); } if (in_array($role->name, self::CORE_ROLES, true) && $normalizedName !== $role->name) { return response()->json(['message' => 'Core roles cannot be renamed.'], 422); } $color = array_key_exists('color', $data) ? $data['color'] : $role->color; $role->update([ 'name' => $normalizedName, 'color' => $color, ]); return response()->json([ 'id' => $role->id, 'name' => $role->name, 'color' => $role->color, ]); } public function destroy(Request $request, Role $role): JsonResponse { if ($error = $this->ensureAdmin($request)) { return $error; } if (in_array($role->name, self::CORE_ROLES, true)) { return response()->json(['message' => 'Core roles cannot be deleted.'], 422); } if ($role->users()->exists()) { return response()->json(['message' => 'Role is assigned to users.'], 422); } $role->delete(); return response()->json(null, 204); } private function normalizeRoleName(string $value): string { $raw = strtoupper(trim($value)); $raw = preg_replace('/\s+/', '_', $raw); $raw = preg_replace('/[^A-Z0-9_]/', '_', $raw); $raw = preg_replace('/_+/', '_', $raw); $raw = trim($raw, '_'); if ($raw === '') { return 'ROLE_'; } if (str_starts_with($raw, 'ROLE_')) { return $raw; } return "ROLE_{$raw}"; } }