diff --git a/services/mam-api/src/routes/assets.js b/services/mam-api/src/routes/assets.js index eda9dfe..06af5d0 100644 --- a/services/mam-api/src/routes/assets.js +++ b/services/mam-api/src/routes/assets.js @@ -15,6 +15,10 @@ const parseRedisUrl = (url) => { return { host: parsed.hostname, port: parseInt(parsed.port, 10) }; }; +const proxyQueue = new Queue('proxy', { + connection: parseRedisUrl(process.env.REDIS_URL || 'redis://queue:6379'), +}); + const thumbnailQueue = new Queue('thumbnail', { connection: parseRedisUrl(process.env.REDIS_URL || 'redis://queue:6379'), }); @@ -324,6 +328,46 @@ router.post('/:id/copy', async (req, res, next) => { } }); +// POST /:id/retry - Re-queue proxy generation for an asset stuck in error state +// +// Proxy failures leave assets at status='error' with no recovery path from the +// UI. This endpoint re-dispatches the proxy job so the worker chain +// (proxy → thumbnail) runs again without manual DB edits. +router.post('/:id/retry', async (req, res, next) => { + try { + const { id } = req.params; + const r = await pool.query('SELECT * FROM assets WHERE id = $1', [id]); + if (r.rows.length === 0) return res.status(404).json({ error: 'Asset not found' }); + const asset = r.rows[0]; + + if (asset.status !== 'error') { + return res.status(400).json({ error: `Asset is not in error state (current: ${asset.status})` }); + } + if (!asset.original_s3_key) { + return res.status(400).json({ error: 'Asset has no source file to reprocess' }); + } + + // Re-use the existing proxy key if one was partially written; otherwise + // construct the canonical key so the worker chain writes to the right place. + const proxyKey = asset.proxy_s3_key || `proxies/${id}.mp4`; + + await proxyQueue.add('generate', { + assetId: id, + inputKey: asset.original_s3_key, + outputKey: proxyKey, + }); + + const updated = await pool.query( + `UPDATE assets SET status = 'processing', updated_at = NOW() WHERE id = $1 RETURNING *`, + [id] + ); + + res.json(updated.rows[0]); + } catch (err) { + next(err); + } +}); + // DELETE /:id - Soft or hard delete router.delete('/:id', async (req, res, next) => { try {