This commit is contained in:
eric
2026-03-26 03:15:35 -05:00
parent 340d363196
commit 1a6f2511c4
3 changed files with 70 additions and 53 deletions

View File

@@ -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,10 +124,9 @@ async function resolveRecovery(
requestedAnonymousUserId?: string,
payjsResult?: PayjsVipEmailResult | null
): Promise<RecoveryResult> {
if (
isAnonymousUserId(requestedAnonymousUserId) &&
(await hasVipByUserId(adminToken, requestedAnonymousUserId))
) {
if (isAnonymousUserId(requestedAnonymousUserId)) {
const requestUserHasVip = await hasVipByUserId(adminToken, requestedAnonymousUserId);
if (requestUserHasVip) {
return {
vip: true,
matchedUserId: requestedAnonymousUserId,
@@ -132,12 +134,16 @@ async function resolveRecovery(
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: linkedId,
matchedUserId: hit.linkedId,
anonymousUserId: isAnonymousUserId(linkedAnonymousUserId)
? linkedAnonymousUserId
: isAnonymousUserId(linkedId)
? linkedId
: isAnonymousUserId(hit.linkedId)
? hit.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,16 +198,20 @@ 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: candidate,
anonymousUserId: isAnonymousUserId(candidate)
? candidate
matchedUserId: hit.candidate,
anonymousUserId: isAnonymousUserId(hit.candidate)
? hit.candidate
: typeof payjsResult?.anonymousUserId === "string" &&
isAnonymousUserId(payjsResult.anonymousUserId.trim())
? payjsResult.anonymousUserId.trim()
@@ -203,6 +219,7 @@ async function resolveRecovery(
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 管理员认证失败" },

View File

@@ -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;

View File

@@ -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 (
<html lang="zh-CN" suppressHydrationWarning>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<body className="antialiased">
<script dangerouslySetInnerHTML={{ __html: themeInitScript }} />
<ThemeProvider>{children}</ThemeProvider>
</body>