Studio: make grid checkbox rectangular and commit table changes

This commit is contained in:
2026-03-01 08:43:48 +01:00
parent 211dc58884
commit e3ca845a6d
89 changed files with 7323 additions and 475 deletions

View File

@@ -44,6 +44,18 @@ Route::prefix('rank')->name('api.rank.')->middleware(['throttle:60,1'])->group(f
* GET /api/v1/artworks/{slug}
* GET /api/v1/categories/{slug}/artworks
*/
// ── Studio Pro API (authenticated) ─────────────────────────────────────────────
Route::middleware(['web', 'auth'])->prefix('studio')->name('api.studio.')->group(function () {
Route::get('artworks', [\App\Http\Controllers\Studio\StudioArtworksApiController::class, 'index'])->name('artworks.index');
Route::post('artworks/bulk', [\App\Http\Controllers\Studio\StudioArtworksApiController::class, 'bulk'])->name('artworks.bulk');
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::post('artworks/{id}/replace-file', [\App\Http\Controllers\Studio\StudioArtworksApiController::class, 'replaceFile'])->whereNumber('id')->name('artworks.replaceFile');
Route::get('tags/search', [\App\Http\Controllers\Studio\StudioArtworksApiController::class, 'searchTags'])->name('tags.search');
});
Route::prefix('v1')->name('api.v1.')->group(function () {
// Public browse feed (authoritative tables only)
Route::get('browse', [\App\Http\Controllers\Api\BrowseController::class, 'index'])

View File

@@ -64,6 +64,29 @@ Schedule::command('skinbase:prune-view-events --days=90')
->name('prune-view-events')
->withoutOverlapping();
// ── Similar Artworks (Hybrid Recommender) ──────────────────────────────────────
// Build co-occurrence pairs from favourites every 4 hours.
Schedule::job(new \App\Jobs\RecBuildItemPairsFromFavouritesJob())
->everyFourHours()
->name('rec-build-item-pairs')
->withoutOverlapping();
// Nightly: recompute tag, behavior, and hybrid similarity lists.
Schedule::job(new \App\Jobs\RecComputeSimilarByTagsJob())
->dailyAt('02:00')
->name('rec-compute-tags')
->withoutOverlapping();
Schedule::job(new \App\Jobs\RecComputeSimilarByBehaviorJob())
->dailyAt('02:15')
->name('rec-compute-behavior')
->withoutOverlapping();
Schedule::job(new \App\Jobs\RecComputeSimilarHybridJob())
->dailyAt('02:30')
->name('rec-compute-hybrid')
->withoutOverlapping();
// ── Ranking Engine V2 ──────────────────────────────────────────────────────────
// Recalculate ranking_score + engagement_velocity every 30 minutes.
// Also syncs V2 scores to rank_artwork_scores so list builds benefit.

View File

@@ -38,6 +38,7 @@ use Inertia\Inertia;
// ── DISCOVER routes (/discover/*) ─────────────────────────────────────────────
Route::prefix('discover')->name('discover.')->group(function () {
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');
@@ -236,6 +237,18 @@ Route::middleware(['auth', \App\Http\Middleware\NoIndexDashboard::class])->prefi
Route::get('/awards', [\App\Http\Controllers\Dashboard\DashboardAwardsController::class, 'index'])->name('awards');
});
// ── Studio Pro (Creator Artwork Manager) ────────────────────────────────────
use App\Http\Controllers\Studio\StudioController;
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');
});
Route::middleware(['auth', 'normalize.username', 'ensure.onboarding.complete'])->group(function () {
// Redirect legacy `/profile` edit path to canonical dashboard profile route.
Route::get('/profile', function () {