morphMany(SavedItem::class, 'saveable'); } public function getIsSavedAttribute() { return $this->isSavedByAuthUser(); } public function getSavedCountAttribute() { return $this->saves()->count(); } // Check the saves() relation directly (not the is_saved attribute, which a // model may override to point at a different table — e.g. Media), so toggle // always decides against the same table it writes to. protected function isSavedByAuthUser(): bool { if (!auth()->check()) { return false; } return $this->saves()->where('user_id', auth()->id())->exists(); } public function toggleSaveStatus() { return $this->isSavedByAuthUser() ? $this->removeSave() : $this->addSave(); } public function addSave() { if (!auth()->check()) { return false; } // Idempotent: never inserts a duplicate even on repeated/racing calls. return SavedItem::firstOrCreate([ 'user_id' => auth()->id(), 'saveable_id' => $this->id, 'saveable_type' => get_class($this), ]); } public function removeSave() { if (!auth()->check()) { return false; } return SavedItem::where([ 'user_id' => auth()->id(), 'saveable_id' => $this->id, 'saveable_type' => get_class($this), ])->delete(); } }