name('media.thumbnail')->where('filename', '.+'); Route::get('/media/avatars/{filename}', [MediaController::class, 'avatar'])->name('media.avatar')->where('filename', '.+'); Route::get('/media/banners/{filename}', [MediaController::class, 'banner'])->name('media.banner')->where('filename', '.+'); Route::get('/media/post-images/{filename}', [MediaController::class, 'postImage'])->name('media.post-image')->where('filename', '.+'); // Root route - show videos Route::get('/', [VideoController::class, 'index'])->name('home'); // Video routes - public Route::get('/videos', [VideoController::class, 'index'])->name('videos.index'); Route::get('/videos/search', [VideoController::class, 'search'])->name('videos.search'); Route::get('/trending', [VideoController::class, 'trending'])->name('videos.trending'); Route::get('/shorts', [VideoController::class, 'shorts'])->name('videos.shorts'); Route::get('/videos/create', [VideoController::class, 'create'])->name('videos.create'); // Redirect legacy numeric video IDs to encoded URLs (backward compatibility) Route::get('/videos/{numericId}', function ($numericId) { $video = \App\Models\Video::findOrFail((int) $numericId); $url = route('videos.show', $video); if (request()->getQueryString()) { $url .= '?' . request()->getQueryString(); } return redirect($url, 301); })->where('numericId', '[0-9]+'); Route::get('/videos/share/{token}', [VideoController::class, 'showByToken'])->name('videos.showByToken'); Route::get('/videos/{video}', [VideoController::class, 'show'])->name('videos.show'); Route::get('/videos/{video}/stream', [VideoController::class, 'stream'])->name('videos.stream'); Route::get('/videos/{video}/hls/{file?}', [VideoController::class, 'hls'])->where(['file' => '.*'])->name('videos.hls'); Route::get('/videos/{video}/download', [VideoController::class, 'download'])->name('videos.download'); Route::get('/videos/{video}/download-mp3', [VideoController::class, 'downloadMp3'])->name('videos.downloadMp3'); Route::post('/videos/{video}/slideshow/generate', [VideoController::class, 'slideshowGenerate'])->name('videos.slideshow.generate'); Route::get('/videos/{video}/slideshow/progress', [VideoController::class, 'slideshowProgress'])->name('videos.slideshow.progress'); Route::get('/videos/{video}/recommendations', [VideoController::class, 'recommendations'])->name('videos.recommendations'); Route::get('/videos/{video}/player-data', [VideoController::class, 'playerData'])->name('videos.playerData'); Route::post('/videos/{video}/share', [VideoController::class, 'recordShare'])->name('videos.recordShare'); Route::get('/videos/{video}/og-image', [VideoController::class, 'ogImage'])->name('videos.ogImage'); Route::get('/s/{token}', [VideoController::class, 'accessShare'])->name('share.access'); Route::get('/videos/{video}/insights', [VideoController::class, 'insights'])->name('videos.insights')->middleware('auth'); Route::get('/videos/{video}/insights/country/{country}', [VideoController::class, 'insightsCountry'])->name('videos.insights.country')->middleware('auth'); Route::get('/videos/{video}/insights/day/{date}', [VideoController::class, 'insightsDay'])->name('videos.insights.day')->middleware('auth'); Route::get('/videos/{video}/insights/downloader/{userId}', [VideoController::class, 'insightsDownloaderHistory'])->name('videos.insights.downloader')->middleware('auth'); // Video routes - auth + verified required Route::middleware(['auth', 'verified'])->group(function () { Route::get('/videos/create', [VideoController::class, 'create'])->name('videos.create'); Route::post('/videos', [VideoController::class, 'store'])->name('videos.store'); Route::get('/videos/{video}/edit', [VideoController::class, 'edit'])->name('videos.edit'); Route::put('/videos/{video}', [VideoController::class, 'update'])->name('videos.update'); Route::post('/videos/{video}/replace-file', [VideoController::class, 'replaceFile'])->name('videos.replaceFile'); Route::delete('/videos/{video}', [VideoController::class, 'destroy'])->name('videos.destroy'); // Like/unlike routes Route::post('/videos/{video}/like', [UserController::class, 'like'])->name('videos.like'); Route::post('/videos/{video}/unlike', [UserController::class, 'unlike'])->name('videos.unlike'); Route::post('/videos/{video}/toggle-like', [UserController::class, 'toggleLike'])->name('videos.toggleLike'); }); // Subscribe + notifications — auth but NOT email verification required Route::middleware(['auth'])->group(function () { Route::post('/channel/{user}/subscribe', [UserController::class, 'toggleSubscribe'])->name('channel.subscribe'); Route::get('/notifications/fetch', [UserController::class, 'fetchNotifications'])->name('notifications.fetch'); Route::post('/notifications/{id}/read', [UserController::class, 'markNotificationRead'])->name('notifications.read'); Route::post('/notifications/read-all', [UserController::class, 'markAllNotificationsRead'])->name('notifications.read-all'); }); // Comment routes Route::get('/videos/{video}/comments', [CommentController::class, 'index'])->name('comments.index'); Route::middleware(['auth', 'verified'])->group(function () { Route::post('/videos/{video}/comments', [CommentController::class, 'store'])->name('comments.store'); Route::put('/comments/{comment}', [CommentController::class, 'update'])->name('comments.update'); Route::delete('/comments/{comment}', [CommentController::class, 'destroy'])->name('comments.destroy'); Route::post('/comments/{comment}/like', [CommentController::class, 'like'])->name('comments.like'); }); // User routes Route::middleware(['auth', 'verified'])->group(function () { // Profile — redirect to own channel Route::get('/profile', fn() => redirect()->route('channel', Auth::user()->channel))->name('profile'); Route::put('/profile', [UserController::class, 'updateProfile'])->name('profile.update'); // Settings Route::get('/settings', [UserController::class, 'settings'])->name('settings'); Route::put('/settings', [UserController::class, 'updateSettings'])->name('settings.update'); // History & Liked Route::get('/history', [UserController::class, 'history'])->name('history'); Route::delete('/history', [UserController::class, 'clearHistory'])->name('history.clear'); Route::get('/liked', [UserController::class, 'liked'])->name('liked'); // Logout all other devices Route::post('/settings/logout-all-devices', [UserController::class, 'logoutAllDevices'])->name('settings.logoutAllDevices'); // Image upload (cropper) Route::post('/image-upload', [ImageUploadController::class, 'upload'])->name('image.upload'); Route::post('/profile/update-avatar', [UserController::class, 'updateAvatar'])->name('profile.updateAvatar'); Route::post('/profile/update-banner', [UserController::class, 'updateBanner'])->name('profile.updateBanner'); }); // Channel - public for viewing. {username} is the user's slug (non-sequential, safe to share). Route::get('/channel/{username?}', [UserController::class, 'channel'])->name('channel'); // Post routes - auth required Route::middleware(['auth'])->group(function () { Route::post('/channel/{user}/posts', [PostController::class, 'store'])->name('posts.store'); Route::delete('/posts/{post}', [PostController::class, 'destroy'])->name('posts.destroy'); Route::post('/posts/{post}/react', [PostController::class, 'react'])->name('posts.react'); }); // Playlist routes use App\Http\Controllers\PlaylistController; // Get user playlists (for dropdown) - MUST be before /playlists/{playlist} route Route::get('/user/playlists', [PlaylistController::class, 'userPlaylists'])->name('playlists.userPlaylists'); // Public playlist routes Route::get('/playlists', [PlaylistController::class, 'index'])->name('playlists.index'); Route::get('/playlists/share/{token}', [PlaylistController::class, 'showByToken'])->name('playlists.showByToken'); Route::get('/playlists/{playlist}', [PlaylistController::class, 'show'])->name('playlists.show'); Route::post('/playlists/{playlist}/share', [PlaylistController::class, 'recordShare'])->name('playlists.recordShare'); Route::get('/ps/{token}', [PlaylistController::class, 'accessShare'])->name('playlists.accessShare'); // Authenticated playlist routes Route::middleware(['auth', 'verified'])->group(function () { Route::get('/playlists/create', [PlaylistController::class, 'create'])->name('playlists.create'); Route::post('/playlists', [PlaylistController::class, 'store'])->name('playlists.store'); Route::get('/playlists/{playlist}/edit', [PlaylistController::class, 'edit'])->name('playlists.edit'); Route::put('/playlists/{playlist}', [PlaylistController::class, 'update'])->name('playlists.update'); Route::delete('/playlists/{playlist}', [PlaylistController::class, 'destroy'])->name('playlists.destroy'); // Playlist video management Route::post('/playlists/{playlist}/videos', [PlaylistController::class, 'addVideo'])->name('playlists.addVideo'); Route::delete('/playlists/{playlist}/videos/{video}', [PlaylistController::class, 'removeVideo'])->name('playlists.removeVideo'); Route::put('/playlists/{playlist}/reorder', [PlaylistController::class, 'reorder'])->name('playlists.reorder'); // Playlist actions Route::get('/playlists/{playlist}/play', [PlaylistController::class, 'playAll'])->name('playlists.playAll'); Route::get('/playlists/{playlist}/shuffle', [PlaylistController::class, 'shuffle'])->name('playlists.shuffle'); // Watch Later Route::post('/videos/{video}/watch-later', [PlaylistController::class, 'watchLater'])->name('videos.watchLater'); }); // Two-Factor Authentication use App\Http\Controllers\TwoFactorController; Route::get('/2fa/challenge', [TwoFactorController::class, 'showChallenge'])->name('2fa.challenge'); Route::post('/2fa/challenge', [TwoFactorController::class, 'verifyChallenge'])->name('2fa.verify'); Route::middleware(['auth'])->group(function () { Route::post('/2fa/setup', [TwoFactorController::class, 'setup'])->name('2fa.setup'); Route::post('/2fa/enable', [TwoFactorController::class, 'enable'])->name('2fa.enable'); Route::post('/2fa/disable', [TwoFactorController::class, 'disable'])->name('2fa.disable'); }); // Signed confirmation link — no auth required (user may click from a different device/browser) Route::get('/2fa/disable/confirm', [TwoFactorController::class, 'confirmDisable'])->name('2fa.disable.confirm'); // Authentication Routes require __DIR__.'/auth.php'; // Super Admin Routes Route::middleware(['auth', 'super_admin'])->prefix('admin')->name('admin.')->group(function () { // Dashboard Route::get('/dashboard', [SuperAdminController::class, 'dashboard'])->name('dashboard'); Route::get('/dashboard/modal', [SuperAdminController::class, 'modalData'])->name('dashboard.modal'); // User Management Route::get('/users', [SuperAdminController::class, 'users'])->name('users'); Route::get('/users/{user}/edit', [SuperAdminController::class, 'editUser'])->name('users.edit'); Route::put('/users/{user}', [SuperAdminController::class, 'updateUser'])->name('users.update'); Route::delete('/users/{user}', [SuperAdminController::class, 'deleteUser'])->name('users.delete'); Route::post('/users/{user}/impersonate', [SuperAdminController::class, 'impersonate'])->name('users.impersonate'); Route::post('/users/{user}/verify', [SuperAdminController::class, 'verifyUser'])->name('users.verify'); // Video Management Route::get('/videos', [SuperAdminController::class, 'videos'])->name('videos'); Route::get('/videos/{video}/analytics', [SuperAdminController::class, 'videoAnalytics'])->name('videos.analytics'); Route::get('/videos/{video}/edit', [SuperAdminController::class, 'editVideo'])->name('videos.edit'); Route::put('/videos/{video}', [SuperAdminController::class, 'updateVideo'])->name('videos.update'); Route::delete('/videos/{video}', [SuperAdminController::class, 'deleteVideo'])->name('videos.delete'); // Orphan cleanup Route::post('/cleanup-orphans', [SuperAdminController::class, 'cleanupOrphanedVideos'])->name('admin.cleanup.orphans'); // Log viewer Route::get('/logs', [SuperAdminController::class, 'logs'])->name('logs'); // Audit log center Route::get('/audit', [SuperAdminController::class, 'auditLogs'])->name('audit'); // System settings Route::get('/settings', [SuperAdminController::class, 'settings'])->name('settings'); Route::post('/settings', [SuperAdminController::class, 'updateSettings'])->name('settings.update'); Route::get('/settings/detect-gpu', [SuperAdminController::class, 'detectGpu'])->name('settings.detect-gpu'); // NAS Storage Route::get('/nas-storage', [SuperAdminController::class, 'nasStorage'])->name('nas-storage'); Route::post('/nas-repair', [SuperAdminController::class, 'nasRepair'])->name('admin.nas.repair'); Route::post('/nas-delete', [SuperAdminController::class, 'nasDelete'])->name('nas.delete'); // NAS Disable Flow Route::post('/nas/disable', [SuperAdminController::class, 'nasDisable'])->name('nas.disable'); Route::get('/nas/migrate-progress', [SuperAdminController::class, 'nasMigrateProgress'])->name('nas.migrate-progress'); // Backup & Restore Route::get('/backup/users-settings', [SuperAdminController::class, 'backupUsersSettings'])->name('backup.users-settings'); Route::post('/backup/restore', [SuperAdminController::class, 'restoreUsersSettings'])->name('backup.restore'); }); // Exit impersonation — accessible by any authenticated user (the impersonated user hitting exit) Route::post('/impersonate/exit', [SuperAdminController::class, 'exitImpersonation']) ->middleware('auth') ->name('impersonate.exit'); // Match Events Routes (removed auth requirement for demo purposes) // In production, wrap with: Route::middleware('auth')->group(function () { // Get match data - public for viewing Route::get('/videos/{video}/match-data', [MatchEventController::class, 'getMatchData'])->name('match.getData'); // Round CRUD Route::post('/videos/{video}/rounds', [MatchEventController::class, 'storeRound'])->name('match.storeRound'); Route::put('/rounds/{round}', [MatchEventController::class, 'updateRound'])->name('match.updateRound'); Route::delete('/rounds/{round}', [MatchEventController::class, 'destroyRound'])->name('match.destroyRound'); // Point CRUD Route::post('/videos/{video}/points', [MatchEventController::class, 'storePoint'])->name('match.storePoint'); Route::put('/points/{point}', [MatchEventController::class, 'updatePoint'])->name('match.updatePoint'); Route::delete('/points/{point}', [MatchEventController::class, 'destroyPoint'])->name('match.destroyPoint'); // Coach Review CRUD Route::post('/videos/{video}/reviews', [MatchEventController::class, 'storeReview'])->name('match.storeReview'); Route::put('/reviews/{review}', [MatchEventController::class, 'updateReview'])->name('match.updateReview'); Route::delete('/reviews/{review}', [MatchEventController::class, 'destroyReview'])->name('match.destroyReview');