name('api.v1.')->group(function () { // Public browse feed (authoritative tables only) Route::get('browse', [\App\Http\Controllers\Api\BrowseController::class, 'index']) ->name('browse'); // Browse by content type + category path (slug-based) Route::get('browse/{contentTypeSlug}/{categoryPath}', [\App\Http\Controllers\Api\BrowseController::class, 'byCategoryPath']) ->where('contentTypeSlug', '[a-z0-9\-]+') ->where('categoryPath', '.+') ->name('browse.category'); // Browse by content type only (slug-based) Route::get('browse/{contentTypeSlug}', [\App\Http\Controllers\Api\BrowseController::class, 'byContentType']) ->where('contentTypeSlug', '[a-z0-9\-]+') ->name('browse.content_type'); // Public artwork by slug Route::get('artworks/{slug}', [\App\Http\Controllers\Api\ArtworkController::class, 'show']) ->where('slug', '[A-Za-z0-9\-]+') ->name('artworks.show'); // Category artworks (Category route-model binding uses slug) Route::get('categories/{category}/artworks', [\App\Http\Controllers\Api\ArtworkController::class, 'categoryArtworks']) ->name('categories.artworks'); // Personalized feed (auth required) Route::middleware(['web', 'auth'])->get('feed', [\App\Http\Controllers\Api\FeedController::class, 'index']) ->name('feed'); }); Route::middleware(['web', 'normalize.username', 'throttle:30,1']) ->get('username/availability', \App\Http\Controllers\Api\UsernameAvailabilityController::class) ->name('api.username.availability'); // Artwork navigation — prev/next neighbors for the fullscreen viewer Route::middleware(['throttle:60,1']) ->get('artworks/navigation/{id}', [\App\Http\Controllers\Api\ArtworkNavigationController::class, 'neighbors']) ->where('id', '[0-9]+') ->name('api.artworks.navigation'); // Artwork page data by ID — for client-side (no-reload) navigation Route::middleware(['throttle:60,1']) ->get('artworks/{id}/page', [\App\Http\Controllers\Api\ArtworkNavigationController::class, 'pageData']) ->where('id', '[0-9]+') ->name('api.artworks.page-data'); Route::middleware(['web', 'auth', 'normalize.username'])->prefix('artworks')->name('api.artworks.')->group(function () { Route::post('/', [\App\Http\Controllers\Api\ArtworkController::class, 'store']) ->name('store'); }); Route::middleware(['web', 'auth', 'normalize.username'])->prefix('uploads')->name('api.uploads.')->group(function () { Route::post('init', [\App\Http\Controllers\Api\UploadController::class, 'init']) ->middleware('throttle:uploads-init') ->name('init'); Route::post('preload', [\App\Http\Controllers\Api\UploadController::class, 'preload']) ->middleware('throttle:uploads-init') ->name('preload'); Route::post('{id}/autosave', [\App\Http\Controllers\Api\UploadController::class, 'autosave']) ->middleware('throttle:uploads-finish') ->name('autosave'); Route::post('{id}/publish', [\App\Http\Controllers\Api\UploadController::class, 'publish']) ->middleware('throttle:uploads-finish') ->name('publish'); Route::get('{id}/status', [\App\Http\Controllers\Api\UploadController::class, 'processingStatus']) ->middleware('throttle:uploads-status') ->name('processing-status'); Route::post('chunk', [\App\Http\Controllers\Api\UploadController::class, 'chunk']) ->middleware('throttle:uploads-init') ->name('chunk'); Route::post('finish', [\App\Http\Controllers\Api\UploadController::class, 'finish']) ->middleware('throttle:uploads-finish') ->name('finish'); Route::post('cancel', [\App\Http\Controllers\Api\UploadController::class, 'cancel']) ->middleware('throttle:uploads-finish') ->name('cancel'); Route::get('status/{id}', [\App\Http\Controllers\Api\UploadController::class, 'status']) ->middleware('throttle:uploads-status') ->name('status'); }); Route::middleware(['web', 'auth', 'admin.moderation'])->prefix('admin/uploads')->name('api.admin.uploads.')->group(function () { Route::get('pending', [\App\Http\Controllers\Api\Admin\UploadModerationController::class, 'pending']) ->name('pending'); Route::post('{id}/approve', [\App\Http\Controllers\Api\Admin\UploadModerationController::class, 'approve']) ->whereUuid('id') ->name('approve'); Route::post('{id}/reject', [\App\Http\Controllers\Api\Admin\UploadModerationController::class, 'reject']) ->whereUuid('id') ->name('reject'); }); Route::middleware(['web', 'auth', 'admin.moderation'])->prefix('admin/reports')->name('api.admin.reports.')->group(function () { Route::get('similar-artworks', [\App\Http\Controllers\Api\Admin\SimilarArtworkReportController::class, 'index']) ->name('similar-artworks'); Route::get('feed-performance', [\App\Http\Controllers\Api\Admin\FeedPerformanceReportController::class, 'index']) ->name('feed-performance'); }); Route::middleware(['web', 'auth', 'admin.moderation'])->prefix('admin/usernames')->name('api.admin.usernames.')->group(function () { Route::get('pending', [\App\Http\Controllers\Api\Admin\UsernameApprovalController::class, 'pending']) ->name('pending'); Route::post('{id}/approve', [\App\Http\Controllers\Api\Admin\UsernameApprovalController::class, 'approve']) ->whereNumber('id') ->name('approve'); Route::post('{id}/reject', [\App\Http\Controllers\Api\Admin\UsernameApprovalController::class, 'reject']) ->whereNumber('id') ->name('reject'); }); Route::post('analytics/similar-artworks', [\App\Http\Controllers\Api\SimilarArtworkAnalyticsController::class, 'store']) ->middleware('throttle:uploads-status') ->name('api.analytics.similar-artworks.store'); Route::middleware(['web', 'auth'])->post('analytics/feed', [\App\Http\Controllers\Api\FeedAnalyticsController::class, 'store']) ->middleware('throttle:uploads-status') ->name('api.analytics.feed.store'); Route::middleware(['web', 'auth', 'normalize.username'])->prefix('discovery')->name('api.discovery.')->group(function () { Route::post('events', [\App\Http\Controllers\Api\DiscoveryEventController::class, 'store']) ->middleware('throttle:uploads-status') ->name('events.store'); }); // ─── Artwork Search (Meilisearch-powered, public) ──────────────────────────── Route::prefix('search')->name('api.search.')->middleware(['web', 'throttle:60,1'])->group(function () { Route::get('artworks', [\App\Http\Controllers\Api\Search\ArtworkSearchController::class, 'index']) ->name('artworks'); Route::get('artworks/tag/{slug}', [\App\Http\Controllers\Api\Search\ArtworkSearchController::class, 'byTag']) ->where('slug', '[a-z0-9\-]+') ->name('artworks.tag'); Route::get('artworks/category/{cat}', [\App\Http\Controllers\Api\Search\ArtworkSearchController::class, 'byCategory']) ->where('cat', '[a-z0-9\-]+') ->name('artworks.category'); Route::get('artworks/related/{id}', [\App\Http\Controllers\Api\Search\ArtworkSearchController::class, 'related']) ->whereNumber('id') ->name('artworks.related'); }); // Tag search/popular: public endpoints (used by SearchBar for all visitors) Route::middleware(['web', 'throttle:60,1'])->prefix('tags')->name('api.tags.')->group(function () { Route::get('search', [\App\Http\Controllers\Api\TagController::class, 'search'])->name('search'); Route::get('popular', [\App\Http\Controllers\Api\TagController::class, 'popular'])->name('popular'); }); Route::middleware(['web', 'auth', 'normalize.username'])->prefix('artworks')->name('api.artworks.tags.')->group(function () { Route::get('{id}/tags', [\App\Http\Controllers\Api\ArtworkTagController::class, 'index'])->whereNumber('id')->name('index'); Route::post('{id}/tags', [\App\Http\Controllers\Api\ArtworkTagController::class, 'store'])->whereNumber('id')->name('store'); Route::put('{id}/tags', [\App\Http\Controllers\Api\ArtworkTagController::class, 'update'])->whereNumber('id')->name('update'); Route::delete('{id}/tags/{tag}', [\App\Http\Controllers\Api\ArtworkTagController::class, 'destroy'])->whereNumber('id')->name('destroy'); }); // Artwork Awards Route::middleware(['web', 'auth', 'normalize.username', 'throttle:20,1']) ->prefix('artworks') ->name('api.artworks.awards.') ->group(function () { Route::post('{id}/award', [\App\Http\Controllers\Api\ArtworkAwardController::class, 'store']) ->whereNumber('id')->name('store'); Route::put('{id}/award', [\App\Http\Controllers\Api\ArtworkAwardController::class, 'update']) ->whereNumber('id')->name('update'); Route::delete('{id}/award', [\App\Http\Controllers\Api\ArtworkAwardController::class, 'destroy']) ->whereNumber('id')->name('destroy'); }); Route::middleware(['web']) ->prefix('artworks') ->name('api.artworks.awards.show.') ->group(function () { Route::get('{id}/awards', [\App\Http\Controllers\Api\ArtworkAwardController::class, 'show'])->whereNumber('id')->name('show'); }); Route::middleware(['web', 'auth', 'normalize.username'])->group(function () { Route::post('artworks/{id}/favorite', [\App\Http\Controllers\Api\ArtworkInteractionController::class, 'favorite']) ->whereNumber('id') ->name('api.artworks.favorite'); Route::post('artworks/{id}/like', [\App\Http\Controllers\Api\ArtworkInteractionController::class, 'like']) ->whereNumber('id') ->name('api.artworks.like'); Route::post('artworks/{id}/report', [\App\Http\Controllers\Api\ArtworkInteractionController::class, 'report']) ->whereNumber('id') ->name('api.artworks.report'); Route::post('users/{id}/follow', [\App\Http\Controllers\Api\ArtworkInteractionController::class, 'follow']) ->whereNumber('id') ->name('api.users.follow'); });