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:
@@ -14,6 +14,7 @@ export const getDatabases = async (req: Request, res: Response) => {
|
||||
host: db.host,
|
||||
port: db.port,
|
||||
database: db.database_name,
|
||||
has_test_env: db.has_test_env || false,
|
||||
}));
|
||||
|
||||
res.json(sanitized);
|
||||
|
||||
@@ -3,13 +3,15 @@ import { AuthRequest } from '../middleware/auth';
|
||||
import { mainPool } from '../config/database';
|
||||
import { databasePoolManager } from '../services/DatabasePoolManager';
|
||||
import { generateSchemaForDatabase } from './schemaController';
|
||||
import { Environment } from '../types';
|
||||
|
||||
// Только админы могут управлять базами данных
|
||||
export const getDatabases = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const result = await mainPool.query(
|
||||
`SELECT id, name, type, host, port, database_name, username, ssl, is_active,
|
||||
aql_base_url, aql_auth_type, aql_auth_value, aql_headers,
|
||||
has_test_env, test_host, test_port, test_database_name, test_username, test_ssl,
|
||||
test_aql_base_url, test_aql_headers,
|
||||
created_at, updated_at
|
||||
FROM databases ORDER BY name`
|
||||
);
|
||||
@@ -28,6 +30,8 @@ export const getDatabase = async (req: AuthRequest, res: Response) => {
|
||||
const result = await mainPool.query(
|
||||
`SELECT id, name, type, host, port, database_name, username, ssl, is_active,
|
||||
aql_base_url, aql_auth_type, aql_auth_value, aql_headers,
|
||||
has_test_env, test_host, test_port, test_database_name, test_username, test_ssl,
|
||||
test_aql_base_url, test_aql_headers,
|
||||
created_at, updated_at
|
||||
FROM databases WHERE id = $1`,
|
||||
[id]
|
||||
@@ -48,18 +52,18 @@ export const createDatabase = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const {
|
||||
name, type, host, port, database_name, username, password, ssl,
|
||||
aql_base_url, aql_auth_type, aql_auth_value, aql_headers
|
||||
aql_base_url, aql_auth_type, aql_auth_value, aql_headers,
|
||||
has_test_env, test_host, test_port, test_database_name, test_username, test_password, test_ssl,
|
||||
test_aql_base_url, test_aql_auth_value, test_aql_headers
|
||||
} = req.body;
|
||||
|
||||
const dbType = type || 'postgresql';
|
||||
|
||||
// Валидация для обычных БД
|
||||
if (dbType !== 'aql') {
|
||||
if (!name || !host || !port || !database_name || !username || !password) {
|
||||
return res.status(400).json({ error: 'Не заполнены обязательные поля' });
|
||||
}
|
||||
} else {
|
||||
// Валидация для AQL
|
||||
if (!name || !aql_base_url) {
|
||||
return res.status(400).json({ error: 'Не заполнены обязательные поля для AQL базы' });
|
||||
}
|
||||
@@ -68,9 +72,11 @@ export const createDatabase = async (req: AuthRequest, res: Response) => {
|
||||
const result = await mainPool.query(
|
||||
`INSERT INTO databases (
|
||||
name, type, host, port, database_name, username, password, ssl, is_active,
|
||||
aql_base_url, aql_auth_type, aql_auth_value, aql_headers
|
||||
aql_base_url, aql_auth_type, aql_auth_value, aql_headers,
|
||||
has_test_env, test_host, test_port, test_database_name, test_username, test_password, test_ssl,
|
||||
test_aql_base_url, test_aql_auth_value, test_aql_headers
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, true, $9, $10, $11, $12)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, true, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)
|
||||
RETURNING *`,
|
||||
[
|
||||
name,
|
||||
@@ -84,17 +90,25 @@ export const createDatabase = async (req: AuthRequest, res: Response) => {
|
||||
aql_base_url || null,
|
||||
aql_auth_type || null,
|
||||
aql_auth_value || null,
|
||||
aql_headers ? JSON.stringify(aql_headers) : null
|
||||
aql_headers ? JSON.stringify(aql_headers) : null,
|
||||
has_test_env || false,
|
||||
test_host || null,
|
||||
test_port || null,
|
||||
test_database_name || null,
|
||||
test_username || null,
|
||||
test_password || null,
|
||||
test_ssl || null,
|
||||
test_aql_base_url || null,
|
||||
test_aql_auth_value || null,
|
||||
test_aql_headers ? JSON.stringify(test_aql_headers) : null,
|
||||
]
|
||||
);
|
||||
|
||||
const newDb = result.rows[0];
|
||||
|
||||
// Добавить пул подключений (только для не-AQL баз)
|
||||
if (dbType !== 'aql') {
|
||||
await databasePoolManager.reloadPool(newDb.id);
|
||||
|
||||
// Generate schema in background for PostgreSQL databases
|
||||
if (dbType === 'postgresql') {
|
||||
generateSchemaForDatabase(newDb.id).catch(err => {
|
||||
console.error('Background schema generation failed:', err.message);
|
||||
@@ -102,9 +116,10 @@ export const createDatabase = async (req: AuthRequest, res: Response) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Не возвращаем пароль
|
||||
delete newDb.password;
|
||||
delete newDb.aql_auth_value; // Также не возвращаем auth value
|
||||
delete newDb.aql_auth_value;
|
||||
delete newDb.test_password;
|
||||
delete newDb.test_aql_auth_value;
|
||||
|
||||
res.status(201).json(newDb);
|
||||
} catch (error: any) {
|
||||
@@ -121,62 +136,67 @@ export const updateDatabase = async (req: AuthRequest, res: Response) => {
|
||||
const { id } = req.params;
|
||||
const {
|
||||
name, type, host, port, database_name, username, password, ssl, is_active,
|
||||
aql_base_url, aql_auth_type, aql_auth_value, aql_headers
|
||||
aql_base_url, aql_auth_type, aql_auth_value, aql_headers,
|
||||
has_test_env, test_host, test_port, test_database_name, test_username, test_password, test_ssl,
|
||||
test_aql_base_url, test_aql_auth_value, test_aql_headers
|
||||
} = req.body;
|
||||
|
||||
// Если пароль/auth не передан, не обновляем его
|
||||
let query;
|
||||
let params;
|
||||
// Build SET clauses and params dynamically to handle optional password fields
|
||||
const setClauses: string[] = [];
|
||||
const params: any[] = [];
|
||||
let idx = 1;
|
||||
|
||||
if (password || aql_auth_value) {
|
||||
query = `
|
||||
UPDATE databases
|
||||
SET name = COALESCE($1, name),
|
||||
type = COALESCE($2, type),
|
||||
host = COALESCE($3, host),
|
||||
port = COALESCE($4, port),
|
||||
database_name = COALESCE($5, database_name),
|
||||
username = COALESCE($6, username),
|
||||
password = COALESCE($7, password),
|
||||
ssl = COALESCE($8, ssl),
|
||||
is_active = COALESCE($9, is_active),
|
||||
aql_base_url = COALESCE($10, aql_base_url),
|
||||
aql_auth_type = COALESCE($11, aql_auth_type),
|
||||
aql_auth_value = COALESCE($12, aql_auth_value),
|
||||
aql_headers = COALESCE($13, aql_headers),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $14
|
||||
RETURNING id, name, type, host, port, database_name, username, ssl, is_active,
|
||||
aql_base_url, aql_auth_type, aql_headers, created_at, updated_at
|
||||
`;
|
||||
params = [
|
||||
name, type, host, port, database_name, username, password || aql_auth_value, ssl, is_active,
|
||||
aql_base_url, aql_auth_type, aql_auth_value, aql_headers ? JSON.stringify(aql_headers) : null, id
|
||||
];
|
||||
} else {
|
||||
query = `
|
||||
UPDATE databases
|
||||
SET name = COALESCE($1, name),
|
||||
type = COALESCE($2, type),
|
||||
host = COALESCE($3, host),
|
||||
port = COALESCE($4, port),
|
||||
database_name = COALESCE($5, database_name),
|
||||
username = COALESCE($6, username),
|
||||
ssl = COALESCE($7, ssl),
|
||||
is_active = COALESCE($8, is_active),
|
||||
aql_base_url = COALESCE($9, aql_base_url),
|
||||
aql_auth_type = COALESCE($10, aql_auth_type),
|
||||
aql_headers = COALESCE($11, aql_headers),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $12
|
||||
RETURNING id, name, type, host, port, database_name, username, ssl, is_active,
|
||||
aql_base_url, aql_auth_type, aql_headers, created_at, updated_at
|
||||
`;
|
||||
params = [
|
||||
name, type, host, port, database_name, username, ssl, is_active,
|
||||
aql_base_url, aql_auth_type, aql_headers ? JSON.stringify(aql_headers) : null, id
|
||||
];
|
||||
}
|
||||
const addField = (clause: string, value: any) => {
|
||||
setClauses.push(`${clause} = $${idx}`);
|
||||
params.push(value);
|
||||
idx++;
|
||||
};
|
||||
|
||||
const addCoalesce = (field: string, value: any) => {
|
||||
setClauses.push(`${field} = COALESCE($${idx}, ${field})`);
|
||||
params.push(value);
|
||||
idx++;
|
||||
};
|
||||
|
||||
addCoalesce('name', name);
|
||||
addCoalesce('type', type);
|
||||
addCoalesce('host', host);
|
||||
addCoalesce('port', port);
|
||||
addCoalesce('database_name', database_name);
|
||||
addCoalesce('username', username);
|
||||
if (password) { addField('password', password); }
|
||||
addCoalesce('ssl', ssl);
|
||||
addCoalesce('is_active', is_active);
|
||||
addCoalesce('aql_base_url', aql_base_url);
|
||||
addCoalesce('aql_auth_type', aql_auth_type);
|
||||
if (aql_auth_value) { addField('aql_auth_value', aql_auth_value); }
|
||||
addCoalesce('aql_headers', aql_headers ? JSON.stringify(aql_headers) : null);
|
||||
addCoalesce('has_test_env', has_test_env);
|
||||
// Test fields: use direct SET (nullable — user may want to clear them)
|
||||
addField('test_host', test_host || null);
|
||||
addField('test_port', test_port || null);
|
||||
addField('test_database_name', test_database_name || null);
|
||||
addField('test_username', test_username || null);
|
||||
if (test_password) { addField('test_password', test_password); }
|
||||
addField('test_ssl', test_ssl !== undefined ? test_ssl : null);
|
||||
addField('test_aql_base_url', test_aql_base_url || null);
|
||||
if (test_aql_auth_value) { addField('test_aql_auth_value', test_aql_auth_value); }
|
||||
addField('test_aql_headers', test_aql_headers ? JSON.stringify(test_aql_headers) : null);
|
||||
|
||||
setClauses.push('updated_at = CURRENT_TIMESTAMP');
|
||||
|
||||
params.push(id);
|
||||
const whereIdx = idx;
|
||||
|
||||
const query = `
|
||||
UPDATE databases
|
||||
SET ${setClauses.join(', ')}
|
||||
WHERE id = $${whereIdx}
|
||||
RETURNING id, name, type, host, port, database_name, username, ssl, is_active,
|
||||
aql_base_url, aql_auth_type, aql_headers,
|
||||
has_test_env, test_host, test_port, test_database_name, test_username, test_ssl,
|
||||
test_aql_base_url, test_aql_headers,
|
||||
created_at, updated_at`;
|
||||
|
||||
const result = await mainPool.query(query, params);
|
||||
|
||||
@@ -184,7 +204,6 @@ export const updateDatabase = async (req: AuthRequest, res: Response) => {
|
||||
return res.status(404).json({ error: 'База данных не найдена' });
|
||||
}
|
||||
|
||||
// Перезагрузить пул (только для не-AQL баз)
|
||||
if (result.rows[0].type !== 'aql') {
|
||||
await databasePoolManager.reloadPool(id);
|
||||
}
|
||||
@@ -203,7 +222,6 @@ export const deleteDatabase = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
// Проверяем, используется ли база данных в эндпоинтах
|
||||
const endpointCheck = await mainPool.query(
|
||||
'SELECT COUNT(*) FROM endpoints WHERE database_id = $1',
|
||||
[id]
|
||||
@@ -224,7 +242,6 @@ export const deleteDatabase = async (req: AuthRequest, res: Response) => {
|
||||
return res.status(404).json({ error: 'База данных не найдена' });
|
||||
}
|
||||
|
||||
// Удалить пул
|
||||
databasePoolManager.removePool(id);
|
||||
|
||||
res.json({ message: 'База данных удалена успешно' });
|
||||
@@ -237,8 +254,8 @@ export const deleteDatabase = async (req: AuthRequest, res: Response) => {
|
||||
export const testDatabaseConnection = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const env = (req.query.env as string)?.toLowerCase() === 'test' ? 'test' : 'prod' as Environment;
|
||||
|
||||
// Получаем тип БД
|
||||
const dbResult = await mainPool.query('SELECT type FROM databases WHERE id = $1', [id]);
|
||||
|
||||
if (dbResult.rows.length === 0) {
|
||||
@@ -248,17 +265,15 @@ export const testDatabaseConnection = async (req: AuthRequest, res: Response) =>
|
||||
const dbType = dbResult.rows[0].type;
|
||||
|
||||
if (dbType === 'aql') {
|
||||
// Для AQL используем aqlExecutor
|
||||
const { aqlExecutor } = require('../services/AqlExecutor');
|
||||
const result = await aqlExecutor.testConnection(id);
|
||||
const result = await aqlExecutor.testConnection(id, env);
|
||||
|
||||
res.json({
|
||||
success: result.success,
|
||||
message: result.success ? 'Подключение успешно' : result.error || 'Ошибка подключения',
|
||||
});
|
||||
} else {
|
||||
// Для обычных БД используем databasePoolManager
|
||||
const isConnected = await databasePoolManager.testConnection(id);
|
||||
const isConnected = await databasePoolManager.testConnection(id, env);
|
||||
|
||||
res.json({
|
||||
success: isConnected,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ApiKeyRequest } from '../middleware/apiKey';
|
||||
import { mainPool } from '../config/database';
|
||||
import { sqlExecutor } from '../services/SqlExecutor';
|
||||
import { scriptExecutor } from '../services/ScriptExecutor';
|
||||
import { EndpointParameter, ScriptQuery, ScriptExecutionError } from '../types';
|
||||
import { EndpointParameter, ScriptQuery, ScriptExecutionError, Environment } from '../types';
|
||||
|
||||
export const executeDynamicEndpoint = async (req: ApiKeyRequest, res: Response) => {
|
||||
const startTime = Date.now();
|
||||
@@ -11,6 +11,9 @@ export const executeDynamicEndpoint = async (req: ApiKeyRequest, res: Response)
|
||||
let endpointId: string | null = null;
|
||||
|
||||
try {
|
||||
const environment: Environment =
|
||||
(req.headers['x-environment'] as string)?.toLowerCase() === 'test' ? 'test' : 'prod';
|
||||
|
||||
// Extract the path from the request (remove /api/v1 prefix)
|
||||
const requestPath = req.path; // This already has the path without /api/v1
|
||||
const requestMethod = req.method.toUpperCase();
|
||||
@@ -29,9 +32,21 @@ export const executeDynamicEndpoint = async (req: ApiKeyRequest, res: Response)
|
||||
});
|
||||
}
|
||||
|
||||
const endpoint = endpointResult.rows[0];
|
||||
let endpoint = endpointResult.rows[0];
|
||||
endpointId = endpoint.id;
|
||||
|
||||
// If test environment requested and a draft exists, serve draft content
|
||||
if (environment === 'test' && endpoint.draft_version_id) {
|
||||
const draftResult = await mainPool.query(
|
||||
`SELECT * FROM endpoint_versions WHERE id = $1 AND status = 'draft'`,
|
||||
[endpoint.draft_version_id]
|
||||
);
|
||||
if (draftResult.rows.length > 0) {
|
||||
const draft = draftResult.rows[0];
|
||||
endpoint = { ...endpoint, ...draft, id: endpoint.id, folder_id: endpoint.folder_id, user_id: endpoint.user_id };
|
||||
}
|
||||
}
|
||||
|
||||
// Check if logging is enabled (on endpoint OR on API key, but log only once)
|
||||
const endpointLogging = endpoint.enable_logging || false;
|
||||
const apiKeyLogging = req.apiKey?.enable_logging || false;
|
||||
@@ -171,7 +186,7 @@ export const executeDynamicEndpoint = async (req: ApiKeyRequest, res: Response)
|
||||
body: aqlBody,
|
||||
queryParams: aqlQueryParams,
|
||||
parameters: requestParams,
|
||||
});
|
||||
}, environment);
|
||||
} else if (executionType === 'script') {
|
||||
// Execute script
|
||||
const scriptLanguage = endpoint.script_language;
|
||||
@@ -199,6 +214,7 @@ export const executeDynamicEndpoint = async (req: ApiKeyRequest, res: Response)
|
||||
scriptQueries,
|
||||
requestParams,
|
||||
endpointParameters: parameters,
|
||||
environment,
|
||||
});
|
||||
|
||||
result = {
|
||||
@@ -231,7 +247,8 @@ export const executeDynamicEndpoint = async (req: ApiKeyRequest, res: Response)
|
||||
result = await sqlExecutor.executeQuery(
|
||||
endpoint.database_id,
|
||||
processedQuery,
|
||||
queryParams
|
||||
queryParams,
|
||||
environment
|
||||
);
|
||||
}
|
||||
|
||||
@@ -264,6 +281,7 @@ export const executeDynamicEndpoint = async (req: ApiKeyRequest, res: Response)
|
||||
error_message: null,
|
||||
ip_address: req.ip || req.socket.remoteAddress || 'unknown',
|
||||
user_agent: req.headers['user-agent'] || 'unknown',
|
||||
environment,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -295,6 +313,7 @@ export const executeDynamicEndpoint = async (req: ApiKeyRequest, res: Response)
|
||||
error_message: error.message,
|
||||
ip_address: req.ip || req.socket.remoteAddress || 'unknown',
|
||||
user_agent: req.headers['user-agent'] || 'unknown',
|
||||
environment: (req.headers['x-environment'] as string)?.toLowerCase() === 'test' ? 'test' : 'prod',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -315,6 +334,7 @@ async function logRequest(data: {
|
||||
error_message: string | null;
|
||||
ip_address: string;
|
||||
user_agent: string;
|
||||
environment?: string;
|
||||
}) {
|
||||
try {
|
||||
await mainPool.query(
|
||||
@@ -322,8 +342,8 @@ async function logRequest(data: {
|
||||
endpoint_id, api_key_id, method, path,
|
||||
request_params, request_body, response_status,
|
||||
response_data, execution_time, error_message,
|
||||
ip_address, user_agent
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`,
|
||||
ip_address, user_agent, environment
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`,
|
||||
[
|
||||
data.endpoint_id,
|
||||
data.api_key_id,
|
||||
@@ -337,6 +357,7 @@ async function logRequest(data: {
|
||||
data.error_message,
|
||||
data.ip_address,
|
||||
data.user_agent,
|
||||
data.environment || 'prod',
|
||||
]
|
||||
);
|
||||
} catch (error) {
|
||||
|
||||
@@ -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