From d97822c7e8a47c498b1f75654822f17a07e1b5a9 Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Fri, 8 May 2026 19:32:51 -0300 Subject: [PATCH] refactor: drop RenderedSlide DTO and content_language null fallbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TemplateImageGenerator->render() now returns a typed array shape {path: string, source_meta: array} instead of a one-off RenderedSlide DTO. Single internal callsite, no need for a dedicated class. - Drop the `?? 'en'` fallback on $workspace->content_language in 6 callsites: the column has a NOT NULL default of 'en' at the DB level, so the null coalesce was dead code. - WorkspaceFactory now seeds content_language, brand_tone, brand_font and image_style explicitly so make() (no DB persist) produces a complete model — DB defaults aren't applied until create(). --- app/Ai/Agents/PostContentGenerator.php | 2 +- app/Ai/Agents/PostContentHumanizer.php | 2 +- app/Ai/Agents/PostContentReviewer.php | 2 +- app/Ai/Agents/PostContentStreamer.php | 2 +- .../Controllers/App/PostTemplateController.php | 2 +- app/Jobs/Ai/StreamPostCreation.php | 18 ++++++++---------- app/Services/Image/RenderedSlide.php | 16 ---------------- app/Services/Image/TemplateImageGenerator.php | 18 ++++++++++-------- database/factories/WorkspaceFactory.php | 4 ++++ .../Image/TemplateImageGeneratorTest.php | 4 ++-- 10 files changed, 29 insertions(+), 41 deletions(-) delete mode 100644 app/Services/Image/RenderedSlide.php diff --git a/app/Ai/Agents/PostContentGenerator.php b/app/Ai/Agents/PostContentGenerator.php index 4de29847..52dc336c 100644 --- a/app/Ai/Agents/PostContentGenerator.php +++ b/app/Ai/Agents/PostContentGenerator.php @@ -62,7 +62,7 @@ public function instructions(): string 'brand_description' => $this->workspace->brand_description ?? '', 'brand_tone' => $this->workspace->brand_tone ?? '', 'brand_voice_notes' => $this->workspace->brand_voice_notes ?? '', - 'content_language' => $this->workspace->content_language ?? 'en', + 'content_language' => $this->workspace->content_language, 'current_content' => $this->currentContent, 'format' => $this->format, 'slide_count' => $this->slideCount, diff --git a/app/Ai/Agents/PostContentHumanizer.php b/app/Ai/Agents/PostContentHumanizer.php index eb0d34a5..d0dfd7a2 100644 --- a/app/Ai/Agents/PostContentHumanizer.php +++ b/app/Ai/Agents/PostContentHumanizer.php @@ -34,7 +34,7 @@ public function instructions(): string 'brand_name' => $this->workspace->name ?? '', 'brand_tone' => $this->workspace->brand_tone ?? '', 'brand_voice_notes' => $this->workspace->brand_voice_notes ?? '', - 'content_language' => $this->workspace->content_language ?? 'en', + 'content_language' => $this->workspace->content_language, 'format' => $this->format, ])->render(); } diff --git a/app/Ai/Agents/PostContentReviewer.php b/app/Ai/Agents/PostContentReviewer.php index 4c87980b..07a39855 100644 --- a/app/Ai/Agents/PostContentReviewer.php +++ b/app/Ai/Agents/PostContentReviewer.php @@ -27,7 +27,7 @@ public function instructions(): string 'brand_name' => $this->workspace->name ?? '', 'brand_tone' => $this->workspace->brand_tone ?? '', 'brand_voice_notes' => $this->workspace->brand_voice_notes ?? '', - 'content_language' => $this->workspace->content_language ?? 'en', + 'content_language' => $this->workspace->content_language, ])->render(); } diff --git a/app/Ai/Agents/PostContentStreamer.php b/app/Ai/Agents/PostContentStreamer.php index 348f3542..091fb0b7 100644 --- a/app/Ai/Agents/PostContentStreamer.php +++ b/app/Ai/Agents/PostContentStreamer.php @@ -35,7 +35,7 @@ public function instructions(): string 'brand_description' => $this->workspace->brand_description ?? '', 'brand_tone' => $this->workspace->brand_tone ?? '', 'brand_voice_notes' => $this->workspace->brand_voice_notes ?? '', - 'content_language' => $this->workspace->content_language ?? 'en', + 'content_language' => $this->workspace->content_language, 'current_content' => $this->currentContent, 'format' => 'single', 'slide_count' => 1, diff --git a/app/Http/Controllers/App/PostTemplateController.php b/app/Http/Controllers/App/PostTemplateController.php index 28b8f30b..c7889097 100644 --- a/app/Http/Controllers/App/PostTemplateController.php +++ b/app/Http/Controllers/App/PostTemplateController.php @@ -79,7 +79,7 @@ public function apply(ApplyPostTemplateRequest $request, string $slug, TemplateI ); if ($rendered) { - $mediaItem = $this->createMediaItem($workspace, $rendered->path); + $mediaItem = $this->createMediaItem($workspace, $rendered['path']); $media[] = $mediaItem; } } diff --git a/app/Jobs/Ai/StreamPostCreation.php b/app/Jobs/Ai/StreamPostCreation.php index cd2cddd7..780a32a2 100644 --- a/app/Jobs/Ai/StreamPostCreation.php +++ b/app/Jobs/Ai/StreamPostCreation.php @@ -21,7 +21,6 @@ use App\Services\Ai\AiImageClient; use App\Services\Ai\RecordAiUsage; use App\Services\Image\BrandColorMapper; -use App\Services\Image\RenderedSlide; use App\Services\Image\TemplateImageGenerator; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -313,15 +312,13 @@ private function notifyReady(Workspace $workspace, Post $post): void $user = User::findOrFail($this->userId); - $locale = $workspace->content_language ?? 'en'; - SendNotification::dispatch( user: $user, workspaceId: $workspace->id, type: NotificationType::PostReady, channel: NotificationChannel::InApp, - title: trans('notifications.post_ready.title', [], $locale), - body: trans('notifications.post_ready.body', [], $locale), + title: trans('notifications.post_ready.title', [], $workspace->content_language), + body: trans('notifications.post_ready.body', [], $workspace->content_language), data: ['post_id' => $post->id], ); } @@ -340,17 +337,18 @@ private function aspectRatioFor(ContentType $type): ?string } /** + * @param array{path: string, source_meta: array} $rendered * @return array */ - private function buildAiMediaItem(Workspace $workspace, RenderedSlide $rendered): array + private function buildAiMediaItem(Workspace $workspace, array $rendered): array { $media = $workspace->media()->create([ 'collection' => 'ai-generated', 'type' => MediaType::Image, - 'path' => $rendered->path, - 'original_filename' => basename($rendered->path), + 'path' => $rendered['path'], + 'original_filename' => basename($rendered['path']), 'mime_type' => 'image/webp', - 'size' => Storage::size($rendered->path), + 'size' => Storage::size($rendered['path']), 'order' => 0, ]); @@ -361,7 +359,7 @@ private function buildAiMediaItem(Workspace $workspace, RenderedSlide $rendered) 'type' => 'image', 'mime_type' => 'image/webp', 'source' => Source::Ai->value, - 'source_meta' => $rendered->sourceMeta, + 'source_meta' => $rendered['source_meta'], ]; } } diff --git a/app/Services/Image/RenderedSlide.php b/app/Services/Image/RenderedSlide.php deleted file mode 100644 index cde28293..00000000 --- a/app/Services/Image/RenderedSlide.php +++ /dev/null @@ -1,16 +0,0 @@ - $sourceMeta - */ - public function __construct( - public readonly string $path, - public readonly array $sourceMeta, - ) {} -} diff --git a/app/Services/Image/TemplateImageGenerator.php b/app/Services/Image/TemplateImageGenerator.php index 5ab2896e..ace9b29d 100644 --- a/app/Services/Image/TemplateImageGenerator.php +++ b/app/Services/Image/TemplateImageGenerator.php @@ -35,10 +35,12 @@ public function __construct( ) {} /** - * Render a slide using Template A (full bleed) or Template B (photo card). + * Render a slide and return the storage path plus the source meta needed + * to regenerate it later. Returns null on failure (e.g. the AI client + * could not produce a usable image). * * @param array $imageKeywords - * @return RenderedSlide|null The storage path + source meta, or null on failure. + * @return array{path: string, source_meta: array}|null */ public function render( Workspace $workspace, @@ -48,7 +50,7 @@ public function render( array $imageKeywords, int $width = self::DEFAULT_WIDTH, int $height = self::DEFAULT_HEIGHT, - ): ?RenderedSlide { + ): ?array { $this->width = $width; $this->height = $height; @@ -59,7 +61,7 @@ public function render( is_string($rawStyle) => ImageStyle::tryFrom($rawStyle) ?? ImageStyle::DEFAULT, default => ImageStyle::DEFAULT, }; - $language = $workspace->content_language ?? 'en'; + $language = $workspace->content_language; $imageData = $this->aiImage->generate( keywords: $imageKeywords, @@ -101,9 +103,9 @@ public function render( ], ); - return new RenderedSlide( - path: $filename, - sourceMeta: [ + return [ + 'path' => $filename, + 'source_meta' => [ 'keywords' => array_values($imageKeywords), 'style' => $imageStyle->value, 'language' => $language, @@ -113,7 +115,7 @@ public function render( 'width' => $this->width, 'height' => $this->height, ], - ); + ]; } /** diff --git a/database/factories/WorkspaceFactory.php b/database/factories/WorkspaceFactory.php index 6b8ca431..3e9de50f 100644 --- a/database/factories/WorkspaceFactory.php +++ b/database/factories/WorkspaceFactory.php @@ -24,6 +24,10 @@ public function definition(): array return [ 'user_id' => User::factory(), 'name' => fake()->company(), + 'content_language' => 'en', + 'brand_tone' => 'professional', + 'brand_font' => 'Inter', + 'image_style' => 'cinematic', ]; } diff --git a/tests/Unit/Services/Image/TemplateImageGeneratorTest.php b/tests/Unit/Services/Image/TemplateImageGeneratorTest.php index 62f317fc..51534512 100644 --- a/tests/Unit/Services/Image/TemplateImageGeneratorTest.php +++ b/tests/Unit/Services/Image/TemplateImageGeneratorTest.php @@ -73,8 +73,8 @@ ); if ($result !== null) { - expect($result->path)->toStartWith('ai-images/')->toEndWith('.webp'); - expect($result->sourceMeta) + expect($result['path'])->toStartWith('ai-images/')->toEndWith('.webp'); + expect($result['source_meta']) ->toHaveKey('keywords') ->toHaveKey('style', 'illustration') ->toHaveKey('model', 'gpt-image-2')