671 lines
38 KiB
PHP
671 lines
38 KiB
PHP
<?php
|
||
|
||
use App\Http\Controllers\AdminController;
|
||
use App\Http\Controllers\Auth\AvailabilityController;
|
||
use App\Http\Controllers\Auth\EmailVerificationCodeController;
|
||
use App\Http\Controllers\GuildController;
|
||
use App\Http\Controllers\GuildActionController;
|
||
use App\Http\Controllers\LocaleController;
|
||
use App\Http\Controllers\SocialController;
|
||
use App\Http\Controllers\WalletController;
|
||
use App\Http\Controllers\VaultController;
|
||
use App\Http\Controllers\VaultPinController;
|
||
use App\Http\Controllers\DepositController;
|
||
use App\Http\Controllers\VipController;
|
||
use App\Http\Controllers\Admin\PromoAdminController;
|
||
use App\Http\Controllers\Admin\SupportAdminController;
|
||
use App\Http\Controllers\EmbedController;
|
||
use App\Http\Controllers\FeedbackController;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\Route;
|
||
use Inertia\Inertia;
|
||
|
||
|
||
Route::get('/', function () {
|
||
return redirect()->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) => "<li><a href='{$base}/preview/{$t}'>{$t}</a></li>", $types);
|
||
$html = "<html><head><title>Mail Previews</title><style>body{background:#0b0b0b;color:#ddd;font-family:Arial,sans-serif;padding:30px} a{color:#ff007a;text-decoration:none} a:hover{text-decoration:underline} .card{background:#0f0f0f;border:1px solid #191919;border-radius:12px;padding:20px;max-width:720px} h1{margin-top:0;color:#fff} ul{columns:2;gap:30px}</style></head><body><div class='card'><h1>Mail Previews (local)</h1><p>Diese Seite ist nur in APP_ENV=local verfügbar.</p><h3>SystemNotificationMail</h3><ul>" . implode('', $links) . "</ul><h3>Weitere Templates</h3><ul><li><a href='{$base}/preview/verify-email'>verify-email.blade.php</a></li></ul></div></body></html>";
|
||
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) => "<li><a href='{$base}/{$g}'>{$g}</a></li>", $games);
|
||
$html = "<html><head><title>Gameplay Previews</title><style>body{background:#0b0b0b;color:#ddd;font-family:Arial,sans-serif;padding:30px} a{color:#ff007a;text-decoration:none} a:hover{text-decoration:underline} .card{background:#0f0f0f;border:1px solid #191919;border-radius:12px;padding:20px;max-width:720px} h1{margin-top:0;color:#fff} ul{list-style:none;padding:0;display:flex;flex-direction:column;gap:8px}</style></head><body><div class='card'><h1>Gameplay Previews (local)</h1><p>Kein Login erforderlich – nur in APP_ENV=local verfügbar.</p><ul>" . implode('', $links) . "</ul></div></body></html>";
|
||
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
|