feat: add reusable gallery carousel and ranking feed infrastructure
This commit is contained in:
69
app/Models/RankArtworkScore.php
Normal file
69
app/Models/RankArtworkScore.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* App\Models\RankArtworkScore
|
||||
*
|
||||
* Materialised ranking scores for a single artwork.
|
||||
* Rebuilt hourly by RankComputeArtworkScoresJob.
|
||||
*
|
||||
* @property int $artwork_id
|
||||
* @property float $score_trending
|
||||
* @property float $score_new_hot
|
||||
* @property float $score_best
|
||||
* @property string $model_version
|
||||
* @property \Carbon\Carbon|null $computed_at
|
||||
*/
|
||||
class RankArtworkScore extends Model
|
||||
{
|
||||
protected $table = 'rank_artwork_scores';
|
||||
|
||||
/** Artwork_id is the primary key; no auto-increment. */
|
||||
protected $primaryKey = 'artwork_id';
|
||||
public $incrementing = false;
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'artwork_id',
|
||||
'score_trending',
|
||||
'score_new_hot',
|
||||
'score_best',
|
||||
'model_version',
|
||||
'computed_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'artwork_id' => 'integer',
|
||||
'score_trending' => 'float',
|
||||
'score_new_hot' => 'float',
|
||||
'score_best' => 'float',
|
||||
'computed_at' => 'datetime',
|
||||
];
|
||||
|
||||
// ── Relations ──────────────────────────────────────────────────────────
|
||||
|
||||
public function artwork(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Artwork::class, 'artwork_id');
|
||||
}
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Map list_type string to the corresponding score column.
|
||||
*/
|
||||
public static function scoreColumn(string $listType): string
|
||||
{
|
||||
return match ($listType) {
|
||||
'new_hot' => 'score_new_hot',
|
||||
'best' => 'score_best',
|
||||
default => 'score_trending',
|
||||
};
|
||||
}
|
||||
}
|
||||
53
app/Models/RankList.php
Normal file
53
app/Models/RankList.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* App\Models\RankList
|
||||
*
|
||||
* Stores an ordered list of artwork IDs for a feed surface.
|
||||
* Rebuilt hourly by RankBuildListsJob.
|
||||
*
|
||||
* scope_id = 0 is the sentinel for "global" scope.
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $scope_type global | category | content_type
|
||||
* @property int $scope_id 0 = global; category.id or content_type.id otherwise
|
||||
* @property string $list_type trending | new_hot | best
|
||||
* @property string $model_version
|
||||
* @property array $artwork_ids Ordered array of artwork IDs
|
||||
* @property \Carbon\Carbon|null $computed_at
|
||||
*/
|
||||
class RankList extends Model
|
||||
{
|
||||
protected $table = 'rank_lists';
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'scope_type',
|
||||
'scope_id',
|
||||
'list_type',
|
||||
'model_version',
|
||||
'artwork_ids',
|
||||
'computed_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'scope_id' => 'integer',
|
||||
'artwork_ids' => 'array',
|
||||
'computed_at' => 'datetime',
|
||||
];
|
||||
|
||||
// ── Scope helpers ──────────────────────────────────────────────────────
|
||||
|
||||
/** Resolve scope_id: null → 0 (global sentinel). */
|
||||
public static function resolveScope(?int $id): int
|
||||
{
|
||||
return $id ?? 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user