253 lines
8.9 KiB
JavaScript
253 lines
8.9 KiB
JavaScript
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}`);
|
||
})
|
||
|
||
|
||
})
|
||
|
||
();
|
||
|
||
|