feat(server): 管理面与成员视图 API + Scalar 在线文档
契约 19 路径全量:引导/会话/改密、engine 桩、keys、overview/logs、payments、 qpu、members、audit、成员视图。鉴权优先、422 信封、登录节流、上游验证接线。
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
import { OpenAPIHono } from "@hono/zod-openapi";
|
||||
import { apiReference } from "@scalar/hono-api-reference";
|
||||
import { logger as honoLogger } from "hono/logger";
|
||||
import { ERR, appLogger } from "@new-api-key-usage/core";
|
||||
import { createRepos, type Db } from "@new-api-key-usage/db";
|
||||
import { jsonError, type AppVars } from "./middleware.ts";
|
||||
import { adminSessionRoutes } from "./routes/admin-session.ts";
|
||||
import { adminEngineRoutes } from "./routes/admin-engine.ts";
|
||||
import { adminKeyRoutes } from "./routes/admin-keys.ts";
|
||||
import { adminMemberRoutes } from "./routes/admin-members.ts";
|
||||
import { adminUsageRoutes } from "./routes/admin-usage.ts";
|
||||
import { adminPaymentRoutes } from "./routes/admin-payments.ts";
|
||||
import { adminAuditRoutes } from "./routes/admin-audit.ts";
|
||||
import { memberViewRoutes } from "./routes/member-view.ts";
|
||||
|
||||
export interface CreateAppOptions {
|
||||
db: Db;
|
||||
skKeyEncryptionSecret: string;
|
||||
newapiBase: string;
|
||||
cookieSecure?: boolean;
|
||||
adminInitUsername?: string;
|
||||
adminInitPassword?: string;
|
||||
/** 可注入的上游验证(测试);默认走 real HTTP */
|
||||
fetchTokenUsage?: AppVars["fetchTokenUsage"];
|
||||
}
|
||||
|
||||
export function createApp(opts: CreateAppOptions) {
|
||||
const app = new OpenAPIHono<{ Variables: AppVars }>({
|
||||
defaultHook: (result, c) => {
|
||||
if (!result.success) {
|
||||
const fields: Record<string, string> = {};
|
||||
for (const issue of result.error.issues) {
|
||||
const key = issue.path.join(".") || "body";
|
||||
fields[key] = issue.message;
|
||||
}
|
||||
return jsonError(c, 422, ERR.validation_failed(fields));
|
||||
}
|
||||
},
|
||||
});
|
||||
const repos = createRepos(opts.db);
|
||||
|
||||
app.use("*", honoLogger());
|
||||
app.use("*", async (c, next) => {
|
||||
c.set("repos", repos);
|
||||
c.set("skKeyEncryptionSecret", opts.skKeyEncryptionSecret);
|
||||
c.set("newapiBase", opts.newapiBase);
|
||||
c.set("cookieSecure", opts.cookieSecure ?? true);
|
||||
if (opts.fetchTokenUsage) c.set("fetchTokenUsage", opts.fetchTokenUsage);
|
||||
if (opts.adminInitUsername)
|
||||
c.set("adminInitUsername", opts.adminInitUsername);
|
||||
if (opts.adminInitPassword)
|
||||
c.set("adminInitPassword", opts.adminInitPassword);
|
||||
await next();
|
||||
});
|
||||
|
||||
app.notFound((c) => {
|
||||
if (c.req.path.startsWith("/api")) {
|
||||
return jsonError(c, 404, ERR.not_found());
|
||||
}
|
||||
return c.text("Not Found", 404);
|
||||
});
|
||||
|
||||
app.onError((err, c) => {
|
||||
appLogger("server").error`unhandled ${c.req.method} ${c.req.path}: ${err}`;
|
||||
return jsonError(c, 500, ERR.internal());
|
||||
});
|
||||
|
||||
app.openAPIRegistry.registerComponent("securitySchemes", "adminSession", {
|
||||
type: "apiKey",
|
||||
in: "cookie",
|
||||
name: "admin_session",
|
||||
description: "管理面会话 cookie(HttpOnly · Path=/api/admin)",
|
||||
});
|
||||
app.openAPIRegistry.registerComponent("securitySchemes", "memberToken", {
|
||||
type: "http",
|
||||
scheme: "bearer",
|
||||
description: "成员视图 Bearer 令牌",
|
||||
});
|
||||
|
||||
app.doc("/api/openapi.json", {
|
||||
openapi: "3.1.0",
|
||||
info: {
|
||||
title: "new-api-key-usage",
|
||||
version: "0.0.0",
|
||||
description:
|
||||
"NewAPI key 用量统计——管理面 `/api/admin/*`(会话 cookie)与成员视图 `/api/member/*`(Bearer)。成功响应无信封,错误见 ErrorEnvelope。",
|
||||
},
|
||||
tags: [
|
||||
{ name: "引导与会话", description: "三态判定 / 登录 / 改密" },
|
||||
{ name: "引擎", description: "同步引擎状态" },
|
||||
{ name: "Key 管理", description: "登记 / 启停 / 归属" },
|
||||
{ name: "成员管理", description: "开户 / 令牌 / 启停" },
|
||||
{ name: "成员视图", description: "令牌自助查询" },
|
||||
],
|
||||
});
|
||||
|
||||
app.get(
|
||||
"/api/docs",
|
||||
apiReference({
|
||||
theme: "kepler",
|
||||
spec: { url: "/api/openapi.json" },
|
||||
}),
|
||||
);
|
||||
|
||||
const admin = new OpenAPIHono<{ Variables: AppVars }>();
|
||||
admin.route("/", adminSessionRoutes());
|
||||
admin.route("/", adminEngineRoutes());
|
||||
admin.route("/", adminKeyRoutes());
|
||||
admin.route("/", adminMemberRoutes());
|
||||
admin.route("/", adminUsageRoutes());
|
||||
admin.route("/", adminPaymentRoutes());
|
||||
admin.route("/", adminAuditRoutes());
|
||||
app.route("/api/admin", admin);
|
||||
|
||||
app.route("/api/member", memberViewRoutes());
|
||||
|
||||
return app;
|
||||
}
|
||||
Reference in New Issue
Block a user