withRouting( web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php', health: '/up', then: function () { // B2B operator routes — served at /operator/* (no /api prefix) \Illuminate\Support\Facades\Route::middleware(['throttle:60,1']) ->prefix('operator') ->group(base_path('routes/operator.php')); }, ) ->withSchedule(function (\Illuminate\Console\Scheduling\Schedule $schedule): void { // Expire old user bonuses every hour $schedule->call(function () { app(\App\Services\BonusService::class)->expireBonuses(); })->hourly()->name('bonus:expire')->withoutOverlapping(); // Recalculate VIP levels based on total wager – runs daily at 03:00 $schedule->call(function () { $thresholds = [5 => 10000, 4 => 2000, 3 => 500, 2 => 100, 1 => 0]; \App\Models\User::chunk(200, function ($users) use ($thresholds) { foreach ($users as $user) { $totalWager = \App\Models\GameBet::where('user_id', $user->id)->sum('wager_amount'); $newLevel = 1; foreach ($thresholds as $level => $min) { if ($totalWager >= $min) { $newLevel = $level; break; } } if ($user->vip_level !== $newLevel) { $user->forceFill(['vip_level' => $newLevel])->save(); } } }); })->dailyAt('03:00')->name('vip:recalculate')->withoutOverlapping(); // Flag / notify inactive users (no login for 90 days) – runs weekly $schedule->call(function () { $cutoff = now()->subDays(90); \App\Models\User::where('last_login_at', '<', $cutoff) ->where('is_banned', false) ->chunk(100, function ($users) { foreach ($users as $user) { \Illuminate\Support\Facades\Log::info('Inactive user flagged', [ 'user_id' => $user->id, 'last_login_at' => $user->last_login_at, ]); } }); })->weekly()->name('users:cleanup-inactive')->withoutOverlapping(); }) ->withMiddleware(function (Middleware $middleware): void { $middleware->encryptCookies(except: ['appearance', 'sidebar_state', 'XSRF-TOKEN']); $middleware->validateCsrfTokens(except: [ 'api/webhooks/nowpayments', 'api/betix/*', 'wallet/deposits', 'locale', 'api/wallet/vault/*', 'api/wallet/vault', // Operator B2B API — authenticated via license key, not CSRF 'operator/*', ]); $middleware->web(append: [ HandleAppearance::class, SetLocale::class, HandleInertiaRequests::class, AddLinkHeadersForPreloadedAssets::class, CheckBanned::class, DetectCiphertextInJson::class, GeoBlockMiddleware::class, MaintenanceModeMiddleware::class, ]); // Route middleware aliases $middleware->alias([ 'restrict' => \App\Http\Middleware\EnforceRestriction::class, 'license.key' => \App\Http\Middleware\ValidateLicenseKey::class, ]); }) ->withExceptions(function (Exceptions $exceptions): void { $exceptions->render(function (\Illuminate\Validation\ValidationException $e, $request) { if ($request->is('api/*')) { return response()->json([ 'message' => 'The given data was invalid.', 'errors' => $e->errors(), ], 422); } }); $exceptions->render(function (\Illuminate\Auth\AuthenticationException $e, $request) { if ($request->is('api/*')) { return response()->json(['message' => 'Unauthenticated.'], 401); } }); $exceptions->render(function (\Symfony\Component\HttpKernel\Exception\HttpException $e, $request) { if ($request->is('api/*')) { return response()->json([ 'message' => $e->getMessage() ?: 'An error occurred.', 'status' => $e->getStatusCode(), ], $e->getStatusCode()); } }); })->create();