has('user_id') || Auth::check()) { if (!session()->has('user_id') && Auth::id()) { session([ 'user_id' => Auth::id(), 'auth_user_id' => Auth::id(), 'logged_in' => true, ]); request()->session()->save(); } return redirect()->route('dashboard'); } return app(LoginController::class)->showLoginForm(request()); })->name('home'); // ============================================ // ROTAS PÚBLICAS DE INFORMAÇÕES // ============================================ Route::get('/informacoes', [InfoController::class, 'index'])->name('informacoes'); Route::get('/info/stats', [InfoController::class, 'getPlatformStats'])->name('info.stats'); Route::get('/info/ranking', [InfoController::class, 'getRankingData'])->name('info.ranking'); Route::get('/info/player/{id}', [InfoController::class, 'getPlayerInfo'])->name('info.player'); Route::get('/info/recent-matches', [InfoController::class, 'getRecentMatches'])->name('info.recent-matches'); // ============================================ // ROTAS DE AUTENTICAÇÃO / GUEST // ============================================ // Apenas a exibição das telas fica protegida pelo guest. // IMPORTANTE: // - GET /login usa o nome antigo "login". // - POST /login usa o nome novo "login.post". // - Mantidos aliases antigos sem duplicar POST /login para evitar conflito de rota/cache. Route::middleware('guest')->group(function () { Route::get('/register', [RegisterController::class, 'showRegistrationForm'])->name('register'); Route::get('/login', [LoginController::class, 'showLoginForm'])->name('login'); Route::get('/forgot-password', function () { return view('auth.forgot-password'); })->name('password.request'); }); // As rotas de processamento POST ficam fora do grupo guest para evitar loops pós-login. Route::post('/register', [RegisterController::class, 'register'])->name('register.store'); Route::post('/login', [LoginController::class, 'login']) ->name('login.post'); // Aliases antigos preservados para telas antigas que ainda chamam estes nomes. // Não duplicam mais POST /login. Route::post('/login/authenticate', [LoginController::class, 'login'])->name('login.authenticate'); Route::post('/login/submit', [LoginController::class, 'login'])->name('login.submit'); // SOCIAL LOGIN - GOOGLE / DISCORD (Fora do guest para evitar interceptações de callback) Route::get('/auth/google', [SocialLoginController::class, 'redirectGoogle'])->name('auth.google.redirect'); Route::get('/auth/google/callback', [SocialLoginController::class, 'callbackGoogle'])->name('auth.google.callback'); Route::get('/auth/discord', [SocialLoginController::class, 'redirectDiscord'])->name('auth.discord.redirect'); Route::get('/auth/discord/callback', [SocialLoginController::class, 'callbackDiscord'])->name('auth.discord.callback'); // ROTAS DE MATCH HUB / READY CHECK GLOBAL // Mantém compatibilidade com o hub flutuante atual e adiciona aliases do fluxo novo. Route::middleware(['auth.session'])->group(function () { Route::get('/gigax/launch', [GigaxSsoController::class, 'launch'])->name('gigax.launch'); Route::get('/hub-floating/status', [MatchHubController::class, 'status'])->name('hub.floating.status'); Route::post('/hub-floating/accept', [MatchHubController::class, 'accept'])->name('hub.floating.accept'); Route::post('/hub-floating/decline', [MatchHubController::class, 'decline'])->name('hub.floating.decline'); // Aliases para o topbar/hub competitivo novo sem quebrar o fluxo antigo. Route::get('/match/ready-check/status', [MatchHubController::class, 'status'])->name('match.ready-check.status'); Route::post('/match/ready-check/confirm', [MatchHubController::class, 'accept'])->name('match.ready-check.confirm'); Route::post('/match/ready-check/decline', [MatchHubController::class, 'decline'])->name('match.ready-check.decline'); Route::get('/amigos/aceitar/{id}', [FriendsController::class, 'accept']) ->name('friends.accept.get'); Route::post('/amigos/aceitar/{id}', [FriendsController::class, 'accept']) ->name('friends.accept'); Route::get('/amigos/recusar/{id}', [FriendsController::class, 'decline']) ->name('friends.decline.get'); Route::post('/amigos/recusar/{id}', [FriendsController::class, 'decline']) ->name('friends.decline'); }); Route::post('/logout', [LoginController::class, 'logout']) ->middleware('auth.session') ->name('logout'); Route::get('/logout', [LoginController::class, 'logout']) ->middleware('auth.session') ->name('logout.get'); // Verificação de sessão (AJAX) Route::get('/check-auth', function (\Illuminate\Http\Request $request) { if (!session()->has('user_id') && Auth::id()) { session([ 'user_id' => Auth::id(), 'auth_user_id' => Auth::id(), 'logged_in' => true, ]); $request->session()->save(); } return app(LoginController::class)->check($request); })->name('check.auth'); // Verificações de disponibilidade (registro) Route::get('/check-nickname', [RegisterController::class, 'checkNickname'])->name('check.nickname'); Route::get('/check-email', [RegisterController::class, 'checkEmail'])->name('check.email'); // ============================================ // ROTAS DE VALIDAÇÃO DE PERFIL CROSSFIRE // ============================================ Route::post('/validate-crossfire-profile', [RegisterController::class, 'validateCrossfireProfile'])->name('validate.crossfire'); // Sincroniza Auth::id() com a sessão manual usada pelo projeto. // Importante para login Google e login normal não caírem para /login após atualizar. Route::get('/auth/session-sync', function (\Illuminate\Http\Request $request) { if (Auth::id()) { $user = Auth::user(); session([ 'user_id' => $user->id, 'auth_user_id' => $user->id, 'user_nickname' => $user->nickname ?? $user->name ?? $user->email, 'user_email' => $user->email, 'user_skill' => $user->skill_level ?? 1, 'user_points' => $user->rank_points ?? 1000, 'user_matches' => $user->total_matches ?? 0, 'user_wins' => $user->wins ?? 0, 'user_losses' => $user->losses ?? 0, 'user_avatar' => $user->avatar_url ?? $user->avatar ?? '/default-avatar.png', 'user_role' => strtolower((string) ($user->role ?? 'user')), 'logged_in' => true, ]); $request->session()->save(); } return redirect()->route('dashboard'); })->middleware('auth')->name('auth.session.sync'); // ============================================ // ROTAS PROTEGIDAS (REQUEREM AUTENTICAÇÃO) // ============================================ Route::middleware(['auth.session'])->group(function () { Route::prefix('amigos')->name('friends.')->group(function () { Route::get('/', [FriendsController::class, 'index'])->name('index'); Route::get('/buscar', [FriendsController::class, 'search'])->name('search'); Route::post('/enviar-convite', [FriendsController::class, 'sendRequest'])->name('send'); Route::post('/aceitar/{id}', [FriendsController::class, 'accept'])->name('accept'); Route::post('/recusar/{id}', [FriendsController::class, 'decline'])->name('decline'); // Compatibilidade: permite abrir o link direto no navegador sem 404. // O botão/formulário principal continua usando POST e CSRF normalmente. Route::get('/aceitar/{id}', [FriendsController::class, 'accept'])->name('accept.get'); Route::get('/recusar/{id}', [FriendsController::class, 'decline'])->name('decline.get'); Route::post('/remover/{id}', [FriendsController::class, 'remove'])->name('remove'); Route::get('/chat/{targetUserId}', [FriendsChatController::class, 'show']) ->whereNumber('targetUserId') ->name('chat'); }); // DASHBOARD - PÁGINA PRINCIPAL APÓS LOGIN Route::get('/dashboard', [QueueController::class, 'index'])->name('dashboard'); // ============================================ // GIGAX UI - ROTAS DO MENU PRINCIPAL // Mantém controllers/SQL existentes e cria aliases seguros para todos os botões do menu. // ============================================ Route::get('/rankings', function () { return redirect()->route('ranking'); })->name('rankings'); Route::get('/times', function () { return redirect()->route('teams.index'); })->name('times'); Route::get('/meu-perfil', function () { return redirect()->route('profile'); })->name('meu-perfil'); Route::get('/perfil/meu', function () { return redirect()->route('profile'); })->name('perfil.meu'); Route::get('/lobby-fila', function () { return redirect()->route('lobby.index'); })->name('lobby-fila'); Route::get('/fila', function () { return redirect()->route('lobby.index'); })->name('fila'); // Perfil do usuário Route::get('/perfil', function () { return redirect('/profile'); })->name('perfil'); Route::get('/perfil/editar', function () { return redirect('/profile?tab=editar'); })->name('perfil.editar'); Route::get('/historico', function () { return redirect('/partidas'); })->name('historico'); Route::get('/configuracoes', function () { if (view()->exists('configuracoes')) { return view('configuracoes'); } if (view()->exists('settings')) { return view('settings'); } if (view()->exists('profile')) { return view('profile', ['tab' => 'configuracoes']); } return redirect()->route('profile')->with('error', 'Página de configurações ainda não encontrada.'); })->name('configuracoes'); Route::get('/profile', [ProfileController::class, 'index'])->name('profile'); Route::prefix('profile')->name('profile.')->group(function () { Route::get('/', [ProfileController::class, 'index'])->name('index'); Route::post('/update', [ProfileController::class, 'update'])->name('update'); Route::post('/hardware-missing', [ProfileController::class, 'updateMissingHardware'])->name('hardware.missing'); Route::post('/avatar', [ProfileController::class, 'updateAvatar'])->name('avatar'); Route::post('/password', [ProfileController::class, 'updatePassword'])->name('password'); Route::get('/stats', [ProfileController::class, 'stats'])->name('stats'); Route::get('/activity', [ProfileController::class, 'activity'])->name('activity'); }); // VALIDAÇÃO COMPETITIVA (opcional para navegar, obrigatória para jogar) Route::prefix('player-verification')->name('player.verification.')->group(function () { Route::get('/', [PlayerVerificationController::class, 'index'])->name('index'); Route::get('/gate-status', [PlayerVerificationController::class, 'gateStatus'])->name('gate-status'); // Compatibilidade: se /player-verification/crossfire for aberto diretamente // pelo navegador (GET), volta para a tela de validação em vez de retornar 405. // O salvamento continua exclusivamente via POST. Route::get('/crossfire', function () { return redirect()->route('player.verification'); })->name('crossfire.form'); Route::post('/crossfire', [PlayerVerificationController::class, 'verifyCrossfire'])->name('crossfire'); Route::get('/discord/redirect', [PlayerVerificationController::class, 'redirectToDiscord'])->name('discord.redirect'); Route::get('/discord/callback', [PlayerVerificationController::class, 'handleDiscordCallback'])->name('discord.callback'); Route::post('/teamspeak', [PlayerVerificationController::class, 'verifyTeamSpeak'])->name('teamspeak'); Route::post('/teamspeak/auto', [PlayerVerificationController::class, 'autoVerifyTeamSpeak'])->name('teamspeak.auto'); }); // Nome legado/curto para compatibilidade com código já alterado Route::get('/player-verification', [PlayerVerificationController::class, 'index'])->name('player.verification'); // ROTA DE AMIGOS (compatibilidade) Route::get('/amigos', [FriendsController::class, 'index'])->name('friends.index'); // Ranking Route::get('/ranking', function () { if (view()->exists('ranking')) { return view('ranking'); } if (view()->exists('rankings')) { return view('rankings'); } return redirect()->route('dashboard')->with('error', 'Página de ranking ainda não encontrada.'); })->name('ranking'); // PÁGINAS DO MENU GIGAX UI V5 - somente views/front-end, sem mexer nos controllers existentes Route::get('/loja', function () { if (view()->exists('loja')) { return view('loja'); } if (view()->exists('shop')) { return view('shop'); } return view('dashboard')->with('error', 'Página da loja ainda não encontrada.'); })->name('loja'); Route::get('/shop', function () { return redirect()->route('loja'); })->name('shop'); Route::get('/store', function () { return redirect()->route('loja'); })->name('store'); Route::get('/suporte', function () { if (view()->exists('suporte')) { return view('suporte'); } if (view()->exists('support')) { return view('support'); } return view('dashboard')->with('error', 'Página de suporte ainda não encontrada.'); })->name('suporte'); Route::get('/support', function () { return redirect()->route('suporte'); })->name('support'); // ============================================ // CENTRAL DE CHAMADOS / TICKETS // ============================================ // Página real e operacional. O formulário "Novo chamado" abre em modal // e os chamados ficam vinculados ao usuário autenticado. Route::get('/usuarios/tickets', [SupportTicketController::class, 'index']) ->name('usuarios.tickets'); Route::post('/usuarios/tickets', [SupportTicketController::class, 'store']) ->name('usuarios.tickets.store'); Route::get('/usuarios/tickets/{id}', [SupportTicketController::class, 'show']) ->whereNumber('id') ->name('usuarios.tickets.show'); // Atalho opcional para abrir a central já com o modal de novo chamado aberto. Route::get('/suporte/novo-chamado', function () { return redirect()->route('usuarios.tickets', ['novo' => 1]); })->name('suporte.novo-chamado'); Route::get('/ajuda', function () { return redirect()->route('suporte'); })->name('ajuda'); Route::get('/settings', function () { return redirect()->route('configuracoes'); })->name('settings'); // SISTEMA DE FILA Route::prefix('queue')->name('queue.')->group(function () { Route::post('/enter', [QueueController::class, 'enter'])->middleware(\App\Http\Middleware\RequireGigaxCurrentForQueue::class)->name('enter'); Route::post('/enter-specific', [QueueController::class, 'enterSpecific'])->middleware(\App\Http\Middleware\RequireGigaxCurrentForQueue::class)->name('enter-specific'); Route::post('/leave', [QueueController::class, 'leave'])->name('leave'); Route::post('/create', [QueueController::class, 'createQueue'])->name('create'); Route::get('/status', [QueueController::class, 'status'])->name('status'); Route::get('/status-flow', [QueueController::class, 'statusFlow'])->name('status-flow'); Route::get('/distribution', [QueueController::class, 'distribution'])->name('distribution'); Route::post('/force-match', [QueueController::class, 'forceCreateMatch'])->name('force'); Route::post('/clear', [QueueController::class, 'clearQueues'])->name('clear'); Route::post('/create-lobby', [QueueController::class, 'createDirectLobby'])->name('create-lobby'); Route::get('/lobbies', [QueueController::class, 'getActiveLobbies'])->name('lobbies'); }); // PARTIDAS ATIVAS Route::prefix('match/active')->name('match.active.')->group(function () { Route::get('/{id}/details', [QueueController::class, 'getMatchDetails'])->name('details'); Route::post('/{id}/end', [QueueController::class, 'endMatch'])->name('end'); Route::post('/{id}/cancel', [QueueController::class, 'cancelMatch'])->name('cancel'); Route::post('/{id}/force-start', [QueueController::class, 'adminForceStart'])->name('force-start'); Route::post('/{id}/admin-end', [QueueController::class, 'adminEndMatch'])->name('admin-end'); Route::post('/{id}/admin-cancel', [QueueController::class, 'adminCancelMatch'])->name('admin-cancel'); }); // HISTÓRICO Route::prefix('matches')->name('matches.')->group(function () { Route::get('/data', [MatchHistoryController::class, 'getPlayerMatches'])->name('data'); Route::get('/stats', [MatchHistoryController::class, 'getPlayerStats'])->name('stats'); Route::get('/map-stats', [MatchHistoryController::class, 'getMapStats'])->name('map-stats'); Route::get('/recent', [MatchHistoryController::class, 'getRecentMatches'])->name('recent'); Route::get('/timeline', [MatchHistoryController::class, 'getPlayerTimeline'])->name('timeline'); }); Route::get('/match/history/{id}/details', [MatchHistoryController::class, 'getMatchDetails'])->name('match.history.details'); // Veto Route::prefix('veto')->name('veto.')->group(function () { Route::get('/{lobbyId}', [VetoController::class, 'show'])->name('show'); Route::post('/iniciar', [VetoController::class, 'iniciar'])->name('iniciar'); Route::post('/vetar', [VetoController::class, 'vetar'])->name('vetar'); Route::get('/status/{lobbyId}', [VetoController::class, 'status'])->name('status'); }); Route::get('/ranking/data', [MatchSystemController::class, 'getRankingData'])->name('ranking.data'); Route::get('/player/{id}/stats', [MatchSystemController::class, 'getPlayerStats'])->name('player.stats'); Route::get('/partidas', [MatchHistoryController::class, 'index'])->name('partidas'); // Lobby Route::prefix('lobby')->name('lobby.')->group(function () { Route::get('/', [LobbyController::class, 'index'])->name('index'); Route::post('/create', [LobbyController::class, 'create'])->name('create'); Route::post('/join-league', [LobbyController::class, 'joinLeague'])->name('joinLeague'); Route::get('/hub/data', [LobbyHubController::class, 'data'])->name('hub.data'); Route::get('/chat/global', [LobbyHubController::class, 'globalMessages'])->name('chat.global'); Route::post('/chat/global', [LobbyHubController::class, 'sendGlobalMessage'])->name('chat.global.send'); Route::get('/chat/private/{targetUserId}', [LobbyHubController::class, 'privateMessages'])->name('chat.private'); Route::post('/chat/private/{targetUserId}', [LobbyHubController::class, 'sendPrivateMessage'])->name('chat.private.send'); Route::get('/party/status', [LobbyController::class, 'partySummaryEndpoint'])->name('party.status'); Route::post('/party/create', [LobbyController::class, 'createParty'])->name('party.create'); Route::post('/party/join', [LobbyController::class, 'joinParty'])->name('party.join'); Route::post('/party/leave', [LobbyController::class, 'leaveParty'])->name('party.leave'); // Alias compatível com links antigos: /lobby/match/128 Route::get('/match/{id}', [LobbyController::class, 'match']) ->whereNumber('id') ->name('match.legacy'); Route::get('/{id}/match', [LobbyController::class, 'match']) ->whereNumber('id') ->name('match'); Route::get('/{id}/status', [LobbyController::class, 'status'])->name('status'); Route::get('/{id}/chat', [LobbyHubController::class, 'lobbyMessages'])->name('chat'); Route::post('/{id}/chat', [LobbyHubController::class, 'sendLobbyMessage'])->name('chat.send'); Route::post('/{id}/captain/report', [LobbyHubController::class, 'captainReport'])->name('captain.report'); Route::post('/{id}/captain/close', [LobbyHubController::class, 'captainCloseMatch'])->name('captain.close'); Route::post('/{id}/join', [LobbyController::class, 'join'])->name('join'); Route::post('/{id}/leave', [LobbyController::class, 'leave'])->name('leave'); Route::post('/{id}/start', [LobbyController::class, 'start'])->name('start'); Route::post('/{id}/ready/start', [LobbyController::class, 'startReadyCheck']) ->whereNumber('id') ->name('ready.start'); /* * Ready-check corrigido: * - POST continua usando o controller/AJAX. * - GET registra o aceite direto e redireciona para a sala, evitando tela JSON. */ Route::post('/{id}/ready/accept', [LobbyController::class, 'acceptReadyCheck']) ->whereNumber('id') ->name('ready.accept'); Route::get('/{id}/ready/accept', function ($id) { $userId = (int) (session('user_id') ?: Auth::id()); if ($userId > 0 && !session()->has('user_id')) { session(['user_id' => $userId]); } if ($userId <= 0) { if (Auth::id()) { session(['user_id' => Auth::id()]); return redirect()->route('dashboard'); } return redirect()->route('login')->with('error', 'Sessao expirada. Faca login novamente.'); } $participant = DB::table('mix_training_participants') ->where('training_match_id', (int) $id) ->where('user_id', $userId) ->whereNull('left_at') ->first(); if (!$participant) { return redirect()->route('lobby.index')->with('error', 'Voce nao participa desta partida.'); } if (!Schema::hasTable('match_ready_players')) { return redirect()->route('lobby.index')->with('error', 'Tabela match_ready_players nao existe.'); } $now = now(); $exists = DB::table('match_ready_players') ->where('match_id', (int) $id) ->where('user_id', $userId) ->exists(); if ($exists) { DB::table('match_ready_players') ->where('match_id', (int) $id) ->where('user_id', $userId) ->update([ 'is_ready' => 1, 'confirmed_at' => $now, 'updated_at' => $now, ]); } else { DB::table('match_ready_players')->insert([ 'match_id' => (int) $id, 'user_id' => $userId, 'team' => (int) ($participant->team ?: 1), 'is_ready' => 1, 'confirmed_at' => $now, 'created_at' => $now, 'updated_at' => $now, ]); } return redirect()->route('lobby.match', (int) $id)->with('success', 'Aceite registrado.'); }) ->whereNumber('id') ->name('ready.accept.get'); Route::get('/{id}/ready/accept-now', function ($id) { $userId = (int) (session('user_id') ?: Auth::id()); if ($userId > 0 && !session()->has('user_id')) { session(['user_id' => $userId]); } if ($userId <= 0) { if (Auth::id()) { session(['user_id' => Auth::id()]); return redirect()->route('dashboard'); } return redirect()->route('login')->with('error', 'Sessao expirada. Faca login novamente.'); } $participant = DB::table('mix_training_participants') ->where('training_match_id', (int) $id) ->where('user_id', $userId) ->whereNull('left_at') ->first(); if (!$participant) { return redirect()->route('lobby.index')->with('error', 'Voce nao participa desta partida.'); } if (!Schema::hasTable('match_ready_players')) { return redirect()->route('lobby.index')->with('error', 'Tabela match_ready_players nao existe.'); } $now = now(); $exists = DB::table('match_ready_players') ->where('match_id', (int) $id) ->where('user_id', $userId) ->exists(); if ($exists) { DB::table('match_ready_players') ->where('match_id', (int) $id) ->where('user_id', $userId) ->update([ 'is_ready' => 1, 'confirmed_at' => $now, 'updated_at' => $now, ]); } else { DB::table('match_ready_players')->insert([ 'match_id' => (int) $id, 'user_id' => $userId, 'team' => (int) ($participant->team ?: 1), 'is_ready' => 1, 'confirmed_at' => $now, 'created_at' => $now, 'updated_at' => $now, ]); } return redirect()->route('lobby.match', (int) $id)->with('success', 'Aceite registrado.'); }) ->whereNumber('id') ->name('ready.accept.now'); Route::post('/{id}/ready/decline', [LobbyController::class, 'declineReadyCheck']) ->whereNumber('id') ->name('ready.decline'); Route::post('/{id}/close', [LobbyController::class, 'close'])->name('close'); Route::get('/{id}', [LobbyController::class, 'show'])->whereNumber('id')->name('show'); }); // Traduções/Redirecionamentos Route::get('/treinamentos', function () { return redirect()->route('trainings.index'); })->name('treinamentos'); Route::get('/jogadores', function () { return redirect()->route('players.index'); })->name('jogadores'); Route::get('/torneios', function () { return redirect()->route('tournaments.index'); })->name('torneios'); // TREINAMENTOS Route::prefix('trainings')->name('trainings.')->group(function () { Route::get('/', [TrainingController::class, 'index'])->name('index'); Route::get('/results', [TrainingController::class, 'getTrainingResults'])->name('results'); Route::post('/create', [TrainingController::class, 'createTraining'])->name('create'); Route::get('/{id}', [TrainingController::class, 'show'])->whereNumber('id')->name('show'); Route::post('/{id}/complete', [TrainingController::class, 'markComplete'])->whereNumber('id')->name('complete'); Route::post('/{id}/result', [TrainingController::class, 'submitTrainingResult'])->whereNumber('id')->name('result'); }); // TIMES Route::prefix('teams')->name('teams.')->group(function () { Route::get('/', [TeamController::class, 'index'])->name('index'); Route::get('/search-players', [TeamController::class, 'searchPlayers'])->name('search'); Route::post('/{id}/convidar-jogar', [PlayerController::class, 'inviteToPlay'])->whereNumber('id')->name('invite-play'); Route::post('/{id}/convidar-evento', [PlayerController::class, 'inviteToEvent'])->whereNumber('id')->name('invite-event'); Route::get('/{id}/stats', [TeamController::class, 'getTeamStats'])->whereNumber('id')->name('stats'); Route::post('/create', [TeamController::class, 'create'])->name('create'); Route::get('/{id}', [TeamController::class, 'show'])->whereNumber('id')->name('show'); Route::post('/create', [TeamController::class, 'create'])->name('create'); Route::post('/{id}/join', [TeamController::class, 'join'])->whereNumber('id')->name('join'); Route::post('/{id}/request-join', [TeamController::class, 'requestJoin'])->whereNumber('id')->name('request-join'); Route::get('/{id}/requests', [TeamController::class, 'getPendingRequests'])->whereNumber('id')->name('requests'); Route::post('/{id}/requests/handle', [TeamController::class, 'handleRequest'])->whereNumber('id')->name('requests.handle'); Route::post('/{id}/leave', [TeamController::class, 'leave'])->whereNumber('id')->name('leave'); Route::put('/{id}', [TeamController::class, 'update'])->whereNumber('id')->name('update'); Route::post('/{id}/logo', [TeamController::class, 'updateLogo'])->whereNumber('id')->name('logo'); Route::post('/{id}/recruit', [TeamController::class, 'recruit'])->whereNumber('id')->name('recruit'); Route::delete('/{id}/member', [TeamController::class, 'removeMember'])->whereNumber('id')->name('member.remove'); Route::post('/{id}/transfer', [TeamController::class, 'transferLeadership'])->whereNumber('id')->name('transfer'); Route::delete('/{id}', [TeamController::class, 'destroy'])->whereNumber('id')->name('destroy'); }); // JOGADORES Route::prefix('players')->name('players.')->group(function () { Route::get('/', [PlayerController::class, 'index'])->name('index'); Route::get('/search', [PlayerController::class, 'search'])->name('search'); Route::get('/{id}', [PublicPlayerProfileController::class, 'show'])->whereNumber('id')->name('show'); Route::get('/{id}/stats', [PlayerController::class, 'getStats'])->whereNumber('id')->name('stats'); Route::get('/{id}/history', [PlayerController::class, 'getMatchHistory'])->whereNumber('id')->name('history'); }); // TORNEIOS Route::prefix('tournaments')->name('tournaments.')->group(function () { Route::get('/', [TournamentController::class, 'index'])->name('index'); Route::post('/create', [TournamentController::class, 'create'])->name('create'); Route::get('/{id}', [TournamentController::class, 'show'])->whereNumber('id')->name('show'); Route::post('/{id}/register', [TournamentController::class, 'register'])->whereNumber('id')->name('register'); Route::post('/{id}/bracket/generate', [TournamentController::class, 'generateBracket'])->whereNumber('id')->name('bracket.generate'); Route::post('/{id}/matches/{matchId}/result', [TournamentController::class, 'result'])->whereNumber('id')->whereNumber('matchId')->name('match.result'); }); }); // ============================================ // OUTRAS ROTAS (AGORA, TWILIO, ADMIN) // ============================================ Route::prefix('agora')->name('agora.')->middleware(['auth.session'])->group(function () { Route::post('/create-channel', [AgoraController::class, 'createChannel'])->name('create'); Route::post('/renew-token', [AgoraController::class, 'renewToken'])->name('renew'); Route::get('/match/{id}/channels', [AgoraController::class, 'getMatchChannels'])->name('channels'); Route::post('/cleanup', [AgoraController::class, 'cleanupExpiredChannels'])->name('cleanup'); }); Route::any('/twilio/voice', [TwilioVoiceController::class, 'voice'])->name('twilio.voice'); Route::post('/twilio/voice/join-room', [TwilioVoiceController::class, 'joinRoom'])->name('twilio.voice.join'); Route::prefix('twilio')->name('twilio.')->middleware(['auth.session'])->group(function () { Route::post('/create-room', [TwilioVoiceController::class, 'createRoom'])->name('create'); Route::post('/join-room', [TwilioVoiceController::class, 'joinRoomByMatch'])->name('join'); Route::post('/leave-room', [TwilioVoiceController::class, 'leaveRoom'])->name('leave'); Route::get('/room-number/{roomName}', [TwilioVoiceController::class, 'getRoomNumber'])->name('number'); }); Route::prefix('match')->name('match.')->middleware(['auth.session'])->group(function () { Route::post('/{matchId}/upload-stats', [MatchStatsController::class, 'uploadStats'])->name('upload-stats'); Route::post('/{matchId}/manual-finish', [MatchStatsController::class, 'manualFinish'])->name('manual-finish'); Route::get('/{matchId}/manual-form', [MatchStatsController::class, 'manualStatsForm'])->name('manual-form'); Route::post('/{matchId}/simple-finish', [MatchStatsController::class, 'simpleFinish'])->name('simple-finish'); Route::post('/{matchId}/captain-close/request', [\App\Http\Controllers\CaptainMatchCloseController::class, 'requestClose'])->name('captain-close.request'); Route::post('/{matchId}/captain-close/confirm', [\App\Http\Controllers\CaptainMatchCloseController::class, 'confirmClose'])->name('captain-close.confirm'); Route::post('/{matchId}/captain-close/cancel', [\App\Http\Controllers\CaptainMatchCloseController::class, 'cancelRequest'])->name('captain-close.cancel'); Route::get('/{matchId}/finish-form', function ($matchId) { return view('match-finish', ['matchId' => $matchId]); })->name('finish-form'); /* * FEEDBACK COMPETITIVO POS-PARTIDA * - Joinha: jogador bom/limpo. * - Suspeita: justificativa obrigatoria. * Mantido dentro do web.php para funcionar direto, sem depender de controller novo. */ Route::get('/{matchId}/feedback/players', function ($matchId) { $userId = (int) (session('user_id') ?: Auth::id()); if ($userId > 0 && !session()->has('user_id')) { session(['user_id' => $userId]); } $matchId = (int) $matchId; if ($userId <= 0) { return response()->json(['success' => false, 'message' => 'Sessao expirada.'], 401); } if ($matchId <= 0) { return response()->json(['success' => false, 'message' => 'Partida invalida.'], 422); } try { if (!Schema::hasTable('player_reputation')) { DB::statement(" IF OBJECT_ID('player_reputation', 'U') IS NULL CREATE TABLE player_reputation ( user_id INT NOT NULL PRIMARY KEY, fairplay_score INT NOT NULL DEFAULT 100, positive_reviews INT NOT NULL DEFAULT 0, suspicious_reviews INT NOT NULL DEFAULT 0, trusted_level NVARCHAR(30) NOT NULL DEFAULT 'neutral', last_feedback_at DATETIME2 NULL, created_at DATETIME2 NULL, updated_at DATETIME2 NULL ) "); } if (!Schema::hasTable('player_match_feedback')) { DB::statement(" IF OBJECT_ID('player_match_feedback', 'U') IS NULL CREATE TABLE player_match_feedback ( id INT IDENTITY(1,1) NOT NULL PRIMARY KEY, match_id INT NOT NULL, reporter_id INT NOT NULL, reported_id INT NOT NULL, feedback_type NVARCHAR(30) NOT NULL, category NVARCHAR(80) NULL, reason NVARCHAR(1000) NULL, source NVARCHAR(30) NOT NULL DEFAULT 'krux', status NVARCHAR(30) NOT NULL DEFAULT 'pending', created_at DATETIME2 NULL, updated_at DATETIME2 NULL ) "); } $players = collect(); if (Schema::hasTable('mix_training_participants')) { $players = collect(DB::select(" SELECT u.id, u.nickname, u.avatar_url, u.skill_level, COALESCE(r.fairplay_score, 100) AS fairplay_score, COALESCE(r.positive_reviews, 0) AS positive_reviews, COALESCE(r.suspicious_reviews, 0) AS suspicious_reviews, COALESCE(r.trusted_level, 'neutral') AS trusted_level FROM mix_training_participants p JOIN users u ON u.id = p.user_id LEFT JOIN player_reputation r ON r.user_id = u.id WHERE p.training_match_id = ? AND p.left_at IS NULL AND u.id <> ? ORDER BY u.nickname ASC ", [$matchId, $userId])); } if ($players->isEmpty() && Schema::hasTable('users')) { $players = collect(DB::select(" SELECT TOP 10 u.id, u.nickname, u.avatar_url, u.skill_level, COALESCE(r.fairplay_score, 100) AS fairplay_score, COALESCE(r.positive_reviews, 0) AS positive_reviews, COALESCE(r.suspicious_reviews, 0) AS suspicious_reviews, COALESCE(r.trusted_level, 'neutral') AS trusted_level FROM users u LEFT JOIN player_reputation r ON r.user_id = u.id WHERE u.id <> ? ORDER BY u.nickname ASC ", [$userId])); } return response()->json([ 'success' => true, 'players' => $players->values(), ]); } catch (\Throwable $e) { return response()->json([ 'success' => false, 'message' => 'Erro ao carregar jogadores para avaliacao.', 'error' => config('app.debug') ? $e->getMessage() : null, ], 500); } })->whereNumber('matchId')->name('feedback.players'); Route::post('/{matchId}/feedback', function (\Illuminate\Http\Request $request, $matchId) { $userId = (int) (session('user_id') ?: Auth::id()); if ($userId > 0 && !session()->has('user_id')) { session(['user_id' => $userId]); } $matchId = (int) $matchId; if ($userId <= 0) { return response()->json(['success' => false, 'message' => 'Sessao expirada.'], 401); } $reportedId = (int) $request->input('reported_user_id'); $type = strtolower(trim((string) $request->input('feedback_type', $request->input('type', '')))); $category = trim((string) $request->input('category', '')); $reason = trim((string) $request->input('reason', $request->input('justification', ''))); if ($matchId <= 0 || $reportedId <= 0) { return response()->json(['success' => false, 'message' => 'Partida ou jogador invalido.'], 422); } if ($reportedId === $userId) { return response()->json(['success' => false, 'message' => 'Voce nao pode avaliar voce mesmo.'], 422); } if (!in_array($type, ['positive', 'clean', 'good', 'suspect', 'suspicious'], true)) { return response()->json(['success' => false, 'message' => 'Tipo de avaliacao invalido.'], 422); } $isSuspicious = in_array($type, ['suspect', 'suspicious'], true); $normalizedType = $isSuspicious ? 'suspicious' : 'positive'; if ($isSuspicious && mb_strlen($reason) < 8) { return response()->json([ 'success' => false, 'message' => 'Para marcar como suspeito, informe uma justificativa com pelo menos 8 caracteres.', ], 422); } if (mb_strlen($reason) > 1000) { $reason = mb_substr($reason, 0, 1000); } if (mb_strlen($category) > 80) { $category = mb_substr($category, 0, 80); } try { DB::statement(" IF OBJECT_ID('player_match_feedback', 'U') IS NULL CREATE TABLE player_match_feedback ( id INT IDENTITY(1,1) NOT NULL PRIMARY KEY, match_id INT NOT NULL, reporter_id INT NOT NULL, reported_id INT NOT NULL, feedback_type NVARCHAR(30) NOT NULL, category NVARCHAR(80) NULL, reason NVARCHAR(1000) NULL, source NVARCHAR(30) NOT NULL DEFAULT 'krux', status NVARCHAR(30) NOT NULL DEFAULT 'pending', created_at DATETIME2 NULL, updated_at DATETIME2 NULL ) "); DB::statement(" IF OBJECT_ID('player_reputation', 'U') IS NULL CREATE TABLE player_reputation ( user_id INT NOT NULL PRIMARY KEY, fairplay_score INT NOT NULL DEFAULT 100, positive_reviews INT NOT NULL DEFAULT 0, suspicious_reviews INT NOT NULL DEFAULT 0, trusted_level NVARCHAR(30) NOT NULL DEFAULT 'neutral', last_feedback_at DATETIME2 NULL, created_at DATETIME2 NULL, updated_at DATETIME2 NULL ) "); DB::statement(" IF OBJECT_ID('krux_giga_risk_alerts', 'U') IS NULL CREATE TABLE krux_giga_risk_alerts ( id INT IDENTITY(1,1) NOT NULL PRIMARY KEY, match_id INT NOT NULL, reported_user_id INT NOT NULL, reporter_id INT NOT NULL, alert_type NVARCHAR(60) NOT NULL, risk_boost INT NOT NULL DEFAULT 40, reason NVARCHAR(1000) NULL, status NVARCHAR(30) NOT NULL DEFAULT 'pending', created_at DATETIME2 NULL, updated_at DATETIME2 NULL ) "); if (Schema::hasTable('mix_training_participants')) { $reporterInMatch = DB::table('mix_training_participants') ->where('training_match_id', $matchId) ->where('user_id', $userId) ->whereNull('left_at') ->exists(); $reportedInMatch = DB::table('mix_training_participants') ->where('training_match_id', $matchId) ->where('user_id', $reportedId) ->whereNull('left_at') ->exists(); if (!$reporterInMatch || !$reportedInMatch) { return response()->json([ 'success' => false, 'message' => 'Avaliacao permitida somente entre jogadores da mesma partida.', ], 403); } } $now = now(); $existing = DB::table('player_match_feedback') ->where('match_id', $matchId) ->where('reporter_id', $userId) ->where('reported_id', $reportedId) ->first(); if ($existing) { DB::table('player_match_feedback') ->where('id', $existing->id) ->update([ 'feedback_type' => $normalizedType, 'category' => $category !== '' ? $category : null, 'reason' => $reason !== '' ? $reason : null, 'status' => 'updated', 'updated_at' => $now, ]); } else { DB::table('player_match_feedback')->insert([ 'match_id' => $matchId, 'reporter_id' => $userId, 'reported_id' => $reportedId, 'feedback_type' => $normalizedType, 'category' => $category !== '' ? $category : null, 'reason' => $reason !== '' ? $reason : null, 'source' => 'krux', 'status' => 'pending', 'created_at' => $now, 'updated_at' => $now, ]); } $positiveCount = (int) DB::table('player_match_feedback') ->where('reported_id', $reportedId) ->where('feedback_type', 'positive') ->count(); $suspiciousCount = (int) DB::table('player_match_feedback') ->where('reported_id', $reportedId) ->where('feedback_type', 'suspicious') ->count(); $score = max(0, min(100, 100 + $positiveCount - ($suspiciousCount * 8))); $trustedLevel = 'neutral'; if ($score >= 95 && $positiveCount >= 5 && $suspiciousCount <= 1) { $trustedLevel = 'trusted'; } elseif ($score <= 45 || $suspiciousCount >= 5) { $trustedLevel = 'high_risk'; } elseif ($score <= 70 || $suspiciousCount >= 3) { $trustedLevel = 'observation'; } $repExists = DB::table('player_reputation')->where('user_id', $reportedId)->exists(); $repRow = [ 'fairplay_score' => $score, 'positive_reviews' => $positiveCount, 'suspicious_reviews' => $suspiciousCount, 'trusted_level' => $trustedLevel, 'last_feedback_at' => $now, 'updated_at' => $now, ]; if ($repExists) { DB::table('player_reputation')->where('user_id', $reportedId)->update($repRow); } else { $repRow['user_id'] = $reportedId; $repRow['created_at'] = $now; DB::table('player_reputation')->insert($repRow); } if ($isSuspicious) { DB::table('krux_giga_risk_alerts')->insert([ 'match_id' => $matchId, 'reported_user_id' => $reportedId, 'reporter_id' => $userId, 'alert_type' => 'player_report_suspicious', 'risk_boost' => 40, 'reason' => $reason !== '' ? $reason : 'Jogador marcado como suspeito no pos-partida.', 'status' => 'pending', 'created_at' => $now, 'updated_at' => $now, ]); } return response()->json([ 'success' => true, 'message' => $isSuspicious ? 'Suspeita registrada. O jogador entrou em observacao para analise.' : 'Joinha registrado. Reputacao positiva atualizada.', 'reputation' => [ 'user_id' => $reportedId, 'fairplay_score' => $score, 'positive_reviews' => $positiveCount, 'suspicious_reviews' => $suspiciousCount, 'trusted_level' => $trustedLevel, ], ]); } catch (\Throwable $e) { return response()->json([ 'success' => false, 'message' => 'Erro ao salvar avaliacao do jogador.', 'error' => config('app.debug') ? $e->getMessage() : null, ], 500); } })->whereNumber('matchId') ->withoutMiddleware([ \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, ]) ->name('feedback.store'); }); Route::get('/debug-form', function () { return view('debug'); }); Route::post('/test-ocr', [MatchStatsController::class, 'testOcr'])->middleware(['auth.session'])->name('test-ocr'); Route::get('/debug', [DebugController::class, 'index']); Route::get('/debug/set', [DebugController::class, 'setSession']); Route::post('/debug/post', [DebugController::class, 'postTest']); // GIGAX/KRUX — RECEBIMENTO DE EVIDÊNCIAS DE MACRO DO GIGA LITE Route::post('/api/client/macro-evidence', [MacroAlertController::class, 'store']) ->withoutMiddleware([ \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, ]) ->name('api.client.macro-evidence'); // GIGAX_PREMIUM_MERCADOPAGO_V1 Route::middleware(['auth.session'])->group(function () { Route::get('/premium', [PremiumController::class, 'index'])->name('premium.index'); Route::post('/premium/checkout', [PremiumController::class, 'checkout'])->name('premium.checkout'); Route::post('/premium/pix', [PremiumController::class, 'pix'])->name('premium.pix'); Route::get('/premium/status', [PremiumController::class, 'status'])->name('premium.status'); Route::post('/premium/boleto', [PremiumController::class, 'boletoForm'])->name('premium.boleto.form'); Route::post('/premium/boleto/gerar', [PremiumController::class, 'boletoCreate'])->name('premium.boleto.create'); Route::get('/premium/retorno', [PremiumController::class, 'retorno'])->name('premium.return'); }); // ADMIN Route::get('/admin', function () { return redirect()->route('admin.index'); })->name('admin.redirect'); Route::get('/admins', function () { return redirect()->route('admin.index'); })->name('admins.redirect'); Route::prefix('admin')->name('admin.')->middleware(['auth.session', 'admin.role:admin'])->group(function () { // YOUTUBERS / LIVES - cadastro simples para o grid do Dashboard. // Não depende de API externa: o admin controla ATIVO e AO VIVO. Route::get('/youtubers', function () { if (!Schema::hasTable('gigax_youtubers')) { Schema::create('gigax_youtubers', function ($table) { $table->bigIncrements('id'); $table->string('channel_name', 120); $table->string('youtube_url', 500); $table->string('avatar_url', 500)->nullable(); $table->string('stream_title', 180)->nullable(); $table->boolean('active')->default(true); $table->boolean('is_live')->default(false); $table->integer('sort_order')->default(0); $table->timestamps(); }); } $youtubers = DB::table('gigax_youtubers')->orderBy('sort_order')->orderBy('channel_name')->get(); return view('Admin.youtubers', compact('youtubers')); })->name('youtubers.index'); Route::post('/youtubers', function (\Illuminate\Http\Request $request) { $data = $request->validate([ 'channel_name' => 'required|string|max:120', 'youtube_url' => 'required|url|max:500', 'avatar_url' => 'nullable|url|max:500', 'stream_title' => 'nullable|string|max:180', 'sort_order' => 'nullable|integer|min:0|max:999', ]); if (!Schema::hasTable('gigax_youtubers')) { Schema::create('gigax_youtubers', function ($table) { $table->bigIncrements('id'); $table->string('channel_name',120); $table->string('youtube_url',500); $table->string('avatar_url',500)->nullable(); $table->string('stream_title',180)->nullable(); $table->boolean('active')->default(true); $table->boolean('is_live')->default(false); $table->integer('sort_order')->default(0); $table->timestamps(); }); } DB::table('gigax_youtubers')->insert([ 'channel_name'=>$data['channel_name'], 'youtube_url'=>$data['youtube_url'], 'avatar_url'=>$data['avatar_url'] ?? null, 'stream_title'=>$data['stream_title'] ?? null, 'active'=>$request->boolean('active'), 'is_live'=>$request->boolean('is_live'), 'sort_order'=>(int)($data['sort_order'] ?? 0), 'created_at'=>now(), 'updated_at'=>now(), ]); return back()->with('success','Youtuber cadastrado com sucesso.'); })->name('youtubers.store'); Route::post('/youtubers/{id}/live', function ($id) { $row=DB::table('gigax_youtubers')->where('id',(int)$id)->first(); if ($row) DB::table('gigax_youtubers')->where('id',(int)$id)->update(['is_live'=>$row->is_live?0:1,'updated_at'=>now()]); return back()->with('success','Status da live atualizado.'); })->whereNumber('id')->name('youtubers.live'); Route::post('/youtubers/{id}/active', function ($id) { $row=DB::table('gigax_youtubers')->where('id',(int)$id)->first(); if ($row) DB::table('gigax_youtubers')->where('id',(int)$id)->update(['active'=>$row->active?0:1,'updated_at'=>now()]); return back()->with('success','Exibição do canal atualizada.'); })->whereNumber('id')->name('youtubers.active'); Route::delete('/youtubers/{id}', function ($id) { DB::table('gigax_youtubers')->where('id',(int)$id)->delete(); return back()->with('success','Youtuber removido.'); })->whereNumber('id')->name('youtubers.destroy'); Route::get('/macros', [MacroAlertController::class, 'index'])->name('macros.index'); Route::get('/macros/{id}', [MacroAlertController::class, 'show'])->whereNumber('id')->name('macros.show'); Route::post('/macros/{id}/resend-discord', [MacroAlertController::class, 'resend'])->whereNumber('id')->name('macros.resend-discord'); Route::get('/', fn () => view('Admin.dashboard'))->name('index'); Route::get('/matches', [MatchManagementController::class, 'index'])->name('matches'); Route::get('/matches/active', [MatchManagementController::class, 'getActiveMatches'])->name('matches.active'); Route::get('/matches/history', [MatchManagementController::class, 'getMatchHistory'])->name('matches.history'); Route::get('/logs', [MatchManagementController::class, 'getAdminLogs'])->name('logs'); Route::get('/summary', [MatchManagementController::class, 'getAdminSummary'])->name('summary'); Route::get('/queues/overview', [MatchManagementController::class, 'getQueueOverview'])->name('queues.overview'); Route::get('/permissions', [MatchManagementController::class, 'getPermissionOverview'])->name('permissions'); Route::get('/anti-cheat', [MatchManagementController::class, 'getAntiCheatOverview'])->name('anti-cheat'); Route::get('/ac-monitor', [AcMonitorController::class, 'index'])->name('ac-monitor'); Route::post('/matches/{id}/ban', [MatchManagementController::class, 'banPlayerFromMatch'])->whereNumber('id')->name('matches.ban'); Route::get('/matches/{id}/details', [MatchManagementController::class, 'getMatchDetails'])->whereNumber('id')->name('matches.details'); Route::get('/matches/{id}/result', [MatchManagementController::class, 'getMatchResult'])->whereNumber('id')->name('matches.result'); Route::post('/matches/{id}/end', [MatchManagementController::class, 'endMatch'])->whereNumber('id')->name('matches.end'); Route::post('/matches/{id}/voice-mode', [MatchManagementController::class, 'updateVoiceMode'])->whereNumber('id')->name('matches.voice-mode'); Route::post('/matches/{id}/cancel', [AdminOperationsController::class, 'cancelMatch'])->whereNumber('id')->name('matches.cancel'); Route::delete('/matches/{id}', [AdminOperationsController::class, 'destroyMatch'])->whereNumber('id')->name('matches.destroy'); Route::post('/queue/clear', [AdminOperationsController::class, 'clearMixQueue'])->name('queue.clear'); Route::post('/toggle-validation', [SystemController::class, 'toggleValidation'])->name('toggle-validation'); Route::get('/voice-settings', [\App\Http\Controllers\Admin\VoiceSettingsController::class, 'index'])->name('voice-settings'); Route::post('/voice-settings', [\App\Http\Controllers\Admin\VoiceSettingsController::class, 'update'])->name('voice-settings.update'); Route::get('/teamspeak', [TeamSpeakAdminController::class, 'index'])->name('teamspeak'); Route::post('/teamspeak/test', [TeamSpeakAdminController::class, 'testConnection'])->name('teamspeak.test'); Route::post('/teamspeak/cleanup-test', [TeamSpeakAdminController::class, 'cleanupDiagnostic'])->name('teamspeak.cleanup-test'); Route::post('/teamspeak/full-test', [TeamSpeakAdminController::class, 'fullDiagnostic'])->name('teamspeak.full-test'); Route::post('/teamspeak/provision', [TeamSpeakAdminController::class, 'provision'])->name('teamspeak.provision'); Route::post('/teamspeak/move', [TeamSpeakAdminController::class, 'move'])->name('teamspeak.move'); Route::post('/teamspeak/cleanup', [TeamSpeakAdminController::class, 'cleanup'])->name('teamspeak.cleanup'); Route::middleware(['admin.role:admin'])->group(function () { Route::get('/users', [AdminUserController::class, 'index'])->name('users'); Route::post('/users/role', [AdminUserController::class, 'updateRole'])->name('users.role'); Route::patch('/users/{id}/professional', [AdminUserController::class, 'updateProfessional'])->name('users.professional'); Route::patch('/users/{id}/membership', [AdminUserController::class, 'updateMembership'])->name('users.membership'); Route::post('/users/permission', [MatchManagementController::class, 'updateUserPermission'])->name('users.permissions'); }); }); // PRESENÇA ONLINE // Corrigido mantendo a estrutura: rota POST agora exige sessão autenticada. // O frontend deve enviar X-CSRF-TOKEN no fetch/AJAX. Route::post('/presence/ping', function () { $userId = session('user_id') ?: Auth::id(); if ($userId && !session()->has('user_id')) { session(['user_id' => $userId]); } if ($userId) { \Illuminate\Support\Facades\DB::table('users') ->where('id', $userId) ->update([ 'is_online' => 1, 'last_seen_at' => now(), ]); } return response()->json(['success' => true]); }) ->middleware(['auth.session']) ->withoutMiddleware([ \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, ]) ->name('presence.ping'); Route::get('/debug-db', function () { return response()->json([ 'default_connection' => config('database.default'), 'database' => DB::connection()->getDatabaseName(), 'has_mix_training_matches' => Schema::hasTable('mix_training_matches'), 'has_updated_at' => Schema::hasColumn('mix_training_matches', 'updated_at'), ]); }); // ============================================ // RECUPERAÇÃO DE SENHA // ============================================ Route::get('/forgot-password', function () { return view('auth.forgot-password'); })->middleware('guest')->name('password.request'); Route::post('/forgot-password', function (\Illuminate\Http\Request $request) { $request->validate(['email' => 'required|email']); $status = Password::sendResetLink( $request->only('email') ); return $status === Password::RESET_LINK_SENT ? back()->with(['success' => 'Link de recuperação enviado. Verifique o e-mail ou o log do Laravel.']) : back()->withErrors(['email' => __($status)]); })->middleware('guest')->name('password.email'); Route::get('/reset-password/{token}', function (string $token) { return view('auth.reset-password', [ 'token' => $token, 'email' => request('email'), ]); })->middleware('guest')->name('password.reset'); Route::post('/reset-password', function (\Illuminate\Http\Request $request) { $request->validate([ 'token' => 'required', 'email' => 'required|email', 'password' => 'required|min:6|confirmed', ]); $status = Password::reset( $request->only('email', 'password', 'password_confirmation', 'token'), function ($user, $password) { DB::table('users') ->where('email', $user->email) ->update([ 'password_hash' => Hash::make($password), 'updated_at' => now(), ]); } ); return $status === Password::PASSWORD_RESET ? redirect('/login')->with('success', 'Senha alterada com sucesso.') : back()->withErrors(['email' => __($status)]); })->middleware('guest')->name('password.update'); Route::get('/debug-login-routes', function () { return response()->json([ 'login_get' => route('login'), 'login_post_exists' => Route::has('login.post'), 'login_authenticate_exists' => Route::has('login.authenticate'), 'login_submit_exists' => Route::has('login.submit'), 'logout_exists' => Route::has('logout'), 'session_id' => session()->getId(), 'csrf_token' => csrf_token(), ]); })->name('debug.login.routes'); Route::get('/debug-krux', function () { return response()->json([ 'app' => config('app.name'), 'env' => config('app.env'), 'url' => config('app.url'), 'auth' => [ 'check' => Auth::check(), 'id' => Auth::id(), ], 'session' => [ 'driver' => config('session.driver'), 'cookie' => config('session.cookie'), 'domain' => config('session.domain'), 'secure' => config('session.secure'), 'same_site' => config('session.same_site'), 'path' => config('session.path'), 'session_id' => session()->getId(), 'has_user_id' => session()->has('user_id'), 'user_id' => session('user_id'), 'logged_in' => session('logged_in'), 'csrf_token' => csrf_token(), ], 'database' => [ 'default' => config('database.default'), 'name' => DB::connection()->getDatabaseName(), 'users_table' => Schema::hasTable('users'), ], 'checks' => [ '419' => 'CSRF/cookie/sessao', '500' => 'Erro PHP, banco, config ou controller', '404' => 'Rota nao existe ou cache de rota', ], ]); }); Route::post('/gigax-app/pair-token', [GigaxArenaAppController::class, 'createPairToken'])->middleware('auth.session')->name('gigax-app.pair-token'); Route::get('/gigax-app/status', [\App\Http\Controllers\GigaxClientGateController::class, 'status'])->middleware('auth.session')->name('gigax-app.status'); // GIGAX_PROGRESSION_ROUTES require __DIR__ . '/gigax_progression.php'; // GIGAX_NOTIFICATIONS_ROUTES require __DIR__ . '/gigax_notifications.php'; // GIGAX CENTRAL NOTIFICATIONS require __DIR__ . '/gigax_central_notifications.php'; // GIGAX_AC_ADMIN_ROUTES require __DIR__ . '/gigax_ac_admin.php'; Route::post('/client/login-presence', [ \App\Http\Controllers\GigaAnticheatController::class, 'loginPresence' ]); // Rotas da página de transmissões GIGAX require __DIR__ . '/gigax_streams.php'; require __DIR__ . '/gigax_site_guard_web.php'; Route::get('/admin/clan-settings',[\App\Http\Controllers\Admin\ClanSettingsController::class,'index'])->name('admin.clan-settings'); Route::post('/admin/clan-settings',[\App\Http\Controllers\Admin\ClanSettingsController::class,'update'])->name('admin.clan-settings.update'); // GIGAX_MODES_ROUTES_V1 require __DIR__.'/gigax_modes.php'; // GIGAX_DISCORD_ADMIN_V39 require __DIR__ . '/gigax_discord_admin.php'; /* // GIGAX_ADMIN_CLANS_V1 */ Route::prefix('admin/clans') ->name('admin.clans.') ->middleware(['auth.session','admin.role:admin']) ->group(function () { Route::get('/', [\App\Http\Controllers\Admin\ClanAdminController::class, 'index'])->name('index'); Route::get('/{id}', [\App\Http\Controllers\Admin\ClanAdminController::class, 'show'])->whereNumber('id')->name('show'); Route::put('/{id}', [\App\Http\Controllers\Admin\ClanAdminController::class, 'update'])->whereNumber('id')->name('update'); Route::post('/{id}/logo', [\App\Http\Controllers\Admin\ClanAdminController::class, 'updateLogo'])->whereNumber('id')->name('logo'); Route::post('/{id}/captain', [\App\Http\Controllers\Admin\ClanAdminController::class, 'transferCaptain'])->whereNumber('id')->name('captain'); Route::delete('/{id}', [\App\Http\Controllers\Admin\ClanAdminController::class, 'destroy'])->whereNumber('id')->name('destroy'); }); /* GIGAX_TOURNAMENT_EDITOR_V1 */ Route::middleware(['auth.session'])->group(function () { Route::get('/tournaments/{id}/edit', [\App\Http\Controllers\Admin\TournamentEditController::class, 'edit'])->whereNumber('id')->name('tournaments.admin.edit'); Route::put('/tournaments/{id}/edit', [\App\Http\Controllers\Admin\TournamentEditController::class, 'update'])->whereNumber('id')->name('tournaments.admin.update'); }); // GIGAX_SUPPORT_TICKETS_V3 require __DIR__ . '/gigax_support_tickets.php'; // GIGAX AC - reset administrativo total de uma conta Route::post('/admin/gigax-ac/{userId}/reset', [\App\Http\Controllers\Admin\GigaxAcResetController::class, 'reset']) ->middleware(['auth']) ->name('admin.gigax-ac.reset'); // GIGAX_EVENT_REPORT_ROUTES_V1 require __DIR__ . '/event_reports.php'; // GIGAX_AC_ADMIN_TOGGLE_V1 require __DIR__ . '/gigax_ac_validation.php';