89 lines
2.5 KiB
TypeScript
89 lines
2.5 KiB
TypeScript
import {
|
|
APIErrorCode,
|
|
APIException,
|
|
type Comment,
|
|
} from "../model/interfaces.ts";
|
|
import { type SQLOutputValue } from "node:sqlite";
|
|
import {
|
|
type CommentRow,
|
|
commentRowToApi,
|
|
db,
|
|
isCommentRow,
|
|
} from "../model/db.ts";
|
|
|
|
const SELECT_COLS =
|
|
`c.id, c.dump_id, c.user_id, c.parent_id, c.body, c.created_at, c.deleted,
|
|
u.username as author_username, u.avatar_mime as author_avatar_mime`;
|
|
|
|
function fetchComment(commentId: string): Comment {
|
|
const row = db.prepare(
|
|
`SELECT ${SELECT_COLS} FROM comments c JOIN users u ON c.user_id = u.id WHERE c.id = ?;`,
|
|
).get(commentId);
|
|
if (!row || !isCommentRow(row as Record<string, SQLOutputValue>)) {
|
|
throw new APIException(APIErrorCode.NOT_FOUND, 404, "Comment not found");
|
|
}
|
|
return commentRowToApi(row as CommentRow);
|
|
}
|
|
|
|
export function getComments(dumpId: string): Comment[] {
|
|
const rows = db.prepare(
|
|
`SELECT ${SELECT_COLS} FROM comments c JOIN users u ON c.user_id = u.id
|
|
WHERE c.dump_id = ? ORDER BY c.created_at ASC;`,
|
|
).all(dumpId);
|
|
const typed = rows as Parameters<typeof isCommentRow>[0][];
|
|
if (!typed.every(isCommentRow)) {
|
|
throw new APIException(
|
|
APIErrorCode.SERVER_ERROR,
|
|
500,
|
|
"Malformed comment data",
|
|
);
|
|
}
|
|
return typed.map(commentRowToApi);
|
|
}
|
|
|
|
export function createComment(
|
|
dumpId: string,
|
|
userId: string,
|
|
body: string,
|
|
parentId?: string,
|
|
): Comment {
|
|
const id = crypto.randomUUID();
|
|
const createdAt = new Date();
|
|
db.prepare(
|
|
`INSERT INTO comments (id, dump_id, user_id, parent_id, body, created_at) VALUES (?, ?, ?, ?, ?, ?);`,
|
|
).run(
|
|
id,
|
|
dumpId,
|
|
userId,
|
|
parentId ?? null,
|
|
body.trim(),
|
|
createdAt.toISOString(),
|
|
);
|
|
return fetchComment(id);
|
|
}
|
|
|
|
export function deleteComment(
|
|
commentId: string,
|
|
requestingUserId: string,
|
|
isAdmin: boolean,
|
|
): { dumpId: string; isPrivate: boolean } {
|
|
const row = db.prepare(
|
|
`SELECT c.dump_id, d.is_private FROM comments c JOIN dumps d ON c.dump_id = d.id WHERE c.id = ?;`,
|
|
).get(commentId) as { dump_id: string; is_private: number } | undefined;
|
|
if (!row) {
|
|
throw new APIException(APIErrorCode.NOT_FOUND, 404, "Comment not found");
|
|
}
|
|
const comment = fetchComment(commentId);
|
|
if (comment.userId !== requestingUserId && !isAdmin) {
|
|
throw new APIException(
|
|
APIErrorCode.UNAUTHORIZED,
|
|
401,
|
|
"Not authorized to delete this comment",
|
|
);
|
|
}
|
|
db.prepare(`UPDATE comments SET deleted = 1, body = '' WHERE id = ?;`).run(
|
|
commentId,
|
|
);
|
|
return { dumpId: row.dump_id, isPrivate: Boolean(row.is_private) };
|
|
}
|