import express from 'express'; import pool from '../db/pool.js'; import { getSignedUrlForObject, deleteObject } from '../s3/client.js'; import { requireAuth } from '../middleware/auth.js'; const router = express.Router(); router.use(requireAuth); // GET / - List assets with filtering router.get('/', async (req, res, next) => { try { const { project_id, bin_id, status, search, media_type, limit = 50, offset = 0, } = req.query; let query = ` SELECT a.*, COUNT(*) OVER() AS full_count FROM assets a WHERE 1=1 `; const params = []; let paramCount = 1; if (project_id) { query += ` AND a.project_id = $${paramCount++}`; params.push(project_id); } if (bin_id) { query += ` AND a.bin_id = $${paramCount++}`; params.push(bin_id); } if (status) { query += ` AND a.status = $${paramCount++}`; params.push(status); } if (media_type) { query += ` AND a.media_type = $${paramCount++}`; params.push(media_type); } if (search) { query += ` AND (a.display_name ILIKE $${paramCount} OR a.notes ILIKE $${paramCount})`; params.push(`%${search}%`); paramCount++; } query += ` ORDER BY a.created_at DESC`; query += ` LIMIT $${paramCount++} OFFSET $${paramCount++}`; params.push(limit, offset); const result = await pool.query(query, params); const total = result.rows.length > 0 ? result.rows[0].full_count : 0; res.json({ assets: result.rows, total, }); } catch (err) { next(err); } }); // GET /:id - Single asset router.get('/:id', async (req, res, next) => { try { const { id } = req.params; const result = await pool.query('SELECT * FROM assets WHERE id = $1', [id]); if (result.rows.length === 0) { return res.status(404).json({ error: 'Asset not found' }); } res.json(result.rows[0]); } catch (err) { next(err); } }); // PATCH /:id - Update asset router.patch('/:id', async (req, res, next) => { try { const { id } = req.params; const { display_name, tags, notes } = req.body; const updates = []; const params = []; let paramCount = 1; if (display_name !== undefined) { updates.push(`display_name = $${paramCount++}`); params.push(display_name); } if (tags !== undefined) { updates.push(`tags = $${paramCount++}`); params.push(tags); } if (notes !== undefined) { updates.push(`notes = $${paramCount++}`); params.push(notes); } if (updates.length === 0) { return res.status(400).json({ error: 'No fields to update' }); } updates.push(`updated_at = NOW()`); params.push(id); const query = ` UPDATE assets SET ${updates.join(', ')} WHERE id = $${paramCount} RETURNING * `; const result = await pool.query(query, params); if (result.rows.length === 0) { return res.status(404).json({ error: 'Asset not found' }); } res.json(result.rows[0]); } catch (err) { next(err); } }); // DELETE /:id - Soft or hard delete router.delete('/:id', async (req, res, next) => { try { const { id } = req.params; const { hard } = req.query; if (hard === 'true') { // Hard delete: get asset info, delete from S3, delete from DB const assetResult = await pool.query( 'SELECT * FROM assets WHERE id = $1', [id] ); if (assetResult.rows.length === 0) { return res.status(404).json({ error: 'Asset not found' }); } const asset = assetResult.rows[0]; // Delete from S3 if (asset.proxy_s3_key) { await deleteObject(asset.proxy_s3_key); } if (asset.thumbnail_s3_key) { await deleteObject(asset.thumbnail_s3_key); } if (asset.original_s3_key) { await deleteObject(asset.original_s3_key); } // Delete from database await pool.query('DELETE FROM assets WHERE id = $1', [id]); res.json({ message: 'Asset deleted permanently' }); } else { // Soft delete: set status to archived const result = await pool.query( 'UPDATE assets SET status = $1, updated_at = NOW() WHERE id = $2 RETURNING *', ['archived', id] ); if (result.rows.length === 0) { return res.status(404).json({ error: 'Asset not found' }); } res.json(result.rows[0]); } } catch (err) { next(err); } }); // GET /:id/stream - Signed URL for proxy playback router.get('/:id/stream', async (req, res, next) => { try { const { id } = req.params; const result = await pool.query( 'SELECT proxy_s3_key FROM assets WHERE id = $1', [id] ); if (result.rows.length === 0) { return res.status(404).json({ error: 'Asset not found' }); } const { proxy_s3_key } = result.rows[0]; if (!proxy_s3_key) { return res.status(400).json({ error: 'No proxy available' }); } const url = await getSignedUrlForObject(proxy_s3_key); res.json({ url }); } catch (err) { next(err); } }); // GET /:id/thumbnail - Signed URL for thumbnail router.get('/:id/thumbnail', async (req, res, next) => { try { const { id } = req.params; const result = await pool.query( 'SELECT thumbnail_s3_key FROM assets WHERE id = $1', [id] ); if (result.rows.length === 0) { return res.status(404).json({ error: 'Asset not found' }); } const { thumbnail_s3_key } = result.rows[0]; if (!thumbnail_s3_key) { return res.status(400).json({ error: 'No thumbnail available' }); } const url = await getSignedUrlForObject(thumbnail_s3_key); res.json({ url }); } catch (err) { next(err); } }); export default router;