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:
GEgorov
2025-10-07 00:04:04 +03:00
commit 8943f5a070
79 changed files with 17032 additions and 0 deletions

View 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,
};
}