Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
111 lines
3.5 KiB
TypeScript
111 lines
3.5 KiB
TypeScript
import express from 'express';
|
|
import cors from 'cors';
|
|
import helmet from 'helmet';
|
|
import cookieParser from 'cookie-parser';
|
|
import compression from 'compression';
|
|
import { pinoHttp } from 'pino-http';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { dirname, join } from 'node:path';
|
|
import yaml from 'js-yaml';
|
|
import fs from 'node:fs';
|
|
import swaggerUi from 'swagger-ui-express';
|
|
import { env } from './config/env.js';
|
|
import { errorHandler, notFoundHandler } from './middleware/errorHandler.js';
|
|
import { sessionMiddleware } from './middleware/session.js';
|
|
import { authRouter } from './routes/auth.js';
|
|
import { dashboardRouter } from './routes/dashboard.js';
|
|
import { adminRouter } from './routes/admin.js';
|
|
import { filesRouter } from './routes/files.js';
|
|
import { linksRouter } from './routes/links.js';
|
|
import { analyticsRouter } from './routes/analytics.js';
|
|
import { store } from './services/store.js';
|
|
import { csrfProtection } from './middleware/csrf.js';
|
|
import { checkGdriveHealth } from './utils/gdrive.js';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
// yaml.load uses DEFAULT_SCHEMA in js-yaml v4 — no arbitrary code execution
|
|
const openapiSpec = yaml.load(
|
|
fs.readFileSync(join(__dirname, 'openapi.yaml'), 'utf8'),
|
|
{ schema: yaml.DEFAULT_SCHEMA },
|
|
) as object;
|
|
|
|
export async function createApp() {
|
|
await store.init();
|
|
|
|
const app = express();
|
|
app.disable('x-powered-by');
|
|
app.set('trust proxy', env.nodeEnv === 'production' ? 1 : false);
|
|
|
|
app.use(helmet({
|
|
crossOriginEmbedderPolicy: false,
|
|
contentSecurityPolicy: env.nodeEnv === 'production'
|
|
? {
|
|
directives: {
|
|
defaultSrc: ["'none'"],
|
|
baseUri: ["'none'"],
|
|
frameAncestors: ["'none'"],
|
|
formAction: ["'none'"],
|
|
},
|
|
}
|
|
: false,
|
|
}));
|
|
app.use(compression());
|
|
app.use(cors({
|
|
origin(origin, callback) {
|
|
if (env.nodeEnv !== 'production' || !origin || origin === env.frontendUrl) {
|
|
callback(null, true);
|
|
return;
|
|
}
|
|
callback(new Error('Not allowed by CORS'));
|
|
},
|
|
credentials: true,
|
|
}));
|
|
app.use(pinoHttp({
|
|
enabled: env.nodeEnv !== 'test',
|
|
redact: ['req.headers.authorization', 'req.headers.cookie'],
|
|
}));
|
|
app.use(express.json({ limit: '1mb' }));
|
|
app.use(cookieParser());
|
|
app.use(csrfProtection);
|
|
app.use(sessionMiddleware);
|
|
app.use('/api', (_req, res, next) => {
|
|
res.setHeader('Cache-Control', 'no-store');
|
|
next();
|
|
});
|
|
|
|
app.get('/health', async (_req, res) => {
|
|
const [dbOk, gdriveOk] = await Promise.all([
|
|
store.read().then(() => true).catch(() => false),
|
|
env.nodeEnv === 'test' ? Promise.resolve(true) : checkGdriveHealth(),
|
|
]);
|
|
|
|
const allOk = dbOk && gdriveOk;
|
|
res.status(allOk ? 200 : 503).json({
|
|
status: allOk ? 'ok' : 'degraded',
|
|
db: dbOk ? 'ok' : 'error',
|
|
storage: gdriveOk ? 'ok' : 'error',
|
|
uptime: process.uptime(),
|
|
version: '0.1.0',
|
|
});
|
|
});
|
|
|
|
// Swagger UI — only mounted outside production or behind admin routes
|
|
if (env.nodeEnv !== 'production') {
|
|
app.use('/api/docs', swaggerUi.serve, swaggerUi.setup(openapiSpec));
|
|
}
|
|
// Always expose the raw spec for tooling
|
|
app.get('/api/openapi.json', (_req, res) => res.json(openapiSpec));
|
|
|
|
app.use('/api/auth', authRouter);
|
|
app.use('/api/admin', adminRouter);
|
|
app.use('/api/v1', analyticsRouter);
|
|
app.use('/api/v1/files', filesRouter);
|
|
app.use('/api/v1/links', linksRouter);
|
|
app.use('/api/v1/dashboard', dashboardRouter);
|
|
|
|
app.use(notFoundHandler);
|
|
app.use(errorHandler);
|
|
|
|
return app;
|
|
}
|