Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
66.67% covered (warning)
66.67%
22 / 33
70.00% covered (warning)
70.00%
7 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
AIPromptService
66.67% covered (warning)
66.67%
22 / 33
70.00% covered (warning)
70.00%
7 / 10
21.26
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAll
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getById
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLatestByProductAndName
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 create
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 update
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 delete
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getProducts
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 trackCacheKey
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 invalidateCache
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
1<?php
2
3namespace App\Http\Services;
4
5use App\Http\Models\AIPrompts;
6use App\Http\Repositories\interfaces\IAIPromptRepository;
7use Illuminate\Support\Collection;
8use Illuminate\Support\Facades\Cache;
9
10/**
11 * Service for AI prompt business logic.
12 *
13 * This service handles all business logic related to AI prompt configurations,
14 * coordinating with the repository for data access. It manages caching and
15 * provides methods for both CRUD operations and internal AI prompt resolution.
16 */
17class AIPromptService
18{
19    /**
20     * Cache key prefix for AI prompts.
21     */
22    private const CACHE_KEY_ALL = 'ai_prompts:all';
23
24    /**
25     * Cache key prefix for product+name lookups.
26     */
27    private const CACHE_KEY_LATEST = 'ai_prompts:latest';
28
29    /**
30     * Cache TTL in seconds (1 hour).
31     */
32    private const CACHE_TTL = 3600;
33
34    public function __construct(
35        private IAIPromptRepository $aiPromptRepository
36    ) {}
37
38    /**
39     * Get all AI prompts ordered by newest first, with optional product filtering.
40     *
41     * @param  string|null  $product  Optional product to filter by
42     * @return Collection<int, AIPrompts> Collection of AI prompts
43     */
44    public function getAll(?string $product = null): Collection
45    {
46        if ($product) {
47            return $this->aiPromptRepository->getAll($product);
48        }
49
50        return Cache::remember(self::CACHE_KEY_ALL, self::CACHE_TTL, function () {
51            return $this->aiPromptRepository->getAll();
52        });
53    }
54
55    /**
56     * Find an AI prompt by its ID.
57     *
58     * @param  string  $id  The AI prompt ID
59     * @return AIPrompts|null The AI prompt or null if not found
60     */
61    public function getById(string $id): ?AIPrompts
62    {
63        return $this->aiPromptRepository->findById($id);
64    }
65
66    /**
67     * Get the latest version of an AI prompt by product and name.
68     *
69     * Used internally by FlyMsgAIService to resolve the correct prompt
70     * configuration for a given product and action.
71     *
72     * @param  string  $product  The product identifier
73     * @param  string  $name  The prompt name/action
74     * @return AIPrompts|null The latest version or null if not found
75     */
76    public function getLatestByProductAndName(string $product, string $name): ?AIPrompts
77    {
78        $cacheKey = self::CACHE_KEY_LATEST.":$product:$name";
79
80        $this->trackCacheKey($product, $cacheKey);
81
82        return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($product, $name) {
83            return $this->aiPromptRepository->getLatestByProductAndName($product, $name);
84        });
85    }
86
87    /**
88     * Create a new AI prompt.
89     *
90     * @param  array{
91     *     product: string,
92     *     name: string,
93     *     model?: string|null,
94     *     version?: int,
95     *     temperature?: float|null,
96     *     tokens?: int|null,
97     *     top_p?: float|null,
98     *     mission?: string|null,
99     *     context?: string|null,
100     *     persona?: string|null,
101     *     instructions?: array<string>|null,
102     *     examples?: array<array{input: string, output: string}>|null,
103     *     constraints?: array<string>|null,
104     *     output_instructions?: array<string>|null,
105     *     threshold?: float|null,
106     *     is_grounding?: bool|null,
107     *     include_hashtags_prompt?: string|null,
108     *     exclude_hashtags_prompt?: string|null,
109     *     include_emojis_prompt?: string|null,
110     *     exclude_emojis_prompt?: string|null,
111     *     existent_content_instructions?: string|null,
112     *     existent_content_constraints?: array<string>|null,
113     *     youtube_url_mission?: string|null,
114     *     blog_url_mission?: string|null,
115     *     youtube_url_instructions?: array<string>|null,
116     *     blog_url_instructions?: array<string>|null
117     * }  $data  The AI prompt data
118     * @return AIPrompts The created AI prompt
119     */
120    public function create(array $data): AIPrompts
121    {
122        $aiPrompt = $this->aiPromptRepository->create($data);
123
124        $this->invalidateCache();
125
126        return $aiPrompt;
127    }
128
129    /**
130     * Update an existing AI prompt.
131     *
132     * @param  AIPrompts  $aiPrompt  The AI prompt to update
133     * @param  array  $data  The update data
134     * @return AIPrompts The updated AI prompt
135     */
136    public function update(AIPrompts $aiPrompt, array $data): AIPrompts
137    {
138        $aiPrompt = $this->aiPromptRepository->update($aiPrompt, $data);
139
140        $this->invalidateCache();
141
142        return $aiPrompt;
143    }
144
145    /**
146     * Delete an AI prompt.
147     *
148     * @param  AIPrompts  $aiPrompt  The AI prompt to delete
149     * @return bool True if deleted successfully
150     */
151    public function delete(AIPrompts $aiPrompt): bool
152    {
153        $result = $this->aiPromptRepository->delete($aiPrompt);
154
155        $this->invalidateCache();
156
157        return $result;
158    }
159
160    /**
161     * Get available product types.
162     *
163     * @return array<string> Array of product types
164     */
165    public function getProducts(): array
166    {
167        return AIPrompts::PRODUCTS;
168    }
169
170    /**
171     * Track a cache key under a product so it can be invalidated later.
172     *
173     * @param  string  $product  The product identifier
174     * @param  string  $cacheKey  The cache key to track
175     */
176    private function trackCacheKey(string $product, string $cacheKey): void
177    {
178        $trackingKey = "ai_prompts:keys:$product";
179        $keys = Cache::get($trackingKey, []);
180
181        if (! in_array($cacheKey, $keys)) {
182            $keys[] = $cacheKey;
183            Cache::put($trackingKey, $keys, self::CACHE_TTL);
184        }
185    }
186
187    /**
188     * Invalidate AI prompt caches.
189     */
190    public function invalidateCache(): void
191    {
192        Cache::forget(self::CACHE_KEY_ALL);
193
194        // Invalidate product+name caches for all known products
195        foreach (AIPrompts::PRODUCTS as $product) {
196            $keys = Cache::get("ai_prompts:keys:$product", []);
197            foreach ($keys as $key) {
198                Cache::forget($key);
199            }
200            Cache::forget("ai_prompts:keys:$product");
201        }
202    }
203}