Files
2026-03-30 10:36:08 -05:00

127 lines
4.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 支付成功后存储 email -> user_id 映射,供「已有账号登录」按邮箱查库
* 同时创建 PB 用户(邮箱+12345678以便后续用邮箱+密码登录
* 需 PocketBase 存在 vip_email_link 集合email, user_id
*/
import { NextRequest, NextResponse } from "next/server";
import { getPocketBaseConfig } from "@/config/services.config";
const VIP_EMAIL_LINK_COLLECTION = "vip_email_link";
const DEFAULT_PASSWORD = "12345678";
async function getAdminToken(): Promise<string | null> {
const email = process.env.POCKETBASE_EMAIL;
const password = process.env.POCKETBASE_PASSWORD;
if (!email || !password) return null;
const pb = getPocketBaseConfig();
const res = await fetch(`${pb.url}/api/admins/auth-with-password`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ identity: email, password }),
});
if (!res.ok) return null;
const data = await res.json();
return data?.token ?? null;
}
/** 若 PB 无该邮箱用户则创建(密码 12345678供后续邮箱登录 */
async function ensurePbUserExists(pbUrl: string, email: string): Promise<void> {
const registerRes = await fetch(`${pbUrl}/api/collections/users/records`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email,
password: DEFAULT_PASSWORD,
passwordConfirm: DEFAULT_PASSWORD,
live_allowed: true,
}),
});
if (registerRes.ok) {
console.log("[store-email-link] 已创建 PB 用户 email=%s", email);
return;
}
const err = await registerRes.json().catch(() => ({}));
const alreadyExists =
err?.data?.email?.message?.includes("unique") ||
err?.message?.toLowerCase?.().includes("already") ||
err?.message?.toLowerCase?.().includes("unique");
if (alreadyExists) {
console.log("[store-email-link] PB 用户已存在 email=%s", email);
return;
}
console.warn("[store-email-link] 创建 PB 用户失败 email=%s status=%s", email, registerRes.status, err);
}
export async function POST(request: NextRequest) {
const body = await request.json().catch(() => ({}));
const email = String(body?.email ?? "").trim().toLowerCase();
const userId = String(body?.user_id ?? body?.userId ?? "").trim();
console.log("[store-email-link] 请求 email=%s user_id=%s", email || "(空)", userId || "(空)");
if (!email || !userId) {
return NextResponse.json({ ok: false, error: "请填写邮箱" }, { status: 400 });
}
const adminToken = await getAdminToken();
if (!adminToken) {
return NextResponse.json({ ok: false, error: "服务未配置" }, { status: 500 });
}
const pb = getPocketBaseConfig();
try {
await ensurePbUserExists(pb.url, email);
} catch (e) {
console.warn("[store-email-link] ensurePbUserExists 异常:", e);
}
const safeEmail = email.replace(/"/g, '\\"');
const filter = `email = "${safeEmail}"`;
try {
const listRes = await fetch(
`${pb.url}/api/collections/${VIP_EMAIL_LINK_COLLECTION}/records?filter=${encodeURIComponent(filter)}&perPage=1`,
{ headers: { Authorization: `Bearer ${adminToken}` } }
);
if (!listRes.ok) {
return NextResponse.json({ ok: false, error: "vip_email_link 集合不存在,请在 PocketBase 中创建" }, { status: 500 });
}
const listData = await listRes.json().catch(() => ({}));
const items = listData?.items ?? [];
const recordBody: Record<string, string> = { email, user_id: userId };
if (/^user\d+$/.test(userId)) {
recordBody.anonymous_user_id = userId;
}
if (items.length > 0) {
await fetch(`${pb.url}/api/collections/${VIP_EMAIL_LINK_COLLECTION}/records/${items[0].id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${adminToken}`,
},
body: JSON.stringify(recordBody),
});
} else {
const createRes = await fetch(`${pb.url}/api/collections/${VIP_EMAIL_LINK_COLLECTION}/records`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${adminToken}`,
},
body: JSON.stringify(recordBody),
});
if (!createRes.ok) {
const err = await createRes.json().catch(() => ({}));
return NextResponse.json({ ok: false, error: err?.message || "写入失败" }, { status: 500 });
}
}
console.log("[store-email-link] 成功 email=%s user_id=%s", email, userId);
return NextResponse.json({ ok: true });
} catch (e) {
console.error("[store-email-link] error:", e);
return NextResponse.json(
{ ok: false, error: e instanceof Error ? e.message : "操作失败" },
{ status: 500 }
);
}
}