- 单一 Key:API 与 MCP 共用一个 wmh_ Key,凭据迁移至 settings 表(0004 删除 api_credentials) - Markdown:入库时 HTML→Markdown(mailhook 同思路),webhook payload 新增 markdown 字段 - /emails 路由改为 /inboxs 并支持域名筛选;设置按钮移入头像下拉 - 主题切换按钮(头像左侧,亮/暗持久化);设置模态仅 全局 Webhook/账号密码 两 tab 且高度限屏 - 列表操作列按钮加 SVG 图标;mailboxes 去 Webhook 列;最近邮件加收件人列
72 lines
2.7 KiB
TypeScript
72 lines
2.7 KiB
TypeScript
/**
|
|
* Console 应用装配(Hono,Workers/Bun 双运行时)。
|
|
*
|
|
* 路由分区:
|
|
* /api/health 健康检查
|
|
* /api/auth/* 用户登录/登出/会话(cookie)
|
|
* /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 { 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<Response>;
|
|
} = {},
|
|
): 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("/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;
|
|
}
|