optimizations

This commit is contained in:
2026-03-28 19:15:39 +01:00
parent 0b25d9570a
commit cab4fbd83e
509 changed files with 1016804 additions and 1605 deletions

673
app/Models/Collection.php Normal file
View File

@@ -0,0 +1,673 @@
<?php
declare(strict_types=1);
namespace App\Models;
use App\Models\CollectionComment;
use App\Models\CollectionDailyStat;
use App\Models\CollectionHistory;
use App\Models\CollectionMember;
use App\Models\CollectionSave;
use App\Models\CollectionSurfacePlacement;
use App\Models\CollectionSubmission;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Collection extends Model
{
use HasFactory, SoftDeletes;
public const LIFECYCLE_DRAFT = 'draft';
public const LIFECYCLE_SCHEDULED = 'scheduled';
public const LIFECYCLE_PUBLISHED = 'published';
public const LIFECYCLE_FEATURED = 'featured';
public const LIFECYCLE_ARCHIVED = 'archived';
public const LIFECYCLE_HIDDEN = 'hidden';
public const LIFECYCLE_RESTRICTED = 'restricted';
public const LIFECYCLE_UNDER_REVIEW = 'under_review';
public const LIFECYCLE_EXPIRED = 'expired';
public const WORKFLOW_DRAFT = 'draft';
public const WORKFLOW_IN_REVIEW = 'in_review';
public const WORKFLOW_APPROVED = 'approved';
public const WORKFLOW_PROGRAMMED = 'programmed';
public const WORKFLOW_ARCHIVED = 'archived';
public const READINESS_READY = 'ready';
public const READINESS_NEEDS_WORK = 'needs_work';
public const READINESS_BLOCKED = 'blocked';
public const HEALTH_HEALTHY = 'healthy';
public const HEALTH_NEEDS_METADATA = 'needs_metadata';
public const HEALTH_STALE = 'stale';
public const HEALTH_LOW_CONTENT = 'low_content';
public const HEALTH_BROKEN_ITEMS = 'broken_items';
public const HEALTH_WEAK_COVER = 'weak_cover';
public const HEALTH_LOW_ENGAGEMENT = 'low_engagement';
public const HEALTH_ATTRIBUTION_INCOMPLETE = 'attribution_incomplete';
public const HEALTH_NEEDS_REVIEW = 'needs_review';
public const HEALTH_DUPLICATE_RISK = 'duplicate_risk';
public const HEALTH_MERGE_CANDIDATE = 'merge_candidate';
public const TYPE_PERSONAL = 'personal';
public const TYPE_COMMUNITY = 'community';
public const TYPE_EDITORIAL = 'editorial';
public const EDITORIAL_OWNER_CREATOR = 'creator';
public const EDITORIAL_OWNER_STAFF_ACCOUNT = 'staff_account';
public const EDITORIAL_OWNER_SYSTEM = 'system';
public const COLLABORATION_CLOSED = 'closed';
public const COLLABORATION_INVITE_ONLY = 'invite_only';
public const COLLABORATION_OPEN = 'open';
public const MODERATION_ACTIVE = 'active';
public const MODERATION_UNDER_REVIEW = 'under_review';
public const MODERATION_REVIEW = self::MODERATION_UNDER_REVIEW;
public const MODERATION_RESTRICTED = 'restricted';
public const MODERATION_HIDDEN = 'hidden';
public const MEMBER_ROLE_OWNER = 'owner';
public const MEMBER_ROLE_EDITOR = 'editor';
public const MEMBER_ROLE_CONTRIBUTOR = 'contributor';
public const MEMBER_ROLE_VIEWER = 'viewer';
public const MEMBER_STATUS_PENDING = 'pending';
public const MEMBER_STATUS_ACTIVE = 'active';
public const MEMBER_STATUS_REVOKED = 'revoked';
public const SUBMISSION_PENDING = 'pending';
public const SUBMISSION_APPROVED = 'approved';
public const SUBMISSION_REJECTED = 'rejected';
public const SUBMISSION_WITHDRAWN = 'withdrawn';
public const COMMENT_VISIBLE = 'visible';
public const COMMENT_HIDDEN = 'hidden';
public const COMMENT_FLAGGED = 'flagged';
public const VISIBILITY_PUBLIC = 'public';
public const VISIBILITY_UNLISTED = 'unlisted';
public const VISIBILITY_PRIVATE = 'private';
public const MODE_MANUAL = 'manual';
public const MODE_SMART = 'smart';
public const SORT_MANUAL = 'manual';
public const SORT_NEWEST = 'newest';
public const SORT_OLDEST = 'oldest';
public const SORT_POPULAR = 'popular';
public const SPOTLIGHT_STYLE_DEFAULT = 'default';
public const SPOTLIGHT_STYLE_EDITORIAL = 'editorial';
public const SPOTLIGHT_STYLE_SEASONAL = 'seasonal';
public const SPOTLIGHT_STYLE_CHALLENGE = 'challenge';
public const SPOTLIGHT_STYLE_COMMUNITY = 'community';
public const PRESENTATION_STANDARD = 'standard';
public const PRESENTATION_EDITORIAL_GRID = 'editorial_grid';
public const PRESENTATION_HERO_GRID = 'hero_grid';
public const PRESENTATION_MASONRY = 'masonry';
public const EMPHASIS_COVER_HEAVY = 'cover_heavy';
public const EMPHASIS_BALANCED = 'balanced';
public const EMPHASIS_ARTWORK_FIRST = 'artwork_first';
protected $fillable = [
'user_id',
'managed_by_user_id',
'title',
'slug',
'lifecycle_state',
'workflow_state',
'readiness_state',
'health_state',
'health_flags_json',
'canonical_collection_id',
'duplicate_cluster_key',
'program_key',
'partner_key',
'trust_tier',
'experiment_key',
'experiment_treatment',
'placement_variant',
'ranking_mode_variant',
'collection_pool_version',
'test_label',
'recommendation_tier',
'ranking_bucket',
'search_boost_tier',
'type',
'editorial_owner_mode',
'editorial_owner_user_id',
'editorial_owner_label',
'description',
'subtitle',
'summary',
'collaboration_mode',
'allow_submissions',
'allow_comments',
'allow_saves',
'moderation_status',
'published_at',
'unpublished_at',
'event_key',
'event_label',
'season_key',
'banner_text',
'badge_label',
'spotlight_style',
'quality_score',
'ranking_score',
'metadata_completeness_score',
'editorial_readiness_score',
'freshness_score',
'engagement_score',
'health_score',
'last_health_check_at',
'last_recommendation_refresh_at',
'placement_eligibility',
'analytics_enabled',
'presentation_style',
'emphasis_mode',
'theme_token',
'series_key',
'series_title',
'series_description',
'series_order',
'campaign_key',
'campaign_label',
'commercial_eligibility',
'promotion_tier',
'sponsorship_label',
'sponsorship_state',
'partner_label',
'ownership_domain',
'commercial_review_state',
'legal_review_state',
'monetization_ready_status',
'brand_safe_status',
'editorial_notes',
'staff_commercial_notes',
'archived_at',
'expired_at',
'history_count',
'cover_artwork_id',
'visibility',
'mode',
'sort_mode',
'artworks_count',
'comments_count',
'saves_count',
'collaborators_count',
'is_featured',
'profile_order',
'views_count',
'likes_count',
'followers_count',
'shares_count',
'smart_rules_json',
'layout_modules_json',
'last_activity_at',
'featured_at',
];
protected $casts = [
'artworks_count' => 'integer',
'comments_count' => 'integer',
'saves_count' => 'integer',
'collaborators_count' => 'integer',
'is_featured' => 'boolean',
'profile_order' => 'integer',
'views_count' => 'integer',
'likes_count' => 'integer',
'followers_count' => 'integer',
'shares_count' => 'integer',
'allow_submissions' => 'boolean',
'allow_comments' => 'boolean',
'allow_saves' => 'boolean',
'analytics_enabled' => 'boolean',
'commercial_eligibility' => 'boolean',
'smart_rules_json' => 'array',
'layout_modules_json' => 'array',
'health_flags_json' => 'array',
'quality_score' => 'decimal:2',
'ranking_score' => 'decimal:2',
'metadata_completeness_score' => 'decimal:2',
'editorial_readiness_score' => 'decimal:2',
'freshness_score' => 'decimal:2',
'engagement_score' => 'decimal:2',
'health_score' => 'decimal:2',
'placement_eligibility' => 'boolean',
'series_order' => 'integer',
'history_count' => 'integer',
'last_activity_at' => 'datetime',
'featured_at' => 'datetime',
'last_health_check_at' => 'datetime',
'last_recommendation_refresh_at' => 'datetime',
'published_at' => 'datetime',
'unpublished_at' => 'datetime',
'archived_at' => 'datetime',
'expired_at' => 'datetime',
];
protected ?bool $cachedVisibilityWindow = null;
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function managedBy(): BelongsTo
{
return $this->belongsTo(User::class, 'managed_by_user_id');
}
public function editorialOwnerUser(): BelongsTo
{
return $this->belongsTo(User::class, 'editorial_owner_user_id');
}
public function coverArtwork(): BelongsTo
{
return $this->belongsTo(Artwork::class, 'cover_artwork_id');
}
public function likes(): HasMany
{
return $this->hasMany(CollectionLike::class);
}
public function saves(): HasMany
{
return $this->hasMany(CollectionSave::class);
}
public function follows(): HasMany
{
return $this->hasMany(CollectionFollow::class);
}
public function members(): HasMany
{
return $this->hasMany(CollectionMember::class);
}
public function submissions(): HasMany
{
return $this->hasMany(CollectionSubmission::class);
}
public function comments(): HasMany
{
return $this->hasMany(CollectionComment::class);
}
public function historyEntries(): HasMany
{
return $this->hasMany(CollectionHistory::class);
}
public function canonicalCollection(): BelongsTo
{
return $this->belongsTo(self::class, 'canonical_collection_id');
}
public function dailyStats(): HasMany
{
return $this->hasMany(CollectionDailyStat::class);
}
public function programAssignments(): HasMany
{
return $this->hasMany(CollectionProgramAssignment::class);
}
public function qualitySnapshots(): HasMany
{
return $this->hasMany(CollectionQualitySnapshot::class);
}
public function recommendationSnapshots(): HasMany
{
return $this->hasMany(CollectionRecommendationSnapshot::class);
}
public function mergeActionsAsSource(): HasMany
{
return $this->hasMany(CollectionMergeAction::class, 'source_collection_id');
}
public function mergeActionsAsTarget(): HasMany
{
return $this->hasMany(CollectionMergeAction::class, 'target_collection_id');
}
public function entityLinks(): HasMany
{
return $this->hasMany(CollectionEntityLink::class);
}
public function savedNotes(): HasMany
{
return $this->hasMany(CollectionSavedNote::class);
}
public function placements(): HasMany
{
return $this->hasMany(CollectionSurfacePlacement::class);
}
public function artworks(): BelongsToMany
{
return $this->belongsToMany(Artwork::class, 'collection_artwork', 'collection_id', 'artwork_id')
->withPivot(['order_num'])
->withTimestamps()
->orderByPivot('order_num');
}
public function publicArtworks(): BelongsToMany
{
return $this->artworks()
->whereNull('artworks.deleted_at')
->where('artworks.is_public', true)
->where('artworks.is_approved', true)
->whereNotNull('artworks.published_at')
->where('artworks.published_at', '<=', now());
}
public function manualRelatedCollections(): BelongsToMany
{
return $this->belongsToMany(self::class, 'collection_related_links', 'collection_id', 'related_collection_id')
->withPivot(['sort_order', 'created_by_user_id'])
->withTimestamps()
->orderBy('collection_related_links.sort_order')
->orderBy('collection_related_links.id');
}
public function scopePublic(Builder $query): Builder
{
return $query
->where('visibility', self::VISIBILITY_PUBLIC)
->where('moderation_status', self::MODERATION_ACTIVE)
->whereNotIn('lifecycle_state', [
self::LIFECYCLE_DRAFT,
self::LIFECYCLE_EXPIRED,
self::LIFECYCLE_HIDDEN,
self::LIFECYCLE_RESTRICTED,
self::LIFECYCLE_UNDER_REVIEW,
])
->where(function (Builder $builder): void {
$builder->whereNull('published_at')
->orWhere('published_at', '<=', now());
})
->where(function (Builder $builder): void {
$builder->whereNull('unpublished_at')
->orWhere('unpublished_at', '>', now());
})
->where(function (Builder $builder): void {
$builder->whereNull('expired_at')
->orWhere('expired_at', '>', now());
});
}
public function scopePublicEligible(Builder $query): Builder
{
return $query
->public()
->where('placement_eligibility', true)
->whereIn('lifecycle_state', [
self::LIFECYCLE_PUBLISHED,
self::LIFECYCLE_FEATURED,
]);
}
public function scopeFeaturedPublic(Builder $query): Builder
{
return $query
->publicEligible()
->where('is_featured', true);
}
public function scopeVisibleOnProfile(Builder $query): Builder
{
return $query->public();
}
public function scopeOwnedBy(Builder $query, int $userId): Builder
{
return $query->where('user_id', $userId);
}
public function isOwnedBy(User|int|null $user): bool
{
$userId = $user instanceof User ? $user->id : $user;
return $userId !== null && (int) $userId === (int) $this->user_id;
}
public function isPubliclyAccessible(): bool
{
if ($this->moderation_status !== self::MODERATION_ACTIVE) {
return false;
}
if ($this->published_at && $this->published_at->isFuture()) {
return false;
}
if ($this->unpublished_at && $this->unpublished_at->lte(now())) {
return false;
}
if ($this->expired_at && $this->expired_at->lte(now()) && $this->lifecycle_state === self::LIFECYCLE_EXPIRED) {
return false;
}
if (! in_array($this->lifecycle_state, [
self::LIFECYCLE_SCHEDULED,
self::LIFECYCLE_PUBLISHED,
self::LIFECYCLE_FEATURED,
self::LIFECYCLE_ARCHIVED,
], true)) {
return false;
}
return in_array($this->visibility, [self::VISIBILITY_PUBLIC, self::VISIBILITY_UNLISTED], true);
}
public function isPubliclyEngageable(): bool
{
return $this->visibility === self::VISIBILITY_PUBLIC;
}
public function displayOwnerName(): string
{
if ($this->type === self::TYPE_EDITORIAL && $this->editorial_owner_mode === self::EDITORIAL_OWNER_SYSTEM) {
return (string) ($this->editorial_owner_label ?: config('collections.editorial.system_owner_label', 'Skinbase Editorial'));
}
$owner = $this->relationLoaded('user') ? $this->user : $this->user()->first();
return (string) ($owner?->name ?: $owner?->username ?: 'Skinbase Curator');
}
public function displayOwnerUsername(): ?string
{
if ($this->type === self::TYPE_EDITORIAL && $this->editorial_owner_mode === self::EDITORIAL_OWNER_SYSTEM) {
return null;
}
$owner = $this->relationLoaded('user') ? $this->user : $this->user()->first();
return $owner?->username;
}
public function hasSystemEditorialOwner(): bool
{
return $this->type === self::TYPE_EDITORIAL && $this->editorial_owner_mode === self::EDITORIAL_OWNER_SYSTEM;
}
public function isCollaborative(): bool
{
return $this->type !== self::TYPE_PERSONAL || $this->collaboration_mode !== self::COLLABORATION_CLOSED;
}
public function activeMemberRoleFor(User|int|null $user): ?string
{
$userId = $user instanceof User ? $user->id : $user;
if ($userId === null) {
return null;
}
if ($this->isOwnedBy($userId)) {
return self::MEMBER_ROLE_OWNER;
}
$members = $this->relationLoaded('members')
? $this->members
: $this->members()->where('status', self::MEMBER_STATUS_ACTIVE)->get();
return $members
->first(fn (CollectionMember $member) => (int) $member->user_id === (int) $userId && $member->status === self::MEMBER_STATUS_ACTIVE)
?->role;
}
public function hasActiveMember(User|int|null $user): bool
{
return $this->activeMemberRoleFor($user) !== null;
}
public function canBeManagedBy(User $user): bool
{
return in_array($this->activeMemberRoleFor($user), [
self::MEMBER_ROLE_OWNER,
self::MEMBER_ROLE_EDITOR,
], true);
}
public function canManageArtworks(User $user): bool
{
return $this->canBeManagedBy($user);
}
public function canManageMembers(User $user): bool
{
return $this->isCollaborative() && $this->canBeManagedBy($user);
}
public function canReceiveSubmissionsFrom(?User $user): bool
{
if (! $user || ! $this->allow_submissions || ! $this->isPubliclyAccessible()) {
return false;
}
if ($this->type === self::TYPE_EDITORIAL) {
return false;
}
if ($this->collaboration_mode === self::COLLABORATION_CLOSED) {
return false;
}
return ! $this->hasActiveMember($user);
}
public function canReceiveCommentsFrom(?User $user): bool
{
return $user !== null && $this->allow_comments && $this->canBeViewedBy($user);
}
public function canBeSavedBy(?User $user): bool
{
return $user !== null && $this->allow_saves && $this->visibility === self::VISIBILITY_PUBLIC && ! $this->isOwnedBy($user);
}
public function isFeatureablePublicly(): bool
{
return $this->visibility === self::VISIBILITY_PUBLIC
&& $this->moderation_status === self::MODERATION_ACTIVE
&& (bool) $this->placement_eligibility
&& in_array($this->lifecycle_state, [self::LIFECYCLE_PUBLISHED, self::LIFECYCLE_FEATURED], true);
}
public function isSmart(): bool
{
return $this->mode === self::MODE_SMART;
}
public function canBeViewedBy(?User $viewer): bool
{
if ($viewer && ($this->isOwnedBy($viewer) || $this->hasActiveMember($viewer))) {
return true;
}
return $this->isPubliclyAccessible();
}
public function resolvedCoverArtwork(bool $publicOnly = false): ?Artwork
{
$cover = $this->relationLoaded('coverArtwork') ? $this->coverArtwork : $this->coverArtwork()->first();
if ($cover && (! $publicOnly || $this->artworkIsPubliclyVisible($cover))) {
return $cover;
}
$relation = $publicOnly ? 'publicArtworks' : 'artworks';
$artworks = $this->relationLoaded($relation)
? $this->getRelation($relation)
: $this->{$relation}()->limit(1)->get();
return $artworks->first();
}
public function syncArtworksCount(): void
{
$this->forceFill([
'artworks_count' => $this->isSmart()
? (int) $this->artworks_count
: $this->artworks()->count(),
])->save();
}
public function inSeries(): bool
{
return filled($this->series_key);
}
public function hasCanonicalTarget(): bool
{
return $this->canonical_collection_id !== null;
}
public function isPlacementEligible(): bool
{
return (bool) $this->placement_eligibility;
}
public function supportsAnalytics(): bool
{
return (bool) $this->analytics_enabled;
}
public function usesPremiumPresentation(): bool
{
return $this->presentation_style !== self::PRESENTATION_STANDARD
|| $this->emphasis_mode !== self::EMPHASIS_BALANCED
|| filled($this->theme_token);
}
private function artworkIsPubliclyVisible(Artwork $artwork): bool
{
return ! $artwork->trashed()
&& (bool) $artwork->is_public
&& (bool) $artwork->is_approved
&& $artwork->published_at !== null
&& $artwork->published_at->lte(now());
}
}