From 727c6765f8e98f205c6490c3f07e485214e4ad7a Mon Sep 17 00:00:00 2001 From: eshmeshek Date: Fri, 13 Mar 2026 15:22:32 +0300 Subject: [PATCH] modified: backend/src/config/dynamicSwagger.ts modified: backend/src/controllers/endpointController.ts new file: backend/src/migrations/009_add_response_schema.sql modified: backend/src/types/index.ts modified: frontend/src/pages/EndpointEditor.tsx modified: frontend/src/types/index.ts --- backend/src/config/dynamicSwagger.ts | 21 ++++--- backend/src/controllers/endpointController.ts | 17 ++++-- .../migrations/009_add_response_schema.sql | 7 +++ backend/src/types/index.ts | 4 ++ frontend/src/pages/EndpointEditor.tsx | 59 ++++++++++++++++++- frontend/src/types/index.ts | 1 + 6 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 backend/src/migrations/009_add_response_schema.sql diff --git a/backend/src/config/dynamicSwagger.ts b/backend/src/config/dynamicSwagger.ts index 1189e49..9a5b9e5 100644 --- a/backend/src/config/dynamicSwagger.ts +++ b/backend/src/config/dynamicSwagger.ts @@ -44,6 +44,7 @@ export async function generateDynamicSwagger(): Promise { e.path, e.parameters, e.is_public, + e.response_schema, fp.full_path as folder_name FROM endpoints e LEFT JOIN folder_path fp ON e.folder_id = fp.id @@ -136,15 +137,17 @@ export async function generateDynamicSwagger(): Promise { description: 'Успешный ответ', content: { 'application/json': { - schema: { - type: 'object', - properties: { - success: { type: 'boolean' }, - data: { type: 'array', items: { type: 'object' } }, - rowCount: { type: 'number' }, - executionTime: { type: 'number' }, - }, - }, + schema: endpoint.response_schema + ? endpoint.response_schema + : { + type: 'object', + properties: { + success: { type: 'boolean' }, + data: { type: 'array', items: { type: 'object' } }, + rowCount: { type: 'number' }, + executionTime: { type: 'number' }, + }, + }, }, }, }, diff --git a/backend/src/controllers/endpointController.ts b/backend/src/controllers/endpointController.ts index a3ef0cc..96baa85 100644 --- a/backend/src/controllers/endpointController.ts +++ b/backend/src/controllers/endpointController.ts @@ -88,6 +88,7 @@ export const createEndpoint = async (req: AuthRequest, res: Response) => { aql_body, aql_query_params, detailed_response, + response_schema, } = req.body; if (!name || !method || !path) { @@ -122,9 +123,9 @@ export const createEndpoint = async (req: AuthRequest, res: Response) => { 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, - aql_method, aql_endpoint, aql_body, aql_query_params, detailed_response + aql_method, aql_endpoint, aql_body, aql_query_params, detailed_response, response_schema ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) RETURNING *`, [ name, @@ -147,6 +148,7 @@ export const createEndpoint = async (req: AuthRequest, res: Response) => { aql_body || null, JSON.stringify(aql_query_params || {}), detailed_response || false, + response_schema ? JSON.stringify(response_schema) : null, ] ); @@ -183,6 +185,7 @@ export const updateEndpoint = async (req: AuthRequest, res: Response) => { aql_body, aql_query_params, detailed_response, + response_schema, } = req.body; const result = await mainPool.query( @@ -206,8 +209,9 @@ export const updateEndpoint = async (req: AuthRequest, res: Response) => { aql_body = $17, aql_query_params = $18, detailed_response = $19, + response_schema = $20, updated_at = CURRENT_TIMESTAMP - WHERE id = $20 + WHERE id = $21 RETURNING *`, [ name, @@ -229,6 +233,7 @@ export const updateEndpoint = async (req: AuthRequest, res: Response) => { aql_body || null, aql_query_params ? JSON.stringify(aql_query_params) : null, detailed_response || false, + response_schema ? JSON.stringify(response_schema) : null, id, ] ); @@ -488,6 +493,7 @@ export const exportEndpoint = async (req: AuthRequest, res: Response) => { is_public: endpoint.is_public || false, enable_logging: endpoint.enable_logging || false, detailed_response: endpoint.detailed_response || false, + response_schema: endpoint.response_schema || null, folder_name: folderName, }; @@ -700,9 +706,9 @@ export const importEndpoint = async (req: AuthRequest, res: Response) => { 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, - aql_method, aql_endpoint, aql_body, aql_query_params, detailed_response + aql_method, aql_endpoint, aql_body, aql_query_params, detailed_response, response_schema ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) RETURNING *`, [ exportData.name, @@ -725,6 +731,7 @@ export const importEndpoint = async (req: AuthRequest, res: Response) => { exportData.aql_body || null, JSON.stringify(exportData.aql_query_params || {}), exportData.detailed_response || false, + exportData.response_schema ? JSON.stringify(exportData.response_schema) : null, ] ); diff --git a/backend/src/migrations/009_add_response_schema.sql b/backend/src/migrations/009_add_response_schema.sql new file mode 100644 index 0000000..1da0ae9 --- /dev/null +++ b/backend/src/migrations/009_add_response_schema.sql @@ -0,0 +1,7 @@ +-- Add response_schema to endpoints for Swagger documentation +-- Stores an OpenAPI-compatible JSON schema describing the 200 response + +ALTER TABLE endpoints +ADD COLUMN IF NOT EXISTS response_schema JSONB DEFAULT NULL; + +COMMENT ON COLUMN endpoints.response_schema IS 'Optional OpenAPI JSON schema for the 200 response, displayed in Swagger documentation.'; diff --git a/backend/src/types/index.ts b/backend/src/types/index.ts index 71b64c3..5ee25ef 100644 --- a/backend/src/types/index.ts +++ b/backend/src/types/index.ts @@ -71,6 +71,8 @@ export interface Endpoint { aql_endpoint?: string; aql_body?: string; aql_query_params?: Record; + // Response schema for Swagger docs + response_schema?: object | null; created_at: Date; updated_at: Date; } @@ -176,5 +178,7 @@ export interface ExportedEndpoint { is_public: boolean; enable_logging: boolean; detailed_response: boolean; + response_schema: object | null; folder_name: string | null; } + diff --git a/frontend/src/pages/EndpointEditor.tsx b/frontend/src/pages/EndpointEditor.tsx index 9f364c3..1ffc3d8 100644 --- a/frontend/src/pages/EndpointEditor.tsx +++ b/frontend/src/pages/EndpointEditor.tsx @@ -75,9 +75,17 @@ export default function EndpointEditor() { aql_query_params: endpointData.aql_query_params || {}, detailed_response: endpointData.detailed_response || false, }); + setResponseSchemaText( + endpointData.response_schema + ? JSON.stringify(endpointData.response_schema, null, 2) + : '' + ); } }, [endpointData]); + const [responseSchemaText, setResponseSchemaText] = useState(''); + const [responseSchemaExpanded, setResponseSchemaExpanded] = useState(false); + const [responseSchemaError, setResponseSchemaError] = useState(''); const [editingQueryIndex, setEditingQueryIndex] = useState(null); const [showScriptCodeEditor, setShowScriptCodeEditor] = useState(false); const [parametersExpanded, setParametersExpanded] = useState(true); @@ -215,7 +223,17 @@ export default function EndpointEditor() { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - saveMutation.mutate(formData); + let parsedSchema = null; + if (responseSchemaText.trim()) { + try { + parsedSchema = JSON.parse(responseSchemaText); + } catch { + setResponseSchemaError('Некорректный JSON'); + setResponseSchemaExpanded(true); + return; + } + } + saveMutation.mutate({ ...formData, response_schema: parsedSchema }); }; // cURL generator @@ -738,6 +756,45 @@ export default function EndpointEditor() { )} + {/* Response schema */} +
+
setResponseSchemaExpanded(!responseSchemaExpanded)} + > +
+ {responseSchemaExpanded ? : } + + {responseSchemaText.trim() && ( + задана + )} + {responseSchemaError && ( + {responseSchemaError} + )} +
+
+ {responseSchemaExpanded && ( +
+

+ JSON Schema в формате OpenAPI для документирования ответа. Если не задана — используется схема по умолчанию. +

+