refactor: Reintroduce history endpoints for resource versioning and rollback functionality in server, ensuring proper initialization and organization of code
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m8s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m8s
This commit is contained in:
+54
-36
@@ -1,39 +1,4 @@
|
||||
// History endpoints (require bucket versioning to be enabled). If versioning disabled, best effort.
|
||||
app.get('/api/history/:resource', async (req, res) => {
|
||||
const { resource } = req.params;
|
||||
const key = resourceToKey(resource);
|
||||
if (!key) return sendError(res, 400, 'Unknown resource', 'E_RESOURCE');
|
||||
try {
|
||||
if (!s3.listObjectVersions) {
|
||||
return sendError(res, 501, 'S3 listObjectVersions not available', 'E_NOT_SUPPORTED');
|
||||
}
|
||||
const out = await s3.listObjectVersions({ Bucket: BUCKET_NAME, Prefix: key, MaxKeys: 20 }).promise();
|
||||
const versions = (out.Versions || [])
|
||||
.filter(v => v.Key === key)
|
||||
.slice(0, 10)
|
||||
.map(v => ({ versionId: v.VersionId, isLatest: v.IsLatest, lastModified: toIso(v.LastModified), size: v.Size, etag: v.ETag }));
|
||||
return res.json({ items: versions });
|
||||
} catch (e) {
|
||||
console.error('history error', e);
|
||||
return sendError(res, 500, 'Error reading history', 'E_S3', { error: String(e?.message || e) });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/history/:resource/rollback', async (req, res) => {
|
||||
const { resource } = req.params;
|
||||
const { versionId } = req.body || {};
|
||||
const key = resourceToKey(resource);
|
||||
if (!key || !versionId) return sendError(res, 400, 'Bad request', 'E_BAD_REQUEST');
|
||||
try {
|
||||
// Copy specific version over same key to rollback
|
||||
await s3.copyObject({ Bucket: BUCKET_NAME, CopySource: `/${BUCKET_NAME}/${encodeURIComponent(key)}?versionId=${encodeURIComponent(versionId)}`, Key: key }).promise();
|
||||
const meta = await headMeta(key);
|
||||
return sendOk(res, meta);
|
||||
} catch (e) {
|
||||
console.error('rollback error', e);
|
||||
return sendError(res, 500, 'Error rollback', 'E_S3', { error: String(e?.message || e) });
|
||||
}
|
||||
});
|
||||
// History endpoints are registered below after app is initialized
|
||||
require('dotenv').config();
|
||||
const express = require('express');
|
||||
const AWS = require('aws-sdk');
|
||||
@@ -42,6 +7,7 @@ const path = require('path');
|
||||
const compression = require('compression');
|
||||
const Ajv = require('ajv');
|
||||
const net = require('net');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const app = express();
|
||||
const port = 3001;
|
||||
@@ -97,6 +63,21 @@ function resourceToKey(resource) {
|
||||
}
|
||||
}
|
||||
|
||||
// Compute sha256 of string
|
||||
function sha256OfString(s) {
|
||||
return crypto.createHash('sha256').update(Buffer.from(String(s), 'utf-8')).digest('hex');
|
||||
}
|
||||
|
||||
function mapAjvErrors(errors) {
|
||||
if (!Array.isArray(errors)) return [];
|
||||
return errors.map((e) => ({
|
||||
message: e.message,
|
||||
instancePath: e.instancePath,
|
||||
keyword: e.keyword,
|
||||
params: e.params,
|
||||
}));
|
||||
}
|
||||
|
||||
// Serve static files from the React app
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
@@ -1020,6 +1001,43 @@ app.delete('/api/locks/:resource', (req, res) => {
|
||||
res.json({ released: true });
|
||||
});
|
||||
|
||||
// History endpoints (require bucket versioning to be enabled). If versioning disabled, best effort.
|
||||
app.get('/api/history/:resource', async (req, res) => {
|
||||
const { resource } = req.params;
|
||||
const key = resourceToKey(resource);
|
||||
if (!key) return sendError(res, 400, 'Unknown resource', 'E_RESOURCE');
|
||||
try {
|
||||
if (!s3.listObjectVersions) {
|
||||
return sendError(res, 501, 'S3 listObjectVersions not available', 'E_NOT_SUPPORTED');
|
||||
}
|
||||
const out = await s3.listObjectVersions({ Bucket: BUCKET_NAME, Prefix: key, MaxKeys: 20 }).promise();
|
||||
const versions = (out.Versions || [])
|
||||
.filter(v => v.Key === key)
|
||||
.slice(0, 10)
|
||||
.map(v => ({ versionId: v.VersionId, isLatest: v.IsLatest, lastModified: toIso(v.LastModified), size: v.Size, etag: v.ETag }));
|
||||
return res.json({ items: versions });
|
||||
} catch (e) {
|
||||
console.error('history error', e);
|
||||
return sendError(res, 500, 'Error reading history', 'E_S3', { error: String(e?.message || e) });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/history/:resource/rollback', async (req, res) => {
|
||||
const { resource } = req.params;
|
||||
const { versionId } = req.body || {};
|
||||
const key = resourceToKey(resource);
|
||||
if (!key || !versionId) return sendError(res, 400, 'Bad request', 'E_BAD_REQUEST');
|
||||
try {
|
||||
// Copy specific version over same key to rollback
|
||||
await s3.copyObject({ Bucket: BUCKET_NAME, CopySource: `/${BUCKET_NAME}/${encodeURIComponent(key)}?versionId=${encodeURIComponent(versionId)}`, Key: key }).promise();
|
||||
const meta = await headMeta(key);
|
||||
return sendOk(res, meta);
|
||||
} catch (e) {
|
||||
console.error('rollback error', e);
|
||||
return sendError(res, 500, 'Error rollback', 'E_S3', { error: String(e?.message || e) });
|
||||
}
|
||||
});
|
||||
|
||||
// Generate MikroTik configuration from filters
|
||||
app.get('/api/filters/generate-config', async (req, res) => {
|
||||
const params = {
|
||||
|
||||
Reference in New Issue
Block a user