feat: AMPP folder sync integration — pre-create folder hierarchy on upload, expose lookup endpoint for Script Task: client.js
This commit is contained in:
parent
2b9499a606
commit
36a462dac4
1 changed files with 115 additions and 0 deletions
115
services/mam-api/src/ampp/client.js
Normal file
115
services/mam-api/src/ampp/client.js
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
// 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;
|
||||
}
|
||||
Loading…
Reference in a new issue