trypost/app/Support/PostStatusRules.php

89 lines
2.3 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
namespace App\Support;
use App\Enums\Post\Status as PostStatus;
use App\Models\Post;
fix: keep post drafts unscheduled by default (#209) * fix: keep post drafts unscheduled by default * Align schedule validation and keep drafts unscheduled. Require scheduled_at only when status is scheduled and the post has no usable future schedule. Share that rule across web, API, and MCP, keep create without a date as null, and preserve the legacy date → 09:00 UTC fallback. * Polish schedule validation typing and tests. Type requiresExplicitSchedule status as ?string, reuse a local status variable in request/tool validation, tighten the web reject assertion, and collapse overlapping MCP unscheduled-create cases. * Centralize status helper in post update validation. Reuse the typed status() helper across FormRequests and the already-parsed $status in UpdatePostTool so schedule checks stay consistent and less noisy. * Share scheduled_at update rules across web, API, and MCP. Centralize schedule validation in PostStatusRules, normalize status parsing in one place, and align past-schedule coverage across entry points. * Cover the full unscheduled-draft checklist in Pest. Add feature coverage for null/past schedule rejection, explicit scheduling, draft saves, publish-now without a schedule, calendar exclusion, and 09:00 UTC date defaults across web, API, and MCP. * Remove normalizeStatus helper. Keep the inline is_string check at the few call sites that read raw request status before validation — no shared wrapper needed. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop is_string status guards from schedule validation. Accept mixed status in PostStatusRules and rely on strict comparisons with Rule::requiredIf / Rule::when — malformed input simply does not match. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Paulo Castellano <paulo@castellanos.llc>
2026-08-01 20:39:18 +00:00
use Illuminate\Validation\Rule;
fix: keep post drafts unscheduled by default (#209) * fix: keep post drafts unscheduled by default * Align schedule validation and keep drafts unscheduled. Require scheduled_at only when status is scheduled and the post has no usable future schedule. Share that rule across web, API, and MCP, keep create without a date as null, and preserve the legacy date → 09:00 UTC fallback. * Polish schedule validation typing and tests. Type requiresExplicitSchedule status as ?string, reuse a local status variable in request/tool validation, tighten the web reject assertion, and collapse overlapping MCP unscheduled-create cases. * Centralize status helper in post update validation. Reuse the typed status() helper across FormRequests and the already-parsed $status in UpdatePostTool so schedule checks stay consistent and less noisy. * Share scheduled_at update rules across web, API, and MCP. Centralize schedule validation in PostStatusRules, normalize status parsing in one place, and align past-schedule coverage across entry points. * Cover the full unscheduled-draft checklist in Pest. Add feature coverage for null/past schedule rejection, explicit scheduling, draft saves, publish-now without a schedule, calendar exclusion, and 09:00 UTC date defaults across web, API, and MCP. * Remove normalizeStatus helper. Keep the inline is_string check at the few call sites that read raw request status before validation — no shared wrapper needed. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop is_string status guards from schedule validation. Accept mixed status in PostStatusRules and rely on strict comparisons with Rule::requiredIf / Rule::when — malformed input simply does not match. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Paulo Castellano <paulo@castellanos.llc>
2026-08-01 20:39:18 +00:00
/**
* Shared post status helpers edit/delete gates plus the `scheduled_at`
* update contract used by web, API, and MCP so those entry points cannot drift.
*/
class PostStatusRules
{
private const EDIT_BLOCKED_MESSAGE_KEY = 'posts.cannot_edit_finalized';
/**
* Statuses where the post can no longer be edited.
*
* @var array<int, PostStatus>
*/
private const EDIT_BLOCKED_STATUSES = [
PostStatus::Published,
PostStatus::PartiallyPublished,
PostStatus::Failed,
PostStatus::Publishing,
];
/**
* Statuses where the post can no longer be deleted.
*
* @var array<int, PostStatus>
*/
private const DELETE_BLOCKED_STATUSES = [
PostStatus::Publishing,
PostStatus::Published,
PostStatus::PartiallyPublished,
];
public static function blocksEditing(Post $post): bool
{
return in_array($post->status, self::EDIT_BLOCKED_STATUSES, true);
}
public static function blocksDeletion(Post $post): bool
{
return in_array($post->status, self::DELETE_BLOCKED_STATUSES, true);
}
public static function editBlockedMessage(): string
{
return __(self::EDIT_BLOCKED_MESSAGE_KEY);
}
fix: keep post drafts unscheduled by default (#209) * fix: keep post drafts unscheduled by default * Align schedule validation and keep drafts unscheduled. Require scheduled_at only when status is scheduled and the post has no usable future schedule. Share that rule across web, API, and MCP, keep create without a date as null, and preserve the legacy date → 09:00 UTC fallback. * Polish schedule validation typing and tests. Type requiresExplicitSchedule status as ?string, reuse a local status variable in request/tool validation, tighten the web reject assertion, and collapse overlapping MCP unscheduled-create cases. * Centralize status helper in post update validation. Reuse the typed status() helper across FormRequests and the already-parsed $status in UpdatePostTool so schedule checks stay consistent and less noisy. * Share scheduled_at update rules across web, API, and MCP. Centralize schedule validation in PostStatusRules, normalize status parsing in one place, and align past-schedule coverage across entry points. * Cover the full unscheduled-draft checklist in Pest. Add feature coverage for null/past schedule rejection, explicit scheduling, draft saves, publish-now without a schedule, calendar exclusion, and 09:00 UTC date defaults across web, API, and MCP. * Remove normalizeStatus helper. Keep the inline is_string check at the few call sites that read raw request status before validation — no shared wrapper needed. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop is_string status guards from schedule validation. Accept mixed status in PostStatusRules and rely on strict comparisons with Rule::requiredIf / Rule::when — malformed input simply does not match. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Paulo Castellano <paulo@castellanos.llc>
2026-08-01 20:39:18 +00:00
/**
* True when status is scheduled and the post has no future schedule to reuse.
*/
public static function requiresExplicitSchedule(?Post $post, mixed $status): bool
{
if ($status !== PostStatus::Scheduled->value) {
return false;
}
$existing = $post?->scheduled_at;
return $existing === null || $existing->isPast();
}
/**
* Validation rules for `scheduled_at` on post update (web, API, MCP).
*
* @return list<mixed>
*/
public static function scheduledAtRules(?Post $post, mixed $status): array
{
return [
Rule::requiredIf(fn (): bool => self::requiresExplicitSchedule($post, $status)),
'nullable',
'date',
Rule::when(
$status === PostStatus::Scheduled->value,
['after:now'],
),
];
}
}