feat(billing): add feature pricing configuration

This commit is contained in:
Jesse_Chen
2026-08-31 03:37:44 +08:00
parent da4c03a857
commit aaaa5fb73c
10 changed files with 518 additions and 1 deletions
@@ -0,0 +1,102 @@
import { NextResponse } from "next/server";
import { z } from "zod";
import { requirePermission } from "@/lib/admin/auth";
import { adminErrorResponse, requestId, requireAdminMutation } from "@/lib/admin/http";
import { queryAdminRows } from "@/lib/admin/database";
export const runtime = "nodejs";
const featureKey = z.enum([
"chat.standard",
"chat.premium",
"rectification",
"report.full",
"report.export",
"profile.extra",
]);
const modelTier = z.enum(["standard", "premium", "internal"]);
const mutationSchema = z.discriminatedUnion("action", [
z.object({
action: z.literal("saveDraft"),
id: z.string().uuid().nullable().optional(),
featureKey,
modelTier,
creditCost: z.number().int().positive(),
reason: z.string().trim().min(1).max(500),
}).strict(),
z.object({
action: z.literal("publish"),
id: z.string().uuid(),
reason: z.string().trim().min(1).max(500),
}).strict(),
]);
type FeaturePricingRow = {
id: string;
feature_key: string;
model_tier: string;
credit_cost: number;
version: number;
status: string;
enabled: boolean;
effective_from: Date | null;
effective_to: Date | null;
updated_at: Date;
};
function output(row: FeaturePricingRow) {
return {
id: row.id,
featureKey: row.feature_key,
modelTier: row.model_tier,
creditCost: row.credit_cost,
version: row.version,
status: row.status,
enabled: row.enabled,
effectiveFrom: row.effective_from?.toISOString() ?? null,
effectiveTo: row.effective_to?.toISOString() ?? null,
updatedAt: row.updated_at.toISOString(),
};
}
export async function GET() {
try {
await requirePermission("billing.products.read");
const rows = await queryAdminRows<FeaturePricingRow>(`
select id, feature_key, model_tier, credit_cost, version, status, enabled,
effective_from, effective_to, updated_at
from public.feature_pricing
order by feature_key, model_tier, version desc
`);
return NextResponse.json({ data: rows.map(output), total: rows.length });
} catch (error) {
return adminErrorResponse(error);
}
}
export async function POST(request: Request) {
try {
const body = mutationSchema.safeParse(await request.json().catch(() => null));
if (!body.success) {
return NextResponse.json({ error: "请求格式不正确", details: body.error.flatten() }, { status: 400 });
}
const permission = body.data.action === "publish" ? "billing.products.publish" : "billing.products.write";
const session = await requireAdminMutation(request, permission);
const rid = requestId(request);
if (body.data.action === "publish") {
const rows = await queryAdminRows<{ id: string }>(
"select public.admin_publish_feature_pricing($1,$2,$3,$4) id",
[session.user.id, body.data.id, body.data.reason, rid],
);
return NextResponse.json({ data: { id: rows[0]!.id, requestId: rid } });
}
const rows = await queryAdminRows<{ id: string }>(
"select public.admin_save_feature_pricing_draft($1,$2,$3,$4,$5,$6,$7) id",
[session.user.id, body.data.id ?? null, body.data.featureKey, body.data.modelTier, body.data.creditCost, body.data.reason, rid],
);
return NextResponse.json({ data: { id: rows[0]!.id, requestId: rid } });
} catch (error) {
return adminErrorResponse(error);
}
}