optimizations

This commit is contained in:
2026-03-28 19:15:39 +01:00
parent 0b25d9570a
commit cab4fbd83e
509 changed files with 1016804 additions and 1605 deletions

View File

@@ -55,6 +55,11 @@ Route::middleware(['web', 'throttle:300,1'])
->whereNumber('id')
->name('api.art.similar');
Route::middleware(['web', 'throttle:120,1'])
->get('art/{id}/similar-ai', \App\Http\Controllers\Api\SimilarAiArtworksController::class)
->whereNumber('id')
->name('api.art.similar-ai');
Route::middleware(['web', 'throttle:5,10'])
->post('art/{id}/view', \App\Http\Controllers\Api\ArtworkViewController::class)
->middleware('forum.bot.protection:api_write')
@@ -75,6 +80,11 @@ Route::middleware(['web', 'throttle:social-read'])
->get('activity', [\App\Http\Controllers\Api\SocialActivityController::class, 'index'])
->name('api.activity');
Route::middleware(['web', 'throttle:social-read'])
->get('profile/{username}/activity', \App\Http\Controllers\Api\ProfileActivityController::class)
->where('username', '[A-Za-z0-9_-]{3,20}')
->name('api.profile.activity');
Route::middleware(['web', 'throttle:social-read'])
->get('comments', [\App\Http\Controllers\Api\SocialCompatibilityController::class, 'comments'])
->name('api.social.comments.index');
@@ -110,6 +120,11 @@ Route::middleware(['web', 'auth'])->prefix('studio')->name('api.studio.')->group
Route::put('artworks/{id}', [\App\Http\Controllers\Studio\StudioArtworksApiController::class, 'update'])->whereNumber('id')->name('artworks.update');
Route::post('artworks/{id}/toggle', [\App\Http\Controllers\Studio\StudioArtworksApiController::class, 'toggle'])->whereNumber('id')->name('artworks.toggle');
Route::get('artworks/{id}/analytics', [\App\Http\Controllers\Studio\StudioArtworksApiController::class, 'analytics'])->whereNumber('id')->name('artworks.analytics');
Route::get('artworks/{id}/ai', [\App\Http\Controllers\Studio\StudioArtworkAiAssistApiController::class, 'show'])->whereNumber('id')->name('artworks.ai.show');
Route::post('artworks/{id}/ai/analyze', [\App\Http\Controllers\Studio\StudioArtworkAiAssistApiController::class, 'analyze'])->whereNumber('id')->name('artworks.ai.analyze');
Route::post('artworks/{id}/ai/apply', [\App\Http\Controllers\Studio\StudioArtworkAiAssistApiController::class, 'apply'])->whereNumber('id')->name('artworks.ai.apply');
Route::post('artworks/{id}/ai/events', [\App\Http\Controllers\Studio\StudioArtworkAiAssistApiController::class, 'event'])->whereNumber('id')->name('artworks.ai.events');
Route::post('artworks/{id}/ai/regenerate', [\App\Http\Controllers\Studio\StudioArtworkAiAssistApiController::class, 'regenerate'])->whereNumber('id')->name('artworks.ai.regenerate');
Route::post('artworks/{id}/replace-file', [\App\Http\Controllers\Studio\StudioArtworksApiController::class, 'replaceFile'])->whereNumber('id')->name('artworks.replaceFile');
// Versioning
Route::get('artworks/{id}/versions', [\App\Http\Controllers\Studio\StudioArtworksApiController::class, 'versions'])->whereNumber('id')->name('artworks.versions');
@@ -117,6 +132,192 @@ Route::middleware(['web', 'auth'])->prefix('studio')->name('api.studio.')->group
Route::get('tags/search', [\App\Http\Controllers\Studio\StudioArtworksApiController::class, 'searchTags'])->name('tags.search');
});
Route::middleware(['web', 'auth'])->prefix('cards')->name('api.cards.')->group(function () {
Route::post('{id}/like', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'like'])
->middleware(['throttle:nova-cards-render', 'forum.bot.protection:api_write'])
->whereNumber('id')
->name('like.alias.store');
Route::delete('{id}/like', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'unlike'])
->whereNumber('id')
->name('like.alias.destroy');
Route::post('{id}/save', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'save'])
->middleware(['throttle:nova-cards-render', 'forum.bot.protection:api_write'])
->whereNumber('id')
->name('save.alias.store');
Route::post('{id}/remix', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'remix'])
->middleware(['throttle:nova-cards-publish', 'forum.bot.protection:api_write'])
->whereNumber('id')
->name('remix.alias.store');
Route::post('drafts', [\App\Http\Controllers\Api\NovaCards\NovaCardDraftController::class, 'store'])
->middleware(['throttle:nova-cards-drafts', 'forum.bot.protection:api_write'])
->name('drafts.store');
Route::get('drafts/{id}', [\App\Http\Controllers\Api\NovaCards\NovaCardDraftController::class, 'show'])
->whereNumber('id')
->name('drafts.show');
Route::patch('drafts/{id}', [\App\Http\Controllers\Api\NovaCards\NovaCardDraftController::class, 'update'])
->middleware(['throttle:nova-cards-autosave', 'forum.bot.protection:api_write'])
->whereNumber('id')
->name('drafts.update');
Route::post('drafts/{id}/autosave', [\App\Http\Controllers\Api\NovaCards\NovaCardDraftController::class, 'autosave'])
->middleware(['throttle:nova-cards-autosave', 'forum.bot.protection:api_write'])
->whereNumber('id')
->name('drafts.autosave');
Route::post('drafts/{id}/background', [\App\Http\Controllers\Api\NovaCards\NovaCardDraftController::class, 'background'])
->middleware(['throttle:nova-cards-background-upload', 'forum.bot.protection:api_write'])
->whereNumber('id')
->name('drafts.background');
Route::post('drafts/{id}/render', [\App\Http\Controllers\Api\NovaCards\NovaCardDraftController::class, 'render'])
->middleware(['throttle:nova-cards-render', 'forum.bot.protection:api_write'])
->whereNumber('id')
->name('drafts.render');
Route::post('drafts/{id}/publish', [\App\Http\Controllers\Api\NovaCards\NovaCardDraftController::class, 'publish'])
->middleware(['throttle:nova-cards-publish', 'forum.bot.protection:api_write'])
->whereNumber('id')
->name('drafts.publish');
Route::delete('drafts/{id}', [\App\Http\Controllers\Api\NovaCards\NovaCardDraftController::class, 'destroy'])
->whereNumber('id')
->name('drafts.destroy');
Route::get('collections', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'collections'])
->name('collections.index');
Route::post('collections', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'storeCollection'])
->middleware(['throttle:nova-cards-drafts', 'forum.bot.protection:api_write'])
->name('collections.store');
Route::patch('collections/{id}', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'updateCollection'])
->middleware(['throttle:nova-cards-drafts', 'forum.bot.protection:api_write'])
->whereNumber('id')
->name('collections.update');
Route::post('collections/{id}/items', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'storeCollectionItem'])
->middleware(['throttle:nova-cards-drafts', 'forum.bot.protection:api_write'])
->whereNumber('id')
->name('collections.items.store');
Route::delete('collections/{id}/items/{cardId}', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'destroyCollectionItem'])
->whereNumber('id')
->whereNumber('cardId')
->name('collections.items.destroy');
Route::get('challenges', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'challenges'])
->name('challenges.index');
Route::get('assets', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'assets'])
->name('assets.index');
Route::get('templates', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'templates'])
->name('templates.index');
Route::post('like/{id}', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'like'])
->middleware(['throttle:nova-cards-render', 'forum.bot.protection:api_write'])
->whereNumber('id')
->name('like');
Route::delete('like/{id}', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'unlike'])
->whereNumber('id')
->name('unlike');
Route::post('favorite/{id}', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'favorite'])
->middleware(['throttle:nova-cards-render', 'forum.bot.protection:api_write'])
->whereNumber('id')
->name('favorite');
Route::delete('favorite/{id}', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'unfavorite'])
->whereNumber('id')
->name('unfavorite');
Route::post('save/{id}', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'save'])
->middleware(['throttle:nova-cards-render', 'forum.bot.protection:api_write'])
->whereNumber('id')
->name('save');
Route::delete('save/{id}', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'unsave'])
->whereNumber('id')
->name('unsave');
Route::post('remix/{id}', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'remix'])
->middleware(['throttle:nova-cards-publish', 'forum.bot.protection:api_write'])
->whereNumber('id')
->name('remix');
Route::post('duplicate/{id}', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'duplicate'])
->middleware(['throttle:nova-cards-drafts', 'forum.bot.protection:api_write'])
->whereNumber('id')
->name('duplicate');
Route::get('drafts/{id}/versions', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'versions'])
->whereNumber('id')
->name('drafts.versions');
Route::post('drafts/{id}/restore/{versionId}', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'restoreVersion'])
->middleware(['throttle:nova-cards-autosave', 'forum.bot.protection:api_write'])
->whereNumber('id')
->whereNumber('versionId')
->name('drafts.restore');
Route::post('drafts/{id}/restore-version/{versionId}', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'restoreVersion'])
->middleware(['throttle:nova-cards-autosave', 'forum.bot.protection:api_write'])
->whereNumber('id')
->whereNumber('versionId')
->name('drafts.restore-version');
Route::post('challenges/{challengeId}/submit/{id}', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'submitChallenge'])
->middleware(['throttle:nova-cards-publish', 'forum.bot.protection:api_write'])
->whereNumber('challengeId')
->whereNumber('id')
->name('challenges.submit');
Route::post('challenges/{id}/submit', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'submitChallengeByChallenge'])
->middleware(['throttle:nova-cards-publish', 'forum.bot.protection:api_write'])
->whereNumber('id')
->name('challenges.submit.alias');
Route::post('challenges/{id}/submit-card', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'submitChallengeByChallenge'])
->middleware(['throttle:nova-cards-publish', 'forum.bot.protection:api_write'])
->whereNumber('id')
->name('challenges.submit-card');
// v3: Creator presets
Route::get('presets', [\App\Http\Controllers\Api\NovaCards\NovaCardPresetController::class, 'index'])
->name('presets.index');
Route::post('presets', [\App\Http\Controllers\Api\NovaCards\NovaCardPresetController::class, 'store'])
->middleware(['throttle:nova-cards-drafts', 'forum.bot.protection:api_write'])
->name('presets.store');
Route::patch('presets/{id}', [\App\Http\Controllers\Api\NovaCards\NovaCardPresetController::class, 'update'])
->middleware(['throttle:nova-cards-drafts', 'forum.bot.protection:api_write'])
->whereNumber('id')
->name('presets.update');
Route::delete('presets/{id}', [\App\Http\Controllers\Api\NovaCards\NovaCardPresetController::class, 'destroy'])
->whereNumber('id')
->name('presets.destroy');
Route::post('presets/capture/{cardId}', [\App\Http\Controllers\Api\NovaCards\NovaCardPresetController::class, 'captureFromCard'])
->middleware(['throttle:nova-cards-drafts', 'forum.bot.protection:api_write'])
->whereNumber('cardId')
->name('presets.capture');
Route::get('presets/{presetId}/apply/{cardId}', [\App\Http\Controllers\Api\NovaCards\NovaCardPresetController::class, 'applyToCard'])
->whereNumber('presetId')
->whereNumber('cardId')
->name('presets.apply');
// v3: Export requests
Route::post('{id}/export', [\App\Http\Controllers\Api\NovaCards\NovaCardExportController::class, 'store'])
->middleware(['throttle:nova-cards-render', 'forum.bot.protection:api_write'])
->whereNumber('id')
->name('export.store');
Route::get('exports/{exportId}', [\App\Http\Controllers\Api\NovaCards\NovaCardExportController::class, 'show'])
->whereNumber('exportId')
->name('exports.show');
// v3: AI-assist suggestions
Route::get('{id}/ai-suggest', [\App\Http\Controllers\Api\NovaCards\NovaCardDiscoveryController::class, 'suggest'])
->middleware(['throttle:nova-cards-render'])
->whereNumber('id')
->name('ai-suggest');
});
Route::middleware(['web', 'throttle:60,1'])->prefix('cards')->name('api.cards.')->group(function () {
Route::get('{id}/lineage', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'lineage'])
->whereNumber('id')
->name('lineage.alias');
Route::get('lineage/{id}', [\App\Http\Controllers\Api\NovaCards\NovaCardInteractionController::class, 'lineage'])
->whereNumber('id')
->name('lineage');
Route::post('share/{id}', [\App\Http\Controllers\Api\NovaCards\NovaCardEngagementController::class, 'share'])
->middleware('forum.bot.protection:api_write')
->whereNumber('id')
->name('share');
Route::post('download/{id}', [\App\Http\Controllers\Api\NovaCards\NovaCardEngagementController::class, 'download'])
->middleware('forum.bot.protection:api_write')
->whereNumber('id')
->name('download');
// v3: Discovery feeds (public, cached)
Route::get('rising', [\App\Http\Controllers\Api\NovaCards\NovaCardDiscoveryController::class, 'rising'])
->name('rising');
Route::get('{id}/related', [\App\Http\Controllers\Api\NovaCards\NovaCardDiscoveryController::class, 'related'])
->whereNumber('id')
->name('related');
});
Route::prefix('v1')->name('api.v1.')->group(function () {
// Public browse feed (authoritative tables only)
Route::get('browse', [\App\Http\Controllers\Api\BrowseController::class, 'index'])
@@ -216,6 +417,12 @@ Route::middleware(['web', 'auth', 'normalize.username'])->prefix('uploads')->nam
->name('vision-suggest');
});
Route::middleware(['web', 'auth'])->prefix('search')->name('api.search.')->group(function () {
Route::post('image', \App\Http\Controllers\Api\ImageSearchController::class)
->middleware(['throttle:20,1', 'forum.bot.protection:api_write'])
->name('image');
});
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');
@@ -233,9 +440,23 @@ Route::middleware(['web', 'auth', 'admin.moderation'])->prefix('admin/reports')-
Route::get('queue', [\App\Http\Controllers\Api\Admin\ModerationReportQueueController::class, 'index'])
->name('queue');
Route::patch('{report}', [\App\Http\Controllers\Api\Admin\ModerationReportQueueController::class, 'update'])
->whereNumber('report')
->name('update');
Route::post('{report}/moderate-target', [\App\Http\Controllers\Api\Admin\ModerationReportQueueController::class, 'moderateTarget'])
->whereNumber('report')
->name('moderate-target');
Route::get('feed-engine-decision', [\App\Http\Controllers\Api\Admin\FeedEngineDecisionController::class, 'index'])
->name('feed-engine-decision');
Route::get('similar-artworks', [\App\Http\Controllers\Api\Admin\SimilarArtworkReportController::class, 'index'])
->name('similar-artworks');
Route::get('discovery-feedback', [\App\Http\Controllers\Api\Admin\DiscoveryFeedbackReportController::class, 'index'])
->name('discovery-feedback');
Route::get('feed-performance', [\App\Http\Controllers\Api\Admin\FeedPerformanceReportController::class, 'index'])
->name('feed-performance');
@@ -256,6 +477,20 @@ Route::middleware(['web', 'auth', 'admin.moderation'])->prefix('admin/usernames'
->name('reject');
});
Route::middleware(['web', 'auth', 'admin.moderation'])->prefix('admin/collections')->name('api.admin.collections.')->group(function () {
Route::patch('{collection}/moderation', [\App\Http\Controllers\Api\Admin\CollectionModerationController::class, 'updateModeration'])
->name('moderation.update');
Route::patch('{collection}/interactions', [\App\Http\Controllers\Api\Admin\CollectionModerationController::class, 'updateInteractions'])
->name('interactions.update');
Route::post('{collection}/unfeature', [\App\Http\Controllers\Api\Admin\CollectionModerationController::class, 'unfeature'])
->name('unfeature');
Route::delete('{collection}/members/{member}', [\App\Http\Controllers\Api\Admin\CollectionModerationController::class, 'destroyMember'])
->name('members.destroy');
});
Route::post('analytics/similar-artworks', [\App\Http\Controllers\Api\SimilarArtworkAnalyticsController::class, 'store'])
->middleware('throttle:uploads-status')
->name('api.analytics.similar-artworks.store');
@@ -272,6 +507,22 @@ Route::middleware(['web', 'auth', 'normalize.username'])->prefix('discovery')->n
Route::post('events', [\App\Http\Controllers\Api\DiscoveryEventController::class, 'store'])
->middleware(['throttle:uploads-status', 'forum.bot.protection:api_write'])
->name('events.store');
Route::post('feedback/hide-artwork', [\App\Http\Controllers\Api\DiscoveryNegativeSignalController::class, 'hideArtwork'])
->middleware(['throttle:uploads-status', 'forum.bot.protection:api_write'])
->name('feedback.hide-artwork');
Route::delete('feedback/hide-artwork', [\App\Http\Controllers\Api\DiscoveryNegativeSignalController::class, 'unhideArtwork'])
->middleware(['throttle:uploads-status', 'forum.bot.protection:api_write'])
->name('feedback.unhide-artwork');
Route::post('feedback/dislike-tag', [\App\Http\Controllers\Api\DiscoveryNegativeSignalController::class, 'dislikeTag'])
->middleware(['throttle:uploads-status', 'forum.bot.protection:api_write'])
->name('feedback.dislike-tag');
Route::delete('feedback/dislike-tag', [\App\Http\Controllers\Api\DiscoveryNegativeSignalController::class, 'undislikeTag'])
->middleware(['throttle:uploads-status', 'forum.bot.protection:api_write'])
->name('feedback.undislike-tag');
});
// ─── Artwork Search (Meilisearch-powered, public) ────────────────────────────
@@ -440,26 +691,32 @@ Route::middleware(['web', 'auth', 'normalize.username', 'throttle:30,1'])
->name('tags');
});
Route::middleware(['web', 'auth', 'throttle:social-read'])
->get('users/suggestions', \App\Http\Controllers\Api\UserSuggestionsController::class)
->name('api.users.suggestions');
// ── Follow system ─────────────────────────────────────────────────────────────
// POST /api/user/{username}/follow → follow a user
// DELETE /api/user/{username}/follow → unfollow a user
// GET /api/user/{username}/followers → paginated followers (public)
// GET /api/user/{username}/following → paginated following (public)
Route::middleware(['web', 'throttle:60,1'])
Route::middleware(['web'])
->prefix('user')
->name('api.user.follow.')
->group(function () {
// Public: list followers / following
Route::get('{username}/followers', [\App\Http\Controllers\Api\FollowController::class, 'followers'])
->middleware('throttle:social-read')
->where('username', '[A-Za-z0-9_-]{3,20}')
->name('followers');
Route::get('{username}/following', [\App\Http\Controllers\Api\FollowController::class, 'following'])
->middleware('throttle:social-read')
->where('username', '[A-Za-z0-9_-]{3,20}')
->name('following');
// Auth-required: follow / unfollow
Route::middleware(['auth', 'normalize.username'])->group(function () {
Route::middleware(['auth', 'normalize.username', 'throttle:follow-write', 'forum.security.firewall:follow', 'forum.bot.protection:follow'])->group(function () {
Route::post('{username}/follow', [\App\Http\Controllers\Api\FollowController::class, 'follow'])
->where('username', '[A-Za-z0-9_-]{3,20}')
->name('follow');