'date', 'target_date' => 'date', 'released_at' => 'datetime', ]; public function getRouteKeyName(): string { return 'slug'; } public function group(): BelongsTo { return $this->belongsTo(Group::class); } public function creator(): BelongsTo { return $this->belongsTo(User::class, 'created_by_user_id'); } public function lead(): BelongsTo { return $this->belongsTo(User::class, 'lead_user_id'); } public function linkedCollection(): BelongsTo { return $this->belongsTo(Collection::class, 'linked_collection_id'); } public function featuredArtwork(): BelongsTo { return $this->belongsTo(Artwork::class, 'linked_featured_artwork_id'); } public function pinnedPost(): BelongsTo { return $this->belongsTo(GroupPost::class, 'pinned_post_id'); } public function artworkLinks(): HasMany { return $this->hasMany(GroupProjectArtwork::class); } public function artworks(): BelongsToMany { return $this->belongsToMany(Artwork::class, 'group_project_artworks') ->withPivot(['sort_order']) ->withTimestamps() ->orderBy('group_project_artworks.sort_order'); } public function memberLinks(): HasMany { return $this->hasMany(GroupProjectMember::class); } public function members(): BelongsToMany { return $this->belongsToMany(User::class, 'group_project_members') ->withPivot(['role_label', 'is_lead']) ->withTimestamps(); } public function assets(): HasMany { return $this->hasMany(GroupAsset::class, 'linked_project_id'); } public function milestones(): HasMany { return $this->hasMany(GroupProjectMilestone::class)->orderBy('sort_order'); } public function releases(): HasMany { return $this->hasMany(GroupRelease::class, 'linked_project_id'); } public function challenges(): HasMany { return $this->hasMany(GroupChallenge::class, 'linked_project_id'); } public function events(): HasMany { return $this->hasMany(GroupEvent::class, 'linked_project_id'); } public function canBeViewedBy(?User $viewer): bool { if ($this->visibility !== self::VISIBILITY_PRIVATE) { return $this->group->canBeViewedBy($viewer); } return $viewer !== null && $this->group->canViewStudio($viewer); } public function coverUrl(): ?string { $path = trim((string) $this->cover_path); if ($path === '') { return null; } if (str_starts_with($path, 'http://') || str_starts_with($path, 'https://')) { return $path; } return rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/') . '/' . ltrim($path, '/'); } }