mirror of
https://github.com/JetSprow/J-Board-Lite.git
synced 2026-05-01 01:14:10 +05:30
fix: handle smtp test action errors
This commit is contained in:
@@ -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> {
|
||||||
|
try {
|
||||||
|
const session = await requireAdmin();
|
||||||
|
const parsed = settingsSchema.parse(Object.fromEntries(formData));
|
||||||
|
await persistAppSettings(session, parsed, "更新系统设置");
|
||||||
|
return { ok: true };
|
||||||
|
} catch (error) {
|
||||||
|
return { ok: false, error: formatActionError(error, "保存失败") };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const smtpTestSchema = z.object({
|
export async function testSmtpSettings(formData: FormData): Promise<SmtpTestActionResult> {
|
||||||
smtpTestEmail: z.string().trim().email("请输入正确的测试邮箱"),
|
let parsed: z.infer<typeof smtpTestSettingsSchema>;
|
||||||
});
|
let next: Awaited<ReturnType<typeof persistAppSettings>>;
|
||||||
|
|
||||||
export async function sendSmtpTestMessage(formData: FormData) {
|
try {
|
||||||
await requireAdmin();
|
const session = await requireAdmin();
|
||||||
const parsed = smtpTestSchema.parse(Object.fromEntries(formData));
|
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);
|
await sendSmtpTestEmail(parsed.smtpTestEmail);
|
||||||
|
return { ok: true };
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
settingsSaved: true,
|
||||||
|
error: formatActionError(error, "请检查 SMTP 配置后重试"),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user