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:
317
backend/src/controllers/endpointController.ts
Normal file
317
backend/src/controllers/endpointController.ts
Normal file
@@ -0,0 +1,317 @@
|
||||
import { Response } from 'express';
|
||||
import { AuthRequest } from '../middleware/auth';
|
||||
import { mainPool } from '../config/database';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export const getEndpoints = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { search, folder_id } = req.query;
|
||||
|
||||
let query = `
|
||||
SELECT e.*, f.name as folder_name
|
||||
FROM endpoints e
|
||||
LEFT JOIN folders f ON e.folder_id = f.id
|
||||
WHERE 1=1
|
||||
`;
|
||||
const params: any[] = [];
|
||||
|
||||
if (folder_id) {
|
||||
query += ` AND e.folder_id = $${params.length + 1}`;
|
||||
params.push(folder_id);
|
||||
}
|
||||
|
||||
if (search) {
|
||||
const searchIndex = params.length + 1;
|
||||
query += ` AND (
|
||||
e.name ILIKE $${searchIndex} OR
|
||||
e.description ILIKE $${searchIndex} OR
|
||||
e.sql_query ILIKE $${searchIndex} OR
|
||||
e.path ILIKE $${searchIndex}
|
||||
)`;
|
||||
params.push(`%${search}%`);
|
||||
}
|
||||
|
||||
query += ` ORDER BY e.created_at DESC`;
|
||||
|
||||
const result = await mainPool.query(query, params);
|
||||
res.json(result.rows);
|
||||
} catch (error) {
|
||||
console.error('Get endpoints error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
};
|
||||
|
||||
export const getEndpoint = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await mainPool.query(
|
||||
`SELECT e.*, f.name as folder_name
|
||||
FROM endpoints e
|
||||
LEFT JOIN folders f ON e.folder_id = f.id
|
||||
WHERE e.id = $1`,
|
||||
[id]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Endpoint not found' });
|
||||
}
|
||||
|
||||
res.json(result.rows[0]);
|
||||
} catch (error) {
|
||||
console.error('Get endpoint error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
};
|
||||
|
||||
export const createEndpoint = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
method,
|
||||
path,
|
||||
database_id,
|
||||
sql_query,
|
||||
parameters,
|
||||
folder_id,
|
||||
is_public,
|
||||
enable_logging,
|
||||
execution_type,
|
||||
script_language,
|
||||
script_code,
|
||||
script_queries,
|
||||
} = req.body;
|
||||
|
||||
if (!name || !method || !path) {
|
||||
return res.status(400).json({ error: 'Missing required fields' });
|
||||
}
|
||||
|
||||
const execType = execution_type || 'sql';
|
||||
|
||||
// Валидация для типа SQL
|
||||
if (execType === 'sql') {
|
||||
if (!database_id || !sql_query) {
|
||||
return res.status(400).json({ error: 'Database ID and SQL query are required for SQL execution type' });
|
||||
}
|
||||
}
|
||||
|
||||
// Валидация для типа Script
|
||||
if (execType === 'script') {
|
||||
if (!script_language || !script_code || !script_queries) {
|
||||
return res.status(400).json({ error: 'Script language, code, and queries are required for script 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
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
|
||||
RETURNING *`,
|
||||
[
|
||||
name,
|
||||
description || '',
|
||||
method,
|
||||
path,
|
||||
database_id || null,
|
||||
sql_query || '',
|
||||
JSON.stringify(parameters || []),
|
||||
folder_id || null,
|
||||
req.user!.id,
|
||||
is_public || false,
|
||||
enable_logging || false,
|
||||
execType,
|
||||
script_language || null,
|
||||
script_code || null,
|
||||
JSON.stringify(script_queries || []),
|
||||
]
|
||||
);
|
||||
|
||||
res.status(201).json(result.rows[0]);
|
||||
} catch (error: any) {
|
||||
console.error('Create endpoint error:', error);
|
||||
if (error.code === '23505') {
|
||||
return res.status(400).json({ error: 'Endpoint path already exists' });
|
||||
}
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateEndpoint = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
method,
|
||||
path,
|
||||
database_id,
|
||||
sql_query,
|
||||
parameters,
|
||||
folder_id,
|
||||
is_public,
|
||||
enable_logging,
|
||||
execution_type,
|
||||
script_language,
|
||||
script_code,
|
||||
script_queries,
|
||||
} = req.body;
|
||||
|
||||
const result = await mainPool.query(
|
||||
`UPDATE endpoints
|
||||
SET name = $1,
|
||||
description = $2,
|
||||
method = $3,
|
||||
path = $4,
|
||||
database_id = $5,
|
||||
sql_query = $6,
|
||||
parameters = $7,
|
||||
folder_id = $8,
|
||||
is_public = $9,
|
||||
enable_logging = $10,
|
||||
execution_type = $11,
|
||||
script_language = $12,
|
||||
script_code = $13,
|
||||
script_queries = $14,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $15
|
||||
RETURNING *`,
|
||||
[
|
||||
name,
|
||||
description,
|
||||
method,
|
||||
path,
|
||||
database_id || null,
|
||||
sql_query,
|
||||
parameters ? JSON.stringify(parameters) : null,
|
||||
folder_id || null,
|
||||
is_public,
|
||||
enable_logging,
|
||||
execution_type,
|
||||
script_language || null,
|
||||
script_code || null,
|
||||
script_queries ? JSON.stringify(script_queries) : null,
|
||||
id,
|
||||
]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Endpoint not found' });
|
||||
}
|
||||
|
||||
res.json(result.rows[0]);
|
||||
} catch (error: any) {
|
||||
console.error('Update endpoint error:', error);
|
||||
if (error.code === '23505') {
|
||||
return res.status(400).json({ error: 'Endpoint path already exists' });
|
||||
}
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteEndpoint = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await mainPool.query(
|
||||
'DELETE FROM endpoints WHERE id = $1 RETURNING id',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Endpoint not found' });
|
||||
}
|
||||
|
||||
res.json({ message: 'Endpoint deleted successfully' });
|
||||
} catch (error) {
|
||||
console.error('Delete endpoint error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
};
|
||||
|
||||
export const testEndpoint = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const {
|
||||
database_id,
|
||||
sql_query,
|
||||
parameters,
|
||||
endpoint_parameters,
|
||||
execution_type,
|
||||
script_language,
|
||||
script_code,
|
||||
script_queries
|
||||
} = req.body;
|
||||
|
||||
const execType = execution_type || 'sql';
|
||||
|
||||
if (execType === 'sql') {
|
||||
if (!database_id) {
|
||||
return res.status(400).json({ error: 'Missing database_id for SQL execution' });
|
||||
}
|
||||
if (!sql_query) {
|
||||
return res.status(400).json({ error: 'Missing sql_query' });
|
||||
}
|
||||
|
||||
// Преобразуем именованные параметры ($paramName) в позиционные ($1, $2, $3...)
|
||||
let processedQuery = sql_query;
|
||||
|
||||
if (endpoint_parameters && Array.isArray(endpoint_parameters)) {
|
||||
endpoint_parameters.forEach((param: any, index: number) => {
|
||||
const paramName = param.name;
|
||||
const position = index + 1;
|
||||
|
||||
// Заменяем все вхождения $paramName на $position
|
||||
const regex = new RegExp(`\\$${paramName}\\b`, 'g');
|
||||
processedQuery = processedQuery.replace(regex, `$${position}`);
|
||||
});
|
||||
}
|
||||
|
||||
const { sqlExecutor } = require('../services/SqlExecutor');
|
||||
const result = await sqlExecutor.executeQuery(database_id, processedQuery, parameters || []);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result.rows,
|
||||
rowCount: result.rowCount,
|
||||
executionTime: result.executionTime,
|
||||
});
|
||||
} else if (execType === 'script') {
|
||||
if (!script_language || !script_code) {
|
||||
return res.status(400).json({ error: 'Missing script_language or script_code' });
|
||||
}
|
||||
|
||||
// Собираем параметры из тестовых значений
|
||||
const requestParams: Record<string, any> = {};
|
||||
if (endpoint_parameters && Array.isArray(endpoint_parameters) && parameters && Array.isArray(parameters)) {
|
||||
endpoint_parameters.forEach((param: any, index: number) => {
|
||||
requestParams[param.name] = parameters[index];
|
||||
});
|
||||
}
|
||||
|
||||
const { scriptExecutor } = require('../services/ScriptExecutor');
|
||||
const scriptResult = await scriptExecutor.execute(script_language, script_code, {
|
||||
databaseId: database_id,
|
||||
scriptQueries: script_queries || [],
|
||||
requestParams,
|
||||
endpointParameters: endpoint_parameters || [],
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: scriptResult.data || scriptResult,
|
||||
rowCount: scriptResult.rowCount || (Array.isArray(scriptResult.data) ? scriptResult.data.length : 0),
|
||||
executionTime: scriptResult.executionTime || 0,
|
||||
});
|
||||
} else {
|
||||
return res.status(400).json({ error: 'Invalid execution_type' });
|
||||
}
|
||||
} catch (error: any) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user