feat(billing): add feature pricing configuration
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user