feat: upload wizard refactor + vision AI tags + artwork versioning
Upload wizard:
- Refactored UploadWizard into modular steps (Step1FileUpload, Step2Details, Step3Publish)
- Extracted reusable hooks: useUploadMachine, useFileValidation, useVisionTags
- Extracted reusable components: CategorySelector, ContentTypeSelector
- Added TagPicker component (studio-style list picker with AI badge + new-tag insertion)
- Fixed TagInput auto-open bug (hasFocusedRef guard)
- Replaced TagInput with TagPicker in UploadSidebar
Vision AI tag suggestions:
- Add UploadVisionSuggestController: sync POST /api/uploads/{id}/vision-suggest
- Calls vision.klevze.net/analyze/all on upload completion (before step 2 opens)
- Two-phase useVisionTags: immediate gateway call + background DB polling
- Trigger fires on uploadReady (not step change) so tags arrive before user sees step 2
- Added vision.gateway config block with VISION_GATEWAY_URL env
Artwork versioning system:
- ArtworkVersion / ArtworkVersionEvent models
- ArtworkVersioningService: createNewVersion, restoreVersion, rate limiting, ranking decay
- Migrations: artwork_versions, artwork_version_events, versioning columns on artworks
- Studio API routes: GET versions, POST restore/{version_id}
- Feature tests: ArtworkVersioningTest (13 cases)
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('artwork_versions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('artwork_id')->index();
|
||||
|
||||
$table->unsignedInteger('version_number');
|
||||
$table->string('file_path');
|
||||
$table->string('file_hash', 64);
|
||||
$table->unsignedInteger('width')->nullable();
|
||||
$table->unsignedInteger('height')->nullable();
|
||||
$table->unsignedBigInteger('file_size')->nullable();
|
||||
$table->text('change_note')->nullable();
|
||||
$table->boolean('is_current')->default(false);
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('artwork_id', 'fk_artwork_versions_artwork')
|
||||
->references('id')->on('artworks')->cascadeOnDelete();
|
||||
|
||||
$table->unique(['artwork_id', 'version_number'], 'uq_artwork_version');
|
||||
$table->index(['artwork_id', 'is_current'], 'idx_artwork_version_current');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('artwork_versions');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('artworks', function (Blueprint $table) {
|
||||
// FK to the active version row (nullable – set after first version is created)
|
||||
$table->unsignedBigInteger('current_version_id')->nullable()->index()->after('hash');
|
||||
|
||||
// Running count of how many versions exist
|
||||
$table->unsignedInteger('version_count')->default(1)->after('current_version_id');
|
||||
|
||||
// Last time the file was replaced
|
||||
$table->timestamp('version_updated_at')->nullable()->after('version_count');
|
||||
|
||||
// Signals that a moderator must approve the new file
|
||||
$table->boolean('requires_reapproval')->default(false)->after('version_updated_at');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('artworks', function (Blueprint $table) {
|
||||
$table->dropIndex(['current_version_id']);
|
||||
$table->dropColumn([
|
||||
'current_version_id',
|
||||
'version_count',
|
||||
'version_updated_at',
|
||||
'requires_reapproval',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('artwork_version_events', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('artwork_id')->index();
|
||||
$table->unsignedBigInteger('user_id')->index();
|
||||
$table->string('action', 50); // create_version | restore_version
|
||||
$table->unsignedBigInteger('version_id')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('artwork_id', 'fk_avevt_artwork')
|
||||
->references('id')->on('artworks')->cascadeOnDelete();
|
||||
$table->foreign('user_id', 'fk_avevt_user')
|
||||
->references('id')->on('users')->cascadeOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('artwork_version_events');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user