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'); 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'); }); // Tag system (auth-protected; 404-only ownership checks handled in requests/controllers) Route::middleware(['web', 'auth', 'normalize.username'])->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'); }); 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'); });