()
| 20 | import { tunnelAuthHook } from './middleware/tunnel-auth.js'; |
| 21 | import { accessAuthHook } from './middleware/access-auth.js'; |
| 22 | import { writeErrorLog } from './utils/error-log.js'; |
| 23 | import { initializeDatabaseRuntime } from './utils/index.js'; |
| 24 | import { runStartupDataMigrations } from './services/database-maintenance.service.js'; |
| 25 | |
| 26 | let hibernationScheduler: HibernationScheduler | null = null; |
| 27 | let memberHeartbeatScheduler: MemberHeartbeatScheduler | null = null; |
| 28 | |
| 29 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); |
| 30 | |
| 31 | export async function buildApp() { |
| 32 | await initializeDatabaseRuntime(); |
| 33 | await runStartupDataMigrations(); |
| 34 | |
| 35 | const app = Fastify({ |
| 36 | logger: { |
| 37 | level: process.env.LOG_LEVEL || 'info', |
| 38 | }, |
| 39 | bodyLimit: 10 * 1024 * 1024, |
| 40 | }); |
| 41 | |
| 42 | app.addHook('onError', async (request, _reply, error) => { |
| 43 | writeErrorLog({ |
| 44 | level: 'error', |
| 45 | source: 'server.fastify.onError', |
| 46 | message: error.message, |
| 47 | error, |
| 48 | metadata: { |
| 49 | method: request.method, |
| 50 | url: request.url, |
| 51 | }, |
| 52 | }); |
| 53 | }); |
| 54 | |
| 55 | // 注册插件 |
| 56 | await app.register(cors, { |
| 57 | origin: true, |
| 58 | }); |
| 59 | |
| 60 | await app.register(fastifyCookie); |
| 61 | |
| 62 | await app.register(multipart, { |
| 63 | limits: { fileSize: 50 * 1024 * 1024 }, // 50MB |
| 64 | }); |
| 65 | |
| 66 | // 隧道 token 认证钩子(仅拦截经 Cloudflare 隧道的请求) |
| 67 | app.addHook('onRequest', tunnelAuthHook); |
| 68 | |
| 69 | // 访问密码认证钩子(启用后保护浏览器业务入口) |
| 70 | app.addHook('onRequest', accessAuthHook); |
| 71 | |
| 72 | // 注册路由 |
| 73 | await registerRoutes(app); |
| 74 | |
| 75 | // 显式指定前端构建目录时,托管静态文件 |
| 76 | if (process.env.AGENT_TOWER_WEB_DIR) { |
| 77 | const webDistPath = path.resolve( |
| 78 | __dirname, |
| 79 | process.env.AGENT_TOWER_WEB_DIR, |
no test coverage detected