new file: .claude/settings.local.json
new file: .gitignore new file: backend/.env.example new file: backend/.gitignore new file: backend/ecosystem.config.js new file: backend/nodemon.json new file: backend/package-lock.json new file: backend/package.json new file: backend/src/config/database.ts new file: backend/src/config/dynamicSwagger.ts new file: backend/src/config/environment.ts new file: backend/src/config/swagger.ts new file: backend/src/controllers/apiKeyController.ts new file: backend/src/controllers/authController.ts new file: backend/src/controllers/databaseController.ts new file: backend/src/controllers/databaseManagementController.ts new file: backend/src/controllers/dynamicApiController.ts new file: backend/src/controllers/endpointController.ts new file: backend/src/controllers/folderController.ts new file: backend/src/controllers/logsController.ts new file: backend/src/controllers/userController.ts new file: backend/src/middleware/apiKey.ts new file: backend/src/middleware/auth.ts new file: backend/src/middleware/logging.ts new file: backend/src/migrations/001_initial_schema.sql new file: backend/src/migrations/002_add_logging.sql new file: backend/src/migrations/003_add_scripting.sql new file: backend/src/migrations/004_add_superadmin.sql new file: backend/src/migrations/run.ts new file: backend/src/migrations/seed.ts new file: backend/src/routes/apiKeys.ts new file: backend/src/routes/auth.ts new file: backend/src/routes/databaseManagement.ts new file: backend/src/routes/databases.ts new file: backend/src/routes/dynamic.ts new file: backend/src/routes/endpoints.ts new file: backend/src/routes/folders.ts new file: backend/src/routes/logs.ts new file: backend/src/routes/users.ts new file: backend/src/server.ts new file: backend/src/services/DatabasePoolManager.ts new file: backend/src/services/ScriptExecutor.ts new file: backend/src/services/SqlExecutor.ts new file: backend/src/types/index.ts new file: backend/tsconfig.json new file: frontend/.gitignore new file: frontend/index.html new file: frontend/nginx.conf new file: frontend/package-lock.json new file: frontend/package.json new file: frontend/postcss.config.js new file: frontend/src/App.tsx new file: frontend/src/components/CodeEditor.tsx
This commit is contained in:
237
backend/src/config/dynamicSwagger.ts
Normal file
237
backend/src/config/dynamicSwagger.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
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<SwaggerSpec> {
|
||||
// Загружаем все эндпоинты из базы с полным путем папки
|
||||
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,
|
||||
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<string>();
|
||||
|
||||
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: {
|
||||
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 - Созданные API эндпоинты',
|
||||
version: '1.0.0',
|
||||
description: `
|
||||
# KIS API Builder - Документация
|
||||
|
||||
## Авторизация
|
||||
|
||||
Для доступа к эндпоинтам используйте 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user