Wire admin studio SSR and search infrastructure
This commit is contained in:
@@ -28,6 +28,7 @@ Route::middleware(['web', 'throttle:60,1'])->prefix('leaderboard')->name('api.le
|
||||
Route::get('artworks', [\App\Http\Controllers\Api\LeaderboardController::class, 'artworks'])->name('artworks');
|
||||
Route::get('groups', [\App\Http\Controllers\Api\LeaderboardController::class, 'groups'])->name('groups');
|
||||
Route::get('stories', [\App\Http\Controllers\Api\LeaderboardController::class, 'stories'])->name('stories');
|
||||
Route::get('worlds', [\App\Http\Controllers\Api\LeaderboardController::class, 'worlds'])->name('worlds');
|
||||
});
|
||||
|
||||
Route::middleware(['web', 'auth', 'creator.access'])->prefix('stories')->name('api.stories.')->group(function () {
|
||||
@@ -49,7 +50,7 @@ Route::middleware(['web', 'auth', 'normalize.username'])->prefix('profile/cover'
|
||||
|
||||
// ── Per-artwork signal tracking (public) ────────────────────────────────────
|
||||
// GET /api/art/{id}/similar → up to 12 similar artworks (Meilisearch)
|
||||
// POST /api/art/{id}/view → record a view (session-deduped, 5 per 10 min)
|
||||
// POST /api/art/{id}/view → record a page visit as a view
|
||||
// POST /api/art/{id}/download → record a download, returns file URL (10/min)
|
||||
Route::middleware(['web', 'throttle:300,1'])
|
||||
->get('art/{id}/similar', \App\Http\Controllers\Api\SimilarArtworksController::class)
|
||||
@@ -61,7 +62,7 @@ Route::middleware(['web', 'throttle:120,1'])
|
||||
->whereNumber('id')
|
||||
->name('api.art.similar-ai');
|
||||
|
||||
Route::middleware(['web', 'throttle:5,10'])
|
||||
Route::middleware(['web', 'throttle:120,1'])
|
||||
->post('art/{id}/view', \App\Http\Controllers\Api\ArtworkViewController::class)
|
||||
->middleware('forum.bot.protection:api_write')
|
||||
->whereNumber('id')
|
||||
@@ -86,6 +87,27 @@ Route::middleware(['web', 'throttle:social-read'])
|
||||
->where('username', '[A-Za-z0-9_-]{3,20}')
|
||||
->name('api.profile.activity');
|
||||
|
||||
Route::middleware(['web', 'throttle:social-read'])
|
||||
->get('profile/{username}/journey', \App\Http\Controllers\Api\ProfileJourneyController::class)
|
||||
->where('username', '[A-Za-z0-9_-]{3,20}')
|
||||
->name('api.profile.journey');
|
||||
|
||||
// Public profile AI biography (read, included in profile payload via journey endpoint)
|
||||
Route::middleware(['web', 'throttle:social-read'])
|
||||
->get('profile/{username}/ai-biography', [\App\Http\Controllers\Api\ProfileAiBiographyController::class, 'show'])
|
||||
->where('username', '[A-Za-z0-9_-]{3,20}')
|
||||
->name('api.profile.ai-biography');
|
||||
|
||||
// Creator-facing AI biography management (authenticated)
|
||||
Route::middleware(['web', 'auth'])->prefix('creator/profile/ai-biography')->name('api.creator.ai-biography.')->group(function () {
|
||||
Route::get('/', [\App\Http\Controllers\Api\AiBiographyController::class, 'status'])->name('status');
|
||||
Route::post('/generate', [\App\Http\Controllers\Api\AiBiographyController::class, 'generate'])->name('generate')->middleware('throttle:5,1');
|
||||
Route::post('/regenerate', [\App\Http\Controllers\Api\AiBiographyController::class, 'regenerate'])->name('regenerate')->middleware('throttle:5,1');
|
||||
Route::patch('/', [\App\Http\Controllers\Api\AiBiographyController::class, 'update'])->name('update')->middleware('throttle:20,1');
|
||||
Route::post('/hide', [\App\Http\Controllers\Api\AiBiographyController::class, 'hide'])->name('hide');
|
||||
Route::post('/show', [\App\Http\Controllers\Api\AiBiographyController::class, 'show'])->name('show');
|
||||
});
|
||||
|
||||
Route::middleware(['web', 'throttle:social-read'])
|
||||
->get('comments', [\App\Http\Controllers\Api\SocialCompatibilityController::class, 'comments'])
|
||||
->name('api.social.comments.index');
|
||||
@@ -117,6 +139,13 @@ Route::prefix('rank')->name('api.rank.')->middleware(['throttle:60,1'])->group(f
|
||||
// ── Studio Pro API (authenticated) ─────────────────────────────────────────────
|
||||
Route::middleware(['web', 'auth'])->prefix('studio')->name('api.studio.')->group(function () {
|
||||
Route::post('events', [\App\Http\Controllers\Studio\StudioEventsApiController::class, 'store'])->name('events.store');
|
||||
Route::get('upload-queue', [\App\Http\Controllers\Studio\StudioUploadQueueApiController::class, 'index'])->name('upload-queue.index');
|
||||
Route::post('upload-queue/batches', [\App\Http\Controllers\Studio\StudioUploadQueueApiController::class, 'store'])->name('upload-queue.store');
|
||||
Route::post('upload-queue/bulk', [\App\Http\Controllers\Studio\StudioUploadQueueApiController::class, 'bulk'])->name('upload-queue.bulk');
|
||||
Route::post('upload-queue/items/{id}/fail', [\App\Http\Controllers\Studio\StudioUploadQueueApiController::class, 'markFailed'])->whereNumber('id')->name('upload-queue.items.fail');
|
||||
Route::post('upload-queue/items/{id}/retry', [\App\Http\Controllers\Studio\StudioUploadQueueApiController::class, 'retry'])->whereNumber('id')->name('upload-queue.items.retry');
|
||||
Route::post('worlds/media/upload', [\App\Http\Controllers\Studio\StudioWorldMediaApiController::class, 'store'])->middleware(['throttle:20,1', 'forum.bot.protection:api_write'])->name('worlds.media.upload');
|
||||
Route::delete('worlds/media', [\App\Http\Controllers\Studio\StudioWorldMediaApiController::class, 'destroy'])->middleware(['throttle:20,1', 'forum.bot.protection:api_write'])->name('worlds.media.destroy');
|
||||
Route::put('preferences', [\App\Http\Controllers\Studio\StudioPreferencesApiController::class, 'updatePreferences'])->name('preferences.settings');
|
||||
Route::put('preferences/profile', [\App\Http\Controllers\Studio\StudioPreferencesApiController::class, 'updateProfile'])->name('preferences.profile');
|
||||
Route::put('preferences/featured', [\App\Http\Controllers\Studio\StudioPreferencesApiController::class, 'updateFeatured'])->name('preferences.featured');
|
||||
@@ -138,6 +167,7 @@ Route::middleware(['web', 'auth'])->prefix('studio')->name('api.studio.')->group
|
||||
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');
|
||||
Route::post('artworks/{id}/revise-media', [\App\Http\Controllers\Studio\StudioArtworksApiController::class, 'reviseMedia'])->whereNumber('id')->name('artworks.reviseMedia');
|
||||
// Versioning
|
||||
Route::get('artworks/{id}/versions', [\App\Http\Controllers\Studio\StudioArtworksApiController::class, 'versions'])->whereNumber('id')->name('artworks.versions');
|
||||
Route::post('artworks/{id}/restore/{version_id}', [\App\Http\Controllers\Studio\StudioArtworksApiController::class, 'restoreVersion'])->whereNumber('id')->whereNumber('version_id')->name('artworks.restoreVersion');
|
||||
|
||||
@@ -5,6 +5,7 @@ use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\User\ProfileController;
|
||||
use App\Models\ContentType;
|
||||
use App\Models\Group;
|
||||
use App\Models\World;
|
||||
use App\Http\Controllers\User\AvatarController;
|
||||
use App\Http\Controllers\Dashboard\ArtworkController as DashboardArtworkController;
|
||||
use App\Http\Controllers\Web\ArtworkPageController;
|
||||
@@ -20,6 +21,7 @@ use App\Http\Controllers\StoryController;
|
||||
use App\Http\Controllers\Web\HomeController;
|
||||
use App\Http\Controllers\Web\FooterController;
|
||||
use App\Http\Controllers\Web\BugReportController;
|
||||
use App\Http\Controllers\Web\WorldController;
|
||||
use App\Http\Controllers\RobotsTxtController;
|
||||
use App\Http\Controllers\SitemapController;
|
||||
use App\Http\Controllers\Web\StaffController;
|
||||
@@ -36,6 +38,7 @@ use App\Http\Controllers\RSS\CreatorFeedController;
|
||||
use App\Http\Controllers\RSS\BlogFeedController;
|
||||
use App\Http\Controllers\Studio\StudioNewsController;
|
||||
use App\Http\Controllers\Studio\StudioController;
|
||||
use App\Http\Controllers\Studio\StudioWorldController;
|
||||
use App\Http\Controllers\DashboardController;
|
||||
use App\Http\Controllers\Community\LatestController;
|
||||
use App\Http\Controllers\User\MembersController;
|
||||
@@ -113,6 +116,7 @@ Route::get('/help', \App\Http\Controllers\Web\HelpCenterPageController::class)->
|
||||
Route::get('/help/studio', \App\Http\Controllers\Web\StudioHelpPageController::class)->name('help.studio');
|
||||
Route::get('/help/upload', \App\Http\Controllers\Web\UploadHelpPageController::class)->name('help.upload');
|
||||
Route::get('/help/cards', \App\Http\Controllers\Web\CardsHelpPageController::class)->name('help.cards');
|
||||
Route::get('/help/worlds', \App\Http\Controllers\Web\WorldsHelpPageController::class)->name('help.worlds');
|
||||
Route::get('/help/profile', \App\Http\Controllers\Web\ProfileHelpPageController::class)->name('help.profile');
|
||||
Route::get('/help/auth', \App\Http\Controllers\Web\AuthHelpPageController::class)->name('help.auth');
|
||||
Route::get('/help/account', \App\Http\Controllers\Web\AccountHelpPageController::class)->name('help.account');
|
||||
@@ -278,6 +282,24 @@ Route::get('/collections/featured', [\App\Http\Controllers\Web\CollectionDiscove
|
||||
->name('collections.featured');
|
||||
Route::get('/collections/trending', [\App\Http\Controllers\Web\CollectionDiscoveryController::class, 'trending'])
|
||||
->name('collections.trending');
|
||||
Route::get('/worlds', [WorldController::class, 'index'])->name('worlds.index');
|
||||
Route::get('/worlds/create', function (Request $request) {
|
||||
$user = $request->user();
|
||||
|
||||
if (! $user) {
|
||||
return redirect()->guest(route('login'));
|
||||
}
|
||||
|
||||
if ($user->can('create', World::class)) {
|
||||
return redirect()->route('studio.worlds.create', $request->query(), 302);
|
||||
}
|
||||
|
||||
return redirect()->route('worlds.index', $request->query(), 302);
|
||||
})
|
||||
->name('worlds.create.redirect');
|
||||
Route::get('/worlds/{world:slug}', [WorldController::class, 'show'])
|
||||
->where('world', '^(?!create$)[a-z0-9]+(?:-[a-z0-9]+)*$')
|
||||
->name('worlds.show');
|
||||
Route::get('/collections/editorial', [\App\Http\Controllers\Web\CollectionDiscoveryController::class, 'editorial'])
|
||||
->name('collections.editorial');
|
||||
Route::get('/collections/community', [\App\Http\Controllers\Web\CollectionDiscoveryController::class, 'community'])
|
||||
@@ -426,6 +448,7 @@ Route::middleware(['auth', 'ensure.onboarding.complete'])->prefix('studio')->nam
|
||||
Route::get('/content', [StudioController::class, 'content'])->name('content');
|
||||
Route::get('/artworks', [StudioController::class, 'artworks'])->name('artworks');
|
||||
Route::get('/drafts', [StudioController::class, 'drafts'])->name('drafts');
|
||||
Route::get('/upload-queue', [StudioController::class, 'uploadQueue'])->name('upload-queue');
|
||||
Route::get('/scheduled', [StudioController::class, 'scheduled'])->name('scheduled');
|
||||
Route::get('/calendar', [StudioController::class, 'calendar'])->name('calendar');
|
||||
Route::get('/archived', [StudioController::class, 'archived'])->name('archived');
|
||||
@@ -471,6 +494,25 @@ Route::middleware(['auth', 'ensure.onboarding.complete'])->prefix('studio')->nam
|
||||
Route::post('/news/{article}/archive', [StudioNewsController::class, 'archive'])->whereNumber('article')->name('news.archive');
|
||||
Route::post('/news/{article}/feature', [StudioNewsController::class, 'feature'])->whereNumber('article')->name('news.feature');
|
||||
Route::post('/news/{article}/pin', [StudioNewsController::class, 'pin'])->whereNumber('article')->name('news.pin');
|
||||
Route::get('/worlds', [StudioWorldController::class, 'index'])->name('worlds.index');
|
||||
Route::get('/worlds/create', [StudioWorldController::class, 'create'])->name('worlds.create');
|
||||
Route::post('/worlds', [StudioWorldController::class, 'store'])->name('worlds.store');
|
||||
Route::get('/worlds/entity-search', [StudioWorldController::class, 'entitySearch'])->name('worlds.entity-search');
|
||||
Route::get('/worlds/{world}/preview', [StudioWorldController::class, 'preview'])->whereNumber('world')->name('worlds.preview');
|
||||
Route::get('/worlds/{world}/edit', [StudioWorldController::class, 'edit'])->whereNumber('world')->name('worlds.edit');
|
||||
Route::patch('/worlds/{world}', [StudioWorldController::class, 'update'])->whereNumber('world')->name('worlds.update');
|
||||
Route::post('/worlds/{world}/submissions/{submission}/approve', [StudioWorldController::class, 'approveSubmission'])->whereNumber('world')->whereNumber('submission')->name('worlds.submissions.approve');
|
||||
Route::post('/worlds/{world}/submissions/{submission}/remove', [StudioWorldController::class, 'removeSubmission'])->whereNumber('world')->whereNumber('submission')->name('worlds.submissions.remove');
|
||||
Route::post('/worlds/{world}/submissions/{submission}/block', [StudioWorldController::class, 'blockSubmission'])->whereNumber('world')->whereNumber('submission')->name('worlds.submissions.block');
|
||||
Route::post('/worlds/{world}/submissions/{submission}/unblock', [StudioWorldController::class, 'unblockSubmission'])->whereNumber('world')->whereNumber('submission')->name('worlds.submissions.unblock');
|
||||
Route::post('/worlds/{world}/submissions/{submission}/restore', [StudioWorldController::class, 'restoreSubmission'])->whereNumber('world')->whereNumber('submission')->name('worlds.submissions.restore');
|
||||
Route::post('/worlds/{world}/submissions/{submission}/feature', [StudioWorldController::class, 'featureSubmission'])->whereNumber('world')->whereNumber('submission')->name('worlds.submissions.feature');
|
||||
Route::post('/worlds/{world}/submissions/{submission}/unfeature', [StudioWorldController::class, 'unfeatureSubmission'])->whereNumber('world')->whereNumber('submission')->name('worlds.submissions.unfeature');
|
||||
Route::post('/worlds/{world}/submissions/{submission}/pending', [StudioWorldController::class, 'pendingSubmission'])->whereNumber('world')->whereNumber('submission')->name('worlds.submissions.pending');
|
||||
Route::post('/worlds/{world}/publish', [StudioWorldController::class, 'publish'])->whereNumber('world')->name('worlds.publish');
|
||||
Route::post('/worlds/{world}/archive', [StudioWorldController::class, 'archive'])->whereNumber('world')->name('worlds.archive');
|
||||
Route::post('/worlds/{world}/duplicate', [StudioWorldController::class, 'duplicate'])->whereNumber('world')->name('worlds.duplicate');
|
||||
Route::post('/worlds/{world}/new-edition', [StudioWorldController::class, 'newEdition'])->whereNumber('world')->name('worlds.new-edition');
|
||||
Route::get('/groups', [\App\Http\Controllers\Studio\GroupStudioController::class, 'index'])->name('groups.index');
|
||||
Route::get('/groups/create', [\App\Http\Controllers\Studio\GroupStudioController::class, 'create'])->name('groups.create');
|
||||
Route::post('/groups', [\App\Http\Controllers\Studio\GroupStudioController::class, 'store'])->name('groups.store');
|
||||
@@ -629,6 +671,15 @@ Route::middleware(['artwork.maturity.access'])->prefix('cp/maturity')->name('cp.
|
||||
Route::post('/{artwork:id}/review', [\App\Http\Controllers\Settings\ArtworkMaturityAdminController::class, 'review'])->whereNumber('artwork')->name('review');
|
||||
});
|
||||
|
||||
Route::middleware(['artwork.maturity.access'])->prefix('cp/ai-biography')->name('cp.ai-biography.')->group(function () {
|
||||
Route::get('/', [\App\Http\Controllers\Settings\AiBiographyAdminController::class, 'index'])->name('index');
|
||||
Route::post('/users/{user}/rebuild', [\App\Http\Controllers\Settings\AiBiographyAdminController::class, 'rebuild'])->whereNumber('user')->name('rebuild');
|
||||
Route::post('/records/{biography}/approve', [\App\Http\Controllers\Settings\AiBiographyAdminController::class, 'approve'])->whereNumber('biography')->name('approve');
|
||||
Route::post('/records/{biography}/flag', [\App\Http\Controllers\Settings\AiBiographyAdminController::class, 'flag'])->whereNumber('biography')->name('flag');
|
||||
Route::post('/records/{biography}/hide', [\App\Http\Controllers\Settings\AiBiographyAdminController::class, 'hide'])->whereNumber('biography')->name('hide');
|
||||
Route::post('/records/{biography}/show', [\App\Http\Controllers\Settings\AiBiographyAdminController::class, 'show'])->whereNumber('biography')->name('show');
|
||||
});
|
||||
|
||||
// ── 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');
|
||||
@@ -779,6 +830,7 @@ Route::middleware(['auth', 'ensure.onboarding.complete'])->group(function () {
|
||||
'draftId' => null,
|
||||
'content_types' => $contentTypes,
|
||||
'suggested_tags' => [],
|
||||
'eligible_worlds' => app(\App\Services\Worlds\WorldSubmissionService::class)->eligibleWorldOptions($user),
|
||||
'group_options' => $availableGroups,
|
||||
'contributor_options_by_group' => $contributorOptionsByGroup,
|
||||
'initial_group' => $initialGroupSlug !== '' ? $initialGroupSlug : null,
|
||||
@@ -838,6 +890,7 @@ Route::middleware(['auth', 'ensure.onboarding.complete'])->group(function () {
|
||||
'draftId' => $id,
|
||||
'content_types' => $contentTypes,
|
||||
'suggested_tags' => [],
|
||||
'eligible_worlds' => app(\App\Services\Worlds\WorldSubmissionService::class)->eligibleWorldOptions($user),
|
||||
'group_options' => $availableGroups,
|
||||
'contributor_options_by_group' => $contributorOptionsByGroup,
|
||||
'initial_group' => $initialGroupSlug !== '' ? $initialGroupSlug : null,
|
||||
|
||||
Reference in New Issue
Block a user