Initialer Laravel Commit für BetiX
This commit is contained in:
82
routes/api.php
Normal file
82
routes/api.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\BonusApiController;
|
||||
use App\Http\Controllers\UserRestrictionApiController;
|
||||
use App\Http\Controllers\VaultApiController;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| API Routes
|
||||
|--------------------------------------------------------------------------
|
||||
| These routes are intended for machine-to-machine access from an external
|
||||
| dashboard. Authentication is enforced inside controllers via a static
|
||||
| Bearer token configured in config/services.php.
|
||||
|
|
||||
| Notes:
|
||||
| - All routes here are automatically prefixed with /api by RouteServiceProvider.
|
||||
| - We expose both unversioned and /v1/ versioned endpoints for flexibility.
|
||||
*/
|
||||
|
||||
// Health & diagnostics
|
||||
Route::get('/health', function () {
|
||||
return response()->json([
|
||||
'status' => 'ok',
|
||||
'timestamp' => now()->toIso8601String(),
|
||||
'version' => 'v1',
|
||||
]);
|
||||
})->middleware('throttle:60,1')->name('api.health');
|
||||
|
||||
Route::get('/ping', function () {
|
||||
return response('pong', 200);
|
||||
})->middleware('throttle:60,1')->name('api.ping');
|
||||
|
||||
// CORS preflight for external admin dashboard or services
|
||||
Route::options('/{any}', function () {
|
||||
return response()->noContent(204);
|
||||
})->where('any', '.*');
|
||||
|
||||
$registerExternalApi = function () {
|
||||
// Read endpoints
|
||||
Route::get('/bonuses', [BonusApiController::class, 'index'])->middleware('throttle:60,1');
|
||||
Route::get('/bonuses/{id}', [BonusApiController::class, 'show'])->whereNumber('id')->middleware('throttle:60,1');
|
||||
|
||||
// Mutating endpoints (stricter throttle)
|
||||
Route::post('/bonuses', [BonusApiController::class, 'store'])->middleware('throttle:20,1');
|
||||
Route::patch('/bonuses/{id}', [BonusApiController::class, 'update'])->whereNumber('id')->middleware('throttle:20,1');
|
||||
Route::delete('/bonuses/{id}', [BonusApiController::class, 'destroy'])->whereNumber('id')->middleware('throttle:20,1');
|
||||
|
||||
// Moderation API for user restrictions (token auth inside controller)
|
||||
Route::get('/users/{id}/restrictions', [UserRestrictionApiController::class, 'listForUser'])->whereNumber('id')->middleware('throttle:60,1');
|
||||
Route::get('/users/{id}/restrictions/check', [UserRestrictionApiController::class, 'checkForUser'])->whereNumber('id')->middleware('throttle:60,1');
|
||||
Route::post('/users/{id}/restrictions', [UserRestrictionApiController::class, 'createForUser'])->whereNumber('id')->middleware('throttle:20,1');
|
||||
Route::post('/users/{id}/restrictions/upsert', [UserRestrictionApiController::class, 'upsertForUser'])->whereNumber('id')->middleware('throttle:20,1');
|
||||
Route::patch('/restrictions/{id}', [UserRestrictionApiController::class, 'update'])->whereNumber('id')->middleware('throttle:20,1');
|
||||
Route::delete('/restrictions/{id}', [UserRestrictionApiController::class, 'destroy'])->whereNumber('id')->middleware('throttle:20,1');
|
||||
|
||||
// External Vault API (token auth inside controller)
|
||||
Route::get('/users/{id}/vault', [VaultApiController::class, 'showForUser'])->whereNumber('id')->middleware('throttle:60,1');
|
||||
Route::post('/users/{id}/vault/deposit', [VaultApiController::class, 'depositForUser'])->whereNumber('id')->middleware('throttle:20,1');
|
||||
Route::post('/users/{id}/vault/withdraw', [VaultApiController::class, 'withdrawForUser'])->whereNumber('id')->middleware('throttle:20,1');
|
||||
};
|
||||
|
||||
// NOWPayments IPN webhook (public). Note: The API middleware group has CSRF disabled by default.
|
||||
Route::post('/webhooks/nowpayments', [\App\Http\Controllers\NowPaymentsWebhookController::class, '__invoke'])
|
||||
->middleware('throttle:60,1')
|
||||
->name('api.webhooks.nowpayments');
|
||||
|
||||
// BetiX Originals: session-ended webhook (public, HMAC-verified inside controller)
|
||||
Route::post('/betix/session-ended', [\App\Http\Controllers\BetiXWebhookController::class, 'sessionEnded'])
|
||||
->middleware('throttle:120,1')
|
||||
->name('api.webhooks.betix');
|
||||
|
||||
// BetiX Originals: per-round balance update (public, HMAC-verified inside controller)
|
||||
Route::post('/betix/round', [\App\Http\Controllers\BetiXWebhookController::class, 'roundUpdate'])
|
||||
->middleware('throttle:600,1')
|
||||
->name('api.webhooks.betix.round');
|
||||
|
||||
// Unversioned (backward compatibility)
|
||||
Route::group([], $registerExternalApi);
|
||||
|
||||
// Versioned alias
|
||||
Route::prefix('v1')->group($registerExternalApi);
|
||||
12
routes/console.php
Normal file
12
routes/console.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Inspiring;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use App\Console\Commands\ReencryptUserData;
|
||||
|
||||
Artisan::command('inspire', function () {
|
||||
$this->comment(Inspiring::quote());
|
||||
})->purpose('Display an inspiring quote');
|
||||
|
||||
// Note: Registering commands via Artisan::starting is not supported in this framework version.
|
||||
// If needed, register commands in app/Console/Kernel.php or rely on auto-discovery.
|
||||
40
routes/operator.php
Normal file
40
routes/operator.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\OperatorController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| B2B Operator / Casino Integration Routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These routes are loaded without the /api prefix by bootstrap/app.php,
|
||||
| so they are available at /operator/* as documented.
|
||||
|
|
||||
| All routes are protected by the license.key middleware which:
|
||||
| - Reads X-License-Key header OR license_key body/query field
|
||||
| - Hashes the key and looks it up in operator_casinos
|
||||
| - Checks the casino is active
|
||||
| - Enforces IP whitelist if configured
|
||||
| - Binds the OperatorCasino instance as $request->attributes->get('operator_casino')
|
||||
|
|
||||
*/
|
||||
|
||||
Route::middleware(['license.key'])->group(function () {
|
||||
|
||||
// Game catalog — list all available games with metadata
|
||||
Route::get('/games', [OperatorController::class, 'games'])
|
||||
->middleware('throttle:120,1')
|
||||
->name('operator.games');
|
||||
|
||||
// Launch a game session for a player — returns launch_url + session_token
|
||||
Route::post('/launch', [OperatorController::class, 'launch'])
|
||||
->middleware('throttle:30,1')
|
||||
->name('operator.launch');
|
||||
|
||||
// Query the current / final state of a session (balance delta, status)
|
||||
Route::get('/session/{token}', [OperatorController::class, 'session'])
|
||||
->middleware('throttle:120,1')
|
||||
->where('token', '[0-9a-f\-]{36}')
|
||||
->name('operator.session');
|
||||
});
|
||||
42
routes/settings.php
Normal file
42
routes/settings.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Settings\PasswordController;
|
||||
use App\Http\Controllers\Settings\ProfileController;
|
||||
use App\Http\Controllers\Settings\TwoFactorAuthenticationController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Inertia\Inertia;
|
||||
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
// Route::redirect('settings', '/settings/profile'); // Removed to allow /settings for public profile config
|
||||
|
||||
Route::get('settings/profile', [ProfileController::class, 'edit'])->name('profile.edit');
|
||||
Route::patch('settings/profile', [ProfileController::class, 'update'])->middleware('throttle:20,1')->name('profile.update');
|
||||
|
||||
// KYC pages & APIs
|
||||
Route::get('settings/kyc', [\App\Http\Controllers\Settings\KycController::class, 'index'])->name('settings.kyc');
|
||||
Route::post('settings/kyc', [\App\Http\Controllers\Settings\KycController::class, 'store'])->middleware('throttle:10,1')->name('settings.kyc.store');
|
||||
Route::delete('settings/kyc/{doc}', [\App\Http\Controllers\Settings\KycController::class, 'destroy'])->middleware('throttle:20,1')->name('settings.kyc.destroy');
|
||||
Route::get('settings/kyc/{doc}/download', [\App\Http\Controllers\Settings\KycController::class, 'download'])->name('settings.kyc.download');
|
||||
|
||||
// Security pages & APIs
|
||||
Route::get('settings/security', [\App\Http\Controllers\Settings\SecurityController::class, 'index'])->name('settings.security');
|
||||
Route::get('settings/security/sessions', [\App\Http\Controllers\Settings\SecurityController::class, 'sessions'])->name('settings.security.sessions');
|
||||
Route::delete('settings/security/sessions/{id}', [\App\Http\Controllers\Settings\SecurityController::class, 'revoke'])->middleware('throttle:20,1')->name('settings.security.sessions.revoke');
|
||||
});
|
||||
|
||||
Route::middleware(['auth', 'verified'])->group(function () {
|
||||
Route::delete('settings/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
|
||||
|
||||
Route::get('settings/password', [PasswordController::class, 'edit'])->name('user-password.edit');
|
||||
|
||||
Route::put('settings/password', [PasswordController::class, 'update'])
|
||||
->middleware('throttle:6,1')
|
||||
->name('user-password.update');
|
||||
|
||||
Route::get('settings/appearance', function () {
|
||||
return Inertia::render('settings/Appearance');
|
||||
})->name('appearance.edit');
|
||||
|
||||
Route::get('settings/two-factor', [TwoFactorAuthenticationController::class, 'show'])
|
||||
->name('two-factor.show');
|
||||
});
|
||||
670
routes/web.php
Normal file
670
routes/web.php
Normal file
@@ -0,0 +1,670 @@
|
||||
<?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
|
||||
Reference in New Issue
Block a user