chore: 初始化 monorepo 脚手架与代码骨架
- Bun workspaces:apps/web(Vue 3.5 + Vite 8 + naive-ui)· apps/server(Hono 4.13 + Drizzle)· packages/shared(契约类型) - 代码骨架:四表 Drizzle schema · 11 个端点占位(501)· 管理面口令中间件 · 同步引擎占位(5 分钟 tick + 防重入) - 命令链:dev / build / test / lint / type-check / db:*(见 AGENTS.md)
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 契约常量 —— 与 `docs/design/API契约.md` 保持同步。
|
||||
*/
|
||||
|
||||
/** 明细分页:默认每页条数(契约 §2.1 请求参数) */
|
||||
export const LOGS_DEFAULT_LIMIT = 50
|
||||
|
||||
/** 明细分页:每页条数上限 */
|
||||
export const LOGS_MAX_LIMIT = 200
|
||||
|
||||
/** 每页条数选择器的可选值 */
|
||||
export const LOGS_LIMIT_OPTIONS = [20, 50, 100, 200] as const
|
||||
|
||||
/** 单次查询的 key 集合上限(初值,技术稿可调) */
|
||||
export const KEYS_MAX = 20
|
||||
|
||||
/** 日志类型(上游 model/log.go;常量注释明确「刻意不用 iota」,枚举值永不改变) */
|
||||
export const LOG_TYPE = {
|
||||
Unknown: 0,
|
||||
Topup: 1,
|
||||
Consume: 2,
|
||||
Manage: 3,
|
||||
System: 4,
|
||||
Error: 5,
|
||||
Refund: 6,
|
||||
Login: 7,
|
||||
} as const
|
||||
|
||||
/** 日志类型的中文标签(展示层用;未知值原样显示数字) */
|
||||
export const LOG_TYPE_LABEL: Record<number, string> = {
|
||||
[LOG_TYPE.Unknown]: '未知',
|
||||
[LOG_TYPE.Topup]: '充值',
|
||||
[LOG_TYPE.Consume]: '消费',
|
||||
[LOG_TYPE.Manage]: '管理',
|
||||
[LOG_TYPE.System]: '系统',
|
||||
[LOG_TYPE.Error]: '错误',
|
||||
[LOG_TYPE.Refund]: '退款',
|
||||
[LOG_TYPE.Login]: '登录',
|
||||
}
|
||||
|
||||
/** 额度美元换算的出厂默认(仅兜底展示;实际取值一律从 qpu_history 时间线来) */
|
||||
export const DEFAULT_QUOTA_PER_UNIT = 500000
|
||||
|
||||
/** 管理面请求头(契约 约定 6) */
|
||||
export const MANAGE_TOKEN_HEADER = 'x-manage-token'
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './types'
|
||||
export * from './constants'
|
||||
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* API 契约类型 —— 与 `docs/design/API契约.md` 保持同步(全链路 snake_case,契约 约定 3)。
|
||||
* 字段集稳定:可空列返回 `null` 而非省略(约定「字段集稳定」)。
|
||||
*/
|
||||
|
||||
// ---------- 通用:错误与元信息 ----------
|
||||
|
||||
/** 错误码枚举(契约 约定 21) */
|
||||
export type ApiErrorCode =
|
||||
| 'invalid_params'
|
||||
| 'manage_unauthorized'
|
||||
| 'not_found'
|
||||
| 'internal'
|
||||
| 'key_rejected'
|
||||
| 'already_registered'
|
||||
| 'upstream_unavailable'
|
||||
|
||||
/** 失败响应体(约定 20:非 2xx + error 信封) */
|
||||
export interface ApiErrorBody {
|
||||
error: {
|
||||
code: ApiErrorCode | (string & {})
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
/** 查看面查询响应统一携带的 meta(约定 22) */
|
||||
export interface QueryMetaKeyRegistered {
|
||||
key_id: number
|
||||
token_name: string
|
||||
registered: true
|
||||
status: 'active' | 'disabled'
|
||||
/** 该 key 自己的同步进度(新登记未首同步时为 null) */
|
||||
log_synced_at: string | null
|
||||
}
|
||||
|
||||
export interface QueryMetaKeyUnregistered {
|
||||
registered: false
|
||||
/** 即时生成、不落库:`sk-…a1b2` */
|
||||
key_hint: string
|
||||
}
|
||||
|
||||
export type QueryMetaKey = QueryMetaKeyRegistered | QueryMetaKeyUnregistered
|
||||
|
||||
export interface QueryMeta {
|
||||
/** 参与 key 中最慢的 log_synced_at(取非 null 的最慢值);全部未同步时为 null */
|
||||
data_until: string | null
|
||||
served_at: string
|
||||
keys: QueryMetaKey[]
|
||||
}
|
||||
|
||||
// ---------- /usage-log ----------
|
||||
|
||||
/** 日志类型(上游 model/log.go 的稳定枚举,照存不过滤) */
|
||||
export type LogType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
|
||||
|
||||
/** 日志行的 `other`(实测 26 键中的常用项;完整键集合见 docs/upstream 实测记录) */
|
||||
export interface LogOther {
|
||||
billing_source?: 'subscription' | 'wallet'
|
||||
cache_tokens?: number
|
||||
model_ratio?: number
|
||||
request_path?: string
|
||||
subscription_id?: number
|
||||
subscription_plan_title?: string
|
||||
subscription_total?: number
|
||||
subscription_used?: number
|
||||
subscription_remain?: number
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
/** 明细行 = `usage_logs` 表 1:1(除内部列 `dedupe_key`)+ 计算字段 */
|
||||
export interface UsageLogRow {
|
||||
id: number
|
||||
key_id: number
|
||||
user_id: number
|
||||
username: string
|
||||
created_at: string
|
||||
type: LogType
|
||||
content: string
|
||||
/** 当前名(join api_keys),非日志快照名 */
|
||||
token_name: string
|
||||
model_name: string
|
||||
quota: number
|
||||
prompt_tokens: number
|
||||
completion_tokens: number
|
||||
use_time: number
|
||||
is_stream: boolean
|
||||
channel: number
|
||||
channel_name: string
|
||||
token_id: number
|
||||
group_name: string
|
||||
ip: string
|
||||
request_id: string
|
||||
upstream_request_id: string | null
|
||||
/** 该行折算实付人民币(错误行 quota = 0 → 0) */
|
||||
amount_rmb: number
|
||||
other: LogOther
|
||||
}
|
||||
|
||||
export interface LogsQueryRequest {
|
||||
/** 必填;去重后最多 KEYS_MAX 个 */
|
||||
keys: string[]
|
||||
/** 必填;ISO 8601(输出 +08:00,输入宽容),左闭右开 [start, end) */
|
||||
start: string
|
||||
end: string
|
||||
/** 可选;缺省 = 全部类型;只作用于本端点 */
|
||||
type?: number[]
|
||||
/** 可选;默认 50,上限 200 */
|
||||
limit?: number
|
||||
offset?: number
|
||||
}
|
||||
|
||||
export interface LogsQueryResponse {
|
||||
items: UsageLogRow[]
|
||||
total: number
|
||||
meta: QueryMeta
|
||||
}
|
||||
|
||||
export interface SummaryRequest {
|
||||
keys: string[]
|
||||
start: string
|
||||
end: string
|
||||
}
|
||||
|
||||
export interface SummaryResponse {
|
||||
request_count: number
|
||||
consume_count: number
|
||||
error_count: number
|
||||
prompt_tokens: number
|
||||
completion_tokens: number
|
||||
cache_tokens: number
|
||||
/** quota 合计(原值,供核对) */
|
||||
quota: number
|
||||
/** 折算实付人民币(跨期按购买期间分段累加) */
|
||||
amount_rmb: number
|
||||
meta: QueryMeta
|
||||
}
|
||||
|
||||
// ---------- /manage/api-keys ----------
|
||||
|
||||
/** 清单行 = `api_keys` 表 1:1(除 key_hash / key_ciphertext 两个永不出接口的敏感列) */
|
||||
export interface ApiKeyRow {
|
||||
id: number
|
||||
token_name: string
|
||||
status: 'active' | 'disabled'
|
||||
/** 停用来源(可辨「已失效」与「已停用」) */
|
||||
disabled_source: 'manual' | 'auto_401' | null
|
||||
consecutive_401s: number
|
||||
registered_at: string
|
||||
log_synced_at: string | null
|
||||
usage_synced_at: string | null
|
||||
total_granted: number
|
||||
total_used: number
|
||||
total_available: number
|
||||
unlimited_quota: boolean
|
||||
model_limits: Record<string, unknown>
|
||||
model_limits_enabled: boolean
|
||||
expires_at: string | null
|
||||
}
|
||||
|
||||
export interface RegisterApiKeyRequest {
|
||||
key: string
|
||||
}
|
||||
|
||||
export interface PatchApiKeyRequest {
|
||||
status: 'active' | 'disabled'
|
||||
}
|
||||
|
||||
// ---------- /manage/purchases ----------
|
||||
|
||||
export type PurchaseKind = 'subscription' | 'topup'
|
||||
|
||||
/** 清单行 = `purchases` 表 1:1(9 列) */
|
||||
export interface PurchaseRow {
|
||||
id: number
|
||||
kind: PurchaseKind
|
||||
paid_at: string
|
||||
paid_amount: number
|
||||
note: string | null
|
||||
period_start: string | null
|
||||
period_end: string | null
|
||||
subscription_id: number | null
|
||||
quota_granted: number | null
|
||||
}
|
||||
|
||||
/** 登记请求(必填:kind / paid_at / paid_amount / quota_granted) */
|
||||
export interface PurchaseCreateRequest {
|
||||
kind: PurchaseKind
|
||||
paid_at: string
|
||||
paid_amount: number
|
||||
quota_granted: number
|
||||
note?: string | null
|
||||
period_start?: string | null
|
||||
period_end?: string | null
|
||||
subscription_id?: number | null
|
||||
}
|
||||
|
||||
/** 编辑请求:部分更新语义(提交哪些改哪些) */
|
||||
export type PurchasePatchRequest = Partial<PurchaseCreateRequest>
|
||||
|
||||
/** 观测项(核对提示的数据面) */
|
||||
export interface ObservationItem {
|
||||
subscription_id: number
|
||||
plan_title: string | null
|
||||
observed_quota_total: number | null
|
||||
first_seen_at: string
|
||||
last_seen_at: string
|
||||
log_count: number
|
||||
/** null = 观测到但未登记(补录引导的目标) */
|
||||
matched_purchase_id: number | null
|
||||
}
|
||||
|
||||
export interface ObservationsResponse {
|
||||
items: ObservationItem[]
|
||||
}
|
||||
|
||||
// ---------- /manage/qpu ----------
|
||||
|
||||
/** 时间线行 = `qpu_history` 表 1:1(3 列,纯只读) */
|
||||
export interface QpuRow {
|
||||
id: number
|
||||
qpu: number
|
||||
effective_from: string
|
||||
}
|
||||
Reference in New Issue
Block a user