Nested comments and public post IDs

Date: 2026-04-04

Nested comments

Added one level of replies to comments. The backend enforces a depth limit of 1 — you can reply to a top-level comment, but not to a reply. Attempting to do so returns a 400.

The schema change is simple:

ALTER TABLE comments ADD COLUMN parent_id INTEGER REFERENCES comments(id) ON DELETE CASCADE;

The repository fetches top-level comments first, then batch-fetches all replies in a single query using WHERE parent_id IN ($1, $2, ...) with dynamically built placeholders. This avoids N+1 — one query for roots, one for all their children.

On the Flutter side, CommentItem renders replies with 56px left indentation and a 2px vertical rule. The Reply button only appears on top-level comments. When tapping Reply, a "Replying to @username" banner appears above the comment input field and the text field gets autofocus.

Public UUIDs for posts

The integer primary key for posts was leaking into public URLs (/posts/42) and file storage paths (/uploads/posts/42/). Added a public_id UUID column with a database default:

ALTER TABLE posts ADD COLUMN public_id UUID NOT NULL DEFAULT gen_random_uuid();
CREATE UNIQUE INDEX idx_posts_public_id ON posts(public_id);

The integer PK stays for internal joins and sub-resources (likes, comments). The UUID is used everywhere else: GET/DELETE /posts/{publicId}, media file paths, and all Flutter navigation. The GetByPublicID repository method shares a getPost() helper with GetByID to avoid duplicating the join query.

A PLAN.md TODO tracks the remaining work: existing media files are still stored under the old integer paths, and UUIDs could be base62-encoded (~22 chars vs 36) for shorter URLs.

← All devlog entries