v2: global player, infinite scroll, image picker, threaded comments
This commit is contained in:
78
api/services/comment-service.ts
Normal file
78
api/services/comment-service.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import {
|
||||
APIErrorCode,
|
||||
APIException,
|
||||
type Comment,
|
||||
} from "../model/interfaces.ts";
|
||||
import {
|
||||
commentRowToApi,
|
||||
type CommentRow,
|
||||
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, unknown>)) {
|
||||
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) };
|
||||
}
|
||||
Reference in New Issue
Block a user