Files
photo-editor/src/http/app.js
Coder 401969cfdb
Some checks failed
test / test (push) Failing after 24s
v2: модульный монолит — очередь, персистентность, безопасность, SSE, тесты
Реализация целевого дизайна (docs/architecture/target-design.md):
- src/: модульная структура (config, logger, errors, http, core: auth/history/jobs/engines/comfy/codex/images)
- Очередь заданий: FIFO, кониурентность codex=1 / comfy-upscale=2, MAX_QUEUED_JOBS=5, JSONL-журнал и восстановление после рестарта (running → interrupted, upscale requeue)
- Персистентные сессии (sha256-хеши токенов), scrypt-хеш пароля, rate-limit входа, Origin-проверка, magic-byte валидация аплоадов
- Атомарная запись манифеста истории, каскадное удаление, URL с фактическим расширением + 302-алиас /image.jpg, миграция history/ → data/history
- Даунскейл sharp до 1024px перед Codex + масштабирование области референса
- SSE /api/events с фолбэком на polling, фронтенд переведён на ES-модули (public/js/)
- Тесты node:test + supertest (27), CI workflow test.yml, Docker/деплой: том kadr-data
- Документация: README, docs/architecture/
2026-08-27 12:52:27 +07:00

147 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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,
};