diff --git a/services/mam-api/src/routes/upload.js b/services/mam-api/src/routes/upload.js index 01d3b5e..84fbae9 100644 --- a/services/mam-api/src/routes/upload.js +++ b/services/mam-api/src/routes/upload.js @@ -10,6 +10,7 @@ import { CompleteMultipartUploadCommand, AbortMultipartUploadCommand, } from '@aws-sdk/client-s3'; +import { getAmppConfig, ensureFolderPath } from '../ampp/client.js'; const router = express.Router(); @@ -30,6 +31,73 @@ const thumbnailQueue = new Queue('thumbnail', { }, }); +// --------------------------------------------------------------- +// AMPP Sync Helpers +// --------------------------------------------------------------- + +/** + * Walk up the bins table to build an ordered array of folder name segments + * from the root ancestor down to the given binId. + */ +async function resolveBinPath(binId) { + const segments = []; + let currentId = binId; + while (currentId) { + const result = await pool.query( + 'SELECT id, name, parent_id FROM bins WHERE id = $1', + [currentId] + ); + if (result.rows.length === 0) break; + const bin = result.rows[0]; + segments.unshift(bin.name); // prepend → final order is root-to-leaf + currentId = bin.parent_id; + } + return segments; +} + +/** + * Fire-and-forget: ensure the AMPP folder hierarchy exists for this asset's + * project/bin, then persist the resulting folder:id on the asset record so + * the AMPP Script Task can look it up and do the final link. + * + * Never throws — logs failures but does NOT fail the Dragon-Wind upload. + */ +async function syncToAmpp(assetId, projectId, binId) { + try { + const config = await getAmppConfig(); + if (!config) return; // AMPP not configured — skip silently + + // Look up project name + const projResult = await pool.query( + 'SELECT name FROM projects WHERE id = $1', + [projectId] + ); + if (projResult.rows.length === 0) return; + const projectName = projResult.rows[0].name; + + // Build folder path: ProjectName / [BinAncestors...] / BinName + const segments = [projectName]; + if (binId) { + const binSegments = await resolveBinPath(binId); + segments.push(...binSegments); + } + + // Create or verify folder hierarchy in AMPP + const folderId = await ensureFolderPath(config, segments); + if (!folderId) return; + + // Persist AMPP folder ID on asset so Script Task can look it up by filename + await pool.query( + 'UPDATE assets SET ampp_folder_id = $1, ampp_synced_at = NOW() WHERE id = $2', + [folderId, assetId] + ); + + console.log(`[AMPP] asset ${assetId} → folder ${folderId} (${segments.join(' / ')})`); + } catch (err) { + console.error(`[AMPP] sync failed for asset ${assetId}:`, err.message); + } +} + // POST /api/v1/upload/init - Initialize a multipart upload router.post('/init', async (req, res, next) => { try { @@ -164,20 +232,22 @@ router.post('/complete', async (req, res, next) => { const asset = result.rows[0]; - // Queue proxy generation job + // Queue proxy and thumbnail generation await proxyQueue.add('generate', { assetId, inputKey: key, outputKey: `proxies/${assetId}.mp4`, }); - // Queue thumbnail generation job await thumbnailQueue.add('generate', { assetId, inputKey: `proxies/${assetId}.mp4`, outputKey: `thumbnails/${assetId}.jpg`, }); + // Sync AMPP folder structure — non-blocking, never fails the upload + syncToAmpp(asset.id, asset.project_id, asset.bin_id); + res.json(asset); } catch (err) { next(err); @@ -266,20 +336,22 @@ router.post('/simple', upload.single('file'), async (req, res, next) => { const result = await pool.query(updateQuery, [assetId]); const asset = result.rows[0]; - // Queue proxy generation job + // Queue proxy and thumbnail generation await proxyQueue.add('generate', { assetId, inputKey: s3Key, outputKey: `proxies/${assetId}.mp4`, }); - // Queue thumbnail generation job await thumbnailQueue.add('generate', { assetId, inputKey: `proxies/${assetId}.mp4`, outputKey: `thumbnails/${assetId}.jpg`, }); + // Sync AMPP folder structure — non-blocking, never fails the upload + syncToAmpp(assetId, projectId, binId || null); + res.json(asset); } catch (err) { next(err);