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:
@@ -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
|
||||
{
|
||||
|
||||
44
app/Models/ArtworkAward.php
Normal file
44
app/Models/ArtworkAward.php
Normal 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);
|
||||
}
|
||||
}
|
||||
40
app/Models/ArtworkAwardStat.php
Normal file
40
app/Models/ArtworkAwardStat.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ class ArtworkComment extends Model
|
||||
protected $table = 'artwork_comments';
|
||||
|
||||
protected $fillable = [
|
||||
'legacy_id',
|
||||
'artwork_id',
|
||||
'user_id',
|
||||
'content',
|
||||
|
||||
47
app/Models/ProfileComment.php
Normal file
47
app/Models/ProfileComment.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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
25
app/Models/TagSynonym.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
37
app/Models/UserFollower.php
Normal file
37
app/Models/UserFollower.php
Normal 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');
|
||||
}
|
||||
}
|
||||
30
app/Models/UserStatistic.php
Normal file
30
app/Models/UserStatistic.php
Normal 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');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user