Some checks failed
test / test (push) Failing after 24s
Реализация целевого дизайна (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/
147 lines
4.8 KiB
JavaScript
147 lines
4.8 KiB
JavaScript
"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,
|
||
};
|