103 lines
3.0 KiB
TypeScript
103 lines
3.0 KiB
TypeScript
|
|
import 'express-async-errors';
|
||
|
|
/**
|
||
|
|
* Express 应用主入口
|
||
|
|
*/
|
||
|
|
import express, {
|
||
|
|
type Request,
|
||
|
|
type Response,
|
||
|
|
} from 'express';
|
||
|
|
import fs from 'node:fs';
|
||
|
|
import path from 'node:path';
|
||
|
|
import { fileURLToPath } from 'node:url';
|
||
|
|
import cors from 'cors';
|
||
|
|
import dotenv from 'dotenv';
|
||
|
|
import notesRoutes from './routes/notes.js';
|
||
|
|
import imagesRoutes from './routes/images.js';
|
||
|
|
import annotationsRoutes from './routes/annotations.js';
|
||
|
|
import projectsRoutes from './routes/projects.js';
|
||
|
|
import commentsRoutes from './routes/comments.js';
|
||
|
|
import authRoutes from './routes/auth.js';
|
||
|
|
import { optionalAuth } from './auth.js';
|
||
|
|
import groupsRoutes from './routes/groups.js';
|
||
|
|
import managementRoutes from './routes/management.js';
|
||
|
|
import storageRoutes from './routes/storage.js';
|
||
|
|
import reviewRoutes from './routes/review.js';
|
||
|
|
import { UPLOADS_DIR } from './upload.js';
|
||
|
|
import { database, databaseDialect } from './database.js';
|
||
|
|
|
||
|
|
dotenv.config();
|
||
|
|
|
||
|
|
const app: express.Application = express();
|
||
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||
|
|
app.set('trust proxy', 1);
|
||
|
|
|
||
|
|
const allowedOrigins = process.env.CORS_ORIGIN?.split(',').map((item) => item.trim()).filter(Boolean);
|
||
|
|
app.use(cors({ origin: allowedOrigins?.length ? allowedOrigins : true, credentials: true }));
|
||
|
|
app.use(express.json({ limit: '20mb' }));
|
||
|
|
app.use(express.urlencoded({ extended: true, limit: '20mb' }));
|
||
|
|
app.use(optionalAuth);
|
||
|
|
|
||
|
|
// 静态图片资源
|
||
|
|
app.use(
|
||
|
|
'/uploads',
|
||
|
|
express.static(UPLOADS_DIR, {
|
||
|
|
maxAge: '7d',
|
||
|
|
immutable: true,
|
||
|
|
}),
|
||
|
|
);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* API 路由
|
||
|
|
*/
|
||
|
|
app.use('/api/notes', notesRoutes);
|
||
|
|
app.use('/api/images', imagesRoutes);
|
||
|
|
app.use('/api/annotations', annotationsRoutes);
|
||
|
|
app.use('/api/projects', projectsRoutes);
|
||
|
|
app.use('/api', commentsRoutes);
|
||
|
|
app.use('/api/auth', authRoutes);
|
||
|
|
app.use('/api/groups', groupsRoutes);
|
||
|
|
app.use('/api/management', managementRoutes);
|
||
|
|
app.use('/api/management/storage-configs', storageRoutes);
|
||
|
|
app.use('/api/review', reviewRoutes);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* health
|
||
|
|
*/
|
||
|
|
app.use('/api/health', async (_req: Request, res: Response) => {
|
||
|
|
await database.one('SELECT 1 AS ready');
|
||
|
|
res.status(200).json({ success: true, message: 'ok', database: databaseDialect });
|
||
|
|
});
|
||
|
|
|
||
|
|
const distDir = path.resolve(__dirname, '..', 'dist');
|
||
|
|
if (process.env.NODE_ENV === 'production' && fs.existsSync(distDir)) {
|
||
|
|
app.use(express.static(distDir, { maxAge: '1h' }));
|
||
|
|
app.get('*', (req, res, next) => {
|
||
|
|
if (req.path.startsWith('/api/')) { next(); return; }
|
||
|
|
res.sendFile(path.join(distDir, 'index.html'));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 错误处理
|
||
|
|
*/
|
||
|
|
app.use((err: Error, _req: Request, res: Response, _next: unknown) => {
|
||
|
|
void _next;
|
||
|
|
console.error('[API Error]', err);
|
||
|
|
// multer 文件类型/大小错误
|
||
|
|
const message = err.message || '服务器内部错误';
|
||
|
|
if (err.message?.includes('不支持的文件类型')) {
|
||
|
|
res.status(400).json({ success: false, error: message });
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
res.status(500).json({ success: false, error: message });
|
||
|
|
});
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 404
|
||
|
|
*/
|
||
|
|
app.use((_req: Request, res: Response) => {
|
||
|
|
res.status(404).json({ success: false, error: 'API not found' });
|
||
|
|
});
|
||
|
|
|
||
|
|
export default app;
|