From e4f8833608c9f12e2aff4fb31c981e3ddef29abd Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Tue, 19 May 2026 14:54:04 -0300 Subject: [PATCH] fix(posts): allow deleting Failed posts + misc terminal-state polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add separate canDelete predicate in Index.vue that includes Failed — the previous EDITABLE_STATUSES gating hid the delete button for failed posts even though the backend allows deleting them. - Edit.vue Echo handler navigates to /show when an in-page real-time status update transitions the post into a read-only state, instead of leaving the user on a stuck readonly editor. - FacebookPublisher: stop using empty() for content checks (treats literal "0" as empty) — compare explicitly against null and "". - Update terminal-state error messages in API + MCP update tool to reflect the broadened guard (no longer Published-only). Adjust the matching test assertion. --- app/Http/Controllers/Api/PostController.php | 2 +- app/Mcp/Tools/Post/UpdatePostTool.php | 2 +- app/Services/Social/FacebookPublisher.php | 10 +++++----- resources/js/pages/posts/Edit.vue | 15 +++++++++------ resources/js/pages/posts/Index.vue | 4 +++- tests/Feature/Mcp/PostPublishToolTest.php | 2 +- 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/app/Http/Controllers/Api/PostController.php b/app/Http/Controllers/Api/PostController.php index c662d53d..bddaa328 100644 --- a/app/Http/Controllers/Api/PostController.php +++ b/app/Http/Controllers/Api/PostController.php @@ -69,7 +69,7 @@ public function update(UpdatePostRequest $request, Post $post): PostResource|Jso if (data_get($result, 'action') === PostAction::Finalized) { return response()->json( - ['message' => 'Cannot edit a published post.'], + ['message' => 'Cannot edit a post in a terminal state.'], Response::HTTP_UNPROCESSABLE_ENTITY ); } diff --git a/app/Mcp/Tools/Post/UpdatePostTool.php b/app/Mcp/Tools/Post/UpdatePostTool.php index 6062a722..d855c1b0 100644 --- a/app/Mcp/Tools/Post/UpdatePostTool.php +++ b/app/Mcp/Tools/Post/UpdatePostTool.php @@ -55,7 +55,7 @@ public function handle(Request $request): Response|ResponseFactory $result = UpdatePost::execute($workspace, $post, $payload); if (data_get($result, 'action') === PostAction::Finalized) { - return Response::error('Cannot edit a published post.'); + return Response::error('Cannot edit a post in a terminal state.'); } /** @var Post $updated */ diff --git a/app/Services/Social/FacebookPublisher.php b/app/Services/Social/FacebookPublisher.php index fdb7519b..51e351ef 100644 --- a/app/Services/Social/FacebookPublisher.php +++ b/app/Services/Social/FacebookPublisher.php @@ -49,7 +49,7 @@ private function publishPost(string $pageId, string $accessToken, ?string $conte { // Text only post if ($media->isEmpty()) { - if (empty($content)) { + if ($content === null || $content === '') { throw new \Exception('Facebook text posts require content. Please add text to your post.'); } @@ -107,7 +107,7 @@ private function publishSingleImagePost(string $pageId, string $accessToken, ?st 'access_token' => $accessToken, ]; - if (! empty($content)) { + if ($content !== null && $content !== '') { $payload['message'] = $content; } @@ -167,7 +167,7 @@ private function publishMultiImagePost(string $pageId, string $accessToken, ?str 'access_token' => $accessToken, ]; - if (! empty($content)) { + if ($content !== null && $content !== '') { $postData['message'] = $content; } @@ -201,7 +201,7 @@ private function publishVideoPost(string $pageId, string $accessToken, ?string $ 'access_token' => $accessToken, ]; - if (! empty($content)) { + if ($content !== null && $content !== '') { $payload['description'] = $content; } @@ -306,7 +306,7 @@ private function publishReel(string $pageId, string $accessToken, ?string $conte 'access_token' => $accessToken, ]; - if (! empty($content)) { + if ($content !== null && $content !== '') { $finishPayload['description'] = $content; } diff --git a/resources/js/pages/posts/Edit.vue b/resources/js/pages/posts/Edit.vue index d312cab1..1582bdda 100644 --- a/resources/js/pages/posts/Edit.vue +++ b/resources/js/pages/posts/Edit.vue @@ -20,7 +20,7 @@ import { ContentType } from '@/enums/content-type'; import { Platform } from '@/enums/platform'; import { PostStatus } from '@/types/post'; import AppLayout from '@/layouts/AppLayout.vue'; -import { destroy as destroyPost, update as updatePost } from '@/routes/app/posts'; +import { destroy as destroyPost, show as showPost, update as updatePost } from '@/routes/app/posts'; import type { PinterestBoard } from '@/types'; interface MediaItem { @@ -514,12 +514,15 @@ const unschedulePost = () => { submit(PostStatus.Draft); }; -// Echo: listen for real-time platform status updates. -// Event fires when any post_platform completes publishing (success or fail). -// Full reload of the post prop so the new status + post_platforms propagate and -// the overlay dismisses. usePostEcho(post.value.id, '.post.platform.status.updated', () => { - router.reload({ only: ['post'] }); + router.reload({ + only: ['post'], + onSuccess: () => { + if (READONLY_STATUSES.includes(post.value.status)) { + router.visit(showPost.url(post.value.id)); + } + }, + }); }); diff --git a/resources/js/pages/posts/Index.vue b/resources/js/pages/posts/Index.vue index ad70d20b..f99ac13a 100644 --- a/resources/js/pages/posts/Index.vue +++ b/resources/js/pages/posts/Index.vue @@ -138,7 +138,9 @@ const getPostPreview = (post: Post): string => post.content?.trim() || trans('calendar.no_content'); const EDITABLE_STATUSES: readonly string[] = [PostStatus.Draft, PostStatus.Scheduled]; +const DELETABLE_STATUSES: readonly string[] = [PostStatus.Draft, PostStatus.Scheduled, PostStatus.Failed]; const canEdit = (post: Post): boolean => EDITABLE_STATUSES.includes(post.status); +const canDelete = (post: Post): boolean => DELETABLE_STATUSES.includes(post.status); const postUrl = (post: Post): string => canEdit(post) ? editPost.url(post.id) : showPost.url(post.id); @@ -303,7 +305,7 @@ useWorkspaceEcho( {{ $t('posts.actions.copy_id') }} -