238 lines
8.4 KiB
JavaScript
238 lines
8.4 KiB
JavaScript
import { YmlParser } from "./src/utils/yml-parser.js";
|
|
import daoPlugin from './src/plugins/dao.js';
|
|
import pino from "pino";
|
|
import pretty from 'pino-pretty';
|
|
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;
|
|
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: pretty({ colorize: true }) }
|
|
];
|
|
}
|
|
else streams = [
|
|
{ level: process.env.STDOUT_LOG_LEVEL || 'trace', stream: pretty({ colorize: true }) }
|
|
];
|
|
|
|
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}`);
|
|
})
|
|
|
|
|
|
})
|
|
|
|
();
|
|
|
|
|