/** * Console 应用装配(Hono,Workers/Bun 双运行时)。 * * 路由分区: * /api/health 健康检查 * /api/auth/* 用户登录/登出/会话(cookie) * /auth/* ZITADEL 统一登录:status / login / callback(OIDC Authorization Code + PKCE) * /api/accounts|domains|mailboxes|emails 管理资源 CRUD(会话保护) * /api/settings/* 设置:全局 Webhook / 改密码(会话保护) * /api/oauth/* Cloudflare OAuth(会话保护) * /api/admin/* 版本/统计/Cron(会话保护) * /internal/email/* Email Worker 内部协议:authorize / ingest(HMAC 签名,docs/11) * /api/v1/mcp MCP JSON-RPC(Bearer MCP Key wmh_) * /api/v1/* 外部 API(Bearer API Key wmh_) * /* 前端 SPA 静态资源 */ import { Hono } from "hono"; import type { Env, AppVars } from "./env.ts"; import { ensureSeeded } from "./db.ts"; import { csrfOriginCheck } from "./middleware.ts"; import { authRoutes } from "./auth.ts"; import { internalRoutes } from "./routes/internal.ts"; import { resourceRoutes } from "./routes/resources.ts"; import { settingsRoutes } from "./routes/settings.ts"; import { oauthRoutes } from "./routes/oauth.ts"; import { oidcRoutes } from "./routes/oidc.ts"; import { apiV1Routes } from "./routes/apiv1.ts"; import { mcpRoutes } from "./routes/mcp.ts"; import { adminRoutes } from "./routes/admin.ts"; import { APP_NAME, APP_VERSION } from "./version.ts"; /** # 函数 * 构造 Console Hono 应用实例 * 参数: opts.serveStatic - 静态资源处理器(本地运行时注入;Workers 端走 ASSETS 绑定) */ export function createApp( opts: { serveStatic?: (request: Request) => Promise; } = {}, ): Hono<{ Bindings: Env; Variables: AppVars }> { const app = new Hono<{ Bindings: Env; Variables: AppVars }>(); // 每个请求先保证种子数据(幂等,内存标志去重) app.use("*", async (c, next) => { await ensureSeeded(c.env); await next(); }); app.use("/api/*", csrfOriginCheck); app.get("/api/health", (c) => c.json({ ok: true, name: APP_NAME, version: APP_VERSION })); app.route("/api/auth", authRoutes()); app.route("/auth", oidcRoutes()); app.route("/internal", internalRoutes()); app.route("/api/oauth", oauthRoutes()); app.route("/api/settings", settingsRoutes()); app.route("/api/admin", adminRoutes()); app.route("/api/v1/mcp", mcpRoutes()); app.route("/api/v1", apiV1Routes()); app.route("/api", resourceRoutes()); // 静态资源 / SPA fallback app.get("/*", async (c) => { const path = new URL(c.req.url).pathname; if (path.startsWith("/api/")) return c.json({ error: "not found" }, 404); if (opts.serveStatic) return opts.serveStatic(c.req.raw); if (c.env.ASSETS) return c.env.ASSETS.fetch(c.req.raw); return c.text("frontend assets not configured", 404); }); return app; }