Files
store-platform/app.js
T
2025-12-07 13:34:02 +03:00

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