import { Router } from 'express'; import { pool } from '../db/pool'; import { requireAuth } from '../auth/middleware'; const router = Router(); router.use(requireAuth); function camelToSnake(str: string): string { return str.replace(/[A-Z]/g, l => `_${l.toLowerCase()}`); } // GET /api/chemicals router.get('/', async (req, res) => { try { const result = await pool.query( 'SELECT *, percentage_full::float AS percentage_full FROM chemicals WHERE user_id = $1 ORDER BY created_at DESC', [req.user!.id] ); // Map snake_case columns back to camelCase for the frontend const rows = result.rows.map(snakeToCamel); res.json(rows); } catch (err) { console.error(err); res.status(500).json({ error: 'Internal server error' }); } }); // POST /api/chemicals router.post('/', async (req, res) => { try { const b = req.body; const result = await pool.query(` INSERT INTO chemicals ( user_id, pi_first_name, physical_state, chemical_name, bldg_code, lab, storage_location, storage_device, number_of_containers, amount_per_container, unit_of_measure, cas_number, chemical_formula, molecular_weight, vendor, catalog_number, lot_number, expiration_date, concentration, percentage_full, needs_manual_entry, scanned_image, comments, barcode, contact ) VALUES ( $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12, $13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25 ) RETURNING *`, [ req.user!.id, b.piFirstName, b.physicalState, b.chemicalName, b.bldgCode, b.lab, b.storageLocation, b.storageDevice, b.numberOfContainers, b.amountPerContainer, b.unitOfMeasure, b.casNumber, b.chemicalFormula || null, b.molecularWeight || null, b.vendor || null, b.catalogNumber || null, b.lotNumber || null, b.expirationDate || null, b.concentration || null, b.percentageFull ?? null, b.needsManualEntry ?? null, b.scannedImage || null, b.comments || null, b.barcode || null, b.contact || null, ] ); res.status(201).json(snakeToCamel(result.rows[0])); } catch (err) { console.error(err); res.status(500).json({ error: 'Internal server error' }); } }); // PATCH /api/chemicals/:id router.patch('/:id', async (req, res) => { try { const skip = new Set(['id', 'user_id', 'created_at', 'updated_at']); const fields = Object.keys(req.body).filter(k => !skip.has(k) && !skip.has(camelToSnake(k))); if (fields.length === 0) return res.status(400).json({ error: 'No fields to update' }); const snakeFields = fields.map(camelToSnake); const setClauses = snakeFields.map((f, i) => `${f} = $${i + 3}`).join(', '); const values = fields.map(f => req.body[f] || null); const result = await pool.query( `UPDATE chemicals SET ${setClauses}, updated_at = NOW() WHERE id = $1 AND user_id = $2 RETURNING *`, [req.params.id, req.user!.id, ...values] ); if (result.rows.length === 0) return res.status(404).json({ error: 'Not found' }); res.json(snakeToCamel(result.rows[0])); } catch (err) { console.error(err); res.status(500).json({ error: 'Internal server error' }); } }); // DELETE /api/chemicals/:id router.delete('/:id', async (req, res) => { try { await pool.query('DELETE FROM chemicals WHERE id = $1 AND user_id = $2', [req.params.id, req.user!.id]); res.status(204).end(); } catch (err) { console.error(err); res.status(500).json({ error: 'Internal server error' }); } }); function snakeToCamel(row: Record): Record { return Object.fromEntries( Object.entries(row).map(([k, v]) => [ k.replace(/_([a-z])/g, (_, l) => l.toUpperCase()), v, ]) ); } export default router;