feat(agent): update a post's settings via a tool and the public API#1752
Conversation
Adds a settings-only update path for a not-yet-published post (scheduled
or draft), reachable both by the agent (web-app chat / external MCP) and
by the public API:
- PostsService.updatePostSettings(orgId, postId, settings, method):
loads the post by (id, org), merges the passed keys into the existing
provider settings (only the passed keys change), runs the same
server-side validation as the dashboard / public create route, and
writes it in place. Content and publish date are left untouched, so
the running publish workflow is not restarted (type "update"). Refuses
a comment id, a published/past post, and settings that fail validation.
- postSettingsTool (agent / MCP): thin wrapper that turns the agent's
[{key,value}] settings into an object and calls the service, surfacing
errors back to the agent so it can retry.
- PUT /public/v1/posts/:id/settings: same service method, so the tool
and the endpoint share one path (Controller -> Service -> Repository).
keepGroup: the update keeps the post's group id stable instead of
rotating it (removed comments are swept by id), so a client holding the
old group (an open calendar) is not invalidated by an out-of-band
settings change. The dashboard keeps its rotate-and-sweep behavior.
Agent prompt: settings changes to an existing post are applied directly
with postSettingsTool; the agent must not open the populated modal for
an existing post (that only creates a new post, duplicating it), and
there is no delete capability.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Contribution-checker quality warning Heuristics that flagged:
If this is a genuine contribution, please add detail to your PR description and tighten the diff scope before reviewers look at it. |
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
|
📝 Docs PR: gitroomhq/postiz-docs#233 — documents the |
|
CLI counterpart: gitroomhq/postiz-agent#13 adds the |
| ); | ||
| } | ||
|
|
||
| if ( | ||
| root.state === 'QUEUE' && | ||
| dayjs.utc(root.publishDate).isBefore(dayjs.utc()) | ||
| ) { | ||
| throw new BadRequestException( | ||
| 'The publish time of this post already passed, it cannot be updated' | ||
| ); | ||
| } |
There was a problem hiding this comment.
Bug: A race condition in updatePostSettings allows updating settings for a post after it has been published, causing data inconsistency.
Severity: MEDIUM
Suggested Fix
To prevent the race condition, re-check the post's state immediately before the final upsert operation within the updatePostSettings function. Alternatively, wrap the state check and the update operation in a database transaction to ensure atomicity.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location:
libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts#L964-L977
Potential issue: The `updatePostSettings` function checks a post's state at the
beginning of its execution. However, it then performs several asynchronous operations
before finally writing the new settings to the database. A race condition can occur if a
separate workflow, `postWorkflowV105`, changes the post's state to `PUBLISHED` during
this time window. The final database `upsert` in `updatePostSettings` does not re-check
the state, leading to settings being updated on a post that has already been published.
This creates a logical inconsistency where the stored settings do not match the content
that was actually published.
Did we get this right? 👍 / 👎 to inform future reviews.
There was a problem hiding this comment.
Real TOCTOU but leaving as-is. It only affects a QUEUE post at the instant it publishes (drafts never publish, past-due QUEUE is already rejected), and worst case is stored settings not matching published content — no double-post or data loss. The same race already exists in the normal edit path via the shared createOrUpdatePost write, so guarding only this method would be incomplete. A transaction isn't right either, since the gap spans validatePosts' provider network calls. If we harden it, the fix is an atomic guarded write (update ... where state in (QUEUE, DRAFT)) in the shared path, tracked separately.
Why
Original requirement: a user had scheduled dozens of TikTok posts with the wrong settings and wanted them batch-updated by an agent. With
postsListTool(from the base PR) to list the posts andpostSettingsTool(this PR) to update each one, an agent can satisfy a single user request that reads as a batch update ("fix the settings on all my TikTok posts") by updating them one at a time.On the public API side, this aligns the existing endpoints with the new agent tools and adds the update capability as a dedicated endpoint where it was missing.
This narrows the previously-closed #1689 to just a settings update (per the "there should be only a settings update" feedback): the update tool is settings-only, and listing was split into the base PR.
Based on
feat/list-posts-tool(#1751, the List Posts PR) — review and merge that first. This PR's own diff is only the settings-update files; the update tool needspostsListToolto obtain the post id it operates on, so the two ship together.Changes
PostsService.updatePostSettings(orgId, postId, settings, method)— the one shared path. Loads the post by(id, org), merges the passed keys into the existing provider settings (only the passed keys change), re-runs the same validation as the dashboard / public create route, and writes in place. Content and publish date are untouched, so the running publish workflow is not restarted (type: 'update'). Rejects a comment id, a published/past post, and settings that fail validation.postSettingsTool(agent / MCP): thin wrapper over the service method; surfaces errors back to the agent so it can retry.PUT /public/v1/posts/:id/settings: same service method, so tool and endpoint share one path (Controller → Service → Repository).keepGroup: an out-of-band settings update keeps the post'sgroupid stable (removed comments are swept by id) so a client holding the old group (an open calendar) isn't invalidated. The dashboard keeps its rotate-and-sweep behavior.postSettingsTool(find the post withpostsListToolfirst); the agent must not open the populated modal for an existing post (that only creates a new post — a duplicate); there is no delete capability.Public API impact
PUT /public/v1/posts/:id/settings. Purely additive.Testing
Fully tested:
Related changes in other repos
postSettingsToolin the MCP "Available Tools" list. (PR link to be added.)🤖 Generated with Claude Code