'boolean', 'is_published' => 'boolean', 'is_dubbed' => 'boolean', 'is_trending' => 'boolean', 'rating' => 'float', 'trending_order' => 'integer', ]; protected static function boot() { parent::boot(); static::creating(function ($anime) { if (empty($anime->slug)) { $base = Str::slug($anime->title ?: 'anime'); $slug = $base; $i = 2; while (static::where('slug', $slug)->exists()) { $slug = $base . '-' . $i++; } $anime->slug = $slug; } }); static::saving(function ($anime) { if (empty($anime->slug)) { $base = Str::slug($anime->title ?: 'anime'); $slug = $base; $i = 2; while (static::where('slug', $slug)->whereKeyNot($anime->id ?? 0)->exists()) { $slug = $base . '-' . $i++; } $anime->slug = $slug; } }); } /** Güvenli detail URL — slug null olsa bile çökmez. */ public function getDetailUrlAttribute(): string { return $this->slug ? route('anime.show', $this->slug) : '#'; } public function genres() { return $this->belongsToMany(Genre::class, 'anime_genre'); } public function seasons() { return $this->hasMany(Season::class)->orderBy('season_number'); } public function episodes() { return $this->hasMany(Episode::class); } public function importJobs() { return $this->hasMany(ImportJob::class); } public function permissions() { return $this->morphMany(ContentPermission::class, 'content', 'content_type', 'content_id'); } /** Cover veya banner URL'sini döndürür (storage veya dış URL) */ private function imageUrl(?string $path): ?string { return MediaUrl::fromStoragePath($path); } public function getCoverUrlAttribute(): ?string { return $this->imageUrl($this->cover_image); } public function getBannerUrlAttribute(): ?string { return $this->imageUrl($this->banner_image); } public function getPermission(string $key): string { $override = $this->permissions()->where('permission_key', $key)->first(); if ($override) return $override->required_membership; $global = PermissionSetting::where('key', $key)->first(); return $global ? $global->required_membership : 'free'; } }