route('dashboard'); })->name('home'); // Public Pages Route::get('/blocked', function () { return Inertia::render('GeoBlocked', [ 'message' => \App\Models\AppSetting::get('geo.settings', [])['block_message'] ?? 'This service is not available in your region.', 'reason' => 'country', ]); })->name('geo.blocked')->withoutMiddleware([\App\Http\Middleware\GeoBlockMiddleware::class]); Route::get('/maintenance', function () { return Inertia::render('Maintenance', ['message' => 'Wir führen gerade Wartungsarbeiten durch.']); })->name('maintenance')->withoutMiddleware([\App\Http\Middleware\MaintenanceModeMiddleware::class]); Route::get('/faq', function () { return Inertia::render('Faq'); })->name('faq'); Route::get('/api/auth/availability', AvailabilityController::class)->name('api.auth.availability'); Route::get('/legal/terms', function () { return Inertia::render('policies/Terms'); })->name('legal.terms'); Route::get('/legal/cookies', function () { return Inertia::render('policies/Cookies'); })->name('legal.cookies'); Route::get('/legal/privacy', function () { return Inertia::render('policies/Privacy'); })->name('legal.privacy'); Route::get('/legal/bonus-policy', function () { return Inertia::render('policies/BonusPolicy'); })->name('legal.bonus'); Route::get('/legal/disputes', function () { return Inertia::render('policies/Disputes'); })->name('legal.disputes'); Route::get('/legal/responsible-gaming', function () { return Inertia::render('policies/ResponsibleGaming'); })->name('legal.responsible'); Route::get('/legal/aml', function () { return Inertia::render('policies/Aml'); })->name('legal.aml'); Route::get('/legal/risk-warnings', function () { return Inertia::render('policies/RiskWarnings'); })->name('legal.risks'); // Email verification via code (must be logged in but not yet verified) Route::post('/email/verify/code', EmailVerificationCodeController::class) ->middleware(['auth:web', 'throttle:10,1']) ->name('verification.verify.code'); // Local-only email preview routes if (app()->environment('local')) { Route::get('/dev/mail/preview/{type}', function (Request $request, string $type) { $payload = $request->except(['_token']); $mailable = new \App\Mail\SystemNotificationMail($type, $payload); return view('emails.notification', $mailable->data); })->name('dev.mail.preview'); Route::get('/dev/mail/preview/verify-email', function () { $user = (object) ['name' => 'Demo User']; $url = url('/email/verify/sample-token'); $code = '123456'; return view('emails.verify-email', compact('user', 'url', 'code')); })->name('dev.mail.preview.verify'); Route::get('/dev/mail', function () { $base = url('/dev/mail'); $types = [ 'deposit', 'withdrawal', 'bonus_available', 'banned', 'chat_banned', 'level_up', 'near_level_up', 'inactivity_check', 'friend_request', 'kyc_error', 'kyc_accepted', 'email_2fa', 'terms_updated', 'cookie_policy_updated', 'privacy_policy_updated', 'bonus_policy_updated', 'dispute_policy_updated', 'responsible_gaming_updated', 'aml_policy_updated', 'risk_warnings_updated', 'new_support_message', 'casino_updated', ]; $links = array_map(fn ($t) => "
  • {$t}
  • ", $types); $html = "Mail Previews

    Mail Previews (local)

    Diese Seite ist nur in APP_ENV=local verfügbar.

    SystemNotificationMail

    Weitere Templates

    "; return response($html)->header('Content-Type', 'text/html'); })->name('dev.mail.index'); // Gameplay preview (no auth required – visual QA only) Route::get('/dev/gameplay/{slug?}', function (string $slug = 'book-of-ra') { $games = [ 'book-of-ra' => [ 'name' => 'Book of Ra Deluxe', 'provider' => 'Demo', 'description' => 'Free Slot Demo', 'src' => 'https://free-slots.games/greenslots/BookOfRaDX/index.php', ], ]; $game = $games[$slug] ?? $games['book-of-ra']; return Inertia::render('GamePlay', [ 'title' => $game['name'], 'slug' => $slug, 'src' => $game['src'], 'provider' => $game['provider'], 'description' => $game['description'], ]); }); // Gameplay preview index (no auth required) Route::get('/dev/gameplay', function () { $base = url('/dev/gameplay'); $games = [ 'dice', 'crash', 'mines', 'plinko', 'gates-of-olympus', 'sweet-bonanza', 'razor-shark', 'mental', 'wanted-dead-or-a-wild', ]; $links = array_map(fn ($g) => "
  • {$g}
  • ", $games); $html = "Gameplay Previews

    Gameplay Previews (local)

    Kein Login erforderlich – nur in APP_ENV=local verfügbar.

    "; return response($html)->header('Content-Type', 'text/html'); })->name('dev.gameplay.index'); } // Note: /login POST and /register POST are handled by Laravel Fortify (FortifyServiceProvider) // Rate limiting is configured in FortifyServiceProvider via RateLimiter::for('login', ...) // Authenticated Routes (Inertia Pages) Route::middleware([ 'restrict:account_ban', 'throttle:1200,1', ])->group(function () { // Authenticated API-ish routes for Vault & User Bonuses (local, no external API) // IMPORTANT: These MUST match exactly the requests made in Gateway tests (e.g. /api/wallet/vault) Route::group(['prefix' => 'api'], function() { Route::get('/wallet/vault', [VaultController::class, 'show'])->name('api.vault.show'); Route::get('/wallet/balance', [WalletController::class, 'balance'])->name('api.wallet.balance'); Route::get('/wallet/bets', function () { $user = \Illuminate\Support\Facades\Auth::user(); if (!$user) return response()->json(['error' => 'Unauthorized'], 401); $bets = \App\Models\GameBet::where('user_id', $user->id) ->orderByDesc('created_at') ->limit(50) ->get() ->map(fn ($b) => [ 'id' => $b->id, 'game' => $b->game_name, 'wager' => (float) $b->wager_amount, 'payout' => (float) $b->payout_amount, 'net' => (float) $b->payout_amount - (float) $b->wager_amount, 'currency' => $b->currency, 'round' => $b->round_number, 'session_token' => $b->session_token, 'server_seed_hash' => $b->server_seed_hash, 'created_at' => $b->created_at?->toIso8601String(), ]); return response()->json(['bets' => $bets]); })->middleware('throttle:60,1')->name('api.wallet.bets'); Route::post('/wallet/vault/deposit', [VaultController::class, 'deposit'])->middleware('throttle:30,1')->name('api.vault.deposit'); Route::post('/wallet/vault/withdraw', [VaultController::class, 'withdraw'])->middleware('throttle:30,1')->name('api.vault.withdraw'); Route::post('/wallet/vault/pin/verify', [VaultPinController::class, 'verify'])->middleware('throttle:60,1')->name('api.vault.pin.verify'); Route::post('/wallet/vault/pin/set', [VaultPinController::class, 'set'])->middleware('throttle:20,1')->name('api.vault.pin.set'); // User Bonuses (Local) Route::get('/user/bonuses', [\App\Http\Controllers\UserBonusController::class, 'index'])->name('api.user.bonuses'); // Explicit API Routes (formerly via Proxy) Route::get('/chat', [\App\Http\Controllers\ChatController::class, 'index'])->middleware('throttle:600,1')->name('api.chat.index'); Route::post('/chat', [\App\Http\Controllers\ChatController::class, 'store'])->middleware('throttle:60,1')->name('api.chat.store'); Route::post('/chat/{id}/react', [\App\Http\Controllers\ChatController::class, 'react'])->middleware('throttle:120,1')->name('api.chat.react'); Route::post('/chat/{id}/report', [\App\Http\Controllers\ChatController::class, 'report'])->whereNumber('id')->middleware('throttle:20,1')->name('api.chat.report'); Route::delete('/chat/{id}', [\App\Http\Controllers\ChatController::class, 'destroy'])->whereNumber('id')->name('api.chat.destroy'); Route::post('/promos/apply', [\App\Http\Controllers\PromoController::class, 'apply'])->middleware('throttle:10,1')->name('api.promos.apply'); Route::get('/users/search', [\App\Http\Controllers\SocialController::class, 'search'])->middleware('throttle:60,1')->name('api.users.search'); Route::get('/bonuses/app', [\App\Http\Controllers\BonusesController::class, 'appIndex'])->name('api.bonuses.app'); // Favorites Route::get('/favorites', [\App\Http\Controllers\FavoriteController::class, 'index'])->middleware('throttle:60,1')->name('api.favorites.index'); Route::post('/favorites', [\App\Http\Controllers\FavoriteController::class, 'store'])->middleware('throttle:30,1')->name('api.favorites.store'); Route::delete('/favorites/{slug}', [\App\Http\Controllers\FavoriteController::class, 'destroy'])->middleware('throttle:30,1')->name('api.favorites.destroy'); // Recently Played Route::get('/recently-played', [\App\Http\Controllers\RecentlyPlayedController::class, 'index'])->middleware('throttle:60,1')->name('api.recently-played'); // Games catalog — used by SearchModal and Dashboard (provider: slug, name, image, type) Route::get('/games', function () { $baseUrl = rtrim((string) config('games.game_base_url', config('app.url')), '/'); $games = collect(config('games.catalog', []))->values()->map(fn ($g) => [ 'slug' => $g['slug'], 'name' => $g['name'], 'provider' => 'BetiX', 'image' => "{$baseUrl}/assets/games/{$g['slug']}.png", 'type' => 'original', ]); return response()->json(['games' => $games]); })->middleware('throttle:120,1')->name('api.games'); // BetiX Originals: game catalog (public, no external backend required) Route::get('/originals', function () { $baseUrl = rtrim((string) config('games.game_base_url', config('app.url')), '/'); $catalog = collect(config('games.catalog', []))->values()->map(fn ($g) => [ 'id' => $g['id'], 'slug' => $g['slug'], 'name' => $g['name'], 'rtp' => $g['rtp'], 'volatility' => $g['volatility'] ?? null, 'min_bet' => $g['min_bet'] ?? null, 'max_bet' => $g['max_bet'] ?? null, 'thumbnail_url' => "{$baseUrl}/assets/games/{$g['slug']}.png", 'image' => "{$baseUrl}/assets/games/{$g['slug']}.png", 'provider' => 'BetiX', 'tag' => 'ORIGINAL', ]); return response()->json(['games' => $catalog]); })->middleware('throttle:60,1')->name('api.originals'); // BetiX Originals: launch a real session via BetiX API Route::post('/originals/launch', function (\Illuminate\Http\Request $request) { $request->validate([ 'game' => ['required', 'string', 'in:dice,crash,mines,plinko'], ]); /** @var \App\Models\User $user */ $user = \Illuminate\Support\Facades\Auth::user(); $client = app(\App\Services\BetiXClient::class); $balance = (float) $user->balance; if ($balance <= 0) { return response()->json(['error' => 'Kein Guthaben vorhanden. Bitte lade dein Konto auf.'], 422); } $payload = [ 'license_key' => config('services.betix.key'), 'player_id' => (string) $user->id, 'balance' => $balance, 'currency' => 'EUR', 'game' => $request->input('game'), 'session_timeout_seconds' => 14400, ]; \Illuminate\Support\Facades\Log::debug('[BetiX] launch attempt', [ 'game' => $request->input('game'), 'player_id' => (string) $user->id, 'balance' => (float) $user->balance, 'api_url' => config('services.betix.url'), 'key_prefix' => substr((string) config('services.betix.key'), 0, 12) . '...', ]); // Reuse an existing valid session to skip the BetiX API call (speeds up reload) $gameBase = rtrim((string) config('games.game_base_url', 'http://localhost:3100'), '/'); $existing = \App\Models\OperatorSession::where('player_id', (string) $user->id) ->where('game_slug', $request->input('game')) ->where('status', 'active') ->where('expires_at', '>', now()) ->latest() ->first(); if ($existing) { \Illuminate\Support\Facades\Log::debug('[BetiX] reusing existing session', [ 'session_token' => substr($existing->session_token, 0, 16) . '...', 'game' => $existing->game_slug, 'expires_at' => $existing->expires_at->toIso8601String(), ]); $launchUrl = "{$gameBase}/{$request->input('game')}?session={$existing->session_token}"; return response()->json([ 'launch_url' => $launchUrl, 'session_token' => $existing->session_token, 'server_seed_hash' => $existing->server_seed_hash, ]); } // No valid session — expire stale ones and create a fresh session \App\Models\OperatorSession::where('player_id', (string) $user->id) ->where('game_slug', $request->input('game')) ->where('status', 'active') ->update(['status' => 'expired']); // Prepare the self-casino record (needed for both remote and local fallback) $selfKey = 'betix.self.' . config('app.key'); $casino = \App\Models\OperatorCasino::firstOrCreate( ['license_key_hash' => hash('sha256', $selfKey)], ['name' => 'BetiX Self', 'status' => 'active'] ); // Try the external BetiX API; fall back to local session generation if unreachable $result = null; try { $result = $client->launch($payload); \Illuminate\Support\Facades\Log::debug('[BetiX] launch success (remote)', [ 'launch_url' => $result['launch_url'] ?? '(missing)', 'session_token' => isset($result['session_token']) ? substr($result['session_token'], 0, 16) . '...' : '(missing)', ]); } catch (\Throwable $e) { \Illuminate\Support\Facades\Log::warning('[BetiX] remote launch unavailable — using local session fallback', [ 'error' => $e->getMessage(), ]); } if ($result === null) { // Local fallback: generate a provably-fair session without the external API $serverSeed = bin2hex(random_bytes(32)); $serverSeedHash = hash('sha256', $serverSeed); $token = (string) \Illuminate\Support\Str::uuid(); $expiresAt = now()->addHours(4); \App\Models\OperatorSession::create([ 'session_token' => $token, 'operator_casino_id' => $casino->id, 'player_id' => (string) $user->id, 'game_slug' => $request->input('game'), 'currency' => 'EUR', 'start_balance' => $balance, 'current_balance' => $balance, 'server_seed' => encrypt($serverSeed), 'server_seed_hash' => $serverSeedHash, 'status' => 'active', 'expires_at' => $expiresAt, ]); $launchUrl = "{$gameBase}/{$request->input('game')}?session={$token}"; return response()->json([ 'launch_url' => $launchUrl, 'session_token' => $token, 'server_seed_hash' => $serverSeedHash, ]); } try { \App\Models\OperatorSession::create([ 'session_token' => $result['session_token'], 'operator_casino_id' => $casino->id, 'player_id' => (string) $user->id, 'game_slug' => $request->input('game'), 'currency' => 'EUR', 'start_balance' => (float) $user->balance, 'current_balance' => (float) $user->balance, 'server_seed' => encrypt($result['session_token']), 'server_seed_hash' => $result['server_seed_hash'] ?? hash('sha256', $result['session_token']), 'status' => 'active', 'expires_at' => now()->addHours(4), ]); } catch (\Throwable $e) { \Illuminate\Support\Facades\Log::error('[BetiX] OperatorSession::create failed', [ 'error' => $e->getMessage(), 'file' => $e->getFile() . ':' . $e->getLine(), ]); // Still return the launch_url — session tracking failure should not block the player } // Rewrite launch_url origin to match GAME_BASE_URL (fixes Mixed Content on HTTPS) $launchUrl = preg_replace('#^https?://localhost:3100#', $gameBase, $result['launch_url']); return response()->json([ 'launch_url' => $launchUrl, 'session_token' => $result['session_token'], 'server_seed_hash' => $result['server_seed_hash'] ?? null, ]); })->middleware(['auth:web', 'throttle:30,1'])->name('api.originals.launch'); }); // Support Chat Routes (must be before the proxy catch-all) Route::middleware(['auth:web'])->group(function () { Route::post('/api/support/start', [\App\Http\Controllers\SupportChatController::class, 'start'])->middleware('throttle:30,1')->name('api.support.start'); Route::post('/api/support/message', [\App\Http\Controllers\SupportChatController::class, 'message'])->middleware('throttle:60,1')->name('api.support.message'); Route::get('/api/support/status', [\App\Http\Controllers\SupportChatController::class, 'status'])->middleware('throttle:60,1')->name('api.support.status'); Route::get('/api/support/stream', [\App\Http\Controllers\SupportChatController::class, 'stream'])->name('api.support.stream'); Route::post('/api/support/stop', [\App\Http\Controllers\SupportChatController::class, 'stop'])->middleware('throttle:30,1')->name('api.support.stop'); Route::post('/api/support/handoff', [\App\Http\Controllers\SupportChatController::class, 'handoff'])->middleware('throttle:10,1')->name('api.support.handoff'); Route::post('/api/support/close', [\App\Http\Controllers\SupportChatController::class, 'close'])->middleware('throttle:10,1')->name('api.support.close'); }); // Live wins feed — last 15 positive payouts Route::get('/api/wins/live', function () { $wins = \App\Models\GameBet::orderByDesc('created_at') ->where('payout_amount', '>', 0) ->limit(15) ->get(['id', 'user_id', 'game_name', 'payout_amount', 'payout_multiplier', 'currency']); return response()->json($wins->values()->map(fn ($b) => [ 'id' => $b->id, 'user' => 'Player#' . $b->user_id, 'game' => $b->game_name, 'amount' => number_format((float) $b->payout_amount, 4) . ' ' . $b->currency, 'multiplier' => (float) $b->payout_multiplier, 'isWin' => true, ])); })->middleware('throttle:60,1')->name('api.wins.live'); // Hall of Fame — all-time top wins by multiplier Route::get('/api/wins/top', function () { $wins = \App\Models\GameBet::orderByDesc('payout_multiplier') ->where('payout_multiplier', '>', 1) ->limit(5) ->get(['id', 'user_id', 'game_name', 'payout_amount', 'payout_multiplier', 'currency']); return response()->json($wins->values()->map(fn ($b, $i) => [ 'id' => $b->id, 'rank' => $i + 1, 'user' => 'Player#' . $b->user_id, 'game' => $b->game_name, 'amount' => number_format((float) $b->payout_amount, 4) . ' ' . $b->currency, 'multiplier' => number_format((float) $b->payout_multiplier, 2) . 'x', 'image' => null, ])); })->middleware('throttle:30,1')->name('api.wins.top'); // Trophy Room Route::get('/trophy', [\App\Http\Controllers\TrophyController::class, 'index'])->middleware('auth:web')->name('trophy'); Route::get('/trophy/{username}', [\App\Http\Controllers\TrophyController::class, 'show'])->name('trophy.user'); Route::get('/dashboard', function () { $baseUrl = rtrim((string) config('games.game_base_url', config('app.url')), '/'); $games = collect(config('games.catalog', []))->values()->map(fn ($g) => [ 'id' => $g['id'], 'slug' => $g['slug'], 'name' => $g['name'], 'rtp' => $g['rtp'], 'image' => "{$baseUrl}/assets/games/{$g['slug']}.png", 'provider' => 'BetiX', 'tag' => 'ORIGINAL', ]); return Inertia::render('Dashboard', ['initialGames' => $games]); })->name('dashboard'); Route::get('/bonuses', function () { return Inertia::render('Bonus'); })->name('bonuses'); Route::get('/vip-levels', [VipController::class, 'index'])->name('vip-levels'); Route::get('/self-exclusion', function () { return Inertia::render('responsible/SelfExclusion'); })->name('self-exclusion'); Route::middleware(['auth:web', 'verified'])->group(function () { Route::get('/wallet', [WalletController::class, 'index'])->name('wallet'); Route::get('/wallet/bets', [WalletController::class, 'bets'])->name('wallet.bets'); Route::post('/vip-levels/claim', [VipController::class, 'claim'])->name('vip.claim'); Route::post('/responsible/limits', function (Request $request) { return back()->with('success', 'Settings received (Mock).'); })->name('responsible.limits'); // Deposits via NOWPayments (user-authenticated) Route::get('/wallet/deposits/currencies', [DepositController::class, 'currencies']) ->middleware('throttle:60,1') ->name('wallet.deposits.currencies'); Route::post('/wallet/deposits', [DepositController::class, 'create']) ->middleware('throttle:20,1') ->name('wallet.deposits.create'); Route::get('/wallet/deposits/history', [DepositController::class, 'history']) ->middleware('throttle:60,1') ->name('wallet.deposits.history'); Route::get('/wallet/deposits/{order_id}', [DepositController::class, 'show']) ->middleware('throttle:60,1') ->whereUuid('order_id') ->name('wallet.deposits.show'); Route::delete('/wallet/deposits/{order_id}', [DepositController::class, 'cancel']) ->middleware('throttle:20,1') ->whereUuid('order_id') ->name('wallet.deposits.cancel'); Route::get('/profile', [SocialController::class, 'me'])->name('profile.me'); Route::post('/profile/update', [SocialController::class, 'update'])->name('profile.update'); Route::get('/profile/{id}', [SocialController::class, 'show'])->name('profile.show'); Route::post('/profile/{id}/tip', [SocialController::class, 'tip'])->name('profile.tip'); }); Route::get('/profile/{username}', [SocialController::class, 'show'])->name('profile.show'); Route::post('/profile/update', [SocialController::class, 'update'])->name('social.profile.update'); Route::post('/profile/upload', [SocialController::class, 'uploadImage'])->name('profile.upload'); Route::post('/profile/{id}/like', [SocialController::class, 'like'])->name('profile.like'); Route::post('/profile/{id}/comment', [SocialController::class, 'comment'])->name('profile.comment'); Route::post('/profile/{id}/report', [SocialController::class, 'report'])->name('profile.report'); Route::middleware(['auth:web'])->group(function () { Route::get('/feedback', [FeedbackController::class, 'showForm'])->name('feedback'); Route::post('/feedback', [FeedbackController::class, 'store'])->name('feedback.store'); }); Route::post('/profile/{id}/tip', [SocialController::class, 'tip'])->middleware('throttle:10,1')->name('profile.tip'); Route::middleware('throttle:20,1')->group(function () { Route::post('/friends/request', [SocialController::class, 'requestFriend'])->name('friends.request'); Route::post('/friends/{id}/accept', [SocialController::class, 'acceptFriend'])->name('friends.accept'); Route::post('/friends/{id}/decline', [SocialController::class, 'declineFriend'])->name('friends.decline'); }); // Social Hub Route::get('/social', [SocialController::class, 'hub'])->name('social.hub'); // Guild Chat API Route::middleware('auth:web')->prefix('api/guild-chat')->group(function () { Route::get('/me', [\App\Http\Controllers\GuildChatController::class, 'myGuild']); Route::get('/{guildId}/members', [\App\Http\Controllers\GuildChatController::class, 'members'])->whereNumber('guildId'); Route::get('/{guildId}', [\App\Http\Controllers\GuildChatController::class, 'messages'])->whereNumber('guildId'); Route::post('/{guildId}', [\App\Http\Controllers\GuildChatController::class, 'send'])->whereNumber('guildId'); }); // Direct Messages API (auth-guarded, web session) Route::middleware('auth:web')->prefix('api/dm')->group(function () { Route::get('/conversations', [\App\Http\Controllers\DirectMessageController::class, 'conversations']); Route::get('/friends', [\App\Http\Controllers\DirectMessageController::class, 'friends']); Route::get('/friends/requests', [\App\Http\Controllers\DirectMessageController::class, 'friendRequests']); Route::get('/{userId}', [\App\Http\Controllers\DirectMessageController::class, 'messages'])->whereNumber('userId'); Route::post('/{userId}', [\App\Http\Controllers\DirectMessageController::class, 'send'])->whereNumber('userId'); Route::post('/messages/{id}/report', [\App\Http\Controllers\DirectMessageController::class, 'report'])->whereNumber('id'); }); Route::get('/settings', function () { return Inertia::render('Social/Settings', [ 'user' => \Illuminate\Support\Facades\Auth::user(), ]); })->name('settings'); // Admin Routes (Inertia Pages) // NOTE: Replacing the old /admin prefix with the new structure Route::prefix('admin')->middleware(['auth:web'])->group(function () { Route::get('/casino', [AdminController::class, 'casinoDashboard'])->name('admin.casino'); Route::get('/users', [AdminController::class, 'usersIndex'])->name('admin.users.index'); Route::get('/users/{id}', [AdminController::class, 'userShow'])->name('admin.users.show'); Route::post('/users/{id}', [AdminController::class, 'updateUser'])->name('admin.users.update'); Route::get('/users/{id}/history', [AdminController::class, 'userHistory'])->name('admin.users.history'); Route::get('/chat', [AdminController::class, 'chatIndex'])->name('admin.chat.index'); Route::post('/chat/toggle-ai', [AdminController::class, 'toggleAi'])->name('admin.chat.toggle-ai'); Route::delete('/chat/{id}', [AdminController::class, 'deleteChatMessage'])->name('admin.chat.delete'); // Report management Route::get('/reports/chat', [AdminController::class, 'chatReports'])->name('admin.reports.chat'); Route::get('/reports/chat/{id}', [AdminController::class, 'chatReportShow'])->whereNumber('id')->name('admin.reports.chat.show'); Route::post('/reports/chat/{id}', [AdminController::class, 'updateChatReport'])->whereNumber('id')->name('admin.reports.chat.update'); Route::post('/reports/chat/{id}/punish', [AdminController::class, 'punishFromChatReport'])->whereNumber('id')->name('admin.reports.chat.punish'); Route::get('/reports/profiles', [AdminController::class, 'profileReports'])->name('admin.reports.profiles'); Route::get('/reports/profiles/{id}', [AdminController::class, 'profileReportShow'])->whereNumber('id')->name('admin.reports.profiles.show'); Route::post('/reports/profiles/{id}', [AdminController::class, 'updateProfileReport'])->whereNumber('id')->name('admin.reports.profiles.update'); Route::post('/reports/profiles/{id}/punish', [AdminController::class, 'punishFromProfileReport'])->whereNumber('id')->name('admin.reports.profiles.punish'); // Feedback management Route::get('/feedback', [FeedbackController::class, 'adminIndex'])->name('admin.feedback.index'); Route::get('/feedback/{id}', [FeedbackController::class, 'adminShow'])->whereNumber('id')->name('admin.feedback.show'); Route::post('/feedback/{id}', [FeedbackController::class, 'adminUpdate'])->whereNumber('id')->name('admin.feedback.update'); // Restriction management Route::post('/restrictions/{id}/lift', [AdminController::class, 'liftRestriction'])->whereNumber('id')->name('admin.restrictions.lift'); Route::post('/restrictions/{id}/extend', [AdminController::class, 'extendRestriction'])->whereNumber('id')->name('admin.restrictions.extend'); // Old settings routes Route::get('/promos', [PromoAdminController::class, 'index'])->name('admin.promos.index'); Route::post('/promos', [PromoAdminController::class, 'store'])->name('admin.promos.store'); Route::patch('/promos/{id}', [PromoAdminController::class, 'update'])->name('admin.promos.update'); Route::get('/support', [SupportAdminController::class, 'index'])->name('admin.support.index'); Route::post('/support/settings', [SupportAdminController::class, 'settings'])->name('admin.support.settings'); Route::post('/support/threads/{thread}/message', [SupportAdminController::class, 'reply'])->name('admin.support.reply'); Route::post('/support/threads/{thread}/close', [SupportAdminController::class, 'close'])->name('admin.support.close'); // Admin Payments Settings (NOWPayments) Route::get('/payments/settings', [\App\Http\Controllers\Admin\PaymentsSettingsController::class, 'show'])->name('admin.payments.settings'); Route::post('/payments/settings', [\App\Http\Controllers\Admin\PaymentsSettingsController::class, 'save'])->name('admin.payments.settings.save'); Route::post('/payments/test', [\App\Http\Controllers\Admin\PaymentsSettingsController::class, 'test'])->name('admin.payments.test'); // Admin Wallets Settings Route::get('/wallets/settings', [\App\Http\Controllers\Admin\WalletsAdminController::class, 'show'])->name('admin.wallets.settings'); Route::post('/wallets/settings', [\App\Http\Controllers\Admin\WalletsAdminController::class, 'save'])->name('admin.wallets.settings.save'); // Site & GeoBlock Settings Route::get('/settings/site', [\App\Http\Controllers\Admin\SiteSettingsController::class, 'show'])->name('admin.settings.site'); Route::post('/settings/site', [\App\Http\Controllers\Admin\SiteSettingsController::class, 'save'])->name('admin.settings.site.save'); Route::get('/settings/geo', [\App\Http\Controllers\Admin\GeoBlockController::class, 'show'])->name('admin.settings.geo'); Route::post('/settings/geo', [\App\Http\Controllers\Admin\GeoBlockController::class, 'save'])->name('admin.settings.geo.save'); }); // Guilds Pages Route::get('/guilds', [GuildController::class, 'index'])->name('guilds.index'); Route::get('/guilds/top', [GuildController::class, 'top'])->name('guilds.top'); // Guild Actions Route::post('/guilds', [GuildActionController::class, 'store'])->name('guilds.store'); Route::post('/guilds/join', [GuildActionController::class, 'join'])->name('guilds.join'); Route::post('/guilds/leave', [GuildActionController::class, 'leave'])->name('guilds.leave'); Route::post('/guilds/kick', [GuildActionController::class, 'kick'])->name('guilds.kick'); Route::post('/guilds/invite/regenerate', [GuildActionController::class, 'regenerateInvite'])->name('guilds.invite.regenerate'); Route::post('/guilds/update', [GuildActionController::class, 'update'])->name('guilds.update'); // Game utility: local fallback Originals list (used if provider API is unavailable) Route::get('/games/list', function () { return response()->json([ [ 'id' => 'plinko', 'slug' => 'plinko', 'name' => 'Plinko', 'provider' => 'BetiX', 'image' => 'https://placehold.co/600x400?text=Plinko', 'tag' => 'ORIGINAL', 'rtp' => 96.0, ], [ 'id' => 'dice', 'slug' => 'dice', 'name' => 'Dice', 'provider' => 'BetiX', 'image' => 'https://placehold.co/600x400?text=Dice', 'tag' => 'ORIGINAL', 'rtp' => 99.0, ], [ 'id' => 'mines', 'slug' => 'mines', 'name' => 'Mines', 'provider' => 'BetiX', 'image' => 'https://placehold.co/600x400?text=Mines', 'tag' => 'ORIGINAL', 'rtp' => 96.0, ], ]); })->name('games.list'); // Game Play Routes (Inertia Pages) Route::get('/games/play/{provider}/{slug}', function (string $provider, string $slug) { $title = ucwords(str_replace(['-', '_'], ' ', $slug)); $game = collect(config('games.catalog', []))->first(fn ($g) => ($g['slug'] ?? '') === $slug); if ($game) { $title = $game['name'] ?? $title; $provider = $game['provider'] ?? $provider; $description = $game['description'] ?? null; } else { $description = null; } return Inertia::render('GamePlay', [ 'title' => $title, 'slug' => $slug, 'src' => null, 'provider' => $provider, 'description' => $description, ]); })->name('games.play'); // Legacy single-segment route — redirect to new format Route::get('/games/play/{slug}', function (string $slug) { $game = collect(config('games.catalog', []))->first(fn ($g) => ($g['slug'] ?? '') === $slug); $provider = $game['provider'] ?? 'betix'; return redirect()->route('games.play', [ 'provider' => \Illuminate\Support\Str::slug($provider), 'slug' => $slug, ], 301); })->name('games.play.legacy'); // Optional direct launch via query (for future third-party providers) Route::get('/games/play', function (Request $request) { $src = $request->query('src'); return Inertia::render('GamePlay', [ 'title' => $request->query('title', 'Game'), 'slug' => null, 'src' => $src, ]); })->name('games.play.direct'); // Internal embed: secure server-side provider launch (keeps provider origin hidden) Route::get('/games/embed/{slug}', [EmbedController::class, 'show']) ->middleware('throttle:60,1') ->name('games.embed'); }); Route::post('/locale', [LocaleController::class, 'set'])->middleware('throttle:30,1')->name('locale.set'); require __DIR__.'/settings.php'; // Authenticated API-ish routes for Vault (local, no external API) // Moved to web.php main group to ensure they match BEFORE the api.proxy catch-all