You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

16 KiB

Comment Email Config Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Add comment email infrastructure with admin SMTP config, guest anonymous email rules, per-user notification preference, and admin test-send capability without blocking comment creation.

Architecture: Extend the existing config registry (global + user) for mail settings and notify preferences, then wire those values into comment creation and notification service logic. Keep notification sending best-effort and side-effect-safe: comment write succeeds even if email send fails. Reuse existing /api/config/*, profile page, and comment service patterns.

Tech Stack: Nuxt 3, Nitro server routes, Drizzle (SQLite), Bun test, existing config service in server/service/config.


File Structure Map

  • Config domain
    • Modify server/service/config/registry.ts for new config keys and validation.
    • Modify app/composables/useGlobalConfig.ts for global config typing updates.
    • Modify app/pages/me/admin/config/index.vue for SMTP form and test-send action.
    • Create server/api/config/global/comment-email-test.post.ts for admin test email.
  • Comment domain
    • Modify packages/drizzle-pkg/database/sqlite/schema/content.ts to add guest email fields.
    • Create a migration under packages/drizzle-pkg/migrations.
    • Modify server/service/post-comments/index.ts to validate/store guest email + anonymous.
    • Modify app/components/PostComments.vue to collect guest email + anonymous toggle and render user hints.
  • Notification domain
    • Create server/service/comment-notify/index.ts for send gating + dispatch.
    • Integrate notify trigger in comment create flow routes.
  • User preference
    • Modify app/pages/me/profile/index.vue and /api/config/me usage to add commentNotifyEnabled.
  • Tests
    • Modify/create tests in server/utils/post-comment-guest.test.ts, comment service tests, and config validation tests.

Task 1: Add Config Keys and Validation

Files:

  • Modify: server/service/config/registry.ts

  • Test: server/service/config/registry.test.ts (create if absent)

  • Step 1: Write the failing validation tests

import { describe, expect, test } from "bun:test";
import { validateConfigValue } from "./registry";

describe("comment mail config validation", () => {
  test("accepts valid from email", () => {
    expect(validateConfigValue("commentMailFromEmail", "sender@example.com")).toBe(true);
  });

  test("rejects invalid from email", () => {
    expect(validateConfigValue("commentMailFromEmail", "bad-email")).toBe(false);
  });

  test("accepts comment notify default", () => {
    expect(validateConfigValue("commentNotifyEnabled", true)).toBe(true);
  });
});
  • Step 2: Run test to verify it fails

Run: bun test server/service/config/registry.test.ts
Expected: FAIL with unknown key/type validation mismatch.

  • Step 3: Implement minimal registry entries
commentEmailNotifyEnabled: defineConfig<boolean>({ key: "commentEmailNotifyEnabled", scope: "global", valueType: "boolean", defaultValue: false, userOverridable: false }),
commentMailFromEmail: defineConfig<string>({
  key: "commentMailFromEmail",
  scope: "global",
  valueType: "string",
  defaultValue: "",
  userOverridable: false,
  validate: (value: string) => !value.trim() || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim()),
}),
commentSmtpHost: defineConfig<string>({ key: "commentSmtpHost", scope: "global", valueType: "string", defaultValue: "", userOverridable: false }),
commentSmtpPort: defineConfig<number>({
  key: "commentSmtpPort",
  scope: "global",
  valueType: "number",
  defaultValue: 465,
  userOverridable: false,
  validate: (value: number) => Number.isInteger(value) && value >= 1 && value <= 65535,
}),
commentSmtpSecure: defineConfig<boolean>({ key: "commentSmtpSecure", scope: "global", valueType: "boolean", defaultValue: true, userOverridable: false }),
commentSmtpUser: defineConfig<string>({ key: "commentSmtpUser", scope: "global", valueType: "string", defaultValue: "", userOverridable: false }),
commentSmtpPass: defineConfig<string>({ key: "commentSmtpPass", scope: "global", valueType: "string", defaultValue: "", userOverridable: false }),
commentNotifyEnabled: defineConfig<boolean>({ key: "commentNotifyEnabled", scope: "both", valueType: "boolean", defaultValue: true, userOverridable: true }),
  • Step 4: Run test to verify it passes

Run: bun test server/service/config/registry.test.ts
Expected: PASS.

  • Step 5: Commit
git add server/service/config/registry.ts server/service/config/registry.test.ts
git commit -m "feat(config): add comment email and notify preference keys"

Task 2: Extend Admin Global Config UI and Global Composable

Files:

  • Modify: app/composables/useGlobalConfig.ts

  • Modify: app/pages/me/admin/config/index.vue

  • Step 1: Write a focused UI behavior test (or component-level assertion)

// Pseudocode if no mounted test infra exists yet:
// assert payload includes commentEmailNotifyEnabled/commentSmtpHost/commentSmtpPort/commentSmtpSecure/commentSmtpUser/commentSmtpPass/commentMailFromEmail
  • Step 2: Run existing frontend checks

Run: bun run typecheck
Expected: FAIL due to missing new config fields in types.

  • Step 3: Implement minimal type + form changes
// useGlobalConfig.ts
export type GlobalConfig = {
  siteName: string;
  allowRegister: boolean;
  commentEmailNotifyEnabled: boolean;
  commentMailFromEmail: string;
  commentSmtpHost: string;
  commentSmtpPort: number;
  commentSmtpSecure: boolean;
  commentSmtpUser: string;
};
<!-- me/admin/config/index.vue -->
<UFormField label="评论邮件通知总开关"><UCheckbox v-model="commentEmailNotifyEnabled" label="开启" /></UFormField>
<UFormField label="发件人邮箱"><UInput v-model="commentMailFromEmail" placeholder="noreply@example.com" /></UFormField>
<UFormField label="SMTP Host"><UInput v-model="commentSmtpHost" /></UFormField>
<UFormField label="SMTP Port"><UInput v-model.number="commentSmtpPort" type="number" /></UFormField>
<UFormField label="SMTP Secure"><UCheckbox v-model="commentSmtpSecure" label="TLS/SSL" /></UFormField>
<UFormField label="SMTP User"><UInput v-model="commentSmtpUser" /></UFormField>
<UFormField label="SMTP Password"><UInput v-model="commentSmtpPass" type="password" autocomplete="new-password" /></UFormField>
  • Step 4: Re-run checks

Run: bun run typecheck
Expected: PASS for touched types/components.

  • Step 5: Commit
git add app/composables/useGlobalConfig.ts app/pages/me/admin/config/index.vue
git commit -m "feat(admin-config): add comment mail smtp settings form"

Task 3: Add Admin Test Send Endpoint and Button

Files:

  • Create: server/api/config/global/comment-email-test.post.ts

  • Modify: app/pages/me/admin/config/index.vue

  • Create/Modify Test: server/api/config/global/comment-email-test.post.test.ts

  • Step 1: Write failing API tests

test("returns 400 when smtp config incomplete", async () => {
  // setup admin session + empty smtp config
  // call POST /api/config/global/comment-email-test
  // expect 400
});
  • Step 2: Run targeted test

Run: bun test server/api/config/global/comment-email-test.post.test.ts
Expected: FAIL with route missing.

  • Step 3: Implement endpoint and UI action
// comment-email-test.post.ts
await requireAdmin(event);
const user = await event.context.auth.requireUser();
const cfg = await getCommentMailConfig(event.context.config);
assertMailConfigReady(cfg);
if (!user.email) throw createError({ statusCode: 400, statusMessage: "管理员未配置邮箱" });
await sendCommentTestMail({ to: user.email, cfg });
return R.success({ ok: true });
<UButton :loading="testingMail" variant="soft" @click="testSendMail">发送测试邮件</UButton>
  • Step 4: Run test again

Run: bun test server/api/config/global/comment-email-test.post.test.ts
Expected: PASS.

  • Step 5: Commit
git add server/api/config/global/comment-email-test.post.ts server/api/config/global/comment-email-test.post.test.ts app/pages/me/admin/config/index.vue
git commit -m "feat(mail): add admin comment email test-send endpoint"

Task 4: Add DB Fields for Guest Email and Anonymous Flag

Files:

  • Modify: packages/drizzle-pkg/database/sqlite/schema/content.ts

  • Create: packages/drizzle-pkg/migrations/0008_comment_guest_email.sql (use next available index)

  • Modify: packages/drizzle-pkg/migrations/meta/_journal.json (if migration tooling requires)

  • Step 1: Write failing service test for guest email rules

test("guest non-anonymous must provide email", async () => {
  await expect(createComment({
    postId, ownerUserId, parentId: null, viewer: null,
    guestDisplayName: "访客", guestIsAnonymous: false, guestEmail: "",
    body: "hello",
  })).rejects.toThrow("请填写邮箱");
});
  • Step 2: Run test to confirm fail

Run: bun test server/service/post-comments/index.test.ts
Expected: FAIL due to unknown fields / missing validation.

  • Step 3: Add schema + migration
guestEmail: text("guest_email"),
guestIsAnonymous: integer("guest_is_anonymous", { mode: "boolean" }).notNull().default(false),
ALTER TABLE post_comments ADD COLUMN guest_email text;
ALTER TABLE post_comments ADD COLUMN guest_is_anonymous integer DEFAULT 0 NOT NULL;
  • Step 4: Run migration/test checks

Run: bun test server/service/post-comments/index.test.ts
Expected: still FAIL until service is implemented (this is expected at this stage).

  • Step 5: Commit
git add packages/drizzle-pkg/database/sqlite/schema/content.ts packages/drizzle-pkg/migrations/0008_comment_guest_email.sql packages/drizzle-pkg/migrations/meta/_journal.json
git commit -m "feat(db): add guest email and anonymous fields for comments"

Task 5: Implement Comment Service Validation and Persist New Fields

Files:

  • Modify: server/service/post-comments/index.ts

  • Modify: server/utils/post-comment-guest.ts

  • Modify: server/utils/post-comment-guest.test.ts

  • Modify: app/components/PostComments.vue

  • Step 1: Expand failing validation tests

test("anonymous guest can omit email", () => {
  expect(validateGuestCommentEmail("", true)).toBe("");
});

test("non-anonymous guest rejects invalid email", () => {
  expect(() => validateGuestCommentEmail("abc", false)).toThrow(GuestCommentValidationError);
});
  • Step 2: Run guest validation tests

Run: bun test server/utils/post-comment-guest.test.ts
Expected: FAIL with validateGuestCommentEmail undefined.

  • Step 3: Implement validation and form payload
export function validateGuestCommentEmail(raw: string, isAnonymous: boolean): string {
  const t = raw.trim();
  if (isAnonymous) return t;
  if (!t) throw new GuestCommentValidationError("请填写邮箱");
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)) throw new GuestCommentValidationError("邮箱格式不正确");
  return t;
}
<UCheckbox v-model="guestIsAnonymous" label="匿名评论(可不填邮箱)" />
<UInput v-model="guestEmail" :required="!guestIsAnonymous" placeholder="你的邮箱" />
  • Step 4: Run tests and typecheck

Run: bun test server/utils/post-comment-guest.test.ts && bun run typecheck
Expected: PASS.

  • Step 5: Commit
git add server/service/post-comments/index.ts server/utils/post-comment-guest.ts server/utils/post-comment-guest.test.ts app/components/PostComments.vue
git commit -m "feat(comments): enforce guest email rules with anonymous override"

Task 6: Add User Notification Preference in Profile

Files:

  • Modify: app/pages/me/profile/index.vue

  • Modify: server/service/config/registry.ts (if not already included in Task 1)

  • Step 1: Write failing profile load/save assertion

// verify /api/config/me result drives state.commentNotifyEnabled and save writes key
  • Step 2: Run typecheck before implementation

Run: bun run typecheck
Expected: FAIL due to missing commentNotifyEnabled state/binding.

  • Step 3: Implement minimal state + save wiring
state.commentNotifyEnabled = typeof cfg.commentNotifyEnabled === "boolean" ? cfg.commentNotifyEnabled : true;
await fetchData('/api/config/me', {
  method: 'PUT',
  notify: false,
  body: { key: 'commentNotifyEnabled', value: state.commentNotifyEnabled },
});
<UCheckbox v-model="state.commentNotifyEnabled" label="接收评论邮件通知" />
  • Step 4: Re-run typecheck

Run: bun run typecheck
Expected: PASS.

  • Step 5: Commit
git add app/pages/me/profile/index.vue
git commit -m "feat(profile): add comment email notify preference toggle"

Task 7: Implement Notification Dispatch on Comment Create

Files:

  • Create: server/service/comment-notify/index.ts

  • Modify: server/api/public/profile/[publicSlug]/posts/[postSlug]/comments.post.ts

  • Modify: server/api/public/unlisted/[publicSlug]/[shareToken]/comments.post.ts

  • Create/Modify Tests: server/service/comment-notify/index.test.ts

  • Step 1: Write failing notify gating tests

test("skips when global switch off", async () => {
  // expect no send call
});

test("skips when target user has notify disabled", async () => {
  // expect no send call
});
  • Step 2: Run targeted tests

Run: bun test server/service/comment-notify/index.test.ts
Expected: FAIL (service missing).

  • Step 3: Implement best-effort notify service
export async function notifyOnCommentReply(input: { postId: number; commentId: number; actorUserId: number | null; parentId: number | null }) {
  if (!input.parentId) return;
  // load target comment author -> user
  // check global config + smtp readiness + user pref + user email
  // send and catch errors with logger.error(...)
}
  • Step 4: Integrate into POST handlers
const id = await createComment(...);
void notifyOnCommentReply({ postId, commentId: id, actorUserId: viewer?.id ?? null, parentId: body.parentId ?? null });
return R.success({ id });
  • Step 5: Run tests

Run: bun test server/service/comment-notify/index.test.ts
Expected: PASS.

  • Step 6: Commit
git add server/service/comment-notify/index.ts server/service/comment-notify/index.test.ts server/api/public/profile/[publicSlug]/posts/[postSlug]/comments.post.ts server/api/public/unlisted/[publicSlug]/[shareToken]/comments.post.ts
git commit -m "feat(notify): send best-effort comment reply emails"

Task 8: End-to-End Verification and Documentation Update

Files:

  • Modify (if needed): docs/superpowers/specs/2026-04-20-comment-email-config-design.md

  • Modify/Create: any README/admin docs that mention config keys

  • Step 1: Run full relevant test suite

Run: bun test server/utils/post-comment-guest.test.ts server/service/comment-notify/index.test.ts server/service/config/registry.test.ts
Expected: PASS.

  • Step 2: Run static verification

Run: bun run typecheck
Expected: PASS.

  • Step 3: Manually verify key flows

Run app and validate:

  1. Admin saves SMTP config and test-send succeeds.
  2. Guest comment requires email unless anonymous checked.
  3. Logged-in user without email can comment but sees warning.
  4. User can toggle notify preference and it affects send decision.
  • Step 4: Commit verification/doc touch-ups
git add docs/superpowers/specs/2026-04-20-comment-email-config-design.md
git commit -m "docs(comment-email): align spec notes with implementation checks"

Self-Review

  • Spec coverage: All approved requirements are mapped to tasks: global SMTP config, guest anonymous/email rule, user notify preference default true, non-blocking send, and admin test-send.
  • Placeholder scan: No TODO/TBD placeholders remain; each code step has concrete snippet and command.
  • Type consistency: Config keys use a consistent prefix (comment*) across registry, UI, and notification service tasks.