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 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}`); }) }) ();