Files
SkinbaseNova/routes/web.php

461 lines
30 KiB
PHP

<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\User\ProfileController;
use App\Models\ContentType;
use App\Http\Controllers\User\AvatarController;
use App\Http\Controllers\Dashboard\ArtworkController as DashboardArtworkController;
use App\Http\Controllers\Web\ArtworkPageController;
use App\Http\Controllers\ArtworkDownloadController;
use App\Http\Controllers\Web\BrowseGalleryController;
use App\Http\Controllers\Web\DiscoverController;
use App\Http\Controllers\Web\ExploreController;
use App\Http\Controllers\Web\BlogController;
use App\Http\Controllers\Web\PageController;
use App\Http\Controllers\StoryController;
use App\Http\Controllers\Web\HomeController;
use App\Http\Controllers\Web\FooterController;
use App\Http\Controllers\Web\StaffController;
use App\Http\Controllers\Web\RssFeedController;
use App\Http\Controllers\Web\ApplicationController;
use App\Http\Controllers\News\NewsController as FrontendNewsController;
use App\Http\Controllers\News\NewsRssController;
use App\Http\Controllers\RSS\GlobalFeedController;
use App\Http\Controllers\RSS\DiscoverFeedController;
use App\Http\Controllers\RSS\ExploreFeedController;
use App\Http\Controllers\RSS\TagFeedController;
use App\Http\Controllers\RSS\CreatorFeedController;
use App\Http\Controllers\RSS\BlogFeedController;
use App\Http\Controllers\Web\StaffApplicationAdminController;
use App\Http\Controllers\Studio\StudioController;
use App\Http\Controllers\DashboardController;
use App\Models\Artwork;
use Inertia\Inertia;
Route::get('/', [HomeController::class, 'index'])->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');
// ── 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');