From df4cac2dc07aa6242570a7fc93b36fae7b4bba44 Mon Sep 17 00:00:00 2001 From: arsam Date: Fri, 18 Sep 2026 23:12:22 +0330 Subject: [PATCH] Import source: backend/src/app Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality. --- backend/src/app.ts | 110 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 backend/src/app.ts diff --git a/backend/src/app.ts b/backend/src/app.ts new file mode 100644 index 0000000..5d5eac1 --- /dev/null +++ b/backend/src/app.ts @@ -0,0 +1,110 @@ +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; +}