Add test/prod environments and endpoint versioning
Phase 1: Test/Prod Database Configurations - Migration 010: test_* columns on databases table, environment column on request_logs - DatabasePoolManager: dual-pool strategy (prod + test), getPool(id, env) with fallback - SqlExecutor, ScriptExecutor, IsolatedScriptExecutor, AqlExecutor: environment param threaded through all execution paths - dynamicApiController: X-Environment header detection, environment in logging - databaseManagementController: CRUD for test credentials, testConnection with ?env=test - Frontend: test env form in DatabaseModal, env toggle in EndpointEditor test panel Phase 2: Endpoint Versioning - Migration 011: endpoint_versions table with full snapshots, backfill v1 for existing endpoints - VersionService: createVersion, saveDraft, publishVersion, rollbackToVersion, getHistory - endpointController: auto-versioning on update, 6 new version management handlers - dynamicApiController: draft serving when environment=test and draft exists - Frontend: Save Draft button, version history panel with publish/rollback actions Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,8 +2,9 @@ import { Response } from 'express';
|
||||
import { AuthRequest } from '../middleware/auth';
|
||||
import { mainPool } from '../config/database';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { ExportedEndpoint, ExportedScriptQuery, ScriptExecutionError } from '../types';
|
||||
import { ExportedEndpoint, ExportedScriptQuery, ScriptExecutionError, Environment } from '../types';
|
||||
import { encryptEndpointData, decryptEndpointData } from '../services/endpointCrypto';
|
||||
import { versionService } from '../services/VersionService';
|
||||
|
||||
export const getEndpoints = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
@@ -242,6 +243,15 @@ export const updateEndpoint = async (req: AuthRequest, res: Response) => {
|
||||
return res.status(404).json({ error: 'Endpoint not found' });
|
||||
}
|
||||
|
||||
// Auto-create published version
|
||||
try {
|
||||
await versionService.createVersionFromEndpoint(
|
||||
id, req.user!.id, req.body.change_message || 'Updated', 'published'
|
||||
);
|
||||
} catch (vErr) {
|
||||
console.error('Auto-version creation failed:', vErr);
|
||||
}
|
||||
|
||||
res.json(result.rows[0]);
|
||||
} catch (error: any) {
|
||||
console.error('Update endpoint error:', error);
|
||||
@@ -287,8 +297,10 @@ export const testEndpoint = async (req: AuthRequest, res: Response) => {
|
||||
aql_endpoint,
|
||||
aql_body,
|
||||
aql_query_params,
|
||||
environment: reqEnv,
|
||||
} = req.body;
|
||||
|
||||
const environment: Environment = reqEnv === 'prod' ? 'prod' : 'test';
|
||||
const execType = execution_type || 'sql';
|
||||
|
||||
if (execType === 'sql') {
|
||||
@@ -314,7 +326,7 @@ export const testEndpoint = async (req: AuthRequest, res: Response) => {
|
||||
}
|
||||
|
||||
const { sqlExecutor } = require('../services/SqlExecutor');
|
||||
const result = await sqlExecutor.executeQuery(database_id, processedQuery, parameters || []);
|
||||
const result = await sqlExecutor.executeQuery(database_id, processedQuery, parameters || [], environment);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -346,6 +358,7 @@ export const testEndpoint = async (req: AuthRequest, res: Response) => {
|
||||
scriptQueries: script_queries || [],
|
||||
requestParams,
|
||||
endpointParameters: endpoint_parameters || [],
|
||||
environment,
|
||||
});
|
||||
|
||||
res.json({
|
||||
@@ -377,7 +390,7 @@ export const testEndpoint = async (req: AuthRequest, res: Response) => {
|
||||
body: aql_body,
|
||||
queryParams: aql_query_params,
|
||||
parameters: requestParams,
|
||||
});
|
||||
}, environment);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -744,3 +757,73 @@ export const importEndpoint = async (req: AuthRequest, res: Response) => {
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
};
|
||||
|
||||
// Version management handlers
|
||||
|
||||
export const getVersionHistory = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const versions = await versionService.getVersionHistory(id);
|
||||
res.json(versions);
|
||||
} catch (error) {
|
||||
console.error('Get version history error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
};
|
||||
|
||||
export const getVersion = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { versionId } = req.params;
|
||||
const version = await versionService.getVersion(versionId);
|
||||
if (!version) return res.status(404).json({ error: 'Version not found' });
|
||||
res.json(version);
|
||||
} catch (error) {
|
||||
console.error('Get version error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
};
|
||||
|
||||
export const publishVersion = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { versionId } = req.params;
|
||||
await versionService.publishVersion(versionId, req.user!.id);
|
||||
res.json({ message: 'Version published' });
|
||||
} catch (error: any) {
|
||||
console.error('Publish version error:', error);
|
||||
res.status(500).json({ error: error.message || 'Internal server error' });
|
||||
}
|
||||
};
|
||||
|
||||
export const rollbackVersion = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id, versionId } = req.params;
|
||||
const newVersion = await versionService.rollbackToVersion(id, versionId, req.user!.id);
|
||||
res.json(newVersion);
|
||||
} catch (error: any) {
|
||||
console.error('Rollback version error:', error);
|
||||
res.status(500).json({ error: error.message || 'Internal server error' });
|
||||
}
|
||||
};
|
||||
|
||||
export const saveDraftVersion = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const draft = await versionService.saveDraft(id, req.body, req.user!.id, req.body.change_message);
|
||||
res.json(draft);
|
||||
} catch (error: any) {
|
||||
console.error('Save draft error:', error);
|
||||
res.status(500).json({ error: error.message || 'Internal server error' });
|
||||
}
|
||||
};
|
||||
|
||||
export const getDraftVersion = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const draft = await versionService.getDraft(id);
|
||||
if (!draft) return res.status(404).json({ error: 'No draft found' });
|
||||
res.json(draft);
|
||||
} catch (error) {
|
||||
console.error('Get draft error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user