diff --git a/app/api/vip/check-by-email/route.ts b/app/api/vip/check-by-email/route.ts index abe5b8b..bcfc9ca 100644 --- a/app/api/vip/check-by-email/route.ts +++ b/app/api/vip/check-by-email/route.ts @@ -32,6 +32,9 @@ type RecoveryResult = { source?: string; }; +const PAYJS_CHECK_BY_EMAIL_TIMEOUT = + Number(process.env.PAYJSAPI_CHECK_BY_EMAIL_TIMEOUT) || 5000; + function resolvePayApiUrl(request: NextRequest): string { const payConfig = getPaymentConfig(); const hostHeader = request.headers.get("host") || request.headers.get("x-forwarded-host"); @@ -58,7 +61,7 @@ async function checkVipByEmailFromPayjsapi( ...(userId ? { user_id: userId } : {}), }), cache: "no-store", - signal: AbortSignal.timeout(15000), + signal: AbortSignal.timeout(PAYJS_CHECK_BY_EMAIL_TIMEOUT), }); if (!res.ok) { return null; @@ -121,23 +124,26 @@ async function resolveRecovery( requestedAnonymousUserId?: string, payjsResult?: PayjsVipEmailResult | null ): Promise { - if ( - isAnonymousUserId(requestedAnonymousUserId) && - (await hasVipByUserId(adminToken, requestedAnonymousUserId)) - ) { - return { - vip: true, - matchedUserId: requestedAnonymousUserId, - anonymousUserId: requestedAnonymousUserId, - source: "request_user_id", - }; + if (isAnonymousUserId(requestedAnonymousUserId)) { + const requestUserHasVip = await hasVipByUserId(adminToken, requestedAnonymousUserId); + if (requestUserHasVip) { + return { + vip: true, + matchedUserId: requestedAnonymousUserId, + anonymousUserId: requestedAnonymousUserId, + source: "request_user_id", + }; + } } - const linkRecord = await findVipEmailLinkByEmail(adminToken, email); + const [linkRecord, fallbackAnonymousUserId] = await Promise.all([ + findVipEmailLinkByEmail(adminToken, email), + findAnonymousUserIdForUser(adminToken, pbUserId), + ]); const linkedAnonymousUserId = typeof linkRecord?.anonymous_user_id === "string" ? linkRecord.anonymous_user_id.trim() - : (await findAnonymousUserIdForUser(adminToken, pbUserId)) ?? ""; + : fallbackAnonymousUserId ?? ""; const linkedIds = Array.from( new Set( [ @@ -148,24 +154,30 @@ async function resolveRecovery( ) ); - for (const linkedId of linkedIds) { - if (!(await hasVipByUserId(adminToken, linkedId))) { - continue; + if (linkedIds.length) { + const linkedVipChecks = await Promise.all( + linkedIds.map(async (linkedId) => ({ + linkedId, + vip: await hasVipByUserId(adminToken, linkedId), + })) + ); + const hit = linkedVipChecks.find((item) => item.vip); + if (hit) { + return { + vip: true, + matchedUserId: hit.linkedId, + anonymousUserId: isAnonymousUserId(linkedAnonymousUserId) + ? linkedAnonymousUserId + : isAnonymousUserId(hit.linkedId) + ? hit.linkedId + : undefined, + source: "vip_email_link", + }; } - - return { - vip: true, - matchedUserId: linkedId, - anonymousUserId: isAnonymousUserId(linkedAnonymousUserId) - ? linkedAnonymousUserId - : isAnonymousUserId(linkedId) - ? linkedId - : undefined, - source: "vip_email_link", - }; } - if (await hasVipByUserId(adminToken, pbUserId)) { + const pbUserHasVip = await hasVipByUserId(adminToken, pbUserId); + if (pbUserHasVip) { return { vip: true, matchedUserId: pbUserId, @@ -186,22 +198,27 @@ async function resolveRecovery( ) ); - for (const candidate of payjsCandidates) { - if (!(await hasVipByUserId(adminToken, candidate))) { - continue; + if (payjsCandidates.length) { + const payjsVipChecks = await Promise.all( + payjsCandidates.map(async (candidate) => ({ + candidate, + vip: await hasVipByUserId(adminToken, candidate), + })) + ); + const hit = payjsVipChecks.find((item) => item.vip); + if (hit) { + return { + vip: true, + matchedUserId: hit.candidate, + anonymousUserId: isAnonymousUserId(hit.candidate) + ? hit.candidate + : typeof payjsResult?.anonymousUserId === "string" && + isAnonymousUserId(payjsResult.anonymousUserId.trim()) + ? payjsResult.anonymousUserId.trim() + : undefined, + source: "payjsapi", + }; } - - return { - vip: true, - matchedUserId: candidate, - anonymousUserId: isAnonymousUserId(candidate) - ? candidate - : typeof payjsResult?.anonymousUserId === "string" && - isAnonymousUserId(payjsResult.anonymousUserId.trim()) - ? payjsResult.anonymousUserId.trim() - : undefined, - source: "payjsapi", - }; } return { vip: false }; @@ -229,8 +246,10 @@ export async function POST(request: NextRequest) { } try { - const authData = await loginPocketBaseUser(email, password); - const adminToken = await getAdminToken(); + const [authData, adminToken] = await Promise.all([ + loginPocketBaseUser(email, password), + getAdminToken(), + ]); if (!adminToken) { return NextResponse.json( { ok: false, vip: false, error: "PocketBase 管理员认证失败" }, diff --git a/app/globals.css b/app/globals.css index 64834b9..08fbe70 100644 --- a/app/globals.css +++ b/app/globals.css @@ -4,6 +4,11 @@ :root, .light { + --font-geist-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, + "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, + "Apple Color Emoji", "Segoe UI Emoji"; + --font-geist-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; --background: #fafafa; --foreground: #171717; --card: #ffffff; diff --git a/app/layout.tsx b/app/layout.tsx index 01a3f90..657dd82 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,13 +1,8 @@ import type { Metadata } from "next"; -import { GeistSans } from "geist/font/sans"; -import { GeistMono } from "geist/font/mono"; import "./globals.css"; import { ThemeProvider } from "./providers/ThemeProvider"; import { themeInitScript } from "./theme-init"; -const geistSans = GeistSans; -const geistMono = GeistMono; - export const metadata: Metadata = { title: "异度星球", description: "数字游民社群", @@ -20,9 +15,7 @@ export default function RootLayout({ }>) { return ( - +