-
Notifications
You must be signed in to change notification settings - Fork 53
Open
Description
Description
DELETE /api/v1/posts/:id returns {"success":true,"message":"Post deleted"} but the post is not deleted.
Steps to Reproduce
- Create a post via
POST /api/v1/posts - Delete via
DELETE /api/v1/posts/:id - Observe success response
GET /api/v1/posts/:id— post still exists
Expected Behavior
Post is deleted (or soft-deleted) and no longer retrievable.
Actual Behavior
Returns success, post unchanged.
Root Cause
PostService.delete() lacks safeguards:
await queryOne('DELETE FROM posts WHERE id = $1', [postId]);- No result verification — doesn't check
rowCount - Schema mismatch — schema has
is_deleted BOOLEANfor soft deletes, code does hard delete - RLS interaction — if DELETE policy is missing (see issue: Security: Missing RLS DELETE policy on posts table (Supabase) #73), query succeeds with zero rows affected
Proposed Fix
Two options:
Option A: Fix hard delete (minimal)
const result = await query(
'DELETE FROM posts WHERE id = $1 RETURNING id',
[postId]
);
if (result.rowCount === 0) {
throw new Error('Failed to delete post');
}Pros: Minimal change
Cons: Loses audit trail, cascades comments, is_deleted column unused
Option B: Implement soft delete
const result = await queryOne(
'UPDATE posts SET is_deleted = true, updated_at = NOW() WHERE id = $1 RETURNING id',
[postId]
);
if (!result) {
throw new Error('Failed to delete post');
}Also requires adding WHERE is_deleted = false to:
findById()getFeed()getPersonalizedFeed()- Same for
CommentService.js
Pros: Uses schema as designed, preserves audit trail, works with UPDATE RLS policy
Cons: More code changes
Additional Context
An ORM with soft-delete middleware (Prisma, Drizzle) would prevent this bug class entirely. The codebase already has hand-rolled query helpers — might be worth the migration.
Metadata
Metadata
Assignees
Labels
No labels