Revert "merge: sync GitHub staging to Gitea"
Deploy staging to test server / deploy (push) Failing after 14m49s

This reverts commit a55c69115d, reversing
changes made to 02c9c9f3d6.
This commit is contained in:
linmeng
2026-07-30 14:38:17 +08:00
parent a55c69115d
commit f4e35974c6
106 changed files with 3228 additions and 1213 deletions
@@ -0,0 +1,132 @@
import crypto from "node:crypto";
import { NextResponse } from "next/server";
import { z } from "zod";
import { requireAdminSession } from "@/lib/admin/auth";
import { isPostgresError, queryAdminRows } from "@/lib/admin/database";
import { adminErrorResponse } from "@/lib/admin/http";
import { suggestedEpayUrls } from "@/lib/epay/config";
import { encryptEpayKey } from "@/lib/epay/encryption";
export const runtime = "nodejs";
const httpUrl = z.string().trim().min(1).max(2048).url().refine((value) => /^https?:\/\//i.test(value), "必须使用 HTTP(S)");
const settingsSchema = z.object({
gatewayUrl: httpUrl,
pid: z.string().trim().min(1).max(200),
notifyUrl: httpUrl,
returnUrl: httpUrl,
siteName: z.string().trim().min(1).max(100),
chatEnabled: z.boolean(),
newKey: z.string().min(1).max(1000).optional(),
}).strict();
type SettingsRow = {
gateway_url: string;
pid: string;
encrypted_key: string;
notify_url: string;
return_url: string;
site_name: string;
chat_enabled: boolean;
updated_at?: Date;
};
function publicSettings(row: SettingsRow, source: "database" | "environment") {
return {
gatewayUrl: row.gateway_url,
pid: row.pid,
notifyUrl: row.notify_url,
returnUrl: row.return_url,
siteName: row.site_name,
chatEnabled: row.chat_enabled,
keyConfigured: Boolean(row.encrypted_key),
complete: Boolean(row.gateway_url && row.pid && row.encrypted_key && row.notify_url && row.return_url && row.site_name),
source,
updatedAt: source === "database" ? row.updated_at?.toISOString() ?? null : null,
};
}
function environmentSettings() {
const defaults = suggestedEpayUrls();
const row: SettingsRow = {
gateway_url: process.env.EPAY_GATEWAY_URL?.trim() || "",
pid: process.env.EPAY_PID?.trim() || "",
encrypted_key: process.env.EPAY_KEY?.trim() ? "configured" : "",
notify_url: process.env.EPAY_NOTIFY_URL?.trim() || defaults.notifyUrl,
return_url: process.env.EPAY_RETURN_URL?.trim() || defaults.returnUrl,
site_name: process.env.EPAY_SITE_NAME?.trim() || "Jyotisha",
chat_enabled: ["true", "1"].includes(process.env.EPAY_CHAT_ENABLED?.trim().toLowerCase() || ""),
};
return publicSettings(row, "environment");
}
async function databaseRow() {
try {
const rows = await queryAdminRows<SettingsRow>(`
select gateway_url, pid, encrypted_key, notify_url, return_url, site_name, chat_enabled, updated_at
from public.epay_settings
where id = true
limit 1
`);
return rows[0] ?? null;
} catch (error) {
if (isPostgresError(error) && error.code === "42P01") return null;
throw error;
}
}
export async function GET() {
try {
await requireAdminSession("read");
const row = await databaseRow();
if (row) return NextResponse.json(publicSettings(row, "database"));
const settings = environmentSettings();
return NextResponse.json(settings.complete || settings.keyConfigured
? settings
: { ...settings, source: "unconfigured" });
} catch (error) {
return adminErrorResponse(error);
}
}
export async function PUT(request: Request) {
try {
const session = await requireAdminSession("write");
const parsed = settingsSchema.safeParse(await request.json().catch(() => null));
if (!parsed.success) return NextResponse.json({ error: "易支付配置参数不正确" }, { status: 400 });
const existing = await databaseRow();
if (!existing && !parsed.data.newKey) {
return NextResponse.json({ error: "首次保存数据库配置时必须输入新的商户密钥" }, { status: 400 });
}
const encryptedKey = parsed.data.newKey
? encryptEpayKey(parsed.data.newKey)
: existing!.encrypted_key;
try {
const rows = await queryAdminRows<SettingsRow>(`
select * from public.admin_save_epay_settings(
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12
)
`, [
session.user.id,
session.user.email,
session.role,
crypto.randomUUID(),
parsed.data.gatewayUrl.replace(/\/+$/, ""),
parsed.data.pid,
encryptedKey,
parsed.data.notifyUrl,
parsed.data.returnUrl,
parsed.data.siteName,
parsed.data.chatEnabled,
Boolean(parsed.data.newKey),
]);
if (!rows[0]) return NextResponse.json({ error: "保存易支付配置失败" }, { status: 500 });
return NextResponse.json(publicSettings(rows[0], "database"));
} catch {
return NextResponse.json({ error: "保存易支付配置失败" }, { status: 500 });
}
} catch (error) {
return adminErrorResponse(error);
}
}
@@ -0,0 +1,46 @@
import { NextResponse } from "next/server";
import { AdminAuthorizationError, requireAdminSession } from "@/lib/admin/auth";
import { adminErrorResponse } from "@/lib/admin/http";
import { epaySubmitUrl, readEpayConfig } from "@/lib/epay/config";
import { assertPublicGatewayUrl } from "@/lib/epay/gateway-policy";
export const runtime = "nodejs";
function reachableStatus(status: number) {
return status >= 200 && status < 500;
}
export async function POST() {
try {
await requireAdminSession("write");
const config = await readEpayConfig();
const submitUrl = epaySubmitUrl(config.gatewayUrl);
await assertPublicGatewayUrl(submitUrl);
const startedAt = performance.now();
let response = await fetch(submitUrl, {
method: "HEAD",
redirect: "manual",
signal: AbortSignal.timeout(8_000),
});
if (response.status === 405 || response.status === 501) {
response = await fetch(submitUrl, {
method: "GET",
redirect: "manual",
signal: AbortSignal.timeout(8_000),
});
}
const available = reachableStatus(response.status);
return NextResponse.json({
available,
message: available ? "当前已保存的易支付配置可访问" : "当前已保存的易支付配置暂不可用",
latencyMs: Math.round(performance.now() - startedAt),
status: response.status,
});
} catch (error) {
if (error instanceof AdminAuthorizationError) return adminErrorResponse(error);
return NextResponse.json({
available: false,
message: "当前已保存的易支付配置暂不可用",
});
}
}