bo5tym52p: List and create comments on post cards

This commit is contained in:
2026-10-19 14:29:07 -06:00
parent de535a500d
commit 5a33546af2
11 changed files with 596 additions and 2 deletions
+34
View File
@@ -0,0 +1,34 @@
import { NextResponse, type NextRequest } from "next/server";
import { createComment, listComments } from "@/lib/comments";
import { SESSION_COOKIE } from "@/lib/cookie";
// GET /api/posts/:id/comments -> feed GET /api/v1/posts/{id}/comments?limit=20&page=1
// POST /api/posts/:id/comments -> feed POST /api/v1/posts/{id}/comments
// body { content } -> feed { content, type: "text", status: "published" }
// 401 { error: "sign_in" } without a session.
export const runtime = "nodejs";
type Ctx = { params: Promise<{ id: string }> };
export async function GET(req: NextRequest, ctx: Ctx) {
const sessionId = req.cookies.get(SESSION_COOKIE)?.value;
const { id } = await ctx.params;
const r = await listComments(sessionId, id);
if (!r.ok) return NextResponse.json({ error: r.error }, { status: r.status });
return NextResponse.json({ comments: r.comments });
}
export async function POST(req: NextRequest, ctx: Ctx) {
const sessionId = req.cookies.get(SESSION_COOKIE)?.value;
const { id } = await ctx.params;
let content: unknown = null;
try {
content = ((await req.json()) as { content?: unknown })?.content;
} catch {
content = null;
}
const r = await createComment(sessionId, id, content);
if (!r.ok) return NextResponse.json({ error: r.error }, { status: r.status });
return NextResponse.json({ comment: r.comment });
}
+14
View File
@@ -47,3 +47,17 @@ button:disabled { opacity: .5; cursor: not-allowed; }
.composer textarea { font: inherit; width: 100%; padding: 12px; border: 1px solid #999; border-radius: 0; resize: vertical; background: #fff; color: var(--ink); }
.composer textarea:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.composer .composer-post { align-self: flex-end; margin: 0; padding: 10px 24px; border-radius: 0; }
.comments { display: flex; flex-direction: column; gap: 10px; padding-top: 10px; border-top: 1px solid #eee; }
.comment-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.comment p { margin: 2px 0 0; font-size: 14px; white-space: pre-wrap; overflow-wrap: anywhere; }
.comment-head { display: flex; gap: 8px; align-items: baseline; font-size: 13px; }
.comment-head time { color: var(--grey); font-size: 12px; }
.comment.removed .comment-removed { color: var(--grey); font-style: italic; }
.comments-empty { margin: 0; color: var(--grey); font-size: 14px; }
.comment-error { margin: 0; color: #a8141b; font-size: 13px; }
.comment-form { display: flex; gap: 8px; }
.comment-form input { flex: 1; margin: 0; }
.comment-form button { margin: 0; }
.link-btn { background: transparent; color: inherit; text-decoration: underline; padding: 0; margin: 0; font-size: inherit; width: auto; }
.comments-toggle { background: transparent; color: var(--grey); padding: 4px 8px; margin: 0; font-size: 14px; font-weight: 400; width: auto; }