Files
store-platform/app.js
abakumov-a-a d6ea4314b0 add docker
2025-12-11 21:47:42 +03:00

253 lines
8.9 KiB
JavaScript
Raw Permalink 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.

import { YmlParser } from "./src/utils/yml-parser.js";
import daoPlugin from './src/plugins/dao.js';
import pino from "pino";
import autoload from '@fastify/autoload';
import fastifySwagger from "@fastify/swagger";
import fastifySwaggerUi from "@fastify/swagger-ui";
import fastifyCors from '@fastify/cors';
import fastifyMultipart from '@fastify/multipart';
import Fastify from 'fastify';
import path from 'path';
import fs from "node:fs";
import { fileURLToPath } from 'url';
import CertManager from "./src/utils/certManager.js";
import fastifyJwt from '@fastify/jwt';
import crypto from 'crypto';
import jwtPlugin from "./src/plugins/authenticate.js";
import authorizePlugin from "./src/plugins/authorize.js";
import fastifyHealthcheck from "fastify-healthcheck";
import minioPlugin from "./src/plugins/minio.js";
(async () => {
let streams;
// Используем pino-pretty только в development режиме
const isDevelopment = process.env.NODE_ENV !== 'production';
let prettyStream = process.stdout;
if (isDevelopment) {
try {
const pretty = (await import('pino-pretty')).default;
prettyStream = pretty({ colorize: true });
} catch (err) {
// Если pino-pretty не установлен, используем обычный stdout
console.warn('pino-pretty не найден, используется обычный вывод логов');
}
}
if (process.env.FILE_LOG_LEVEL !== undefined) {
const logFileStream = fs.createWriteStream(`./logs/event-log.log`, { flags: 'a' });
logFileStream.on('error', (err) => {
console.error('Ошибка при создании потока для файла логов:', err);
});
streams = [
{ level: process.env.FILE_LOG_LEVEL || 'debug', stream: logFileStream },
{ level: process.env.STDOUT_LOG_LEVEL || 'trace', stream: prettyStream }
];
}
else {
streams = [
{ level: process.env.STDOUT_LOG_LEVEL || 'trace', stream: prettyStream }
];
}
const logger = pino(
{ level: process.env.LOG_LEVEL || 'info' },
pino.multistream(streams));
let yml = new YmlParser;
const settings = yml.loadConfig("./conf/config.yml");
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
logger.info("Settings:", settings)
const JWT_SECRET = crypto.randomBytes(32).toString('hex');
let option = {
pluginTimeout: 10000,
logger: {
level: process.env.LOG_LEVEL || 'info',
stream: pino.multistream(streams),
},
trustProxy: true // Включаем доверие к прокси для правильной обработки X-Forwarded-* заголовков от nginx
};
if (settings.web.tsl) {
let sert = new CertManager(settings.tsl)
option.https = await sert.getOptionTSL();
}
const app = Fastify(option);
// Настройка CORS для фронтенда
await app.register(fastifyCors, {
origin: true, // Разрешить все источники в разработке
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
});
// Регистрируем multipart для загрузки файлов
await app.register(fastifyMultipart, {
limits: {
fileSize: 10 * 1024 * 1024, // 10MB
files: 10
},
attachFieldsToBody: false // Не прикрепляем поля к body, обрабатываем через parts()
});
// Настройка Swagger
const swaggerOptions = {
openapi: {
openapi: '3.0.0',
info: {
title: 'Shop Platform API',
description: 'RESTful API для интернет-магазина с полным функционалом управления товарами, заказами, пользователями и корзиной',
version: '1.0.0',
contact: {
name: 'Shop Platform Support'
}
},
servers: [
{
url: settings.web.tsl
? `https://${settings.tsl.host}:${settings.web.https_port}`
: `http://${settings.web.host}:${settings.web.http_port}`,
description: 'Shop Platform Server'
}
],
tags: [
{ name: 'products', description: 'Операции с товарами' },
{ name: 'product-images', description: 'Управление изображениями товаров' },
{ name: 'categories', description: 'Операции с категориями товаров' },
{ name: 'users', description: 'Управление пользователями' },
{ name: 'roles', description: 'Управление ролями и правами доступа' },
{ name: 'permissions', description: 'Управление правами доступа' },
{ name: 'resources', description: 'Управление ресурсами системы' },
{ name: 'actions', description: 'Управление действиями над ресурсами' },
{ name: 'orders', description: 'Управление заказами' },
{ name: 'cart', description: 'Операции с корзиной покупок' },
{ name: 'reviews', description: 'Управление отзывами на товары' },
{ name: 'addresses', description: 'Управление адресами доставки' },
{ name: 'auth', description: 'Авторизация и регистрация пользователей' },
{ name: 'settings', description: 'Настройки системы' },
{ name: 'example', description: 'Примеры endpoints' }
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
description: 'JWT токен авторизации'
}
},
schemas: {
Error: {
type: 'object',
properties: {
success: { type: 'boolean', example: false },
message: { type: 'string' }
}
},
Success: {
type: 'object',
properties: {
success: { type: 'boolean', example: true },
data: { type: 'object' }
}
},
Pagination: {
type: 'object',
properties: {
page: { type: 'integer', example: 1 },
limit: { type: 'integer', example: 20 },
total: { type: 'integer', example: 150 },
totalPages: { type: 'integer', example: 8 },
hasNext: { type: 'boolean', example: true },
hasPrev: { type: 'boolean', example: false }
}
}
}
}
},
exposeRoute: true
};
const swaggerUiOptions = {
routePrefix: '/swagger',
uiConfig: {
docExpansion: 'list',
deepLinking: true,
filter: true,
showExtensions: true,
showCommonExtensions: true,
tryItOutEnabled: true
},
uiHooks: {
onRequest: function (request, reply, next) { next() },
preHandler: function (request, reply, next) { next() }
},
staticCSP: true,
transformStaticCSP: (header) => header,
transformSpecification: (swaggerObject, request, reply) => {
return swaggerObject;
},
transformSpecificationClone: true
};
// Регистрируем Swagger (должен быть зарегистрирован до Swagger UI)
await app.register(fastifySwagger, swaggerOptions);
await app.register(fastifySwaggerUi, swaggerUiOptions);
app.register(fastifyJwt, {
secret: JWT_SECRET,
sign: { expiresIn: '1h' },
// Настройка извлечения токена из заголовков
decode: {
complete: false
},
// Формат токена: Bearer <token>
formatUser: (payload) => {
return payload;
}
});
app.register(jwtPlugin, settings);
app.register(authorizePlugin); // Плагин для проверки прав доступа
// Регистрируем DAO плагин ПЕРЕД роутами (роуты используют fastify.dao)
await app.register(daoPlugin, settings.database);
// Регистрируем MinIO плагин
if (settings.minio) {
await app.register(minioPlugin, settings.minio);
}
app.register(fastifyHealthcheck);
// Автозагрузка роутов из src/routes/v1 (после DAO, чтобы fastify.dao был доступен)
await app.register(autoload, {
dir: path.join(__dirname, 'src/routes/v1'),
options: { prefix: '/api/v1/' },
ignorePattern: /^.*\.test\.js$/,
maxDepth: 1
});
app.listen({ port: settings.web.tsl ? settings.web.https_port : settings.web.http_port,host:settings.web.host }, (err, address) => {
if (err) {
console.error(err);
process.exit(1);
}
logger.info(`Server listening at ${address}`);
})
})
();