import { all, chunks, first, MAX_IN_LIST, placeholders } from "./db";
import { REACTION_CONTENTS } from "~/lib/reactions";
import type { ReactionContent, ReactionSubject, ReactionSummary } from "~/lib/reactions";
function isReaction(value: string): value is ReactionContent {
return REACTION_CONTENTS.some((content) => content === value);
}
/** Aggregated reactions per subject id, with whether the viewer has reacted. */
export async function reactionSummaries(
subjectType: ReactionSubject,
ids: string[],
viewerId: string | null,
): Promise<Map<string, ReactionSummary[]>> {
const out = new Map<string, ReactionSummary[]>();
const unique = [...new Set(ids)];
for (const part of chunks(unique, MAX_IN_LIST - 2)) {
const rows = await all<{ subject_id: string; content: string; count: number; mine: number }>(
`SELECT subject_id, content, COUNT(*) AS count, MAX(user_id = ?) AS mine FROM reactions
WHERE subject_type = ? AND subject_id IN (${placeholders(part.length)}) GROUP BY subject_id, content`,
viewerId ?? "",
subjectType,
...part,
);
for (const row of rows) {
if (!isReaction(row.content)) continue;
const list = out.get(row.subject_id) ?? [];
list.push({ content: row.content, count: row.count, viewerReacted: row.mine === 1 });
out.set(row.subject_id, list);
}
}
for (const list of out.values())
list.sort((left, right) => REACTION_CONTENTS.indexOf(left.content) - REACTION_CONTENTS.indexOf(right.content));
return out;
}
/** Repository that owns a reactable subject, used to authorize reactions. */
export async function subjectRepoId(subjectType: ReactionSubject, subjectId: string): Promise<string | null> {
const queries: Record<ReactionSubject, string> = {
issue: "SELECT repo_id FROM issues WHERE id = ?",