import log4js from "logger"; import nodemailer from "nodemailer"; const logger = log4js.getLogger("COMMENT_NOTIFY"); type NotifyGlobalConfig = { enabled: boolean; fromEmail: string; smtpHost: string; smtpPort: number; smtpSecure: boolean; smtpUser: string; smtpPass: string; }; type ReceiverProfile = { email: string | null; username: string | null; nickname: string | null; }; type NotifyDeps = { getParentAuthorUserId: (parentId: number) => Promise; getGlobalConfig: () => Promise; getReceiverNotifyEnabled: (userId: number) => Promise; getReceiverProfile: (userId: number) => Promise; sendMail: (input: { toEmail: string; fromEmail: string; replyBody: string }) => Promise; }; function hasValue(value: string): boolean { return value.trim().length > 0; } function isSmtpConfigReady(config: NotifyGlobalConfig): boolean { return ( config.enabled && hasValue(config.fromEmail) && hasValue(config.smtpHost) && hasValue(config.smtpUser) && hasValue(config.smtpPass) && Number.isInteger(config.smtpPort) && config.smtpPort >= 1 && config.smtpPort <= 65535 ); } async function defaultGetParentAuthorUserId(parentId: number): Promise { const { dbGlobal } = await import("drizzle-pkg/lib/db"); const { postComments } = await import("drizzle-pkg/lib/schema/content"); const { eq } = await import("drizzle-orm"); const [parent] = await dbGlobal .select({ authorUserId: postComments.authorUserId, }) .from(postComments) .where(eq(postComments.id, parentId)) .limit(1); return parent?.authorUserId ?? null; } async function defaultGetGlobalConfig(): Promise { const { getGlobalConfigValue } = await import("../config"); return { enabled: await getGlobalConfigValue("commentEmailNotifyEnabled"), fromEmail: await getGlobalConfigValue("commentMailFromEmail"), smtpHost: await getGlobalConfigValue("commentSmtpHost"), smtpPort: await getGlobalConfigValue("commentSmtpPort"), smtpSecure: await getGlobalConfigValue("commentSmtpSecure"), smtpUser: await getGlobalConfigValue("commentSmtpUser"), smtpPass: await getGlobalConfigValue("commentSmtpPass"), }; } async function defaultGetReceiverNotifyEnabled(userId: number): Promise { const { getMergedConfigValue } = await import("../config"); return getMergedConfigValue(userId, "commentNotifyEnabled"); } async function defaultGetReceiverProfile(userId: number): Promise { const { dbGlobal } = await import("drizzle-pkg/lib/db"); const { users } = await import("drizzle-pkg/lib/schema/auth"); const { eq } = await import("drizzle-orm"); const [user] = await dbGlobal .select({ email: users.email, username: users.username, nickname: users.nickname, }) .from(users) .where(eq(users.id, userId)) .limit(1); return user ?? null; } async function defaultSendMail(input: { toEmail: string; fromEmail: string; replyBody: string }): Promise { const { getGlobalConfigValue } = await import("../config"); const smtpHost = await getGlobalConfigValue("commentSmtpHost"); const smtpPort = await getGlobalConfigValue("commentSmtpPort"); const smtpSecure = await getGlobalConfigValue("commentSmtpSecure"); const smtpUser = await getGlobalConfigValue("commentSmtpUser"); const smtpPass = await getGlobalConfigValue("commentSmtpPass"); const transporter = nodemailer.createTransport({ host: smtpHost, port: smtpPort, secure: smtpSecure, connectionTimeout: 10_000, greetingTimeout: 10_000, socketTimeout: 15_000, auth: { user: smtpUser, pass: smtpPass, }, }); await transporter.sendMail({ from: input.fromEmail, to: input.toEmail.trim(), subject: "[Person Panel] 你收到一条评论回复", text: `你收到了一条新的评论回复。\n\n回复内容:\n${input.replyBody}\n`, }); } function getDefaultDeps(): NotifyDeps { return { getParentAuthorUserId: defaultGetParentAuthorUserId, getGlobalConfig: defaultGetGlobalConfig, getReceiverNotifyEnabled: defaultGetReceiverNotifyEnabled, getReceiverProfile: defaultGetReceiverProfile, sendMail: defaultSendMail, }; } export async function notifyReplyCommentCreated( input: { parentId: number | null; actorUserId: number | null; replyBody: string; }, deps: NotifyDeps = getDefaultDeps(), ): Promise { try { if (input.parentId == null) { return; } const receiverUserId = await deps.getParentAuthorUserId(input.parentId); if (receiverUserId == null) { return; } if (input.actorUserId != null && input.actorUserId === receiverUserId) { return; } const globalConfig = await deps.getGlobalConfig(); if (!isSmtpConfigReady(globalConfig)) { return; } const receiverNotifyEnabled = await deps.getReceiverNotifyEnabled(receiverUserId); if (!receiverNotifyEnabled) { return; } const receiver = await deps.getReceiverProfile(receiverUserId); if (!receiver?.email || !hasValue(receiver.email)) { return; } await deps.sendMail({ toEmail: receiver.email, fromEmail: globalConfig.fromEmail, replyBody: input.replyBody, }); } catch (error) { logger.warn("failed to send reply comment notify email", error); } }