Files
LabWise/server/src/routes/chemicals.ts
2026-03-20 02:38:33 -05:00

108 lines
3.7 KiB
TypeScript

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<string, unknown>): Record<string, unknown> {
return Object.fromEntries(
Object.entries(row).map(([k, v]) => [
k.replace(/_([a-z])/g, (_, l) => l.toUpperCase()),
v,
])
);
}
export default router;