115 lines
3.3 KiB
JavaScript
115 lines
3.3 KiB
JavaScript
// Wild Dragon — AMPP FramelightX API Client
|
|
// Handles authenticated requests, folder hierarchy creation.
|
|
|
|
import pool from '../db/pool.js';
|
|
|
|
/**
|
|
* Load AMPP credentials from the settings table.
|
|
* Returns null if not yet configured.
|
|
*/
|
|
export async function getAmppConfig() {
|
|
const result = await pool.query(
|
|
"SELECT key, value FROM settings WHERE key IN ('ampp_base_url', 'ampp_token')"
|
|
);
|
|
const config = {};
|
|
for (const row of result.rows) {
|
|
config[row.key] = row.value;
|
|
}
|
|
if (!config.ampp_base_url || !config.ampp_token) {
|
|
return null;
|
|
}
|
|
return config;
|
|
}
|
|
|
|
/**
|
|
* Make an authenticated request to the AMPP REST API.
|
|
*/
|
|
async function amppRequest(config, method, path, body = null) {
|
|
const base = config.ampp_base_url.replace(/\/$/, '');
|
|
const url = `${base}/${path.replace(/^\//, '')}`;
|
|
const opts = {
|
|
method,
|
|
headers: {
|
|
Authorization: `Bearer ${config.ampp_token}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
};
|
|
if (body !== null) {
|
|
opts.body = typeof body === 'string' ? body : JSON.stringify(body);
|
|
}
|
|
const res = await fetch(url, opts);
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => '');
|
|
throw new Error(`AMPP ${method} ${path} → ${res.status}: ${text}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
/**
|
|
* Ensure a nested folder path exists in AMPP, creating any missing segments.
|
|
*
|
|
* @param {object} config - Config object from getAmppConfig()
|
|
* @param {string[]} segments - Ordered path segments, e.g. ['ShowName', 'Videos', 'B-Roll']
|
|
* @returns {string|null} The folder:id of the leaf folder, or null if segments is empty.
|
|
*/
|
|
export async function ensureFolderPath(config, segments) {
|
|
let parentId = null;
|
|
let currentPath = '';
|
|
let folderId = null;
|
|
|
|
for (const rawSegment of segments) {
|
|
const segment = rawSegment.trim();
|
|
if (!segment) continue;
|
|
|
|
currentPath = currentPath ? `${currentPath}/${segment}` : segment;
|
|
const expectedDepth = currentPath.split('/').length;
|
|
|
|
// URL-encode each segment individually, keep slashes as separators
|
|
const encodedPath = currentPath
|
|
.split('/')
|
|
.map((s) => encodeURIComponent(s))
|
|
.join('/');
|
|
|
|
// Check whether this path already exists in AMPP
|
|
try {
|
|
const resp = await amppRequest(
|
|
config,
|
|
'GET',
|
|
`api/v1/store/folder/folders/hierarchy?path=${encodedPath}`
|
|
);
|
|
if (resp && typeof resp === 'object') {
|
|
const hlist = resp['hierarchy:list'];
|
|
if (Array.isArray(hlist) && hlist.length === expectedDepth) {
|
|
const candidate = String(hlist[hlist.length - 1]?.['folder:id'] || '').trim();
|
|
if (candidate) {
|
|
folderId = candidate;
|
|
parentId = folderId;
|
|
continue; // Already exists — move to next segment
|
|
}
|
|
}
|
|
}
|
|
} catch {
|
|
// Lookup failed — fall through to create
|
|
}
|
|
|
|
// Create the folder
|
|
const createBody = { 'name:text': segment };
|
|
if (parentId) createBody['parentFolders:tags'] = [parentId];
|
|
|
|
const cresp = await amppRequest(
|
|
config,
|
|
'POST',
|
|
'api/v1/store/folder/folders',
|
|
createBody
|
|
);
|
|
folderId = String(cresp?.['folder:id'] || '').trim();
|
|
if (!folderId) {
|
|
throw new Error(
|
|
`No folder:id in create response for segment "${segment}" (path: ${currentPath})`
|
|
);
|
|
}
|
|
parentId = folderId;
|
|
}
|
|
|
|
return folderId || null;
|
|
}
|