Files
gitlab-instance-0a899031_di…/app/api/vip/check/route.ts
2026-03-13 04:41:51 -05:00

94 lines
3.4 KiB
TypeScript
Raw 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.
import { NextRequest, NextResponse } from "next/server";
import { getPocketBaseConfig, SITE_ID, VIP_MASTER_SITE_ID } from "@/config/services.config";
const COOKIE_NAME = "pb_session";
async function getAdminToken(): Promise<string | null> {
const email = process.env.POCKETBASE_EMAIL || process.env.PB_ADMIN_EMAIL;
const password = process.env.POCKETBASE_PASSWORD || process.env.PB_ADMIN_PASSWORD;
if (!email || !password) return null;
const pb = getPocketBaseConfig();
const paths = ["/api/admins/auth-with-password", "/api/collections/_superusers/auth-with-password"];
for (const path of paths) {
const res = await fetch(`${pb.url}${path}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ identity: email, password }),
});
if (res.ok) {
const data = await res.json();
if (data?.token) return data.token;
}
}
return null;
}
/** 检查单条 site_vip 是否有效 */
function isValidVip(record: { expires_at?: string | null } | null): boolean {
if (!record) return false;
const now = new Date().toISOString();
return !(record.expires_at && record.expires_at < now);
}
export async function GET(request: NextRequest) {
const token = await getAdminToken();
if (!token) return NextResponse.json({ ok: true, vip: false });
let userId: string | null = null;
// 优先用邮箱查Header 传 email更可靠
const email = request.nextUrl.searchParams.get("email")?.trim();
if (email) {
const pb = getPocketBaseConfig();
const filter = `email="${email.replace(/"/g, '\\"')}"`;
const collections = [pb.usersCollection, "_pb_users_auth_"];
for (const coll of collections) {
const userRes = await fetch(
`${pb.url}/api/collections/${coll}/records?filter=${encodeURIComponent(filter)}&perPage=1`,
{ headers: { Authorization: `Bearer ${token}` } }
);
if (userRes.ok) {
const userData = await userRes.json();
userId = userData?.items?.[0]?.id ?? null;
if (userId) break;
}
}
}
// 无邮箱或未查到:回退到 cookie 中的 userId
if (!userId) {
const cookie = request.cookies.get(COOKIE_NAME)?.value;
if (!cookie) return NextResponse.json({ ok: true, vip: false });
try {
const payload = JSON.parse(Buffer.from(cookie, "base64url").toString("utf-8"));
userId = payload?.userId ?? null;
} catch {
return NextResponse.json({ ok: true, vip: false });
}
}
if (!userId) return NextResponse.json({ ok: true, vip: false });
try {
const pb = getPocketBaseConfig();
// 1) 本专题站 VIPsite_id = SITE_ID2) vip 站 master VIPsite_id = vip可解锁所有 digital 专题站
const filter = `user_id = "${userId}" && (site_id = "${SITE_ID}" || site_id = "${VIP_MASTER_SITE_ID}")`;
const res = await fetch(
`${pb.url}/api/collections/site_vip/records?filter=${encodeURIComponent(filter)}&perPage=2&sort=-expires_at`,
{ headers: { Authorization: `Bearer ${token}` } }
);
if (!res.ok) return NextResponse.json({ ok: true, vip: false });
const data = await res.json();
const items = data?.items ?? [];
const record = items.find((r: { expires_at?: string | null }) => isValidVip(r));
const vip = !!record;
return NextResponse.json({
ok: true,
vip,
expires_at: record?.expires_at ?? null,
});
} catch {
return NextResponse.json({ ok: true, vip: false });
}
}