import express from 'express'; import pool from '../db/pool.js'; import { validateUuid } from '../middleware/errors.js'; import { v4 as uuidv4 } from 'uuid'; const router = express.Router(); router.param('id', (req, res, next) => validateUuid('id')(req, res, next)); // GET / - List bins. Filter by project_id when supplied; otherwise return // every bin across every project so the Library / asset-context-menu can // present a global "move to bin" picker. router.get('/', async (req, res, next) => { try { const { project_id } = req.query; const params = []; let where = ''; if (project_id) { where = 'WHERE b.project_id = $1'; params.push(project_id); } const result = await pool.query( `SELECT b.*, p.name AS project_name, (SELECT COUNT(*)::int FROM assets a WHERE a.bin_id = b.id) AS asset_count FROM bins b LEFT JOIN projects p ON p.id = b.project_id ${where} ORDER BY b.created_at DESC`, params ); res.json(result.rows); } catch (err) { next(err); } }); // POST / - Create bin router.post('/', async (req, res, next) => { try { const { project_id, name, parent_id } = req.body; if (!project_id || !name) { return res.status(400).json({ error: 'project_id and name are required' }); } const id = uuidv4(); const result = await pool.query( `INSERT INTO bins (id, project_id, name, parent_id, created_at, updated_at) VALUES ($1, $2, $3, $4, NOW(), NOW()) RETURNING *`, [id, project_id, name, parent_id || null] ); res.status(201).json(result.rows[0]); } catch (err) { next(err); } }); // PATCH /:id - Update bin router.patch('/:id', async (req, res, next) => { try { const { id } = req.params; const { name, parent_id } = req.body; const updates = []; const params = []; let paramCount = 1; if (name !== undefined) { updates.push(`name = $${paramCount++}`); params.push(name); } if (parent_id !== undefined) { updates.push(`parent_id = $${paramCount++}`); params.push(parent_id || null); } 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 bins 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: 'Bin not found' }); } res.json(result.rows[0]); } catch (err) { next(err); } }); // DELETE /:id - Delete bin router.delete('/:id', async (req, res, next) => { try { const { id } = req.params; const result = await pool.query( 'DELETE FROM bins WHERE id = $1 RETURNING *', [id] ); if (result.rows.length === 0) { return res.status(404).json({ error: 'Bin not found' }); } res.json({ message: 'Bin deleted', bin: result.rows[0] }); } catch (err) { next(err); } }); // POST /:id/assets - Add asset to bin router.post('/:id/assets', async (req, res, next) => { try { const { id } = req.params; const { asset_id } = req.body; if (!asset_id) { return res.status(400).json({ error: 'asset_id is required' }); } // Verify bin exists const binCheck = await pool.query('SELECT id FROM bins WHERE id = $1', [id]); if (binCheck.rows.length === 0) { return res.status(404).json({ error: 'Bin not found' }); } // Update asset's bin_id const result = await pool.query( 'UPDATE assets SET bin_id = $1, updated_at = NOW() WHERE id = $2 RETURNING *', [id, asset_id] ); 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/assets/:assetId - Remove asset from bin router.delete('/:id/assets/:assetId', async (req, res, next) => { try { const { id, assetId } = req.params; // Verify bin exists const binCheck = await pool.query('SELECT id FROM bins WHERE id = $1', [id]); if (binCheck.rows.length === 0) { return res.status(404).json({ error: 'Bin not found' }); } // Remove asset from bin const result = await pool.query( 'UPDATE assets SET bin_id = NULL, updated_at = NOW() WHERE id = $1 AND bin_id = $2 RETURNING *', [assetId, id] ); if (result.rows.length === 0) { return res.status(404).json({ error: 'Asset not found in this bin' }); } res.json({ message: 'Asset removed from bin', asset: result.rows[0] }); } catch (err) { next(err); } }); export default router;