feat(settings): add S3 / object-storage settings routes (GET, PUT, test)
This commit is contained in:
parent
b1457f0aad
commit
ab504841c3
1 changed files with 110 additions and 8 deletions
|
|
@ -2,11 +2,117 @@ import express from 'express';
|
|||
import pool from '../db/pool.js';
|
||||
import { requireAuth } from '../middleware/auth.js';
|
||||
import { getAmppConfig } from '../ampp/client.js';
|
||||
import { rebuildS3Client, buildTestClient, testS3Connection, getS3Bucket } from '../s3/client.js';
|
||||
|
||||
const router = express.Router();
|
||||
router.use(requireAuth);
|
||||
|
||||
// ── AMPP integration ───────────────────────────────────────────────────────
|
||||
// ── S3 / Object Storage ───────────────────────────────────────────────────────
|
||||
|
||||
router.get('/s3', async (req, res, next) => {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`SELECT key, value FROM settings
|
||||
WHERE key IN ('s3_endpoint','s3_bucket','s3_access_key','s3_secret_key','s3_region')`
|
||||
);
|
||||
const out = {
|
||||
s3_endpoint: '',
|
||||
s3_bucket: getS3Bucket(),
|
||||
s3_access_key: '',
|
||||
s3_secret_key_exists: false,
|
||||
s3_region: 'us-east-1',
|
||||
};
|
||||
for (const { key, value } of result.rows) {
|
||||
if (key === 's3_secret_key') {
|
||||
out.s3_secret_key_exists = !!value;
|
||||
} else {
|
||||
out[key] = value || '';
|
||||
}
|
||||
}
|
||||
res.json(out);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/s3', async (req, res, next) => {
|
||||
try {
|
||||
const { s3_endpoint, s3_bucket, s3_access_key, s3_secret_key, s3_region } = req.body;
|
||||
|
||||
if (!s3_endpoint) return res.status(400).json({ error: 's3_endpoint is required' });
|
||||
if (!s3_bucket) return res.status(400).json({ error: 's3_bucket is required' });
|
||||
|
||||
const keys = { s3_endpoint, s3_bucket, s3_region: s3_region || 'us-east-1' };
|
||||
if (s3_access_key) keys.s3_access_key = s3_access_key;
|
||||
if (s3_secret_key) keys.s3_secret_key = s3_secret_key;
|
||||
|
||||
for (const [key, value] of Object.entries(keys)) {
|
||||
await pool.query(
|
||||
`INSERT INTO settings (key, value, updated_at) VALUES ($1, $2, NOW())
|
||||
ON CONFLICT (key) DO UPDATE SET value = $2, updated_at = NOW()`,
|
||||
[key, value.trim()]
|
||||
);
|
||||
}
|
||||
|
||||
// Fetch the full current config (including any previously saved secret)
|
||||
const saved = await pool.query(
|
||||
`SELECT key, value FROM settings
|
||||
WHERE key IN ('s3_endpoint','s3_bucket','s3_access_key','s3_secret_key','s3_region')
|
||||
AND value IS NOT NULL AND value <> ''`
|
||||
);
|
||||
const cfg = {};
|
||||
for (const { key, value } of saved.rows) {
|
||||
switch (key) {
|
||||
case 's3_endpoint': cfg.endpoint = value; break;
|
||||
case 's3_bucket': cfg.bucket = value; break;
|
||||
case 's3_access_key': cfg.accessKey = value; break;
|
||||
case 's3_secret_key': cfg.secretKey = value; break;
|
||||
case 's3_region': cfg.region = value; break;
|
||||
}
|
||||
}
|
||||
rebuildS3Client(cfg);
|
||||
|
||||
res.json({ message: 'S3 settings saved and applied' });
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
// Test with the values the browser just typed (before saving), or with saved
|
||||
// creds if the fields are left blank. Secret is sent only if user typed it.
|
||||
router.post('/s3/test', async (req, res, next) => {
|
||||
try {
|
||||
const { s3_endpoint, s3_bucket, s3_access_key, s3_secret_key, s3_region } = req.body;
|
||||
|
||||
// Merge submitted values with anything already saved in the DB
|
||||
const saved = await pool.query(
|
||||
`SELECT key, value FROM settings
|
||||
WHERE key IN ('s3_endpoint','s3_bucket','s3_access_key','s3_secret_key','s3_region')
|
||||
AND value IS NOT NULL AND value <> ''`
|
||||
);
|
||||
const fromDb = {};
|
||||
for (const { key, value } of saved.rows) fromDb[key] = value;
|
||||
|
||||
const cfg = {
|
||||
endpoint: (s3_endpoint || fromDb.s3_endpoint || '').trim(),
|
||||
bucket: (s3_bucket || fromDb.s3_bucket || getS3Bucket()).trim(),
|
||||
accessKey: (s3_access_key || fromDb.s3_access_key || '').trim(),
|
||||
secretKey: (s3_secret_key || fromDb.s3_secret_key || '').trim(),
|
||||
region: (s3_region || fromDb.s3_region || 'us-east-1').trim(),
|
||||
};
|
||||
|
||||
if (!cfg.endpoint) return res.status(400).json({ error: 'S3 endpoint is required' });
|
||||
if (!cfg.bucket) return res.status(400).json({ error: 'S3 bucket is required' });
|
||||
|
||||
const client = buildTestClient(cfg);
|
||||
const result = await testS3Connection(client, cfg.bucket);
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
res.status(400).json({ ok: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// ── AMPP integration ───────────────────────────────────────────────────────────
|
||||
|
||||
router.get('/ampp', async (req, res, next) => {
|
||||
try {
|
||||
|
|
@ -65,8 +171,7 @@ router.post('/ampp/test', async (req, res, next) => {
|
|||
}
|
||||
});
|
||||
|
||||
// ── Hardware inventory ─────────────────────────────────────────────────────
|
||||
// GET /api/v1/settings/hardware — aggregate GPU/BMD capabilities from all cluster nodes
|
||||
// ── Hardware inventory ────────────────────────────────────────────────────────
|
||||
|
||||
router.get('/hardware', async (req, res, next) => {
|
||||
try {
|
||||
|
|
@ -90,8 +195,7 @@ router.get('/hardware', async (req, res, next) => {
|
|||
}
|
||||
});
|
||||
|
||||
// ── GPU/Transcoding settings ───────────────────────────────────────────────
|
||||
// Keys: gpu_transcode_enabled, gpu_codec, gpu_preset, gpu_bitrate_mbps, gpu_node
|
||||
// ── GPU / Transcoding ─────────────────────────────────────────────────────────
|
||||
|
||||
router.get('/transcoding', async (req, res, next) => {
|
||||
try {
|
||||
|
|
@ -131,9 +235,7 @@ router.put('/transcoding', async (req, res, next) => {
|
|||
}
|
||||
});
|
||||
|
||||
// ── Capture service routing ────────────────────────────────────────────────
|
||||
// capture_service_url — points to the remote capture node's API
|
||||
// When set, the local capture proxy routes all requests to this URL instead of the sidecar
|
||||
// ── Capture service routing ───────────────────────────────────────────────────
|
||||
|
||||
router.get('/capture-service', async (req, res, next) => {
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in a new issue