Add Persistent Comment Threads
Integrate a full‑stack commenting and threading feature into the existing app with real database persistence and UI.
The route
6 steps to Done
- 01
Create comment and thread data models
Define persistent storage for comments and their threads.
Preview prompt + verify gate ▾ Hide ▴
1. Open the project's ORM schema file (e.g., prisma/schema.prisma, models/*.js). 2. Add a `Comment` model/table with the following columns: `id` (primary key, UUID or auto‑increment), `content` (text, required), `authorId` (foreign key to Users, required), `createdAt` (timestamp default now), `updatedAt` (timestamp default now, on update), `parentCommentId` (nullable foreign key to Comment.id for replies), `threadId` (foreign key to Thread.id, required). 3. Add a `Thread` model/table with columns: `id` (primary key), `entityId` (foreign key to the object the thread belongs to, e.g., Post.id), `createdAt` (timestamp default now). 4. Define the proper relations (one‑to‑many Thread→Comments, self‑referencing one‑to‑many Comment→Comment). 5. Generate a migration script using the project's standard CLI (e.g., `prisma migrate dev`, `sequelize db:migrate`). 6. Run the migration and verify the new tables exist via the database console.
- ✓ Comment table exists with all required columns.
- ✓ Thread table exists with a link to the parent entity.
- ✓ Foreign‑key constraints are correctly defined.
- ✓ Migration runs cleanly and reports success.
- 02
Implement comment & thread API endpoints
Expose CRUD operations for comments and threads over HTTP.
Preview prompt + verify gate ▾ Hide ▴
1. In the API folder, add a new file `comments.js` (or similar). 2. Implement the following routes: `POST /api/comments` – validates body (content, authorId, threadId, optional parentCommentId), creates a Comment record, returns the created comment with 201. `GET /api/comments?threadId={id}` – fetches all comments for a thread, including nested replies, ordered by createdAt. `PUT /api/comments/:id` – verifies the requester is the comment author, updates content, updates `updatedAt`, returns the updated comment. `DELETE /api/comments/:id` – verifies author, deletes comment (cascade delete its child replies or set their parent to null). 3. Add a `POST /api/threads` endpoint that receives an `entityId` (e.g., postId) and creates a Thread record. 4. Secure all routes with existing auth middleware. 5. Export the router and register it in the main server file. 6. Write unit tests (using Jest/Mocha) that cover each route and assert proper DB persistence.
- ✓ POST /api/comments creates a comment in the DB.
- ✓ GET /api/comments?threadId= returns a JSON array of comments.
- ✓ PUT /api/comments/:id updates the comment content.
- ✓ DELETE /api/comments/:id removes the comment.
- ✓ POST /api/threads creates a thread linked to an entity.
- 03
Add backend logic for threaded replies
Enable nested comments and prevent cyclic parent relationships.
Preview prompt + verify gate ▾ Hide ▴
1. In `services/commentService.js` (or the equivalent), add a function `createComment({content, authorId, threadId, parentCommentId})` that: a) If parentCommentId is provided, verifies that the parent comment exists and belongs to the same thread. b) Checks that assigning the parent does not create a cycle (walk up the parent chain). c) Inserts the comment using the ORM and returns the saved record. 2. Add a function `getThreadComments(threadId)` that queries all comments for the thread and builds a nested JSON structure: each comment includes a `replies` array of its direct children. Use a single query followed by in‑memory tree construction to avoid N+1 queries. 3. Export these functions and update the API routes to call them instead of raw ORM calls. 4. Write integration tests that: a) Create a comment, then a reply, then a second‑level reply; b) Attempt to set a comment's parent to one of its descendants and expect a validation error.
- ✓ createComment validates parent belongs to same thread.
- ✓ createComment rejects cyclic parent assignments.
- ✓ getThreadComments returns a correctly nested JSON tree.
- ✓ Integration tests pass for nesting and cycle detection.
- 04
Build React comment list component
Render a thread of comments with proper visual nesting.
Preview prompt + verify gate ▾ Hide ▴
1. In `components/CommentList.jsx` (or .tsx), import `useEffect` and `useState`. 2. Accept a `threadId` prop. 3. On mount, call `GET /api/comments?threadId={threadId}` to fetch the nested comment JSON produced by `getThreadComments`. 4. Store the data in state and render recursively: for each comment render author, content, timestamps, and a Reply button; if `replies` array exists, render a nested `CommentList` (or a `CommentItem` that maps its `replies`). 5. Apply indentation (e.g., margin-left) proportional to nesting depth. 6. Use existing UI library (e.g., Material‑UI) for consistency. 7. Export the component and add it to the relevant page (e.g., PostDetail page) passing the correct `threadId`. 8. Ensure the component handles loading and error states without showing placeholders.
- ✓ Component fetches comments from the API on mount.
- ✓ Comments display author, content, and timestamps.
- ✓ Nested replies are indented and rendered recursively.
- ✓ Component shows a loading indicator while fetching.
- ✓ No placeholder or dummy data is shown.
- 05
Create comment input & reply UI
Allow users to add new comments and replies directly from the UI.
Preview prompt + verify gate ▾ Hide ▴
1. Create `components/CommentForm.jsx` that accepts props: `threadId`, optional `parentCommentId`, and an `onSuccess` callback. 2. Use controlled inputs (or React Hook Form) for a textarea named `content`. 3. On submit, POST to `/api/comments` with body `{content, authorId: currentUser.id, threadId, parentCommentId}`. 4. Show validation errors if content is empty. 5. After a successful response, clear the form and invoke `onSuccess` so the comment list refreshes. 6. In `CommentItem` (the individual comment renderer), render a Reply button that toggles the visibility of `CommentForm` with the appropriate `parentCommentId`. 7. Ensure the form disables the submit button while awaiting the network response. 8. Style the form to match the app's design system.
- ✓ CommentForm sends a real POST request to /api/comments.
- ✓ Form validates non‑empty content.
- ✓ Reply button opens a nested CommentForm under the target comment.
- ✓ After submission, the new comment appears in CommentList.
- ✓ Submit button disables while request is pending.
- 06
End‑to‑end verification and persistence test
Confirm that comments survive page reloads and are shared across users.
Preview prompt + verify gate ▾ Hide ▴
1. Write a Cypress test `comments_spec.js` that: a) Logs in as User A, navigates to a page with a thread, creates a top‑level comment via the UI, asserts that the comment text appears. b) Reloads the page, asserts the comment still appears. c) Logs out, logs in as User B, navigates to the same thread, asserts the comment from User A is visible. d) User B adds a reply, asserts it appears under User A's comment. e) Reloads and confirms both comments persist. 2. Ensure the test uses real API endpoints and not mock intercepts. 3. Run the test suite and confirm all steps pass. 4. Manually verify in the database console that the comment rows have correct `authorId`, `threadId`, and `parentCommentId` values.
- ✓ Comment created by User A persists after page reload.
- ✓ User B can see User A's comment.
- ✓ Reply by User B appears nested under User A's comment.
- ✓ Database rows contain correct foreign keys.