Files
new-api-key-usage/packages/core/src/schemas.ts
T
skycurtain d47cb5543c feat(engine): 实现同步引擎与 engine_status 快照
apps/engine 独立进程(syncRound + 防重叠调度);上游 log/token;QPU 采样;
server 读 engine_status;时刻出参 toUnixSec;中文日志;zod-openapi 扩展顺序修复。
2026-10-07 12:54:13 +08:00

242 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { z } from "zod";
/**
* 契约 zod schema(字段权威 = API契约三篇;DTO 类型见 types.ts)。
* OpenAPI 装饰(.openapi 组件名等)留在 apps/server,core 不依赖 hono。
*/
export const errorShape = z.object({
code: z.string(),
message: z.string(),
fields: z.record(z.string(), z.string()).optional(),
key_id: z.number().int().optional(),
});
export const errorEnvelope = z.object({ error: errorShape });
export type ErrorEnvelope = z.infer<typeof errorEnvelope>;
export const setupState = z.object({ initialized: z.boolean() });
export const usernameBody = z.object({ username: z.string() });
export const setupBody = z.object({
username: z.string().min(1).max(64),
password: z.string().min(8).max(128),
});
export const loginBody = z.object({
username: z.string().min(1),
password: z.string().min(1),
});
export const changePasswordBody = z.object({
old_password: z.string().min(1),
new_password: z.string().min(8).max(128),
});
export const timeRangeQuery = z.object({
from: z.string().min(1),
to: z.string().min(1),
});
export const pageQuery = z.object({
page: z.coerce.number().int().min(1).default(1),
page_size: z.coerce.number().int().min(1).max(200).default(50),
});
export const usageTotals = z.object({
count: z.number().int(),
quota: z.number(),
quota_usd: z.string(),
});
export const logItem = z.object({
id: z.number().int(),
api_key_id: z.number().int(),
created_at: z.number().int(),
token_name: z.string(),
model_name: z.string(),
type: z.number().int(),
content: z.string(),
quota: z.number(),
quota_usd: z.string(),
prompt_tokens: z.number().int(),
completion_tokens: z.number().int(),
use_time: z.number().int(),
is_stream: z.boolean(),
billing_source: z.string().nullable(),
subscription_id: z.number().int().nullable(),
});
export const logPage = z.object({
items: z.array(logItem),
total: z.number().int(),
});
export const keyItem = z.object({
id: z.number().int(),
token_name: z.string(),
status: z.enum(["active", "disabled"]),
member_id: z.number().int().nullable(),
member_name: z.string().nullable(),
registered_at: z.number().int(),
log_synced_at: z.number().int().nullable(),
usage_synced_at: z.number().int().nullable(),
total_used: z.number(),
total_available: z.number(),
total_granted: z.number(),
unlimited_quota: z.boolean(),
expires_at: z.number().int().nullable(),
});
export const registerKeyBody = z.object({
sk_key: z.string().min(8),
member_id: z.number().int().positive().nullable().optional(),
});
export const patchKeyBody = z.object({
status: z.enum(["active", "disabled"]).optional(),
member_id: z.number().int().positive().nullable().optional(),
});
export const memberItem = z.object({
id: z.number().int(),
name: z.string(),
remark: z.string(),
status: z.enum(["active", "disabled"]),
token_issued_at: z.number().int().nullable(),
created_at: z.number().int(),
key_count: z.number().int(),
});
export const createMemberBody = z.object({
name: z.string().min(1).max(64),
remark: z.string().optional().default(""),
});
export const patchMemberBody = z.object({
status: z.enum(["active", "disabled"]),
});
export const memberWithToken = z.object({
member: memberItem,
token: z.string(),
});
export const tokenOnly = z.object({ token: z.string() });
export const memberMe = z.object({
id: z.number().int(),
name: z.string(),
});
export const paymentItem = z.object({
id: z.number().int(),
kind: z.enum(["subscription", "topup"]),
paid_at: z.number().int(),
paid_amount: z.number(),
remark: z.string(),
period_start: z.number().int().nullable(),
period_end: z.number().int().nullable(),
subscription_id: z.number().int().nullable(),
quota_granted: z.number().int().nullable(),
quota_usd: z.string(),
});
export const engineStatus = z.object({
running: z.boolean(),
sync_interval_ms: z.number().int().optional(),
last_round: z
.object({
started_at: z.number().int(),
keys_total: z.number().int(),
succeeded: z.number().int(),
failed: z.number().int(),
failed_api_key_ids: z.array(z.number().int()),
})
.nullable(),
});
export const auditLogItem = z.object({
id: z.number().int(),
created_at: z.number().int(),
operator: z.string().nullable(),
domain: z.enum(["key", "payment", "member", "account"]),
action: z.string(),
summary: z.string(),
});
export const memberOverview = z.object({
member: z.object({
id: z.number().int().nullable(),
name: z.string().nullable(),
}),
usage: usageTotals,
by_source: z.array(
z.object({
billing_source: z.string(),
count: z.number().int(),
quota: z.number(),
quota_usd: z.string(),
}),
),
keys: z.array(
z.object({
key_id: z.number().int(),
token_name: z.string(),
log_synced_at: z.number().int().nullable(),
usage: usageTotals,
}),
),
payments: z.array(z.looseObject({ id: z.number().int() })),
});
export const createPaymentBody = z
.object({
kind: z.enum(["subscription", "topup"]),
paid_at: z.string().min(1),
paid_amount: z.number(),
remark: z.string().default(""),
period_start: z.string().min(1).nullable().optional(),
period_end: z.string().min(1).nullable().optional(),
subscription_id: z.number().int().positive().nullable().optional(),
quota_granted: z.number().int().nullable().optional(),
})
.superRefine((val, ctx) => {
if (val.kind === "subscription") {
if (!val.period_start) {
ctx.addIssue({
code: "custom",
message: "订阅必须填写期间起点",
path: ["period_start"],
});
}
if (val.subscription_id == null) {
ctx.addIssue({
code: "custom",
message: "订阅必须填写订阅编号",
path: ["subscription_id"],
});
}
}
if (val.quota_granted == null) {
ctx.addIssue({
code: "custom",
message: "必须填写本笔额度",
path: ["quota_granted"],
});
}
});
export {
setupBody as setupBodySchema,
loginBody as loginBodySchema,
changePasswordBody as changePasswordBodySchema,
registerKeyBody as registerKeyBodySchema,
patchKeyBody as patchKeyBodySchema,
createMemberBody as createMemberBodySchema,
patchMemberBody as patchMemberBodySchema,
createPaymentBody as createPaymentBodySchema,
timeRangeQuery as timeRangeQuerySchema,
pageQuery as pageQuerySchema,
};