Files
CShare/backend/src/app.ts
T
arsam df4cac2dc0 Import source: backend/src/app
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:12:22 +03:30

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;
}