feat: platform admin backend — controllers, services, routes, tests

Add cross-organisation admin API endpoints behind role:super_admin middleware:
- AdminOrganisationController: CRUD with search, filter, billing_status management
- AdminUserController: user management with role assignment across orgs
- AdminStatsController: platform-wide aggregate statistics
- AdminActivityLogController: filterable activity log viewer
- AdminImpersonationController + ImpersonationService: user impersonation with
  token-based session management and activity logging
- BillingStatus enum, form requests, API resources, 23 feature tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-14 23:33:16 +02:00
parent ec31646a93
commit ddf26dad33
18 changed files with 1299 additions and 0 deletions

View File

@@ -36,6 +36,11 @@ use App\Http\Controllers\Api\V1\PasswordResetController;
use App\Http\Controllers\Api\V1\PortalMeController;
use App\Http\Controllers\Api\V1\Portal\PortalShiftController;
use App\Http\Controllers\Api\V1\UserOrganisationTagController;
use App\Http\Controllers\Api\V1\Admin\AdminOrganisationController;
use App\Http\Controllers\Api\V1\Admin\AdminUserController;
use App\Http\Controllers\Api\V1\Admin\AdminStatsController;
use App\Http\Controllers\Api\V1\Admin\AdminActivityLogController;
use App\Http\Controllers\Api\V1\Admin\AdminImpersonationController;
use App\Models\FestivalSection;
use App\Models\Organisation;
use Illuminate\Support\Facades\Gate;
@@ -78,8 +83,33 @@ Route::post('public/check-email', CheckEmailController::class)->middleware('thro
Route::post('events/{event}/volunteer-register', VolunteerRegistrationController::class)->middleware('throttle:5,1');
Route::post('portal/token-auth', [PortalTokenController::class, 'auth'])->middleware('throttle:10,1');
// Platform Admin routes
Route::prefix('admin')
->middleware(['auth:sanctum', 'role:super_admin'])
->name('admin.')
->group(function () {
// Organisations
Route::apiResource('organisations', AdminOrganisationController::class);
// Users
Route::apiResource('users', AdminUserController::class)
->except(['store']);
// Platform statistics
Route::get('stats', [AdminStatsController::class, 'index']);
// Activity log
Route::get('activity-log', [AdminActivityLogController::class, 'index']);
// Impersonation (start)
Route::post('impersonate/{user}', [AdminImpersonationController::class, 'start']);
});
// Protected routes
Route::middleware('auth:sanctum')->group(function () {
// Impersonation (stop — accessible by impersonated user, not just super_admin)
Route::post('admin/stop-impersonation', [AdminImpersonationController::class, 'stop']);
// Auth
Route::get('auth/me', MeController::class);
Route::post('auth/logout', LogoutController::class);