import { mainPool } from './database'; interface SwaggerPath { [method: string]: { tags: string[]; summary: string; description: string; security: { apiKey: [] }[]; parameters: any[]; responses: any; }; } interface SwaggerSpec { openapi: string; info: any; servers: any[]; components: any; paths: { [path: string]: SwaggerPath }; tags: any[]; } export async function generateDynamicSwagger(): Promise { // Загружаем все эндпоинты из базы с полным путем папки const endpointsResult = await mainPool.query(` WITH RECURSIVE folder_path AS ( -- Базовый случай: папки без родителя SELECT id, name, parent_id, name::text as full_path FROM folders WHERE parent_id IS NULL UNION ALL -- Рекурсивный случай: добавляем дочерние папки SELECT f.id, f.name, f.parent_id, (fp.full_path || ' / ' || f.name)::text as full_path FROM folders f INNER JOIN folder_path fp ON f.parent_id = fp.id ) SELECT e.id, e.name, e.description, e.method, e.path, e.parameters, e.is_public, e.response_schema, fp.full_path as folder_name FROM endpoints e LEFT JOIN folder_path fp ON e.folder_id = fp.id ORDER BY fp.full_path, e.name `); const endpoints = endpointsResult.rows; // Группируем теги по папкам const tags: any[] = []; const folderSet = new Set(); endpoints.forEach((endpoint: any) => { const folderName = endpoint.folder_name || 'Без категории'; if (!folderSet.has(folderName)) { folderSet.add(folderName); tags.push({ name: folderName, description: `Эндпоинты в папке "${folderName}"`, }); } }); // Генерируем пути const paths: { [path: string]: SwaggerPath } = {}; endpoints.forEach((endpoint: any) => { const method = endpoint.method.toLowerCase(); const swaggerPath = endpoint.path.replace(/\/api\/v1/, ''); const folderName = endpoint.folder_name || 'Без категории'; // Парсим параметры - PostgreSQL может вернуть как JSON строку, так и уже распарсенный объект const parameters: any[] = []; const bodyParams: any = {}; const bodyRequired: string[] = []; let params: any[] = []; if (endpoint.parameters) { if (typeof endpoint.parameters === 'string') { try { params = JSON.parse(endpoint.parameters); } catch (e) { params = []; } } else if (Array.isArray(endpoint.parameters)) { params = endpoint.parameters; } } if (params.length > 0) { params.forEach((param: any) => { if (param.in === 'body') { // Body параметры идут в requestBody bodyParams[param.name] = { type: param.type || 'string', description: param.description || '', default: param.default_value, }; if (param.required) { bodyRequired.push(param.name); } } else { // Query и path параметры parameters.push({ name: param.name, in: param.in || 'query', required: param.required || false, description: param.description || '', schema: { type: param.type || 'string', default: param.default_value, }, }); } }); } if (!paths[swaggerPath]) { paths[swaggerPath] = {} as SwaggerPath; } const endpointSpec: any = { tags: [folderName], summary: endpoint.name, description: endpoint.description || '', security: endpoint.is_public ? [] : [{ apiKey: [] }], parameters, responses: { '200': { description: 'Успешный ответ', content: { 'application/json': { schema: endpoint.response_schema ? endpoint.response_schema : { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'array', items: { type: 'object' } }, rowCount: { type: 'number' }, executionTime: { type: 'number' }, }, }, }, }, }, '400': { description: 'Ошибка в запросе', content: { 'application/json': { schema: { type: 'object', properties: { error: { type: 'string' }, }, }, }, }, }, '401': { description: 'Не авторизован (неверный или отсутствующий API ключ)', }, '403': { description: 'Доступ запрещен (нет прав на этот эндпоинт)', }, '500': { description: 'Внутренняя ошибка сервера', }, }, }; // Добавляем requestBody если есть body параметры if (Object.keys(bodyParams).length > 0) { endpointSpec.requestBody = { required: bodyRequired.length > 0, content: { 'application/json': { schema: { type: 'object', properties: bodyParams, required: bodyRequired, }, }, }, }; } paths[swaggerPath][method] = endpointSpec; }); return { openapi: '3.0.0', info: { title: 'KIS API Builder', version: '1.0.0', description: ` # Документация ## Авторизация Для доступа к эндпоинтам используйте API ключ, полученный от администратора системы. 1. Нажмите кнопку **Authorize** справа вверху 2. Введите ваш API ключ в поле **x-api-key** 3. Нажмите **Authorize** Теперь вы можете тестировать доступные вам эндпоинты прямо в этой документации. ## Примечание Публичные эндпоинты доступны без API ключа. `.trim(), }, servers: [ { url: '/api/v1', description: 'API эндпоинты', }, ], components: { securitySchemes: { apiKey: { type: 'apiKey', in: 'header', name: 'x-api-key', description: 'API ключ для доступа к эндпоинтам', }, }, }, paths, tags, }; }