fix: handle smtp test action errors

This commit is contained in:
JetSprow
2026-04-29 11:16:05 +10:00
parent 4f56f419bb
commit 22a2e13c35
2 changed files with 98 additions and 26 deletions

View File

@@ -38,6 +38,30 @@ const settingsSchema = z.object({
smtpFromEmail: z.string().trim().email("发件邮箱格式不正确").optional().or(z.literal("")), smtpFromEmail: z.string().trim().email("发件邮箱格式不正确").optional().or(z.literal("")),
}); });
const smtpTestEmailSchema = z.string().trim().email("请输入正确的测试邮箱");
const smtpTestSettingsSchema = settingsSchema.extend({
smtpTestEmail: smtpTestEmailSchema,
});
type AdminSession = Awaited<ReturnType<typeof requireAdmin>>;
type SettingsActionResult = { ok: true } | { ok: false; error: string };
type SmtpTestActionResult =
| { ok: true }
| { ok: false; error: string; settingsSaved?: boolean };
function formatActionError(error: unknown, fallback: string) {
if (error instanceof z.ZodError) {
return error.issues[0]?.message ?? fallback;
}
if (error instanceof Error && error.message.trim()) {
return error.message.trim();
}
if (typeof error === "string" && error.trim()) {
return error.trim();
}
return fallback;
}
function buildSettingsUpdate(parsed: z.infer<typeof settingsSchema>, current: Awaited<ReturnType<typeof getAppConfig>>) { function buildSettingsUpdate(parsed: z.infer<typeof settingsSchema>, current: Awaited<ReturnType<typeof getAppConfig>>) {
const smtpEnabled = parsed.smtpEnabled === "true"; const smtpEnabled = parsed.smtpEnabled === "true";
const emailVerificationRequired = parsed.emailVerificationRequired === "true"; const emailVerificationRequired = parsed.emailVerificationRequired === "true";
@@ -87,9 +111,22 @@ function buildSettingsUpdate(parsed: z.infer<typeof settingsSchema>, current: Aw
return next; return next;
} }
export async function saveAppSettings(formData: FormData) { function revalidateSettingsViews() {
const session = await requireAdmin(); revalidatePath("/admin/settings");
const parsed = settingsSchema.parse(Object.fromEntries(formData)); revalidatePath("/login");
revalidatePath("/register");
revalidatePath("/dashboard");
revalidatePath("/subscriptions");
revalidatePath("/admin/nodes");
revalidatePath("/account");
revalidatePath("/admin/commerce");
}
async function persistAppSettings(
session: AdminSession,
parsed: z.infer<typeof settingsSchema>,
message: string,
) {
const current = await getAppConfig(); const current = await getAppConfig();
const next = buildSettingsUpdate(parsed, current); const next = buildSettingsUpdate(parsed, current);
@@ -105,26 +142,49 @@ export async function saveAppSettings(formData: FormData) {
targetType: "AppConfig", targetType: "AppConfig",
targetId: current.id, targetId: current.id,
targetLabel: next.siteName, targetLabel: next.siteName,
message: "更新系统设置", message,
}); });
revalidatePath("/admin/settings"); revalidateSettingsViews();
revalidatePath("/login");
revalidatePath("/register"); return next;
revalidatePath("/dashboard");
revalidatePath("/subscriptions");
revalidatePath("/admin/nodes");
revalidatePath("/account");
revalidatePath("/admin/commerce");
} }
export async function saveAppSettings(formData: FormData): Promise<SettingsActionResult> {
const smtpTestSchema = z.object({ try {
smtpTestEmail: z.string().trim().email("请输入正确的测试邮箱"), const session = await requireAdmin();
}); const parsed = settingsSchema.parse(Object.fromEntries(formData));
await persistAppSettings(session, parsed, "更新系统设置");
export async function sendSmtpTestMessage(formData: FormData) { return { ok: true };
await requireAdmin(); } catch (error) {
const parsed = smtpTestSchema.parse(Object.fromEntries(formData)); return { ok: false, error: formatActionError(error, "保存失败") };
await sendSmtpTestEmail(parsed.smtpTestEmail); }
}
export async function testSmtpSettings(formData: FormData): Promise<SmtpTestActionResult> {
let parsed: z.infer<typeof smtpTestSettingsSchema>;
let next: Awaited<ReturnType<typeof persistAppSettings>>;
try {
const session = await requireAdmin();
parsed = smtpTestSettingsSchema.parse(Object.fromEntries(formData));
next = await persistAppSettings(session, parsed, "测试发信前更新系统设置");
} catch (error) {
return { ok: false, error: formatActionError(error, "测试邮件发送失败") };
}
if (!next.smtpEnabled) {
return { ok: false, settingsSaved: true, error: "测试发信前请先开启邮件服务" };
}
try {
await sendSmtpTestEmail(parsed.smtpTestEmail);
return { ok: true };
} catch (error) {
return {
ok: false,
settingsSaved: true,
error: formatActionError(error, "请检查 SMTP 配置后重试"),
};
}
} }

View File

@@ -6,7 +6,7 @@ import { Button, buttonVariants } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { saveAppSettings, sendSmtpTestMessage } from "@/actions/admin/settings"; import { saveAppSettings, testSmtpSettings } from "@/actions/admin/settings";
import { toast } from "sonner"; import { toast } from "sonner";
import { getErrorMessage } from "@/lib/errors"; import { getErrorMessage } from "@/lib/errors";
@@ -52,7 +52,11 @@ export function SettingsForm({ config, coupons }: { config: AppConfig; coupons:
async function handleSubmit(formData: FormData) { async function handleSubmit(formData: FormData) {
setSaving(true); setSaving(true);
try { try {
await saveAppSettings(formData); const result = await saveAppSettings(formData);
if (!result.ok) {
toast.error(result.error);
return;
}
toast.success("设置已保存"); toast.success("设置已保存");
} catch (error) { } catch (error) {
toast.error(getErrorMessage(error, "保存失败")); toast.error(getErrorMessage(error, "保存失败"));
@@ -67,8 +71,16 @@ export function SettingsForm({ config, coupons }: { config: AppConfig; coupons:
setTestingEmail(true); setTestingEmail(true);
try { try {
await sendSmtpTestMessage(new FormData(form)); const result = await testSmtpSettings(new FormData(form));
toast.success("测试邮件已发送"); if (!result.ok) {
toast.error(
result.settingsSaved
? `设置已保存,但测试邮件没有发出:${result.error}`
: result.error,
);
return;
}
toast.success("设置已保存,测试邮件已发送");
} catch (error) { } catch (error) {
toast.error(getErrorMessage(error, "测试邮件发送失败")); toast.error(getErrorMessage(error, "测试邮件发送失败"));
} finally { } finally {
@@ -208,7 +220,7 @@ export function SettingsForm({ config, coupons }: { config: AppConfig; coupons:
<Mail className="size-4 text-primary" /> SMTP <Mail className="size-4 text-primary" /> SMTP
</div> </div>
<p className="text-xs leading-5 text-muted-foreground"> <p className="text-xs leading-5 text-muted-foreground">
使 SMTP
</p> </p>
<div className="grid gap-5 md:grid-cols-3"> <div className="grid gap-5 md:grid-cols-3">
<div className="space-y-2"> <div className="space-y-2">