@@ -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