Files
Jyotisha/frontend/src/app/api/admin/packages/route.ts
T
linmeng dd8e2ad9c7
Deploy staging to test server / deploy (push) Successful in 3m42s
fix: restore staging package and epay management
Use direct PostgreSQL access for package and Z-Pay administration so self-hosted staging can load and save settings reliably, while restoring admin scrolling and default channel collapse.
2026-07-30 09:33:41 +08:00

115 lines
4.1 KiB
TypeScript

import { NextResponse } from "next/server";
import { z } from "zod";
import { requireAdminSession } from "@/lib/admin/auth";
import { queryAdminRows } from "@/lib/admin/database";
import { adminErrorResponse } from "@/lib/admin/http";
export const runtime = "nodejs";
const schema = z.object({
name: z.string().trim().min(1).max(80),
description: z.string().trim().max(500),
priceCents: z.number().int().positive().max(100_000_000),
credits: z.number().int().positive().max(10_000_000),
sortOrder: z.number().int().min(-100_000).max(100_000),
enabled: z.boolean(),
}).strict();
const updateSchema = schema.extend({ id: z.string().uuid() });
const idSchema = z.object({ id: z.string().uuid() }).strict();
type PackageRow = {
id: string;
name: string;
description: string;
price_cents: number;
credits: number;
sort_order: number;
enabled: boolean;
created_at: Date;
updated_at: Date;
};
function output(row: PackageRow) {
return {
id: row.id,
name: row.name,
description: row.description,
priceCents: row.price_cents,
credits: row.credits,
sortOrder: row.sort_order,
enabled: row.enabled,
createdAt: row.created_at.toISOString(),
updatedAt: row.updated_at.toISOString(),
};
}
export async function GET() {
try {
await requireAdminSession("read");
const rows = await queryAdminRows<PackageRow>(`
select id, name, description, price_cents, credits, sort_order, enabled, created_at, updated_at
from public.payment_packages
order by sort_order, created_at
`);
return NextResponse.json({ packages: rows.map(output) });
} catch (error) {
return adminErrorResponse(error);
}
}
export async function POST(request: Request) {
try {
const auth = await requireAdminSession("write");
const parsed = schema.safeParse(await request.json().catch(() => null));
if (!parsed.success) return NextResponse.json({ error: "套餐参数不正确" }, { status: 400 });
const p = parsed.data;
const rows = await queryAdminRows<PackageRow>(`
insert into public.payment_packages
(name, description, price_cents, credits, sort_order, enabled, created_by)
values ($1, $2, $3, $4, $5, $6, $7)
returning id, name, description, price_cents, credits, sort_order, enabled, created_at, updated_at
`, [p.name, p.description, p.priceCents, p.credits, p.sortOrder, p.enabled, auth.user.id]);
return NextResponse.json({ package: output(rows[0]) }, { status: 201 });
} catch (error) {
return adminErrorResponse(error);
}
}
export async function PATCH(request: Request) {
try {
await requireAdminSession("write");
const parsed = updateSchema.safeParse(await request.json().catch(() => null));
if (!parsed.success) return NextResponse.json({ error: "套餐参数不正确" }, { status: 400 });
const p = parsed.data;
const rows = await queryAdminRows<PackageRow>(`
update public.payment_packages
set name = $2, description = $3, price_cents = $4, credits = $5,
sort_order = $6, enabled = $7, updated_at = clock_timestamp()
where id = $1
returning id, name, description, price_cents, credits, sort_order, enabled, created_at, updated_at
`, [p.id, p.name, p.description, p.priceCents, p.credits, p.sortOrder, p.enabled]);
if (!rows[0]) return NextResponse.json({ error: "套餐不存在" }, { status: 404 });
return NextResponse.json({ package: output(rows[0]) });
} catch (error) {
return adminErrorResponse(error);
}
}
export async function DELETE(request: Request) {
try {
await requireAdminSession("write");
const parsed = idSchema.safeParse(await request.json().catch(() => null));
if (!parsed.success) return NextResponse.json({ error: "套餐参数不正确" }, { status: 400 });
const rows = await queryAdminRows<{ id: string }>(`
update public.payment_packages
set enabled = false, updated_at = clock_timestamp()
where id = $1
returning id
`, [parsed.data.id]);
if (!rows[0]) return NextResponse.json({ error: "套餐不存在" }, { status: 404 });
return NextResponse.json({ ok: true });
} catch (error) {
return adminErrorResponse(error);
}
}