'boolean', ]; protected $appends = [ 'average_rating', 'user_rating', 'ratings_count', 'comments_count', 'has_user_commented', 'user_comment', 'user_comment_id', 'has_user_rated', 'is_saved', 'saved_count', 'is_liked', 'likes_count', 'url', 'deep_link', ]; public function image() { return $this->belongsTo(Image::class); } // Image shown on the detail (show-by-id) screen; image() is the list thumbnail. public function detailImage() { return $this->belongsTo(Image::class, 'detail_image_id'); } public function categories() { return $this->belongsToMany(Category::class, 'category_media'); } public function subCategories() { return $this->belongsToMany(SubCategory::class, 'media_sub_category'); } public function tags() { return $this->belongsToMany(Tag::class, 'media_tag'); } public function plays() { return $this->hasMany(MediaPlay::class); } public function notes() { return $this->morphMany(Note::class, 'noteable'); } public function myNote() { return $this->morphOne(Note::class, 'noteable')->where('user_id', auth()->id()); } public function savedBy() { return $this->belongsToMany(User::class, 'saved_media') ->withTimestamps(); } public function getUrlAttribute() { if ($this->file_path) return asset('storage/' . $this->file_path); return $this->external_url; } // Public deep link for this media (canonical aramejan.com form). public function getDeepLinkAttribute(): ?string { $canonical = config('deeplinks.media_pattern'); if ($canonical) { return rtrim(str_replace('{id}', $this->id, $canonical), '/'); } return url('/media/' . $this->id); } // is_saved / saved_count come from the HasSaves trait (saved_items table), // so the /saves/* endpoints and the media list/show all agree. }