import "server-only"; import { and, eq, isNull, ne, sql } from "drizzle-orm"; import { alias } from "drizzle-orm/pg-core"; import { getDb } from "@/db"; import { comments, commentThreads, commentReadState, guides } from "@/db/schema"; import { getCommentTarget, requireCommentAdmin } from "./repository"; import type { CommentViewer } from "./types"; export async function unreadCommentCounts(viewer: CommentViewer) { requireCommentAdmin(viewer); const global = alias(commentReadState, "global_read"); const local = alias(commentReadState, "target_read"); const target = sql`case when ${commentThreads.guideId} is not null then 'guide:' || ${commentThreads.guideId}::text else 'stygian:' || ${commentThreads.scheduleId}::text end`; const rows = await getDb().select({ target, count: sql`count(*)::int` }).from(comments) .innerJoin(commentThreads, eq(comments.threadId, commentThreads.id)) .leftJoin(guides, eq(guides.id, commentThreads.guideId)) .leftJoin(global, and(eq(global.userId, viewer.id), eq(global.target, "all"))) .leftJoin(local, and(eq(local.userId, viewer.id), eq(local.target, target))) .where(and(isNull(comments.deletedAt), isNull(guides.trashedAt), ne(comments.authorId, viewer.id), sql`${comments.createdAt} > greatest(coalesce(${global.seenAt}, 'epoch'::timestamptz), coalesce(${local.seenAt}, 'epoch'::timestamptz))`)) .groupBy(target); return { counts: Object.fromEntries(rows.map((row) => [row.target, row.count])), total: rows.reduce((sum, row) => sum + row.count, 0) }; } export async function markCommentsRead(viewer: CommentViewer, target: string) { requireCommentAdmin(viewer); if (target !== "all") await getCommentTarget(target, viewer); await getDb().insert(commentReadState).values({ userId: viewer.id, target }) .onConflictDoUpdate({ target: [commentReadState.userId, commentReadState.target], set: { seenAt: sql`now()` } }); }