refactor: drop RenderedSlide DTO and content_language null fallbacks
- 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().
This commit is contained in:
parent
4f3d9ff015
commit
d97822c7e8
10 changed files with 29 additions and 41 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<string, mixed>} $rendered
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Image;
|
||||
|
||||
class RenderedSlide
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $sourceMeta
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $path,
|
||||
public readonly array $sourceMeta,
|
||||
) {}
|
||||
}
|
||||
|
|
@ -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<int, string> $imageKeywords
|
||||
* @return RenderedSlide|null The storage path + source meta, or null on failure.
|
||||
* @return array{path: string, source_meta: array<string, mixed>}|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,
|
||||
],
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
Loading…
Reference in a new issue