DELETE /api/public/v1/posts/{id} returns 500 instead of 404 whenever the id does not resolve to a live post — a nonexistent id, or one that has already been soft-deleted.
Cause
PostsService.getPost builds its return object guarding some fields against an empty result and not others:
const list = {
group: posts?.[0]?.group, // guarded
posts: await Promise.all((posts || []).map(...)), // guarded
integrationPicture: posts[0]?.integration?.picture, // guarded
integration: posts[0].integrationId, // NOT guarded <-- throws
settings: JSON.parse(posts[0].settings || '{}'), // NOT guarded
};
Three fields in the same object literal use ?. / || [], so the empty case was clearly anticipated. The next two lines dereference posts[0] directly. When posts is empty, posts[0] is undefined and .integrationId throws.
The public API controller then propagates it:
async deletePost(org, id) {
const getPostById = await this._postsService.getPost(org.id, id);
return this._postsService.deletePost(org.id, getPostById.group);
}
Reproduction
Any id that does not resolve — it does not need to have existed:
curl -X DELETE -H "Authorization: $POSTIZ_API_KEY" \
http://localhost:5000/api/public/v1/posts/thisidneverexisted
Observed: HTTP 500
ERROR [ExceptionsHandler] TypeError: Cannot read properties of undefined (reading 'integrationId')
at PostsService.getPost (dist/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.js:367:35)
at async PublicIntegrationsController.deletePost (dist/apps/backend/src/public-api/routes/v1/public.integrations.controller.js:160:29)
Expected: 404 Not Found.
Why it is worth fixing beyond the status code
Deleting an already-deleted post hits this, so a client retrying a delete — after a timeout, or reconciling its own state against Postiz — gets a 500 that reads as a server fault rather than "already gone". That makes the operation effectively non-idempotent from the caller's side, and the error text points at integrationId, which sends you looking at integrations rather than at a missing post.
We hit it reconciling ~1,300 stale drafts and spent a while attributing the 500 to our own credentials and then to our database networking before reading the trace.
Suggested fix
Guard the two remaining fields the same way as their neighbours, and let the controller return 404 on an empty result:
integration: posts?.[0]?.integrationId,
settings: JSON.parse(posts?.[0]?.settings || '{}'),
Happy to open a PR if that shape is agreeable.
Environment
ghcr.io/gitroomhq/postiz-app:latest, image sha256:785f97312f66a347f…
- self-hosted, Postgres + Temporal via docker compose
DELETE /api/public/v1/posts/{id}returns 500 instead of 404 whenever the id does not resolve to a live post — a nonexistent id, or one that has already been soft-deleted.Cause
PostsService.getPostbuilds its return object guarding some fields against an empty result and not others:Three fields in the same object literal use
?./|| [], so the empty case was clearly anticipated. The next two lines dereferenceposts[0]directly. Whenpostsis empty,posts[0]isundefinedand.integrationIdthrows.The public API controller then propagates it:
Reproduction
Any id that does not resolve — it does not need to have existed:
Observed:
HTTP 500Expected:
404 Not Found.Why it is worth fixing beyond the status code
Deleting an already-deleted post hits this, so a client retrying a delete — after a timeout, or reconciling its own state against Postiz — gets a 500 that reads as a server fault rather than "already gone". That makes the operation effectively non-idempotent from the caller's side, and the error text points at
integrationId, which sends you looking at integrations rather than at a missing post.We hit it reconciling ~1,300 stale drafts and spent a while attributing the 500 to our own credentials and then to our database networking before reading the trace.
Suggested fix
Guard the two remaining fields the same way as their neighbours, and let the controller return 404 on an empty result:
Happy to open a PR if that shape is agreeable.
Environment
ghcr.io/gitroomhq/postiz-app:latest, imagesha256:785f97312f66a347f…