Browse Source

fix(comment): include context in notify failure logs

Pass postId and commentId into reply notification flow and log structured failure context with receiverUserId and reason while keeping best-effort behavior unchanged.

Made-with: Cursor
main
npmrun 3 weeks ago
parent
commit
5bd43eb835
  1. 2
      server/api/public/profile/[publicSlug]/posts/[postSlug]/comments.post.ts
  2. 2
      server/api/public/unlisted/[publicSlug]/[shareToken]/comments.post.ts
  3. 19
      server/service/comment-notify/index.test.ts
  4. 22
      server/service/comment-notify/index.ts

2
server/api/public/profile/[publicSlug]/posts/[postSlug]/comments.post.ts

@ -47,6 +47,8 @@ export default defineEventHandler(async (event) => {
}); });
void notifyReplyCommentCreated({ void notifyReplyCommentCreated({
postId: ctx.id,
commentId: newCommentId,
parentId: parsed.parentId, parentId: parsed.parentId,
actorUserId: viewer?.id ?? null, actorUserId: viewer?.id ?? null,
replyBody: parsed.body, replyBody: parsed.body,

2
server/api/public/unlisted/[publicSlug]/[shareToken]/comments.post.ts

@ -47,6 +47,8 @@ export default defineEventHandler(async (event) => {
}); });
void notifyReplyCommentCreated({ void notifyReplyCommentCreated({
postId: ctx.id,
commentId: newCommentId,
parentId: parsed.parentId, parentId: parsed.parentId,
actorUserId: viewer?.id ?? null, actorUserId: viewer?.id ?? null,
replyBody: parsed.body, replyBody: parsed.body,

19
server/service/comment-notify/index.test.ts

@ -45,7 +45,7 @@ describe("notifyReplyCommentCreated", () => {
smtpPass: "smtp-pass", smtpPass: "smtp-pass",
}); });
await notifyReplyCommentCreated({ parentId: 100, actorUserId: 1, replyBody: "hello" }, deps); await notifyReplyCommentCreated({ postId: 10, commentId: 200, parentId: 100, actorUserId: 1, replyBody: "hello" }, deps);
expect(state.sendMailCalled).toBe(false); expect(state.sendMailCalled).toBe(false);
}); });
@ -54,7 +54,7 @@ describe("notifyReplyCommentCreated", () => {
const { deps, state } = createDeps(); const { deps, state } = createDeps();
deps.getReceiverNotifyEnabled = async () => false; deps.getReceiverNotifyEnabled = async () => false;
await notifyReplyCommentCreated({ parentId: 100, actorUserId: 1, replyBody: "hello" }, deps); await notifyReplyCommentCreated({ postId: 10, commentId: 201, parentId: 100, actorUserId: 1, replyBody: "hello" }, deps);
expect(state.sendMailCalled).toBe(false); expect(state.sendMailCalled).toBe(false);
}); });
@ -62,7 +62,7 @@ describe("notifyReplyCommentCreated", () => {
test("无 parentId -> 不发送", async () => { test("无 parentId -> 不发送", async () => {
const { deps, state } = createDeps(); const { deps, state } = createDeps();
await notifyReplyCommentCreated({ parentId: null, actorUserId: 1, replyBody: "hello" }, deps); await notifyReplyCommentCreated({ postId: 10, commentId: 202, parentId: null, actorUserId: 1, replyBody: "hello" }, deps);
expect(state.sendMailCalled).toBe(false); expect(state.sendMailCalled).toBe(false);
}); });
@ -71,8 +71,19 @@ describe("notifyReplyCommentCreated", () => {
const { deps, state } = createDeps(); const { deps, state } = createDeps();
deps.getParentAuthorUserId = async () => 1; deps.getParentAuthorUserId = async () => 1;
await notifyReplyCommentCreated({ parentId: 100, actorUserId: 1, replyBody: "hello" }, deps); await notifyReplyCommentCreated({ postId: 10, commentId: 203, parentId: 100, actorUserId: 1, replyBody: "hello" }, deps);
expect(state.sendMailCalled).toBe(false); expect(state.sendMailCalled).toBe(false);
}); });
test("发送异常 -> 不抛出(best-effort)", async () => {
const { deps } = createDeps();
deps.sendMail = async () => {
throw new Error("smtp send failed");
};
await expect(
notifyReplyCommentCreated({ postId: 10, commentId: 204, parentId: 100, actorUserId: 1, replyBody: "hello" }, deps),
).resolves.toBeUndefined();
});
}); });

22
server/service/comment-notify/index.ts

@ -31,6 +31,16 @@ function hasValue(value: string): boolean {
return value.trim().length > 0; return value.trim().length > 0;
} }
function getReason(error: unknown): string {
if (error instanceof Error && hasValue(error.message)) {
return error.message;
}
if (typeof error === "string" && hasValue(error)) {
return error;
}
return "unknown";
}
function isSmtpConfigReady(config: NotifyGlobalConfig): boolean { function isSmtpConfigReady(config: NotifyGlobalConfig): boolean {
return ( return (
config.enabled && config.enabled &&
@ -138,18 +148,21 @@ function getDefaultDeps(): NotifyDeps {
export async function notifyReplyCommentCreated( export async function notifyReplyCommentCreated(
input: { input: {
postId: number;
commentId: number;
parentId: number | null; parentId: number | null;
actorUserId: number | null; actorUserId: number | null;
replyBody: string; replyBody: string;
}, },
deps: NotifyDeps = getDefaultDeps(), deps: NotifyDeps = getDefaultDeps(),
): Promise<void> { ): Promise<void> {
let receiverUserId: number | null = null;
try { try {
if (input.parentId == null) { if (input.parentId == null) {
return; return;
} }
const receiverUserId = await deps.getParentAuthorUserId(input.parentId); receiverUserId = await deps.getParentAuthorUserId(input.parentId);
if (receiverUserId == null) { if (receiverUserId == null) {
return; return;
} }
@ -179,6 +192,11 @@ export async function notifyReplyCommentCreated(
replyBody: input.replyBody, replyBody: input.replyBody,
}); });
} catch (error) { } catch (error) {
logger.warn("failed to send reply comment notify email", error); logger.warn("failed to send reply comment notify email", {
postId: input.postId,
commentId: input.commentId,
receiverUserId,
reason: getReason(error),
});
} }
} }

Loading…
Cancel
Save