import { APIErrorCode, APIException, type User } from "../model/interfaces.ts"; import { db, isUserRow, userRowToApi } from "../model/db.ts"; import { notifyCommentOwnerLike } from "./notification-service.ts"; export function castCommentLike(commentId: string, userId: string): number { let likeCount: number; try { db.exec("BEGIN;"); db.prepare( `INSERT INTO comment_likes (comment_id, user_id, created_at) VALUES (?, ?, ?);`, ).run(commentId, userId, new Date().toISOString()); db.prepare( `UPDATE comments SET like_count = like_count + 1 WHERE id = ?;`, ).run(commentId); const row = db.prepare( `SELECT like_count FROM comments WHERE id = ?;`, ).get(commentId) as { like_count: number } | undefined; db.exec("COMMIT;"); likeCount = row?.like_count ?? 0; } catch (err) { try { db.exec("ROLLBACK;"); } catch { /* ignore if no active transaction */ } if (err instanceof Error && err.message.includes("UNIQUE constraint")) { throw new APIException( APIErrorCode.VALIDATION_ERROR, 409, "Already liked", ); } throw err; } // Notification is best-effort — must not prevent like_ack from being sent try { notifyCommentOwnerLike(userId, commentId); } catch { /* ignore */ } return likeCount; } export function removeCommentLike(commentId: string, userId: string): number { try { db.exec("BEGIN;"); const result = db.prepare( `DELETE FROM comment_likes WHERE comment_id = ? AND user_id = ?;`, ).run(commentId, userId); if (result.changes === 0) { db.exec("ROLLBACK;"); throw new APIException(APIErrorCode.NOT_FOUND, 404, "Like not found"); } db.prepare( `UPDATE comments SET like_count = like_count - 1 WHERE id = ?;`, ).run(commentId); const row = db.prepare( `SELECT like_count FROM comments WHERE id = ?;`, ).get(commentId) as { like_count: number } | undefined; db.exec("COMMIT;"); return row?.like_count ?? 0; } catch (err) { if (err instanceof APIException) throw err; db.exec("ROLLBACK;"); throw err; } } export function getUserCommentLikes(userId: string): string[] { const rows = db.prepare( `SELECT comment_id FROM comment_likes WHERE user_id = ?;`, ).all(userId) as { comment_id: string }[]; return rows.map((r) => r.comment_id); } export function getCommentLikers( commentId: string, page: number, limit: number, ): { items: User[]; total: number } { const offset = (page - 1) * limit; const totalRow = db.prepare( `SELECT COUNT(*) as count FROM comment_likes WHERE comment_id = ?;`, ).get(commentId) as { count: number } | undefined; const rawRows = db.prepare( `SELECT u.id, u.username, u.password_hash, u.is_admin, u.created_at, u.updated_at, u.avatar_mime, u.description, u.invited_by, u.email, i.username as invited_by_username FROM users u LEFT JOIN users i ON i.id = u.invited_by INNER JOIN comment_likes cl ON cl.comment_id = ? AND cl.user_id = u.id ORDER BY cl.created_at DESC LIMIT ? OFFSET ?;`, ).all(commentId, limit, offset); if (!rawRows.every(isUserRow)) { throw new APIException( APIErrorCode.SERVER_ERROR, 500, "Malformed user data", ); } return { items: rawRows.map(userRowToApi), total: totalRow?.count ?? 0 }; }