modified: backend/src/services/AqlExecutor.ts

new file:   backend/src/services/AqlExecutor.ts.backup
This commit is contained in:
GEgorov
2025-10-21 12:59:45 +03:00
parent 9f7a255e33
commit 4164964fd1
2 changed files with 273 additions and 4 deletions

View File

@@ -140,12 +140,21 @@ export class AqlExecutor {
console.log('Body:', processedBody || '(no body)');
console.log('===================\n');
// Выполняем HTTP запрос
const response = await fetch(fullUrl, {
// Формируем опции для fetch
// ВАЖНО: body не должен передаваться для GET запросов
// и если он undefined, иначе fetch может изменить метод на GET
const fetchOptions: RequestInit = {
method: config.method,
headers,
body: processedBody,
});
};
// Добавляем body только если он есть и метод поддерживает body
if (processedBody && config.method !== 'GET') {
fetchOptions.body = processedBody;
}
// Выполняем HTTP запрос
const response = await fetch(fullUrl, fetchOptions);
const executionTime = Date.now() - startTime;

View File

@@ -0,0 +1,260 @@
import { QueryResult, DatabaseConfig } from '../types';
import { mainPool } from '../config/database';
interface AqlRequestConfig {
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
endpoint: string;
body?: string;
queryParams?: Record<string, string>;
parameters?: Record<string, any>;
}
export class AqlExecutor {
/**
* Получает конфигурацию AQL базы данных
*/
private async getDatabaseConfig(databaseId: string): Promise<DatabaseConfig | null> {
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, any>): 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<string, string>, requestParams: Record<string, any>): string {
const processedParams: Record<string, string> = {};
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<QueryResult> {
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<string, string> = {
'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);
}
// Логируем запрос
console.log('\n=== AQL Request ===');
console.log('URL:', fullUrl);
console.log('Method:', config.method);
console.log('Headers:', JSON.stringify(headers, null, 2));
console.log('Body:', processedBody || '(no body)');
console.log('===================\n');
// Выполняем 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();
console.log('\n=== AQL Error Response ===');
console.log('Status:', response.status);
console.log('Response:', errorText);
console.log('==========================\n');
throw new Error(`AQL API error (${response.status}): ${errorText}`);
}
// Обрабатываем пустой ответ (204 No Content)
if (response.status === 204) {
console.log('\n=== AQL Response ===');
console.log('Status: 204 (No Content)');
console.log('====================\n');
return {
rows: [],
rowCount: 0,
executionTime,
};
}
// Парсим JSON ответ
const responseText = await response.text();
console.log('\n=== AQL Response ===');
console.log('Status:', response.status);
console.log('Raw Response:', responseText);
console.log('====================\n');
// Если ответ пустой, возвращаем пустой результат
if (!responseText || responseText.trim() === '') {
return {
rows: [],
rowCount: 0,
executionTime,
};
}
let data;
try {
data = JSON.parse(responseText);
} catch (e) {
console.error('Failed to parse JSON response:', e);
throw new Error(`Invalid JSON response: ${responseText.substring(0, 200)}`);
}
// Возвращаем данные как есть
return {
rows: data,
rowCount: Array.isArray(data) ? data.length : (data ? 1 : 0),
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();