Add NAS file manager integration and all pending platform changes
- Installed p7h/nas-file-manager package via private VCS repo - Published config/nas-file-manager.php with super_admin middleware restriction - Added NAS env vars to .env.example - Created admin/nas-storage page with connection info panel and file browser widget - Added NAS Storage link to admin sidebar (super_admin only) - Added SuperAdminController@nasStorage method and admin.nas-storage route - Includes all accumulated branch changes: profile wall, 2FA, audit logs, settings panel, country/phone/timezone components, posts, slideshow, playlist shares, video downloads/shares, comment likes, notifications, social links, and more Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
1 parent
d44490dfe0
commit
0b2e95ea65
161 files changed
+27496
-7250
No files matched your search
+46
-3
@@ -2,16 +2,59 @@
|
||||
|
||||
use App\Http\Controllers\Auth\AuthenticatedSessionController;
|
||||
use App\Http\Controllers\Auth\RegisteredUserController;
|
||||
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware('guest')->group(function () {
|
||||
Route::get('register', [RegisteredUserController::class, 'create'])->name('register');
|
||||
Route::post('register', [RegisteredUserController::class, 'store'])->name('register.store');
|
||||
|
||||
Route::post('register', [RegisteredUserController::class, 'store'])
|
||||
->middleware('throttle:5,1') // max 5 registrations per minute per IP
|
||||
->name('register.store');
|
||||
|
||||
Route::get('login', [AuthenticatedSessionController::class, 'create'])->name('login');
|
||||
Route::post('login', [AuthenticatedSessionController::class, 'store'])->name('login.store');
|
||||
Route::post('login', [AuthenticatedSessionController::class, 'store'])
|
||||
->middleware('throttle:10,1') // max 10 login attempts per minute per IP
|
||||
->name('login.store');
|
||||
});
|
||||
|
||||
// Email verification
|
||||
Route::middleware('auth')->group(function () {
|
||||
Route::get('email/verify', fn () => view('auth.verify-email'))
|
||||
->name('verification.notice');
|
||||
|
||||
Route::get('email/verify/{id}/{hash}', function (Request $request, $id, $hash) {
|
||||
// If the link belongs to a different account, log out and ask them to
|
||||
// sign in as the right account before clicking the link again.
|
||||
if (Auth::check() && (string) Auth::id() !== (string) $id) {
|
||||
session()->forget('impersonator_id');
|
||||
Auth::logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
return redirect()->route('login')
|
||||
->with('info', 'Please log in with the account that owns this verification link, then click the link again.');
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
|
||||
// Validate the hash against the authenticated user's email
|
||||
if (! hash_equals(sha1($user->getEmailForVerification()), (string) $hash)) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
if (! $user->hasVerifiedEmail()) {
|
||||
$user->markEmailAsVerified();
|
||||
event(new \Illuminate\Auth\Events\Verified($user));
|
||||
}
|
||||
|
||||
return redirect('/videos')->with('verified', true);
|
||||
})->middleware('signed')->name('verification.verify');
|
||||
|
||||
Route::post('email/verification-notification', function (Request $request) {
|
||||
$request->user()->sendEmailVerificationNotification();
|
||||
return back()->with('resent', true);
|
||||
})->middleware('throttle:3,1')->name('verification.send');
|
||||
|
||||
Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])->name('logout');
|
||||
});
|
||||
+99
-9
@@ -1,10 +1,13 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\CommentController;
|
||||
use App\Http\Controllers\ImageUploadController;
|
||||
use App\Http\Controllers\MatchEventController;
|
||||
use App\Http\Controllers\PostController;
|
||||
use App\Http\Controllers\SuperAdminController;
|
||||
use App\Http\Controllers\UserController;
|
||||
use App\Http\Controllers\VideoController;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
// Root route - show videos
|
||||
@@ -16,14 +19,35 @@ Route::get('/videos/search', [VideoController::class, 'search'])->name('videos.s
|
||||
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::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 required
|
||||
Route::middleware('auth')->group(function () {
|
||||
// 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');
|
||||
@@ -36,18 +60,27 @@ Route::middleware('auth')->group(function () {
|
||||
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')->group(function () {
|
||||
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')->group(function () {
|
||||
// Profile
|
||||
Route::get('/profile', [UserController::class, 'profile'])->name('profile');
|
||||
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
|
||||
@@ -56,11 +89,27 @@ Route::middleware('auth')->group(function () {
|
||||
|
||||
// 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, own channel requires auth
|
||||
Route::get('/channel/{userId?}', [UserController::class, 'channel'])->name('channel');
|
||||
// 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;
|
||||
@@ -70,10 +119,13 @@ Route::get('/user/playlists', [PlaylistController::class, 'userPlaylists'])->nam
|
||||
|
||||
// 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')->group(function () {
|
||||
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');
|
||||
@@ -93,6 +145,18 @@ Route::middleware('auth')->group(function () {
|
||||
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';
|
||||
|
||||
@@ -100,20 +164,46 @@ require __DIR__.'/auth.php';
|
||||
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');
|
||||
});
|
||||
|
||||
// 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 () {
|
||||
|
||||
|
||||
Reference in new issue
Block a user