fix(gallery): fill tall portrait cards to full block width with object-cover crop
- ArtworkCard: add w-full to nova-card-media, use absolute inset-0 on img so object-cover fills the max-height capped box instead of collapsing the width - MasonryGallery.css: add width:100% to media container, position img absolutely so top/bottom is cropped rather than leaving dark gaps - Add React MasonryGallery + ArtworkCard components and entry point - Add recommendation system: UserRecoProfile model/DTO/migration, SuggestedCreatorsController, SuggestedTagsController, Recommendation services, config/recommendations.php - SimilarArtworksController, DiscoverController, HomepageService updates - Update routes (api + web) and discover/for-you views - Refresh favicon assets, update vite.config.js
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Phase 1 recommendation engine: user preference cache table.
|
||||
*
|
||||
* Stores pre-computed tag/category/creator affinity profiles so that
|
||||
* the "For You" feed can be generated without heavy joins on every request.
|
||||
*
|
||||
* TTL enforcement: the application checks `updated_at` + config TTL to decide
|
||||
* whether to rebuild. A background job (or on-demand compute) refreshes stale rows.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('user_reco_profiles', function (Blueprint $table): void {
|
||||
$table->unsignedBigInteger('user_id')->primary();
|
||||
$table->json('top_tags_json')->nullable()->comment('Top tag slugs ordered by weighted score (up to 20)');
|
||||
$table->json('top_categories_json')->nullable()->comment('Top category slugs ordered by weight (up to 5)');
|
||||
$table->json('followed_creator_ids_json')->nullable()->comment('Followed creator IDs (up to 50)');
|
||||
$table->json('tag_weights_json')->nullable()->comment('Normalised tag slug → weight map');
|
||||
$table->json('category_weights_json')->nullable()->comment('Normalised category slug → weight map');
|
||||
$table->json('disliked_tag_ids_json')->nullable()->comment('Hidden/blocked tag slugs (future use)');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('user_id')
|
||||
->references('id')
|
||||
->on('users')
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->index('updated_at', 'urp_updated_at_idx');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('user_reco_profiles');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user