trypost/app/Services/Automation/GenerateNodeValidator.php
Paulo Castellano d35cc5277f Enforce carousel min slides in Generate and expose min_media_count on API/MCP.
GenerateNodeValidator and the Generate UI now respect ContentType::minMediaCount, and content-type listings share accept/min flags via toListingArray().

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 23:51:10 -03:00

81 lines
2.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Automation;
use App\Enums\PostPlatform\ContentType;
/**
* Backend mirror of the Generate node's frontend compliance: validates that the
* number of AI images intended for each selected account fits that account's
* content-type media rules. Uses the ContentType enum as the single source of
* truth (same `maxMediaCount`/`supportsImage`/`requiresMedia` the publish flow
* uses) and reuses the post editor's compliance messages so the wording matches
* exactly. AI image generation is capped at MAX_GENERATED_IMAGES regardless of
* how many a platform technically allows.
*/
final class GenerateNodeValidator
{
public const MAX_GENERATED_IMAGES = 10;
/**
* First compliance issue for a generate node's config, or null when valid.
*
* @param array<string, mixed> $config
*/
public function issueFor(array $config): ?string
{
$accounts = data_get($config, 'accounts');
if (! is_array($accounts) || $accounts === []) {
return null;
}
// Single source of truth: 0 = text-only, 1 = single image, 2+ = carousel.
$imageCount = (int) data_get($config, 'target_slide_count', 1);
foreach ($accounts as $entry) {
$contentType = ContentType::tryFrom((string) data_get($entry, 'content_type'));
if (! $contentType instanceof ContentType) {
continue;
}
$issue = $this->issueForAccount($contentType, $imageCount);
if ($issue !== null) {
return $issue;
}
}
return null;
}
private function issueForAccount(ContentType $contentType, int $imageCount): ?string
{
// Generate only produces images — video-only formats (Reel, Video Pin, …)
// can never be satisfied by this node.
if (! $contentType->supportsImage()) {
return __('automations.errors.generate_image_format_required');
}
$min = $contentType->minMediaCount();
if ($min > 0 && $imageCount < $min) {
return __('posts.edit.compliance.too_few_files', ['min' => (string) $min]);
}
if ($contentType->requiresMedia() && $imageCount === 0) {
return __('posts.edit.compliance.requires_media');
}
$max = min(self::MAX_GENERATED_IMAGES, $contentType->maxMediaCount());
if ($imageCount > $max) {
return __('posts.edit.compliance.too_many_files', ['max' => (string) $max]);
}
return null;
}
}