// node_api/routes/assets.js const express = require('express'); const { requirePermission } = require('../middleware/auth'); const { getPool } = require('../db/connection'); const router = express.Router(); // Get database pool const pool = getPool(); /** * Get all assets for the user's organization */ router.get('/', requirePermission('read'), async (req, res) => { try { const query = ` SELECT a.id, a.name, a.description, a.asset_identifier, a.serial_number, a.model_number, a.manufacturer, a.acquisition_date, a.acquisition_cost, a.status, a.notes, a.date_created, a.date_updated, a.created_by, a.updated_by, a.organization_id, c.id as category_id, c.name as category_name, l.id as location_id, l.name as location_name, f.id as image_id, f.filename_download as image_filename FROM assets a LEFT JOIN asset_categories c ON a.category_id = c.id LEFT JOIN locations l ON a.location_id = l.id LEFT JOIN directus_files f ON a.image_url = f.id WHERE a.organization_id = $1::uuid ORDER BY a.date_created DESC `; const result = await pool.query(query, [req.user.organization_id]); // Transform the data to match frontend expectations const assets = result.rows.map(row => ({ id: row.id, name: row.name, description: row.description, asset_identifier: row.asset_identifier, serial_number: row.serial_number, model_number: row.model_number, manufacturer: row.manufacturer, acquisition_date: row.acquisition_date, acquisition_cost: row.acquisition_cost, status: row.status, notes: row.notes, date_created: row.date_created, date_updated: row.date_updated, created_by: row.created_by, updated_by: row.updated_by, organization_id: row.organization_id, category_id: row.category_id ? { id: row.category_id, name: row.category_name } : null, location_id: row.location_id ? { id: row.location_id, name: row.location_name } : null, image_url: row.image_id ? { id: row.image_id, filename_download: row.image_filename } : null })); console.log(`📊 Retrieved ${assets.length} assets for user ${req.user.email}`); res.json({ data: assets }); } catch (error) { console.error('Get assets error:', error); res.status(500).json({ error: 'Failed to retrieve assets' }); } }); /** * Get a specific asset by ID */ router.get('/:id', requirePermission('read'), async (req, res) => { try { const { id } = req.params; const query = ` SELECT a.id, a.name, a.description, a.asset_identifier, a.serial_number, a.model_number, a.manufacturer, a.acquisition_date, a.acquisition_cost, a.status, a.notes, a.date_created, a.date_updated, a.created_by, a.updated_by, a.organization_id, c.id as category_id, c.name as category_name, l.id as location_id, l.name as location_name, f.id as image_id, f.filename_download as image_filename FROM assets a LEFT JOIN asset_categories c ON a.category_id = c.id LEFT JOIN locations l ON a.location_id = l.id LEFT JOIN directus_files f ON a.image_url = f.id WHERE a.id = $1::uuid AND a.organization_id = $2::uuid `; const result = await pool.query(query, [id, req.user.organization_id]); if (result.rows.length === 0) { return res.status(404).json({ error: 'Asset not found' }); } const row = result.rows[0]; const asset = { id: row.id, name: row.name, description: row.description, asset_identifier: row.asset_identifier, serial_number: row.serial_number, model_number: row.model_number, manufacturer: row.manufacturer, acquisition_date: row.acquisition_date, acquisition_cost: row.acquisition_cost, status: row.status, notes: row.notes, date_created: row.date_created, date_updated: row.date_updated, created_by: row.created_by, updated_by: row.updated_by, organization_id: row.organization_id, category_id: row.category_id ? { id: row.category_id, name: row.category_name } : null, location_id: row.location_id ? { id: row.location_id, name: row.location_name } : null, image_url: row.image_id ? { id: row.image_id, filename_download: row.image_filename } : null }; console.log(`🔍 Retrieved asset ${id} for user ${req.user.email}`); res.json({ data: asset }); } catch (error) { console.error('Get asset error:', error); res.status(500).json({ error: 'Failed to retrieve asset' }); } }); /** * Update an asset */ router.patch('/:id', requirePermission('update'), async (req, res) => { try { const { id } = req.params; const updateData = req.body; // Build dynamic update query const updateFields = []; const values = []; let paramCount = 1; // Add updatable fields const allowedFields = [ 'name', 'description', 'asset_identifier', 'serial_number', 'model_number', 'manufacturer', 'acquisition_date', 'acquisition_cost', 'status', 'notes', 'category_id', 'location_id', 'image_url' ]; for (const field of allowedFields) { if (updateData[field] !== undefined) { updateFields.push(`${field} = $${paramCount}`); values.push(updateData[field]); paramCount++; } } if (updateFields.length === 0) { return res.status(400).json({ error: 'No valid fields to update' }); } // Add timestamp and user tracking updateFields.push(`date_updated = NOW()`); updateFields.push(`updated_by = $${paramCount}`); values.push(req.user.id); paramCount++; // Add where conditions values.push(id); values.push(req.user.organization_id); const updateQuery = ` UPDATE assets SET ${updateFields.join(', ')} WHERE id = $${paramCount}::uuid AND organization_id = $${paramCount + 1}::uuid RETURNING id `; const updateResult = await pool.query(updateQuery, values); if (updateResult.rows.length === 0) { return res.status(404).json({ error: 'Asset not found or unauthorized' }); } // Fetch the updated asset with relationships const fetchQuery = ` SELECT a.id, a.name, a.description, a.asset_identifier, a.serial_number, a.model_number, a.manufacturer, a.acquisition_date, a.acquisition_cost, a.status, a.notes, a.date_created, a.date_updated, a.created_by, a.updated_by, a.organization_id, c.id as category_id, c.name as category_name, l.id as location_id, l.name as location_name, f.id as image_id, f.filename_download as image_filename FROM assets a LEFT JOIN asset_categories c ON a.category_id = c.id LEFT JOIN locations l ON a.location_id = l.id LEFT JOIN directus_files f ON a.image_url = f.id WHERE a.id = $1::uuid AND a.organization_id = $2::uuid `; const result = await pool.query(fetchQuery, [id, req.user.organization_id]); const row = result.rows[0]; const asset = { id: row.id, name: row.name, description: row.description, asset_identifier: row.asset_identifier, serial_number: row.serial_number, model_number: row.model_number, manufacturer: row.manufacturer, acquisition_date: row.acquisition_date, acquisition_cost: row.acquisition_cost, status: row.status, notes: row.notes, date_created: row.date_created, date_updated: row.date_updated, created_by: row.created_by, updated_by: row.updated_by, organization_id: row.organization_id, category_id: row.category_id ? { id: row.category_id, name: row.category_name } : null, location_id: row.location_id ? { id: row.location_id, name: row.location_name } : null, image_url: row.image_id ? { id: row.image_id, filename_download: row.image_filename } : null }; console.log(`✏️ Updated asset ${id} for user ${req.user.email}`); res.json({ data: asset }); } catch (error) { console.error('Update asset error:', error); console.error('Error details:', { message: error.message, code: error.code, detail: error.detail, constraint: error.constraint }); res.status(500).json({ error: 'Failed to update asset', details: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }); /** * Create a new asset */ router.post('/', requirePermission('create'), async (req, res) => { try { const assetData = req.body; const insertQuery = ` INSERT INTO assets ( name, description, asset_identifier, serial_number, model_number, manufacturer, acquisition_date, acquisition_cost, status, notes, category_id, location_id, image_url, organization_id, created_by, date_created ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, NOW() ) RETURNING id `; const values = [ assetData.name, assetData.description, assetData.asset_identifier, assetData.serial_number, assetData.model_number, assetData.manufacturer, assetData.acquisition_date, assetData.acquisition_cost, assetData.status || 'active', assetData.notes, assetData.category_id, assetData.location_id, assetData.image_url, req.user.organization_id, req.user.id ]; const result = await pool.query(insertQuery, values); const assetId = result.rows[0].id; // Fetch the created asset with relationships const fetchQuery = ` SELECT a.id, a.name, a.description, a.asset_identifier, a.serial_number, a.model_number, a.manufacturer, a.acquisition_date, a.acquisition_cost, a.status, a.notes, a.date_created, a.date_updated, a.created_by, a.updated_by, a.organization_id, c.id as category_id, c.name as category_name, l.id as location_id, l.name as location_name, f.id as image_id, f.filename_download as image_filename FROM assets a LEFT JOIN asset_categories c ON a.category_id = c.id LEFT JOIN locations l ON a.location_id = l.id LEFT JOIN directus_files f ON a.image_url = f.id WHERE a.id = $1 `; const fetchResult = await pool.query(fetchQuery, [assetId]); const row = fetchResult.rows[0]; const asset = { id: row.id, name: row.name, description: row.description, asset_identifier: row.asset_identifier, serial_number: row.serial_number, model_number: row.model_number, manufacturer: row.manufacturer, acquisition_date: row.acquisition_date, acquisition_cost: row.acquisition_cost, status: row.status, notes: row.notes, date_created: row.date_created, date_updated: row.date_updated, created_by: row.created_by, updated_by: row.updated_by, organization_id: row.organization_id, category_id: row.category_id ? { id: row.category_id, name: row.category_name } : null, location_id: row.location_id ? { id: row.location_id, name: row.location_name } : null, image_url: row.image_id ? { id: row.image_id, filename_download: row.image_filename } : null }; console.log(`➕ Created asset ${assetId} for user ${req.user.email}`); res.status(201).json({ data: asset }); } catch (error) { console.error('Create asset error:', error); res.status(500).json({ error: 'Failed to create asset' }); } }); /** * Delete an asset */ router.delete('/:id', requirePermission('delete'), async (req, res) => { try { const { id } = req.params; const deleteQuery = ` DELETE FROM assets WHERE id = $1::uuid AND organization_id = $2::uuid RETURNING id `; const result = await pool.query(deleteQuery, [id, req.user.organization_id]); if (result.rows.length === 0) { return res.status(404).json({ error: 'Asset not found or unauthorized' }); } console.log(`🗑️ Deleted asset ${id} for user ${req.user.email}`); res.status(204).send(); } catch (error) { console.error('Delete asset error:', error); res.status(500).json({ error: 'Failed to delete asset' }); } }); module.exports = router;