create([ 'name' => $command->name, 'description' => $command->description, 'body' => $command->body, ]); $prompt->tags()->sync($this->tagIds($command->tags)); return $prompt; }); } public function update(UpdatePrompt $command, Prompt $prompt): Prompt { return DB::transaction(function () use ($command, $prompt) { $prompt->forceFill([ 'name' => $command->name, 'description' => $command->description, 'body' => $command->body, ])->save(); $prompt->tags()->sync($this->tagIds($command->tags)); return $prompt; }); } public function delete(Prompt $prompt): void { $prompt->delete(); } public function duplicate(Prompt $prompt): Prompt { return DB::transaction(function () use ($prompt) { // Stats reset to their column defaults (favorite off, usage 0, never used). $copy = Prompt::query()->create([ 'name' => $this->uniqueCopyName($prompt->name), 'description' => $prompt->description, 'body' => $prompt->body, ]); $copy->tags()->sync($prompt->tags->pluck('id')->all()); return $copy; }); } public function toggleFavorite(Prompt $prompt): Prompt { $prompt->forceFill(['is_favorite' => ! $prompt->is_favorite])->save(); return $prompt; } public function recordUsage(Prompt $prompt): Prompt { $prompt->forceFill([ 'usage_count' => $prompt->usage_count + 1, 'last_used_at' => now(), ])->save(); return $prompt; } /** * Normalize tag names (trim, lowercase, dedupe, drop empties) and * resolve them to existing-or-created tag IDs. * * @param list $names * @return list */ private function tagIds(array $names): array { $uniqueNames = []; foreach ($names as $name) { $name = strtolower(trim((string) $name)); if ($name !== '') { $uniqueNames[$name] = true; } } $ids = []; foreach (array_keys($uniqueNames) as $name) { $tag = Tag::query()->firstOrCreate(['name' => $name]); $ids[] = $tag->id; } return $ids; } private function uniqueCopyName(string $name): string { $candidate = $name.' (copy)'; $guard = 0; while (Prompt::query()->where('name', $candidate)->exists() && $guard < 50) { $candidate .= ' (copy)'; $guard++; } return $candidate; } }