FT reference: FT343 (NENE2-FT/threadlog) — Two-level threaded comment system with tombstone deletion (content replaced with [deleted]), reply depth enforcement, post-scoped isolation, and reply-to-deleted prevention, 14 tests / 40+ assertions PASS.
This guide shows how to build a comment system with one level of replies: root comments can receive replies, but replies cannot be replied to (maximum depth = 1). Deleted comments are tombstoned, preserving the thread structure.
CREATE TABLE comments ( id INTEGER PRIMARY KEY AUTOINCREMENT, post_id TEXT NOT NULL, -- opaque post identifier parent_id INTEGER REFERENCES comments(id), -- NULL = root comment author TEXT NOT NULL, content TEXT NOT NULL, deleted INTEGER NOT NULL DEFAULT 0, deleted_at TEXT, created_at TEXT NOT NULL);
1 2 3 4 5 6 7 8 9 10
parent_id IS NULL = root comment; parent_id IS NOT NULL = reply.
public function canReceiveReply(int $commentId): bool{ $row = $this->findById($commentId); if ($row === null) { throw new CommentNotFoundException($commentId); } if ($row['deleted']) { throw new CommentDeletedException($commentId); } // Only root comments (parent_id = null) can have replies return $row['parent_id'] === null;}
How-to: Threaded Comments API
This guide shows how to build a comment system with one level of replies: root comments can receive replies, but replies cannot be replied to (maximum depth = 1). Deleted comments are tombstoned, preserving the thread structure.
Schema
2
3
4
5
6
7
8
9
10
parent_id IS NULL= root comment;parent_id IS NOT NULL= reply.Endpoints
POST/posts/{postId}/commentsGET/posts/{postId}/commentsGET/posts/{postId}/comments/{id}POST/posts/{postId}/comments/{id}/repliesDELETE/posts/{postId}/comments/{id}Create Root Comment
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
List Comments
2
3
4
5
6
7
8
9
10
11
12
13
14
Comments are scoped to
post_id.post-1's comments never appear inpost-2's list.Get Single Comment
2
3
4
5
6
7
8
9
10
11
12
Add Reply (Max Depth = 1)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Depth Check Implementation
2
3
4
5
6
7
8
9
10
11
12
Return 409 when
canReceiveReply()returns false.Tombstone Deletion
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Tombstoning preserves thread structure. Replies remain visible even after the parent is deleted.
2
3
4
5
6
7
Tombstone SQL
2
3
4
5
What NOT to do
detailis betterpost_idscope in queries