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
This commit is contained in:
GEgorov
2025-10-07 19:33:50 +03:00
parent 7d8fddfe4f
commit 713e9ba7f7
9 changed files with 793 additions and 147 deletions

View File

@@ -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<number | null>(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({
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Тип выполнения</label>
<select
value={formData.execution_type}
onChange={(e) => setFormData({ ...formData, execution_type: e.target.value as 'sql' | 'script' })}
className="input w-full"
>
<option value="sql">SQL Запрос</option>
<option value="script">Скрипт (JavaScript/Python)</option>
</select>
</div>
{!isAqlDatabase && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Тип выполнения</label>
<select
value={formData.execution_type}
onChange={(e) => setFormData({ ...formData, execution_type: e.target.value as 'sql' | 'script' })}
className="input w-full"
>
<option value="sql">QL Запрос</option>
<option value="script">Скрипт (JavaScript/Python) + QL запросы</option>
</select>
</div>
)}
<div className={`grid ${formData.execution_type === 'sql' ? 'grid-cols-3' : 'grid-cols-2'} gap-4`}>
<div className={`grid ${(!isAqlDatabase && formData.execution_type === 'sql') || isAqlDatabase ? 'grid-cols-3' : 'grid-cols-2'} gap-4`}>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Путь</label>
<input
@@ -180,18 +202,18 @@ export default function EndpointModal({
placeholder="/api/v1/users"
/>
</div>
{formData.execution_type === 'sql' && (
{(formData.execution_type === 'sql' || isAqlDatabase) && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">База данных</label>
<select
required={formData.execution_type === 'sql'}
required
value={formData.database_id}
onChange={(e) => setFormData({ ...formData, database_id: e.target.value })}
onChange={(e) => setFormData({ ...formData, database_id: e.target.value, execution_type: databases.find(db => db.id === e.target.value)?.type === 'aql' ? 'aql' : 'sql' })}
className="input w-full"
>
<option value="">Выберите базу данных</option>
{databases.map((db) => (
<option key={db.id} value={db.id}>{db.name}</option>
<option key={db.id} value={db.id}>{db.name} ({db.type})</option>
))}
</select>
</div>
@@ -210,7 +232,7 @@ export default function EndpointModal({
<label className="block text-sm font-medium text-gray-700">
Параметры запроса
<span className="text-xs text-gray-500 ml-2">
(используйте $имяПараметра в SQL запросе)
(используйте $имяПараметра в QL запросе)
</span>
</label>
<button
@@ -321,7 +343,75 @@ export default function EndpointModal({
)}
</div>
{formData.execution_type === 'sql' ? (
{formData.execution_type === 'aql' ? (
<>
{/* AQL Configuration */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">AQL HTTP Метод</label>
<select
value={formData.aql_method}
onChange={(e) => setFormData({ ...formData, aql_method: e.target.value as 'GET' | 'POST' | 'PUT' | 'DELETE' })}
className="input w-full"
>
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">AQL Endpoint URL</label>
<div className="mb-2 p-2 bg-blue-50 border border-blue-200 rounded text-xs text-blue-700">
<div>Используйте <code className="bg-blue-100 px-1 rounded">$параметр</code> для подстановки</div>
<div>Пример: <code className="bg-blue-100 px-1 rounded">/view/$viewId/GetFullCuidIsLink</code></div>
</div>
<input
type="text"
required
value={formData.aql_endpoint}
onChange={(e) => setFormData({ ...formData, aql_endpoint: e.target.value })}
className="input w-full"
placeholder="/view/15151180-f7f9-4ecc-a48c-25c083511907/GetFullCuidIsLink"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">AQL Body (JSON)</label>
<div className="mb-2 p-2 bg-blue-50 border border-blue-200 rounded text-xs text-blue-700">
<div>Используйте <code className="bg-blue-100 px-1 rounded">$параметр</code> в JSON для подстановки</div>
</div>
<textarea
value={formData.aql_body}
onChange={(e) => setFormData({ ...formData, aql_body: e.target.value })}
className="input w-full font-mono text-sm"
rows={6}
placeholder='{"aql": "select c from COMPOSITION c where c/uid/value= &apos;$compositionId&apos;"}'
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">AQL Query Parameters (JSON)</label>
<div className="mb-2 p-2 bg-blue-50 border border-blue-200 rounded text-xs text-blue-700">
<div>Формат: <code className="bg-blue-100 px-1 rounded">{`{"key": "value", "CompositionLink": "$linkValue"}`}</code></div>
</div>
<textarea
value={typeof formData.aql_query_params === 'string' ? formData.aql_query_params : JSON.stringify(formData.aql_query_params, null, 2)}
onChange={(e) => {
try {
const parsed = JSON.parse(e.target.value);
setFormData({ ...formData, aql_query_params: parsed });
} catch {
// Игнорируем невалидный JSON - не обновляем состояние
}
}}
className="input w-full font-mono text-sm"
rows={4}
placeholder='{"CompositionLink": "ehr:compositions/$compositionId"}'
/>
</div>
</>
) : formData.execution_type === 'sql' ? (
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">SQL Запрос</label>
<div className="mb-2 p-2 bg-blue-50 border border-blue-200 rounded text-xs text-blue-700 space-y-1">
@@ -512,9 +602,11 @@ export default function EndpointModal({
onClick={() => testMutation.mutate()}
disabled={
testMutation.isPending ||
(formData.execution_type === 'sql'
? (!formData.database_id || !formData.sql_query)
: !formData.script_code
(formData.execution_type === 'aql'
? (!formData.database_id || !formData.aql_endpoint)
: formData.execution_type === 'sql'
? (!formData.database_id || !formData.sql_query)
: !formData.script_code
)
}
className="btn btn-secondary flex items-center gap-2"