feat : ask for noti
CI / Verify (push) Successful in 1m50s
CI / Build immutable images and deploy (push) Successful in 2m59s

This commit is contained in:
2026-10-08 02:23:59 +07:00 Unverified
parent 5812d99715
commit 9bc710c94b
34 changed files with 10913 additions and 201 deletions
+10 -2
View File
@@ -5,7 +5,8 @@ import { notifyCommentChange } from "./events";
import { and, asc, desc, eq, inArray, isNull, lt, or, sql, type SQL } from "drizzle-orm";
import { alias } from "drizzle-orm/pg-core";
import { getDb, type Database } from "@/db";
import { commentThreads, comments, commentRevisions, commentAttachments, commentRevisionAttachments, commentReactions, catalogCharacters, guides, stygianSchedules, users } from "@/db/schema";
import { commentThreads, comments, commentRevisions, commentAttachments, commentRevisionAttachments, commentReactions, catalogCharacters, guides, stygianSchedules, users, notifications } from "@/db/schema";
import { notifyNotificationChange } from "@/lib/notifications/events";
import { getMediaStorage, publicMediaUrl } from "@/lib/media/storage";
import { getCustomerSession } from "@/lib/auth/server";
import { isAuthorizedAdmin } from "@/lib/auth/authorization";
@@ -14,7 +15,7 @@ import { HttpError } from "@/lib/security/http";
import { decodeCursor, encodeCursor, parseTarget, uuidSchema } from "./validation";
import type { CommentHistoryItem, CommentImage, CommentItem, CommentPage, CommentViewer } from "./types";
type Reader = Pick<Database, "select" | "insert" | "update" | "delete" | "execute">;
type Reader = Pick<Database, "select" | "selectDistinct" | "insert" | "update" | "delete" | "execute">;
export async function getCommentViewer(): Promise<CommentViewer | null> {
const session = await getCustomerSession();
if (!session) return null;
@@ -199,11 +200,14 @@ export async function withCommentLock<T>(id: string, viewer: CommentViewer, task
export async function mutateComment(id: string, viewer: CommentViewer, action: "delete" | "reaction" | "moderation" | "heart", value?: number | boolean) {
let target = "";
let deletedImages: { objectKey: string }[] = [];
let notificationUsers: { userId: string }[] = [];
const result = await withCommentLock(id, viewer, async (tx, context) => {
target = context.destination.target;
const c = context.comment;
if (action === "moderation") {
requireCommentAdmin(viewer);
notificationUsers = await tx.selectDistinct({ userId: notifications.userId }).from(notifications)
.innerJoin(comments, eq(comments.id, notifications.commentId)).where(or(eq(comments.id, id), eq(comments.rootId, id)));
await tx.update(comments).set({ hidden: value as boolean }).where(eq(comments.id, id));
const [actor] = await tx.select({ id: users.id, name: users.name }).from(users).where(eq(users.id, viewer.id));
await writeAuditLog(tx, auditActor(actor), { action: value ? "comment.hidden" : "comment.restored", targetType: "comment", targetId: id,
@@ -221,6 +225,8 @@ export async function mutateComment(id: string, viewer: CommentViewer, action: "
) select id from descendants`;
deletedImages = await tx.select({ objectKey: commentAttachments.objectKey }).from(commentAttachments)
.where(sql`${commentAttachments.commentId} in (${descendants})`);
notificationUsers = await tx.selectDistinct({ userId: notifications.userId }).from(notifications)
.where(sql`${notifications.commentId} in (${descendants})`);
// Delete the entire subtree in one statement so self-referencing foreign keys remain valid.
// Revisions, attachment metadata, revision links, and reactions cascade automatically.
await tx.delete(comments).where(sql`${comments.id} in (${descendants})`);
@@ -234,6 +240,8 @@ export async function mutateComment(id: string, viewer: CommentViewer, action: "
.onConflictDoUpdate({ target: [commentReactions.commentId, commentReactions.userId], set: { value: value as number } });
}
});
for (let offset = 0; offset < notificationUsers.length; offset += 10)
await Promise.all(notificationUsers.slice(offset, offset + 10).map(user => notifyNotificationChange(user.userId)));
await notifyCommentChange(target);
if (deletedImages.length) {
try {