feat: increase gallery grid from 4 to 5 columns per row on desktopfeat: increase gallery grid from 4 to 5 columns per row on desktop

This commit is contained in:
2026-02-25 19:11:23 +01:00
parent 5c97488e80
commit 0032aec02f
131 changed files with 15674 additions and 597 deletions

View File

@@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use App\Services\ThumbnailService;
use Illuminate\Support\Facades\DB;
use Laravel\Scout\Searchable;
/**
* App\Models\Artwork
@@ -23,7 +24,7 @@ use Illuminate\Support\Facades\DB;
*/
class Artwork extends Model
{
use HasFactory, SoftDeletes;
use HasFactory, SoftDeletes, Searchable;
protected $table = 'artworks';
@@ -173,6 +174,77 @@ class Artwork extends Model
return $this->hasMany(ArtworkFeature::class, 'artwork_id');
}
public function awards(): HasMany
{
return $this->hasMany(ArtworkAward::class);
}
public function awardStat(): HasOne
{
return $this->hasOne(ArtworkAwardStat::class);
}
/**
* Build the Meilisearch document for this artwork.
* Includes all fields required for search, filtering, sorting, and display.
*/
public function toSearchableArray(): array
{
$this->loadMissing(['user', 'tags', 'categories.contentType', 'stats', 'awardStat']);
$stat = $this->stats;
$awardStat = $this->awardStat;
// Orientation derived from pixel dimensions
$orientation = 'square';
if ($this->width && $this->height) {
if ($this->width > $this->height) {
$orientation = 'landscape';
} elseif ($this->height > $this->width) {
$orientation = 'portrait';
}
}
// Resolution string e.g. "1920x1080"
$resolution = ($this->width && $this->height)
? $this->width . 'x' . $this->height
: '';
// Primary category slug (first attached category)
$primaryCategory = $this->categories->first();
$category = $primaryCategory?->slug ?? '';
$content_type = $primaryCategory?->contentType?->slug ?? '';
// Tag slugs array
$tags = $this->tags->pluck('slug')->values()->all();
return [
'id' => $this->id,
'slug' => $this->slug,
'title' => $this->title,
'description' => (string) ($this->description ?? ''),
'author_id' => $this->user_id,
'author_name' => $this->user?->name ?? 'Skinbase',
'category' => $category,
'content_type' => $content_type,
'tags' => $tags,
'resolution' => $resolution,
'orientation' => $orientation,
'downloads' => (int) ($stat?->downloads ?? 0),
'likes' => (int) ($stat?->favorites ?? 0),
'views' => (int) ($stat?->views ?? 0),
'created_at' => $this->published_at?->toDateString() ?? $this->created_at?->toDateString() ?? '',
'is_public' => (bool) $this->is_public,
'is_approved' => (bool) $this->is_approved,
'awards' => [
'gold' => $awardStat?->gold_count ?? 0,
'silver' => $awardStat?->silver_count ?? 0,
'bronze' => $awardStat?->bronze_count ?? 0,
'score' => $awardStat?->score_total ?? 0,
],
];
}
// Scopes
public function scopePublic(Builder $query): Builder
{

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ArtworkAward extends Model
{
protected $table = 'artwork_awards';
protected $fillable = [
'artwork_id',
'user_id',
'medal',
'weight',
];
protected $casts = [
'artwork_id' => 'integer',
'user_id' => 'integer',
'weight' => 'integer',
];
public const MEDALS = ['gold', 'silver', 'bronze'];
public const WEIGHTS = [
'gold' => 3,
'silver' => 2,
'bronze' => 1,
];
public function artwork(): BelongsTo
{
return $this->belongsTo(Artwork::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ArtworkAwardStat extends Model
{
protected $table = 'artwork_award_stats';
public $primaryKey = 'artwork_id';
public $incrementing = false;
public $timestamps = false;
protected $fillable = [
'artwork_id',
'gold_count',
'silver_count',
'bronze_count',
'score_total',
'updated_at',
];
protected $casts = [
'artwork_id' => 'integer',
'gold_count' => 'integer',
'silver_count' => 'integer',
'bronze_count' => 'integer',
'score_total' => 'integer',
'updated_at' => 'datetime',
];
public function artwork(): BelongsTo
{
return $this->belongsTo(Artwork::class);
}
}

View File

@@ -18,6 +18,7 @@ class ArtworkComment extends Model
protected $table = 'artwork_comments';
protected $fillable = [
'legacy_id',
'artwork_id',
'user_id',
'content',

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ProfileComment extends Model
{
protected $table = 'profile_comments';
protected $fillable = [
'profile_user_id',
'author_user_id',
'body',
'is_active',
];
protected $casts = [
'is_active' => 'boolean',
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
/** Profile owner */
public function profileUser(): BelongsTo
{
return $this->belongsTo(User::class, 'profile_user_id');
}
/** Comment author */
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'author_user_id');
}
public function authorProfile(): BelongsTo
{
return $this->belongsTo(UserProfile::class, 'author_user_id', 'user_id');
}
/** Scope: only active (not removed) comments */
public function scopeActive($query)
{
return $query->where('is_active', true);
}
}

View File

@@ -7,6 +7,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
final class Tag extends Model
{
@@ -32,6 +33,11 @@ final class Tag extends Model
->withPivot(['source', 'confidence']);
}
public function synonyms(): HasMany
{
return $this->hasMany(TagSynonym::class);
}
public function getRouteKeyName(): string
{
return 'slug';

25
app/Models/TagSynonym.php Normal file
View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
final class TagSynonym extends Model
{
public $timestamps = false;
protected $table = 'tag_synonyms';
protected $fillable = [
'tag_id',
'synonym',
];
public function tag(): BelongsTo
{
return $this->belongsTo(Tag::class);
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -73,6 +74,38 @@ class User extends Authenticatable
return $this->hasOne(UserProfile::class, 'user_id');
}
public function statistics(): HasOne
{
return $this->hasOne(UserStatistic::class, 'user_id');
}
/** Users that follow this user */
public function followers(): BelongsToMany
{
return $this->belongsToMany(
User::class,
'user_followers',
'user_id',
'follower_id'
)->withPivot('created_at');
}
/** Users that this user follows */
public function following(): BelongsToMany
{
return $this->belongsToMany(
User::class,
'user_followers',
'follower_id',
'user_id'
)->withPivot('created_at');
}
public function profileComments(): HasMany
{
return $this->hasMany(ProfileComment::class, 'profile_user_id');
}
public function hasRole(string $role): bool
{
return strtolower((string) ($this->role ?? '')) === strtolower($role);

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class UserFollower extends Model
{
protected $table = 'user_followers';
public $timestamps = false;
protected $fillable = [
'user_id',
'follower_id',
];
protected $casts = [
'created_at' => 'datetime',
];
const CREATED_AT = 'created_at';
const UPDATED_AT = null;
/** The user being followed */
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
/** The user who is following */
public function follower(): BelongsTo
{
return $this->belongsTo(User::class, 'follower_id');
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class UserStatistic extends Model
{
protected $table = 'user_statistics';
protected $primaryKey = 'user_id';
public $incrementing = false;
protected $keyType = 'int';
protected $fillable = [
'user_id',
'uploads',
'downloads',
'pageviews',
'awards',
'profile_views',
];
public $timestamps = true;
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
}