// Storage 服务端入口: 业务 API + 图片 API + 静态托管 (SPA) // 端口由部署环境决定 (PORT env), 本地缺省 3002。见 部署环境.md import 'dotenv/config' import path from 'node:path' import fs from 'node:fs' import express from 'express' import cors from 'cors' import appRouter from './routes/app.js' import imagesRouter from './routes/images.js' import { initDb } from './db.js' const PORT = Number(process.env.PORT) || 3002 const DIST_DIR = path.join(process.cwd(), 'dist') const HAS_DIST = fs.existsSync(DIST_DIR) const app = express() app.use(cors()) app.use(express.json({ limit: '2mb' })) app.get('/healthz', (_req, res) => res.json({ ok: true, ts: Date.now() })) app.get('/api/health', (_req, res) => res.json({ ok: true, ts: Date.now() })) // 业务接口 app.use('/api', appRouter) // 图片接口 (签名 URL 鉴权), 需在 SPA fallback 之前挂载 app.use('/api', imagesRouter) if (HAS_DIST) { app.use(express.static(DIST_DIR, { index: false, maxAge: '1h', etag: true })) app.get(/^\/(?!api\/).*/, (_req, res, next) => { const indexHtml = path.join(DIST_DIR, 'index.html') if (!fs.existsSync(indexHtml)) return next() res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate') res.sendFile(indexHtml) }) } app.use((err, _req, res, _next) => { console.error('[api error]', err) const code = err && err.status ? Number(err.status) : 500 if (code >= 500) console.error(err.stack || err) res.status(code).json({ error: (err && err.message) || '服务器内部错误' }) }) initDb() .then(() => { app.listen(PORT, () => { console.log(`[storage-api] listening on http://127.0.0.1:${PORT}`) console.log(`[storage-api] static dist: ${HAS_DIST ? DIST_DIR : '(not built, API only)'}`) }) }) .catch((e) => { console.error('[db] failed to init DuckDB:', e) process.exit(1) })