import { APIErrorCode, APIException } from "../model/interfaces.ts"; import { db } from "../model/db.ts"; import { notifyDumpOwnerUpvote } from "./notification-service.ts"; export function castVote(dumpId: string, userId: string): number { let voteCount: number; try { db.exec("BEGIN;"); db.prepare( `INSERT INTO votes (dump_id, user_id, created_at) VALUES (?, ?, ?);`, ).run(dumpId, userId, new Date().toISOString()); db.prepare( `UPDATE dumps SET vote_count = vote_count + 1 WHERE id = ?;`, ).run(dumpId); const row = db.prepare( `SELECT vote_count FROM dumps WHERE id = ?;`, ).get(dumpId) as { vote_count: number } | undefined; db.exec("COMMIT;"); voteCount = row?.vote_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 voted", ); } throw err; } // Notification is best-effort — must not prevent vote_ack from being sent try { notifyDumpOwnerUpvote(userId, dumpId); } catch { /* ignore */ } return voteCount; } export function removeVote(dumpId: string, userId: string): number { try { db.exec("BEGIN;"); const result = db.prepare( `DELETE FROM votes WHERE dump_id = ? AND user_id = ?;`, ).run(dumpId, userId); if (result.changes === 0) { db.exec("ROLLBACK;"); throw new APIException(APIErrorCode.NOT_FOUND, 404, "Vote not found"); } db.prepare( `UPDATE dumps SET vote_count = vote_count - 1 WHERE id = ?;`, ).run(dumpId); const row = db.prepare( `SELECT vote_count FROM dumps WHERE id = ?;`, ).get(dumpId) as { vote_count: number } | undefined; db.exec("COMMIT;"); return row?.vote_count ?? 0; } catch (err) { if (err instanceof APIException) throw err; db.exec("ROLLBACK;"); throw err; } } export function getUserVotes(userId: string): string[] { const rows = db.prepare( `SELECT dump_id FROM votes WHERE user_id = ?;`, ).all(userId) as { dump_id: string }[]; return rows.map((r) => r.dump_id); }