"use strict"; const path = require("path"); const express = require("express"); const multer = require("multer"); const { requestContext } = require("./middleware/request-context"); const { originCheck } = require("./middleware/origin-check"); const { createAuthMiddleware } = require("./middleware/auth"); const { RateLimiter } = require("./middleware/rate-limit"); const { errorHandler } = require("./middleware/error-handler"); const { createHealthRoutes } = require("./routes/health.routes"); const { createAuthRoutes } = require("./routes/auth.routes"); const { createHistoryRoutes } = require("./routes/history.routes"); const { createPreviewRoutes } = require("./routes/preview.routes"); const { createEditRoutes } = require("./routes/edit.routes"); const { createUpscaleRoutes } = require("./routes/upscale.routes"); const { createSuggestRoutes } = require("./routes/suggest.routes"); const { createJobsRoutes } = require("./routes/jobs.routes"); const { createEventsRoutes } = require("./routes/events.routes"); const { createResultRoutes } = require("./routes/result.routes"); const { isHeic } = require("../core/images/heic"); const { BadRequestError, NotFoundError } = require("../errors"); /** * Создаёт экземпляр Express приложения с внедрением зависимостей (DI). * @param {Object} deps * @param {import('../config').loadConfig} deps.config * @param {import('../logger').Logger} deps.logger * @param {import('../core/auth/session-store').SessionStore} deps.sessionStore * @param {string} deps.passwordHash * @param {import('../core/history/history-service').HistoryService} deps.historyService * @param {import('../core/jobs/job-service').JobService} deps.jobService * @param {import('../core/comfy/comfy-client').ComfyClient} deps.comfyClient * @param {import('../core/comfy/comfy-runtime').ComfyRuntime} deps.comfyRuntime * @param {import('events').EventEmitter} deps.events * @returns {express.Application} */ function createApp(deps) { const { config, logger, sessionStore, passwordHash, historyService, jobService, comfyClient, comfyRuntime, events, } = deps; const app = express(); app.disable("x-powered-by"); if (config.TRUST_PROXY) { app.set("trust proxy", 1); } // Static files from public/ const publicDir = path.join(__dirname, "../../public"); app.use( express.static(publicDir, { maxAge: "1h", setHeaders: (res, filePath) => { if (filePath.endsWith("index.html")) { res.setHeader("cache-control", "no-cache"); } }, }) ); // Request context & body parsing app.use(express.json({ limit: "16kb" })); app.use(requestContext(logger)); app.use("/api", originCheck()); // Multer instance for uploads const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: config.MAX_UPLOAD_BYTES, files: 2, fields: 8, }, fileFilter: (_request, file, callback) => { const supported = new Set([ "image/jpeg", "image/jpg", "image/png", "image/webp", "image/heic", "image/heif", ]); if (supported.has(file.mimetype.toLowerCase()) || isHeic(file)) { callback(null, true); return; } callback( new BadRequestError( "Формат файла не поддерживается. Загрузите фотографию в формате JPEG, PNG, WebP или HEIC/HEIF." ) ); }, }); // Auth & Rate Limiter const authMiddleware = createAuthMiddleware(sessionStore); const rateLimiter = new RateLimiter({ windowMs: config.LOGIN_RATE_LIMIT.windowMs, max: config.LOGIN_RATE_LIMIT.max, logger, }); // 1. Public API routes app.use("/api", createHealthRoutes({ comfyRuntime, jobService, historyService, config })); app.use("/api", createAuthRoutes({ sessionStore, passwordHash, rateLimiter, config, logger })); // 2. Protected API routes app.use("/api", authMiddleware.requireAuth); app.use("/api", createHistoryRoutes({ historyService, upload, logger })); app.use("/api", createPreviewRoutes({ upload })); app.use("/api", createEditRoutes({ jobService, historyService, upload, logger })); app.use("/api", createUpscaleRoutes({ jobService, historyService, upload, logger })); app.use("/api", createSuggestRoutes({ jobService, historyService, upload, logger })); app.use("/api", createJobsRoutes({ jobService })); app.use("/api", createEventsRoutes({ jobService, events, logger })); app.use("/api", createResultRoutes({ comfyClient })); // 404 handler for unmatched /api requests app.use("/api", (_req, _res, next) => { next(new NotFoundError("Эндпоинт не найден.")); }); // Global error handler app.use(errorHandler(logger)); return app; } module.exports = { createApp, };