name('index'); Route::get('/home', [HomeController::class, 'index']); // ── DISCOVER (/discover/*) ──────────────────────────────────────────────────── Route::prefix('discover')->name('discover.')->group(function () { Route::get('/', fn () => redirect('/discover/trending', 301)); Route::get('/trending', [DiscoverController::class, 'trending'])->name('trending'); Route::get('/rising', [DiscoverController::class, 'rising'])->name('rising'); Route::get('/fresh', [DiscoverController::class, 'fresh'])->name('fresh'); Route::get('/top-rated', [DiscoverController::class, 'topRated'])->name('top-rated'); Route::get('/most-downloaded', [DiscoverController::class, 'mostDownloaded'])->name('most-downloaded'); Route::get('/on-this-day', [DiscoverController::class, 'onThisDay'])->name('on-this-day'); Route::middleware('auth')->get('/following', [DiscoverController::class, 'following'])->name('following'); Route::middleware('auth')->get('/for-you', [DiscoverController::class, 'forYou'])->name('for-you'); }); // ── EXPLORE (/explore/*) ────────────────────────────────────────────────────── Route::prefix('explore')->name('explore.')->group(function () { Route::get('/', [ExploreController::class, 'index'])->name('index'); Route::get('/members', fn () => redirect('/members', 301))->name('members.redirect'); Route::get('/memebers', fn () => redirect('/members', 301))->name('memebers.redirect'); Route::get('/{type}', [ExploreController::class, 'byType']) ->where('type', 'artworks|wallpapers|skins|photography|other') ->name('type'); Route::get('/{type}/{mode}', [ExploreController::class, 'byTypeMode']) ->where('type', 'artworks|wallpapers|skins|photography|other') ->where('mode', 'trending|new-hot|best|latest') ->name('type.mode'); }); // ── BLOG (/blog/*) ──────────────────────────────────────────────────────────── Route::prefix('blog')->name('blog.')->group(function () { Route::get('/', [BlogController::class, 'index'])->name('index'); Route::get('/{slug}', [BlogController::class, 'show'])->where('slug', '[a-z0-9\-]+')->name('show'); }); // ── PAGES (DB-driven static pages) ─────────────────────────────────────────── Route::get('/pages/{slug}', [PageController::class, 'show']) ->where('slug', '[a-z0-9\-]+') ->name('pages.show'); Route::get('/about', [PageController::class, 'marketing'])->defaults('slug', 'about')->name('about'); Route::get('/help', [PageController::class, 'marketing'])->defaults('slug', 'help')->name('help'); Route::get('/contact', [PageController::class, 'marketing'])->defaults('slug', 'contact')->name('contact'); Route::get('/legal/{section}', [PageController::class, 'legal']) ->where('section', 'terms|privacy|cookies') ->name('legal'); // ── FOOTER ──────────────────────────────────────────────────────────────────── Route::get('/rss-feeds', [RssFeedController::class, 'index'])->name('rss-feeds'); Route::get('/faq', [FooterController::class, 'faq'])->name('faq'); Route::get('/rules-and-guidelines', [FooterController::class, 'rules'])->name('rules'); Route::get('/privacy-policy', [FooterController::class, 'privacyPolicy'])->name('privacy-policy'); Route::get('/terms-of-service', [FooterController::class, 'termsOfService'])->name('terms-of-service'); Route::get('/staff', [StaffController::class, 'index'])->name('staff'); Route::get('/contact', [ApplicationController::class, 'show'])->name('contact.show'); Route::post('/contact', [ApplicationController::class, 'submit'])->middleware('throttle:6,1')->name('contact.submit'); // ── ADMIN: Staff applications ───────────────────────────────────────────────── Route::middleware(['auth'])->prefix('admin')->name('admin.')->group(function () { Route::get('/applications', [StaffApplicationAdminController::class, 'index'])->name('applications.index'); Route::get('/applications/{staffApplication}', [StaffApplicationAdminController::class, 'show'])->name('applications.show'); }); // ── RSS 2.0 feeds (/rss/*) ──────────────────────────────────────────────────── Route::middleware('throttle:60,1')->group(function () { Route::get('/rss', GlobalFeedController::class)->name('rss.global'); Route::prefix('rss/discover')->name('rss.discover.')->group(function () { Route::get('/', [DiscoverFeedController::class, 'index'])->name('index'); Route::get('/trending', [DiscoverFeedController::class, 'trending'])->name('trending'); Route::get('/fresh', [DiscoverFeedController::class, 'fresh'])->name('fresh'); Route::get('/rising', [DiscoverFeedController::class, 'rising'])->name('rising'); }); Route::prefix('rss/explore')->name('rss.explore.')->group(function () { Route::get('/{type}', [ExploreFeedController::class, 'byType']) ->where('type', 'artworks|wallpapers|skins|photography|other') ->name('type'); Route::get('/{type}/{mode}', [ExploreFeedController::class, 'byTypeMode']) ->where('type', 'artworks|wallpapers|skins|photography|other') ->where('mode', 'trending|latest|best') ->name('type.mode'); }); Route::get('/rss/tag/{slug}', TagFeedController::class) ->where('slug', '[a-z0-9\-]+') ->name('rss.tag'); Route::get('/rss/creator/{username}', CreatorFeedController::class) ->where('username', '[A-Za-z0-9_\-]{3,20}') ->name('rss.creator'); Route::get('/rss/blog', BlogFeedController::class)->name('rss.blog'); }); // ── CREATORS (/creators/*) ──────────────────────────────────────────────────── Route::prefix('creators')->name('creators.')->group(function () { Route::get('/top', [\App\Http\Controllers\User\TopAuthorsController::class, 'index'])->name('top'); Route::get('/rising', [DiscoverController::class, 'risingCreators'])->name('rising'); }); // ── STORIES (/stories/*) ────────────────────────────────────────────────────── Route::prefix('stories')->name('stories.')->group(function () { Route::get('/', [StoryController::class, 'index'])->name('index'); Route::get('/tag/{tag}', [StoryController::class, 'tag']) ->where('tag', '[a-z0-9\-]+') ->name('tag'); Route::get('/creator/{username}', [StoryController::class, 'creator']) ->where('username', '[A-Za-z0-9_\-]{1,50}') ->name('creator'); Route::get('/author/{username}', fn (string $username) => redirect()->route('stories.creator', ['username' => $username], 301)) ->where('username', '[A-Za-z0-9_\-]{1,50}') ->name('author'); Route::get('/category/{category}', [StoryController::class, 'category']) ->where('category', 'creator_story|tutorial|interview|project_breakdown|announcement|resource') ->name('category'); Route::get('/{slug}', [StoryController::class, 'show']) ->where('slug', '[a-z0-9\-]+') ->name('show'); }); Route::middleware(['auth', 'ensure.onboarding.complete', 'creator.access'])->prefix('creator/stories')->name('creator.stories.')->group(function () { Route::get('/', [StoryController::class, 'dashboard'])->name('index'); Route::get('/create', [StoryController::class, 'create'])->name('create'); Route::post('/', [StoryController::class, 'store'])->name('store'); Route::get('/artworks/search', [StoryController::class, 'searchArtworks'])->name('artworks.search'); Route::post('/upload-image', [StoryController::class, 'uploadImage'])->name('upload-image'); Route::get('/{story}/edit', [StoryController::class, 'edit'])->name('edit'); Route::put('/{story}', [StoryController::class, 'update'])->name('update'); Route::delete('/{story}', [StoryController::class, 'destroy'])->name('destroy'); Route::post('/{story}/autosave', [StoryController::class, 'autosave'])->name('autosave'); Route::post('/{story}/submit-review', [StoryController::class, 'submitForReview'])->name('submit-review'); Route::post('/{story}/publish', [StoryController::class, 'publishNow'])->name('publish-now'); Route::get('/{story}/preview', [StoryController::class, 'preview'])->name('preview'); Route::get('/{story}/analytics', [StoryController::class, 'analytics'])->name('analytics'); }); // ── TAGS ────────────────────────────────────────────────────────────────────── Route::get('/tags', [\App\Http\Controllers\Web\TagController::class, 'index'])->name('tags.index'); Route::get('/tag/{tag:slug}', [\App\Http\Controllers\Web\TagController::class, 'show']) ->where('tag', '[a-z0-9\-]+') ->name('tags.show'); Route::get('/tags/{tag}', [\App\Http\Controllers\Web\Posts\HashtagFeedController::class, 'index']) ->where('tag', '[A-Za-z][A-Za-z0-9_]{1,63}') ->name('feed.hashtag'); // ── CATEGORIES DIRECTORY ───────────────────────────────────────────────────── Route::get('/categories', [CategoryController::class, 'index'])->name('categories.index'); // ── FOLLOWING (shortcut) ────────────────────────────────────────────────────── Route::middleware('auth')->get('/following', function () { return redirect()->route('dashboard.following'); })->name('following.redirect'); // ── ART / ARTWORKS ──────────────────────────────────────────────────────────── Route::get('/art/{id}/{slug?}', [ArtworkPageController::class, 'show']) ->where('id', '\d+') ->name('art.show'); Route::get('/download/artwork/{id}', ArtworkDownloadController::class) ->whereNumber('id') ->middleware('throttle:downloads') ->name('art.download'); // ── NEWS (/news/*) ──────────────────────────────────────────────────────────── Route::prefix('news')->name('news.')->group(function () { Route::get('/', [FrontendNewsController::class, 'index'])->name('index'); Route::get('category/{slug}', [FrontendNewsController::class, 'category'])->name('category'); Route::get('tag/{slug}', [FrontendNewsController::class, 'tag'])->name('tag'); Route::get('{slug}', [FrontendNewsController::class, 'show']) ->where('slug', '[a-z0-9\-]+') ->name('show'); }); Route::get('/rss/news', [NewsRssController::class, 'feed'])->name('news.rss'); // ── PROFILES (@username) ────────────────────────────────────────────────────── Route::get('/@{username}', [ProfileController::class, 'showByUsername']) ->where('username', '[A-Za-z0-9_-]{3,20}') ->name('profile.show'); Route::middleware('auth')->post('/@{username}/follow', [ProfileController::class, 'toggleFollow']) ->where('username', '[A-Za-z0-9_-]{3,20}') ->name('profile.follow'); Route::middleware('auth')->post('/@{username}/comment', [ProfileController::class, 'storeComment']) ->where('username', '[A-Za-z0-9_-]{3,20}') ->name('profile.comment'); // ── DASHBOARD ───────────────────────────────────────────────────────────────── Route::get('/dashboard', [DashboardController::class, 'index']) ->middleware(['auth', 'verified']) ->name('dashboard'); Route::middleware(['auth', 'creator.access'])->prefix('creator')->name('creator.')->group(function () { Route::get('/artworks', fn () => redirect()->route('studio.artworks'))->name('artworks'); Route::get('/analytics', fn () => redirect()->route('studio.analytics'))->name('analytics'); }); Route::middleware(['auth', \App\Http\Middleware\NoIndexDashboard::class])->prefix('dashboard')->name('dashboard.')->group(function () { Route::get('/artworks', [DashboardArtworkController::class, 'index'])->name('artworks.index'); Route::get('/artworks/{id}/edit', [DashboardArtworkController::class, 'edit'])->whereNumber('id')->name('artworks.edit'); Route::put('/artworks/{id}', [DashboardArtworkController::class, 'update'])->whereNumber('id')->name('artworks.update'); Route::delete('/artworks/{id}', [DashboardArtworkController::class, 'destroy'])->whereNumber('id')->name('artworks.destroy'); Route::get('/favorites', [\App\Http\Controllers\Dashboard\FavoriteController::class, 'index'])->name('favorites'); Route::delete('/favorites/{artwork}', [\App\Http\Controllers\Dashboard\FavoriteController::class, 'destroy'])->name('favorites.destroy'); Route::get('/followers', [\App\Http\Controllers\Dashboard\FollowerController::class, 'index'])->name('followers'); Route::get('/following', [\App\Http\Controllers\Dashboard\FollowingController::class, 'index'])->name('following'); Route::get('/comments', [\App\Http\Controllers\Dashboard\CommentController::class, 'index'])->name('comments'); Route::get('/gallery', [\App\Http\Controllers\Dashboard\DashboardGalleryController::class, 'index'])->name('gallery'); Route::get('/awards', [\App\Http\Controllers\Dashboard\DashboardAwardsController::class, 'index'])->name('awards'); }); // Canonical dashboard profile / settings Route::middleware(['auth'])->get('/dashboard/profile', [ProfileController::class, 'editSettings'])->name('dashboard.profile'); Route::middleware(['auth'])->get('/settings/profile', [ProfileController::class, 'editSettings'])->name('settings.profile'); // ── STUDIO Pro (/studio/*) ──────────────────────────────────────────────────── Route::middleware(['auth', 'ensure.onboarding.complete'])->prefix('studio')->name('studio.')->group(function () { Route::get('/', [StudioController::class, 'index'])->name('index'); Route::get('/artworks', [StudioController::class, 'artworks'])->name('artworks'); Route::get('/artworks/drafts', [StudioController::class, 'drafts'])->name('drafts'); Route::get('/artworks/archived', [StudioController::class, 'archived'])->name('archived'); Route::get('/artworks/{id}/edit', [StudioController::class, 'edit'])->whereNumber('id')->name('artworks.edit'); Route::get('/artworks/{id}/analytics', [StudioController::class, 'analytics'])->whereNumber('id')->name('artworks.analytics'); Route::get('/analytics', [StudioController::class, 'analyticsOverview'])->name('analytics'); }); // ── SETTINGS / PROFILE EDIT ─────────────────────────────────────────────────── Route::middleware(['auth', 'normalize.username', 'ensure.onboarding.complete'])->group(function () { Route::get('/profile', fn () => redirect()->route('dashboard.profile', [], 301))->name('legacy.profile.redirect'); Route::get('/settings', [ProfileController::class, 'edit'])->name('settings'); Route::get('/profile/edit', [ProfileController::class, 'edit'])->name('profile.edit'); Route::match(['post','put','patch'], '/profile', [ProfileController::class, 'update'])->name('profile.update'); Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy'); Route::match(['post', 'put'], '/profile/password', [ProfileController::class, 'password'])->name('profile.password'); Route::post('/avatar/upload', [AvatarController::class, 'upload'])->middleware('throttle:20,1')->name('avatar.upload'); Route::post('/settings/profile/update', [ProfileController::class, 'updateProfileSection'])->middleware('forum.bot.protection:profile_update')->name('settings.profile.update'); Route::post('/settings/account/username', [ProfileController::class, 'updateUsername'])->middleware('forum.bot.protection:profile_update')->name('settings.account.username'); Route::post('/settings/account/update', [ProfileController::class, 'updateAccountSection'])->middleware('forum.bot.protection:profile_update')->name('settings.account.update'); Route::post('/settings/email/request', [ProfileController::class, 'requestEmailChange']) ->middleware('throttle:email-change-request') ->name('settings.email.request'); Route::post('/settings/email/verify', [ProfileController::class, 'verifyEmailChange']) ->middleware('throttle:10,1') ->name('settings.email.verify'); Route::post('/settings/personal/update', [ProfileController::class, 'updatePersonalSection'])->middleware('forum.bot.protection:profile_update')->name('settings.personal.update'); Route::post('/settings/notifications/update', [ProfileController::class, 'updateNotificationsSection'])->middleware('forum.bot.protection:profile_update')->name('settings.notifications.update'); Route::post('/settings/security/password', [ProfileController::class, 'updateSecurityPassword'])->middleware('forum.bot.protection:profile_update')->name('settings.security.password'); }); // ── UPLOAD ──────────────────────────────────────────────────────────────────── Route::middleware(['auth', 'ensure.onboarding.complete'])->group(function () { Route::get('/upload', function () { $contentTypes = ContentType::with(['rootCategories.children'])->get()->map(function ($ct) { return [ 'id' => $ct->id, 'name' => $ct->name, 'categories' => $ct->rootCategories->map(function ($c) { return [ 'id' => $c->id, 'name' => $c->name, 'children' => $c->children->map(function ($ch) { return ['id' => $ch->id, 'name' => $ch->name]; })->values()->all(), ]; })->values()->all(), ]; })->values()->all(); return Inertia::render('Upload/Index', [ 'draftId' => null, 'content_types' => $contentTypes, 'suggested_tags' => [], 'filesCdnUrl' => config('cdn.files_url'), 'chunkSize' => (int) config('uploads.chunk.max_bytes', 5242880), 'feature_flags' => [ 'uploads_v2' => (bool) config('features.uploads_v2', false), ], ]); })->name('upload'); Route::get('/upload/draft/{id}', function (string $id) { $contentTypes = ContentType::with(['rootCategories.children'])->get()->map(function ($ct) { return [ 'id' => $ct->id, 'name' => $ct->name, 'categories' => $ct->rootCategories->map(function ($c) { return [ 'id' => $c->id, 'name' => $c->name, 'children' => $c->children->map(function ($ch) { return ['id' => $ch->id, 'name' => $ch->name]; })->values()->all(), ]; })->values()->all(), ]; })->values()->all(); return Inertia::render('Upload/Index', [ 'draftId' => $id, 'content_types' => $contentTypes, 'suggested_tags' => [], 'filesCdnUrl' => config('cdn.files_url'), 'chunkSize' => (int) config('uploads.chunk.max_bytes', 5242880), 'feature_flags' => [ 'uploads_v2' => (bool) config('features.uploads_v2', false), ], ]); })->whereUuid('id')->name('upload.draft'); }); // ── AUTH ────────────────────────────────────────────────────────────────────── require __DIR__.'/auth.php'; // ── LEGACY ROUTES ───────────────────────────────────────────────────────────── require __DIR__.'/legacy.php'; // ── SEARCH ──────────────────────────────────────────────────────────────────── Route::get('/search', [\App\Http\Controllers\Web\SearchController::class, 'index'])->name('search'); // ── MISC ────────────────────────────────────────────────────────────────────── Route::view('/data-deletion', 'privacy.data-deletion')->name('privacy.data_deletion'); Route::view('/blank', 'blank')->name('blank'); // ── ADMIN ───────────────────────────────────────────────────────────────────── Route::middleware(['auth'])->prefix('admin')->name('admin.')->group(function () { Route::get('uploads/moderation', fn () => Inertia::render('Admin/UploadQueue')) ->middleware('admin.moderation') ->name('uploads.moderation'); Route::get('usernames/moderation', fn () => Inertia::render('Admin/UsernameQueue')) ->middleware('admin.moderation') ->name('usernames.moderation'); Route::resource('artworks', \App\Http\Controllers\Admin\ArtworkController::class)->except(['show']); Route::get('reports', fn () => view('admin.reports.queue')) ->middleware('admin.moderation') ->name('reports.queue'); Route::get('reports/tags', [\App\Http\Controllers\Admin\TagInteractionReportController::class, 'index']) ->middleware('admin.moderation') ->name('reports.tags'); Route::middleware('admin.moderation')->prefix('early-growth')->name('early-growth.')->group(function () { Route::get('/', [\App\Http\Controllers\Admin\EarlyGrowthAdminController::class, 'index'])->name('index'); Route::delete('/cache', [\App\Http\Controllers\Admin\EarlyGrowthAdminController::class, 'flushCache'])->name('cache.flush'); Route::get('/status', [\App\Http\Controllers\Admin\EarlyGrowthAdminController::class, 'status'])->name('status'); }); Route::middleware('admin.moderation')->prefix('stories')->name('stories.')->group(function () { Route::get('/review', [\App\Http\Controllers\Admin\StoryAdminController::class, 'review'])->name('review'); Route::get('/', [\App\Http\Controllers\Admin\StoryAdminController::class, 'index'])->name('index'); Route::get('/create', [\App\Http\Controllers\Admin\StoryAdminController::class, 'create'])->name('create'); Route::post('/', [\App\Http\Controllers\Admin\StoryAdminController::class, 'store'])->name('store'); Route::get('/{story}', [\App\Http\Controllers\Admin\StoryAdminController::class, 'show'])->name('show'); Route::post('/{story}/approve', [\App\Http\Controllers\Admin\StoryAdminController::class, 'approve'])->name('approve'); Route::post('/{story}/reject', [\App\Http\Controllers\Admin\StoryAdminController::class, 'reject'])->name('reject'); Route::get('/{story}/edit', [\App\Http\Controllers\Admin\StoryAdminController::class, 'edit'])->name('edit'); Route::put('/{story}', [\App\Http\Controllers\Admin\StoryAdminController::class, 'update'])->name('update'); Route::delete('/{story}', [\App\Http\Controllers\Admin\StoryAdminController::class, 'destroy'])->name('destroy'); Route::post('/{story}/publish', [\App\Http\Controllers\Admin\StoryAdminController::class, 'publish'])->name('publish'); Route::get('/moderation/comments', [\App\Http\Controllers\Admin\StoryAdminController::class, 'moderateComments'])->name('comments.moderation'); }); }); // ── MESSAGES ────────────────────────────────────────────────────────────────── Route::middleware(['auth', 'ensure.onboarding.complete']) ->get('/messages/attachments/{id}', [\App\Http\Controllers\Api\Messaging\AttachmentController::class, 'show']) ->whereNumber('id') ->name('messages.attachments.show'); Route::middleware(['auth', 'ensure.onboarding.complete'])->prefix('messages')->name('messages.')->group(function () { Route::get('/', [\App\Http\Controllers\Messaging\MessagesPageController::class, 'index'])->name('index'); Route::get('/{id}', [\App\Http\Controllers\Messaging\MessagesPageController::class, 'show'])->whereNumber('id')->name('show'); }); // ── COMMUNITY ACTIVITY ──────────────────────────────────────────────────────── Route::match(['get', 'post'], '/community/chat', [\App\Http\Controllers\Community\ChatController::class, 'index']) ->name('community.chat'); Route::get('/community/activity', [\App\Http\Controllers\Web\CommunityActivityController::class, 'index']) ->name('community.activity'); // ── FEEDS ───────────────────────────────────────────────────────────────────── Route::middleware(['auth', 'ensure.onboarding.complete']) ->get('/feed/following', [\App\Http\Controllers\Web\Posts\FollowingFeedController::class, 'index']) ->name('feed.following'); Route::get('/feed/trending', [\App\Http\Controllers\Web\Posts\TrendingFeedController::class, 'index']) ->name('feed.trending'); Route::middleware(['auth']) ->get('/feed/saved', [\App\Http\Controllers\Web\Posts\SavedFeedController::class, 'index']) ->name('feed.saved'); Route::get('/feed/search', [\App\Http\Controllers\Web\Posts\SearchFeedController::class, 'index']) ->name('feed.search'); // ── CONTENT BROWSER (artwork / category universal router) ───────────────────── // Bind the artwork route parameter by slug when possible, but don't hard-fail. // Some URLs that match this shape are actually nested category paths such as // /skins/audio/blazemedia-pro, which should fall through to category handling. Route::bind('artwork', function ($value) { return Artwork::where('slug', $value)->first(); }); Route::get('/{contentTypeSlug}/{categoryPath}/{artwork}', [BrowseGalleryController::class, 'showArtwork']) ->where('contentTypeSlug', 'photography|wallpapers|skins|other') ->where('categoryPath', '[^/]+(?:/[^/]+)*') ->name('artworks.show'); Route::get('/{contentTypeSlug}/{path?}', [BrowseGalleryController::class, 'content']) ->where('contentTypeSlug', 'photography|wallpapers|skins|other') ->where('path', '.*') ->name('content.route'); // ── FALLBACK 404 — must be last ─────────────────────────────────────────────── Route::fallback(function (\Illuminate\Http\Request $request) { return app(\App\Http\Controllers\Web\ErrorController::class)->handleNotFound($request); })->name('404.fallback');