From 713e9ba7f73702abd2df1cf5bf40d4eab62c7546 Mon Sep 17 00:00:00 2001 From: GEgorov Date: Tue, 7 Oct 2025 19:33:50 +0300 Subject: [PATCH] modified: backend/src/controllers/databaseManagementController.ts modified: backend/src/controllers/dynamicApiController.ts modified: backend/src/controllers/endpointController.ts new file: backend/src/migrations/005_add_aql_support.sql new file: backend/src/services/AqlExecutor.ts modified: backend/src/types/index.ts modified: frontend/src/components/EndpointModal.tsx modified: frontend/src/pages/Databases.tsx modified: frontend/src/types/index.ts --- .../databaseManagementController.ts | 133 +++++++-- .../src/controllers/dynamicApiController.ts | 33 ++- backend/src/controllers/endpointController.ts | 71 ++++- .../src/migrations/005_add_aql_support.sql | 40 +++ backend/src/services/AqlExecutor.ts | 230 +++++++++++++++ backend/src/types/index.ts | 14 +- frontend/src/components/EndpointModal.tsx | 136 +++++++-- frontend/src/pages/Databases.tsx | 269 ++++++++++++------ frontend/src/types/index.ts | 14 +- 9 files changed, 793 insertions(+), 147 deletions(-) create mode 100644 backend/src/migrations/005_add_aql_support.sql create mode 100644 backend/src/services/AqlExecutor.ts diff --git a/backend/src/controllers/databaseManagementController.ts b/backend/src/controllers/databaseManagementController.ts index d698a9e..f2fe2f5 100644 --- a/backend/src/controllers/databaseManagementController.ts +++ b/backend/src/controllers/databaseManagementController.ts @@ -7,7 +7,10 @@ import { databasePoolManager } from '../services/DatabasePoolManager'; export const getDatabases = async (req: AuthRequest, res: Response) => { try { const result = await mainPool.query( - 'SELECT id, name, type, host, port, database_name, username, ssl, is_active, created_at, updated_at FROM databases ORDER BY name' + `SELECT id, name, type, host, port, database_name, username, ssl, is_active, + aql_base_url, aql_auth_type, aql_auth_value, aql_headers, + created_at, updated_at + FROM databases ORDER BY name` ); res.json(result.rows); @@ -22,7 +25,10 @@ export const getDatabase = async (req: AuthRequest, res: Response) => { const { id } = req.params; const result = await mainPool.query( - 'SELECT id, name, type, host, port, database_name, username, ssl, is_active, created_at, updated_at FROM databases WHERE id = $1', + `SELECT id, name, type, host, port, database_name, username, ssl, is_active, + aql_base_url, aql_auth_type, aql_auth_value, aql_headers, + created_at, updated_at + FROM databases WHERE id = $1`, [id] ); @@ -39,26 +45,58 @@ export const getDatabase = async (req: AuthRequest, res: Response) => { export const createDatabase = async (req: AuthRequest, res: Response) => { try { - const { name, type, host, port, database_name, username, password, ssl } = req.body; + const { + name, type, host, port, database_name, username, password, ssl, + aql_base_url, aql_auth_type, aql_auth_value, aql_headers + } = req.body; - if (!name || !host || !port || !database_name || !username || !password) { - return res.status(400).json({ error: 'Не заполнены обязательные поля' }); + const dbType = type || 'postgresql'; + + // Валидация для обычных БД + if (dbType !== 'aql') { + if (!name || !host || !port || !database_name || !username || !password) { + return res.status(400).json({ error: 'Не заполнены обязательные поля' }); + } + } else { + // Валидация для AQL + if (!name || !aql_base_url) { + return res.status(400).json({ error: 'Не заполнены обязательные поля для AQL базы' }); + } } const result = await mainPool.query( - `INSERT INTO databases (name, type, host, port, database_name, username, password, ssl, is_active) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, true) + `INSERT INTO databases ( + name, type, host, port, database_name, username, password, ssl, is_active, + aql_base_url, aql_auth_type, aql_auth_value, aql_headers + ) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, true, $9, $10, $11, $12) RETURNING *`, - [name, type || 'postgresql', host, port, database_name, username, password, ssl || false] + [ + name, + dbType, + host || '', + port || 0, + database_name || '', + username || '', + password || '', + ssl || false, + aql_base_url || null, + aql_auth_type || null, + aql_auth_value || null, + aql_headers ? JSON.stringify(aql_headers) : null + ] ); const newDb = result.rows[0]; - // Добавить пул подключений - await databasePoolManager.reloadPool(newDb.id); + // Добавить пул подключений (только для не-AQL баз) + if (dbType !== 'aql') { + await databasePoolManager.reloadPool(newDb.id); + } // Не возвращаем пароль delete newDb.password; + delete newDb.aql_auth_value; // Также не возвращаем auth value res.status(201).json(newDb); } catch (error: any) { @@ -73,13 +111,16 @@ export const createDatabase = async (req: AuthRequest, res: Response) => { export const updateDatabase = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; - const { name, type, host, port, database_name, username, password, ssl, is_active } = req.body; + const { + name, type, host, port, database_name, username, password, ssl, is_active, + aql_base_url, aql_auth_type, aql_auth_value, aql_headers + } = req.body; - // Если пароль не передан, не обновляем его + // Если пароль/auth не передан, не обновляем его let query; let params; - if (password) { + if (password || aql_auth_value) { query = ` UPDATE databases SET name = COALESCE($1, name), @@ -88,14 +129,22 @@ export const updateDatabase = async (req: AuthRequest, res: Response) => { port = COALESCE($4, port), database_name = COALESCE($5, database_name), username = COALESCE($6, username), - password = $7, + password = COALESCE($7, password), ssl = COALESCE($8, ssl), is_active = COALESCE($9, is_active), + aql_base_url = COALESCE($10, aql_base_url), + aql_auth_type = COALESCE($11, aql_auth_type), + aql_auth_value = COALESCE($12, aql_auth_value), + aql_headers = COALESCE($13, aql_headers), updated_at = CURRENT_TIMESTAMP - WHERE id = $10 - RETURNING id, name, type, host, port, database_name, username, ssl, is_active, created_at, updated_at + WHERE id = $14 + RETURNING id, name, type, host, port, database_name, username, ssl, is_active, + aql_base_url, aql_auth_type, aql_headers, created_at, updated_at `; - params = [name, type, host, port, database_name, username, password, ssl, is_active, id]; + params = [ + name, type, host, port, database_name, username, password || aql_auth_value, ssl, is_active, + aql_base_url, aql_auth_type, aql_auth_value, aql_headers ? JSON.stringify(aql_headers) : null, id + ]; } else { query = ` UPDATE databases @@ -107,11 +156,18 @@ export const updateDatabase = async (req: AuthRequest, res: Response) => { username = COALESCE($6, username), ssl = COALESCE($7, ssl), is_active = COALESCE($8, is_active), + aql_base_url = COALESCE($9, aql_base_url), + aql_auth_type = COALESCE($10, aql_auth_type), + aql_headers = COALESCE($11, aql_headers), updated_at = CURRENT_TIMESTAMP - WHERE id = $9 - RETURNING id, name, type, host, port, database_name, username, ssl, is_active, created_at, updated_at + WHERE id = $12 + RETURNING id, name, type, host, port, database_name, username, ssl, is_active, + aql_base_url, aql_auth_type, aql_headers, created_at, updated_at `; - params = [name, type, host, port, database_name, username, ssl, is_active, id]; + params = [ + name, type, host, port, database_name, username, ssl, is_active, + aql_base_url, aql_auth_type, aql_headers ? JSON.stringify(aql_headers) : null, id + ]; } const result = await mainPool.query(query, params); @@ -120,8 +176,10 @@ export const updateDatabase = async (req: AuthRequest, res: Response) => { return res.status(404).json({ error: 'База данных не найдена' }); } - // Перезагрузить пул - await databasePoolManager.reloadPool(id); + // Перезагрузить пул (только для не-AQL баз) + if (result.rows[0].type !== 'aql') { + await databasePoolManager.reloadPool(id); + } res.json(result.rows[0]); } catch (error: any) { @@ -172,12 +230,33 @@ export const testDatabaseConnection = async (req: AuthRequest, res: Response) => try { const { id } = req.params; - const isConnected = await databasePoolManager.testConnection(id); + // Получаем тип БД + const dbResult = await mainPool.query('SELECT type FROM databases WHERE id = $1', [id]); - res.json({ - success: isConnected, - message: isConnected ? 'Подключение успешно' : 'Ошибка подключения', - }); + if (dbResult.rows.length === 0) { + return res.status(404).json({ success: false, error: 'База данных не найдена' }); + } + + const dbType = dbResult.rows[0].type; + + if (dbType === 'aql') { + // Для AQL используем aqlExecutor + const { aqlExecutor } = require('../services/AqlExecutor'); + const result = await aqlExecutor.testConnection(id); + + res.json({ + success: result.success, + message: result.success ? 'Подключение успешно' : result.error || 'Ошибка подключения', + }); + } else { + // Для обычных БД используем databasePoolManager + const isConnected = await databasePoolManager.testConnection(id); + + res.json({ + success: isConnected, + message: isConnected ? 'Подключение успешно' : 'Ошибка подключения', + }); + } } catch (error) { console.error('Test connection error:', error); res.status(500).json({ error: 'Ошибка тестирования подключения' }); diff --git a/backend/src/controllers/dynamicApiController.ts b/backend/src/controllers/dynamicApiController.ts index 9fddd9c..5afbb0e 100644 --- a/backend/src/controllers/dynamicApiController.ts +++ b/backend/src/controllers/dynamicApiController.ts @@ -141,7 +141,38 @@ export const executeDynamicEndpoint = async (req: ApiKeyRequest, res: Response) let result; const executionType = endpoint.execution_type || 'sql'; - if (executionType === 'script') { + if (executionType === 'aql') { + // Execute AQL query + const aqlMethod = endpoint.aql_method; + const aqlEndpoint = endpoint.aql_endpoint; + const aqlBody = endpoint.aql_body; + + let aqlQueryParams: Record = {}; + if (endpoint.aql_query_params) { + if (typeof endpoint.aql_query_params === 'string') { + try { + aqlQueryParams = JSON.parse(endpoint.aql_query_params); + } catch (e) { + aqlQueryParams = {}; + } + } else if (typeof endpoint.aql_query_params === 'object') { + aqlQueryParams = endpoint.aql_query_params; + } + } + + if (!aqlMethod || !aqlEndpoint) { + return res.status(500).json({ error: 'AQL configuration is incomplete' }); + } + + const { aqlExecutor } = require('../services/AqlExecutor'); + result = await aqlExecutor.executeAqlQuery(endpoint.database_id, { + method: aqlMethod, + endpoint: aqlEndpoint, + body: aqlBody, + queryParams: aqlQueryParams, + parameters: requestParams, + }); + } else if (executionType === 'script') { // Execute script const scriptLanguage = endpoint.script_language; const scriptCode = endpoint.script_code; diff --git a/backend/src/controllers/endpointController.ts b/backend/src/controllers/endpointController.ts index 65890f2..1c3be7a 100644 --- a/backend/src/controllers/endpointController.ts +++ b/backend/src/controllers/endpointController.ts @@ -81,6 +81,10 @@ export const createEndpoint = async (req: AuthRequest, res: Response) => { script_language, script_code, script_queries, + aql_method, + aql_endpoint, + aql_body, + aql_query_params, } = req.body; if (!name || !method || !path) { @@ -103,13 +107,21 @@ export const createEndpoint = async (req: AuthRequest, res: Response) => { } } + // Валидация для типа AQL + if (execType === 'aql') { + if (!database_id || !aql_method || !aql_endpoint) { + return res.status(400).json({ error: 'Database ID, AQL method, and AQL endpoint are required for AQL execution type' }); + } + } + const result = await mainPool.query( `INSERT INTO endpoints ( name, description, method, path, database_id, sql_query, parameters, folder_id, user_id, is_public, enable_logging, - execution_type, script_language, script_code, script_queries + execution_type, script_language, script_code, script_queries, + aql_method, aql_endpoint, aql_body, aql_query_params ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19) RETURNING *`, [ name, @@ -127,6 +139,10 @@ export const createEndpoint = async (req: AuthRequest, res: Response) => { script_language || null, script_code || null, JSON.stringify(script_queries || []), + aql_method || null, + aql_endpoint || null, + aql_body || null, + JSON.stringify(aql_query_params || {}), ] ); @@ -158,6 +174,10 @@ export const updateEndpoint = async (req: AuthRequest, res: Response) => { script_language, script_code, script_queries, + aql_method, + aql_endpoint, + aql_body, + aql_query_params, } = req.body; const result = await mainPool.query( @@ -176,8 +196,12 @@ export const updateEndpoint = async (req: AuthRequest, res: Response) => { script_language = $12, script_code = $13, script_queries = $14, + aql_method = $15, + aql_endpoint = $16, + aql_body = $17, + aql_query_params = $18, updated_at = CURRENT_TIMESTAMP - WHERE id = $15 + WHERE id = $19 RETURNING *`, [ name, @@ -194,6 +218,10 @@ export const updateEndpoint = async (req: AuthRequest, res: Response) => { script_language || null, script_code || null, script_queries ? JSON.stringify(script_queries) : null, + aql_method || null, + aql_endpoint || null, + aql_body || null, + aql_query_params ? JSON.stringify(aql_query_params) : null, id, ] ); @@ -242,7 +270,11 @@ export const testEndpoint = async (req: AuthRequest, res: Response) => { execution_type, script_language, script_code, - script_queries + script_queries, + aql_method, + aql_endpoint, + aql_body, + aql_query_params, } = req.body; const execType = execution_type || 'sql'; @@ -305,6 +337,37 @@ export const testEndpoint = async (req: AuthRequest, res: Response) => { rowCount: scriptResult.rowCount || (Array.isArray(scriptResult.data) ? scriptResult.data.length : 0), executionTime: scriptResult.executionTime || 0, }); + } else if (execType === 'aql') { + if (!database_id) { + return res.status(400).json({ error: 'Missing database_id for AQL execution' }); + } + if (!aql_method || !aql_endpoint) { + return res.status(400).json({ error: 'Missing aql_method or aql_endpoint' }); + } + + // Собираем параметры из тестовых значений + const requestParams: Record = {}; + if (endpoint_parameters && Array.isArray(endpoint_parameters) && parameters && Array.isArray(parameters)) { + endpoint_parameters.forEach((param: any, index: number) => { + requestParams[param.name] = parameters[index]; + }); + } + + const { aqlExecutor } = require('../services/AqlExecutor'); + const result = await aqlExecutor.executeAqlQuery(database_id, { + method: aql_method, + endpoint: aql_endpoint, + body: aql_body, + queryParams: aql_query_params, + parameters: requestParams, + }); + + res.json({ + success: true, + data: result.rows, + rowCount: result.rowCount, + executionTime: result.executionTime, + }); } else { return res.status(400).json({ error: 'Invalid execution_type' }); } diff --git a/backend/src/migrations/005_add_aql_support.sql b/backend/src/migrations/005_add_aql_support.sql new file mode 100644 index 0000000..ddbc541 --- /dev/null +++ b/backend/src/migrations/005_add_aql_support.sql @@ -0,0 +1,40 @@ +-- Add AQL support to databases table +ALTER TABLE databases + ALTER COLUMN type TYPE VARCHAR(50); + +-- Update the type check constraint to include 'aql' +ALTER TABLE databases + DROP CONSTRAINT IF EXISTS databases_type_check; + +ALTER TABLE databases + ADD CONSTRAINT databases_type_check + CHECK (type IN ('postgresql', 'mysql', 'mssql', 'aql')); + +-- Add AQL-specific columns to databases table +ALTER TABLE databases + ADD COLUMN IF NOT EXISTS aql_base_url TEXT, + ADD COLUMN IF NOT EXISTS aql_auth_type VARCHAR(50) CHECK (aql_auth_type IN ('basic', 'bearer', 'custom')), + ADD COLUMN IF NOT EXISTS aql_auth_value TEXT, + ADD COLUMN IF NOT EXISTS aql_headers JSONB DEFAULT '{}'::jsonb; + +-- Add AQL support to endpoints table +ALTER TABLE endpoints + ALTER COLUMN execution_type TYPE VARCHAR(50); + +-- Update execution_type check constraint to include 'aql' +ALTER TABLE endpoints + DROP CONSTRAINT IF EXISTS endpoints_execution_type_check; + +ALTER TABLE endpoints + ADD CONSTRAINT endpoints_execution_type_check + CHECK (execution_type IN ('sql', 'script', 'aql')); + +-- Add AQL-specific columns to endpoints table +ALTER TABLE endpoints + ADD COLUMN IF NOT EXISTS aql_method VARCHAR(10) CHECK (aql_method IN ('GET', 'POST', 'PUT', 'DELETE')), + ADD COLUMN IF NOT EXISTS aql_endpoint TEXT, + ADD COLUMN IF NOT EXISTS aql_body TEXT, + ADD COLUMN IF NOT EXISTS aql_query_params JSONB DEFAULT '{}'::jsonb; + +-- Create index for AQL endpoints +CREATE INDEX IF NOT EXISTS idx_endpoints_execution_type ON endpoints(execution_type); diff --git a/backend/src/services/AqlExecutor.ts b/backend/src/services/AqlExecutor.ts new file mode 100644 index 0000000..2435a5c --- /dev/null +++ b/backend/src/services/AqlExecutor.ts @@ -0,0 +1,230 @@ +import { QueryResult, DatabaseConfig } from '../types'; +import { mainPool } from '../config/database'; + +interface AqlRequestConfig { + method: 'GET' | 'POST' | 'PUT' | 'DELETE'; + endpoint: string; + body?: string; + queryParams?: Record; + parameters?: Record; +} + +export class AqlExecutor { + /** + * Получает конфигурацию AQL базы данных + */ + private async getDatabaseConfig(databaseId: string): Promise { + const result = await mainPool.query( + 'SELECT * FROM databases WHERE id = $1 AND type = $2', + [databaseId, 'aql'] + ); + + if (result.rows.length === 0) { + return null; + } + + return result.rows[0]; + } + + /** + * Заменяет параметры вида $paramName в строке на значения из объекта parameters + */ + private replaceParameters(template: string, parameters: Record): string { + let result = template; + + // Находим все параметры вида $paramName + const paramMatches = template.match(/\$\w+/g) || []; + const uniqueParams = [...new Set(paramMatches.map(p => p.substring(1)))]; + + uniqueParams.forEach((paramName) => { + const regex = new RegExp(`\\$${paramName}\\b`, 'g'); + const value = parameters[paramName]; + + if (value !== undefined && value !== null) { + // Для строк в JSON нужны кавычки, для чисел - нет + const replacement = typeof value === 'string' + ? value + : JSON.stringify(value); + result = result.replace(regex, replacement); + } else { + result = result.replace(regex, ''); + } + }); + + return result; + } + + /** + * Строит query string из объекта параметров + */ + private buildQueryString(params: Record, requestParams: Record): string { + const processedParams: Record = {}; + + for (const [key, value] of Object.entries(params)) { + processedParams[key] = this.replaceParameters(value, requestParams); + } + + const queryString = new URLSearchParams(processedParams).toString(); + return queryString ? `?${queryString}` : ''; + } + + /** + * Выполняет AQL запрос + */ + async executeAqlQuery( + databaseId: string, + config: AqlRequestConfig + ): Promise { + const startTime = Date.now(); + + try { + // Получаем конфигурацию БД + const dbConfig = await this.getDatabaseConfig(databaseId); + + if (!dbConfig) { + throw new Error(`AQL database with id ${databaseId} not found or not configured`); + } + + if (!dbConfig.aql_base_url) { + throw new Error(`AQL base URL not configured for database ${databaseId}`); + } + + const parameters = config.parameters || {}; + + // Обрабатываем endpoint с параметрами + const processedEndpoint = this.replaceParameters(config.endpoint, parameters); + + // Обрабатываем query параметры + const queryString = config.queryParams + ? this.buildQueryString(config.queryParams, parameters) + : ''; + + // Формируем полный URL + const fullUrl = `${dbConfig.aql_base_url}${processedEndpoint}${queryString}`; + + // Обрабатываем body с параметрами + let processedBody: string | undefined; + if (config.body) { + processedBody = this.replaceParameters(config.body, parameters); + } + + // Формируем заголовки + const headers: Record = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }; + + // Добавляем аутентификацию + if (dbConfig.aql_auth_type === 'basic' && dbConfig.aql_auth_value) { + headers['Authorization'] = `Basic ${dbConfig.aql_auth_value}`; + } else if (dbConfig.aql_auth_type === 'bearer' && dbConfig.aql_auth_value) { + headers['Authorization'] = `Bearer ${dbConfig.aql_auth_value}`; + } else if (dbConfig.aql_auth_type === 'custom' && dbConfig.aql_auth_value) { + headers['Authorization'] = dbConfig.aql_auth_value; + } + + // Добавляем кастомные заголовки из конфигурации БД + if (dbConfig.aql_headers) { + const customHeaders = typeof dbConfig.aql_headers === 'string' + ? JSON.parse(dbConfig.aql_headers) + : dbConfig.aql_headers; + + Object.assign(headers, customHeaders); + } + + // Выполняем HTTP запрос + const response = await fetch(fullUrl, { + method: config.method, + headers, + body: processedBody, + }); + + const executionTime = Date.now() - startTime; + + // Проверяем статус ответа + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`AQL API error (${response.status}): ${errorText}`); + } + + // Парсим JSON ответ + const data = await response.json(); + + // Нормализуем ответ к формату QueryResult + let rows: any[]; + let rowCount: number; + + if (Array.isArray(data)) { + rows = data; + rowCount = data.length; + } else if (data && typeof data === 'object') { + // Если ответ - объект, оборачиваем его в массив + rows = [data]; + rowCount = 1; + } else { + rows = []; + rowCount = 0; + } + + return { + rows, + rowCount, + executionTime, + }; + } catch (error: any) { + console.error('AQL Execution Error:', error); + throw new Error(`AQL Error: ${error.message}`); + } + } + + /** + * Тестирует AQL запрос + */ + async testAqlQuery( + databaseId: string, + config: AqlRequestConfig + ): Promise<{ success: boolean; error?: string }> { + try { + await this.executeAqlQuery(databaseId, config); + return { success: true }; + } catch (error: any) { + return { success: false, error: error.message }; + } + } + + /** + * Тестирует подключение к AQL базе + */ + async testConnection(databaseId: string): Promise<{ success: boolean; error?: string }> { + try { + const dbConfig = await this.getDatabaseConfig(databaseId); + + if (!dbConfig) { + return { success: false, error: 'Database not found' }; + } + + if (!dbConfig.aql_base_url) { + return { success: false, error: 'AQL base URL not configured' }; + } + + // Пробуем выполнить простой запрос для проверки соединения + const response = await fetch(dbConfig.aql_base_url, { + method: 'GET', + headers: { + 'Accept': 'application/json', + }, + }); + + if (response.ok || response.status === 404) { + // 404 тоже OK - это значит что сервер доступен + return { success: true }; + } else { + return { success: false, error: `HTTP ${response.status}` }; + } + } catch (error: any) { + return { success: false, error: error.message }; + } + } +} + +export const aqlExecutor = new AqlExecutor(); diff --git a/backend/src/types/index.ts b/backend/src/types/index.ts index 6bcff4c..795dbff 100644 --- a/backend/src/types/index.ts +++ b/backend/src/types/index.ts @@ -1,7 +1,7 @@ export interface DatabaseConfig { id: string; name: string; - type: 'postgresql' | 'mysql' | 'mssql'; + type: 'postgresql' | 'mysql' | 'mssql' | 'aql'; host: string; port: number; database_name: string; @@ -9,6 +9,11 @@ export interface DatabaseConfig { password: string; ssl: boolean; is_active?: boolean; + // AQL-specific fields + aql_base_url?: string; + aql_auth_type?: 'basic' | 'bearer' | 'custom'; + aql_auth_value?: string; + aql_headers?: Record; created_at?: Date; updated_at?: Date; } @@ -57,10 +62,15 @@ export interface Endpoint { user_id: string; is_public: boolean; enable_logging: boolean; - execution_type: 'sql' | 'script'; + execution_type: 'sql' | 'script' | 'aql'; script_language?: 'javascript' | 'python'; script_code?: string; script_queries?: ScriptQuery[]; + // AQL-specific fields + aql_method?: 'GET' | 'POST' | 'PUT' | 'DELETE'; + aql_endpoint?: string; + aql_body?: string; + aql_query_params?: Record; created_at: Date; updated_at: Date; } diff --git a/frontend/src/components/EndpointModal.tsx b/frontend/src/components/EndpointModal.tsx index c99dee6..3695272 100644 --- a/frontend/src/components/EndpointModal.tsx +++ b/frontend/src/components/EndpointModal.tsx @@ -36,11 +36,20 @@ export default function EndpointModal({ script_language: endpoint?.script_language || 'javascript', script_code: endpoint?.script_code || '', script_queries: endpoint?.script_queries || [], + // AQL-specific fields + aql_method: endpoint?.aql_method || 'GET', + aql_endpoint: endpoint?.aql_endpoint || '', + aql_body: endpoint?.aql_body || '', + aql_query_params: endpoint?.aql_query_params || {}, }); const [editingQueryIndex, setEditingQueryIndex] = useState(null); const [showScriptCodeEditor, setShowScriptCodeEditor] = useState(false); + // Определяем тип выбранной базы данных + const selectedDatabase = databases.find(db => db.id === formData.database_id); + const isAqlDatabase = selectedDatabase?.type === 'aql'; + const saveMutation = useMutation({ mutationFn: (data: any) => endpoint ? endpointsApi.update(endpoint.id, data) : endpointsApi.create(data), @@ -72,7 +81,18 @@ export default function EndpointModal({ } }); - if (formData.execution_type === 'script') { + if (formData.execution_type === 'aql') { + return endpointsApi.test({ + database_id: formData.database_id || '', + execution_type: 'aql', + aql_method: formData.aql_method || 'GET', + aql_endpoint: formData.aql_endpoint || '', + aql_body: formData.aql_body || '', + aql_query_params: typeof formData.aql_query_params === 'string' ? {} : formData.aql_query_params || {}, + parameters: paramValues, + endpoint_parameters: formData.parameters, + } as any); + } else if (formData.execution_type === 'script') { // Для скриптов используем database_id из первого запроса или пустую строку const scriptQueries = formData.script_queries || []; const firstDbId = scriptQueries.length > 0 ? scriptQueries[0].database_id : ''; @@ -156,19 +176,21 @@ export default function EndpointModal({ /> -
- - -
+ {!isAqlDatabase && ( +
+ + +
+ )} -
+
- {formData.execution_type === 'sql' && ( + {(formData.execution_type === 'sql' || isAqlDatabase) && (
@@ -210,7 +232,7 @@ export default function EndpointModal({