KV-store backed Express 5 app for tracking electronic components, their arbitrary fields, and inventory locations (physical, BOM, digital). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
153 lines
5.0 KiB
JavaScript
153 lines
5.0 KiB
JavaScript
import express from 'express';
|
|
import { generate_id } from './lib/ids.mjs';
|
|
import {
|
|
list_fields, get_field, set_field, delete_field,
|
|
list_components, get_component, set_component, delete_component,
|
|
list_inventory, get_inventory_entry, set_inventory_entry, delete_inventory_entry,
|
|
} from './lib/storage.mjs';
|
|
|
|
const app = express();
|
|
app.use(express.json());
|
|
app.use(express.static(new URL('./public/', import.meta.url).pathname));
|
|
|
|
const PORT = process.env.PORT ?? 3020;
|
|
|
|
function ok(res, data = {}) { res.json({ ok: true, ...data }); }
|
|
function fail(res, msg, status = 400) { res.status(status).json({ ok: false, error: msg }); }
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Field definitions
|
|
// ---------------------------------------------------------------------------
|
|
|
|
app.get('/api/fields', (req, res) => {
|
|
ok(res, { fields: list_fields() });
|
|
});
|
|
|
|
app.post('/api/fields', (req, res) => {
|
|
const { name, unit = '', description = '' } = req.body;
|
|
if (!name?.trim()) return fail(res, 'name is required');
|
|
const field = {
|
|
id: generate_id(),
|
|
name: name.trim(),
|
|
unit: unit.trim(),
|
|
description: description.trim(),
|
|
created_at: Date.now(),
|
|
};
|
|
set_field(field);
|
|
ok(res, { field });
|
|
});
|
|
|
|
app.put('/api/fields/:id', (req, res) => {
|
|
const existing = get_field(req.params.id);
|
|
if (!existing) return fail(res, 'not found', 404);
|
|
const { name, unit, description } = req.body;
|
|
const updated = { ...existing };
|
|
if (name !== undefined) updated.name = name.trim();
|
|
if (unit !== undefined) updated.unit = unit.trim();
|
|
if (description !== undefined) updated.description = description.trim();
|
|
set_field(updated);
|
|
ok(res, { field: updated });
|
|
});
|
|
|
|
app.delete('/api/fields/:id', (req, res) => {
|
|
if (!delete_field(req.params.id)) return fail(res, 'not found', 404);
|
|
ok(res);
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Components
|
|
// ---------------------------------------------------------------------------
|
|
|
|
app.get('/api/components', (req, res) => {
|
|
ok(res, { components: list_components() });
|
|
});
|
|
|
|
app.post('/api/components', (req, res) => {
|
|
const { name, description = '', fields = {} } = req.body;
|
|
if (!name?.trim()) return fail(res, 'name is required');
|
|
const now = Date.now();
|
|
const component = {
|
|
id: generate_id(),
|
|
name: name.trim(),
|
|
description: description.trim(),
|
|
fields,
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
set_component(component);
|
|
ok(res, { component });
|
|
});
|
|
|
|
app.get('/api/components/:id', (req, res) => {
|
|
const component = get_component(req.params.id);
|
|
if (!component) return fail(res, 'not found', 404);
|
|
ok(res, { component });
|
|
});
|
|
|
|
app.put('/api/components/:id', (req, res) => {
|
|
const existing = get_component(req.params.id);
|
|
if (!existing) return fail(res, 'not found', 404);
|
|
const { name, description, fields } = req.body;
|
|
const updated = { ...existing, updated_at: Date.now() };
|
|
if (name !== undefined) updated.name = name.trim();
|
|
if (description !== undefined) updated.description = description.trim();
|
|
if (fields !== undefined) updated.fields = fields;
|
|
set_component(updated);
|
|
ok(res, { component: updated });
|
|
});
|
|
|
|
app.delete('/api/components/:id', (req, res) => {
|
|
if (!delete_component(req.params.id)) return fail(res, 'not found', 404);
|
|
ok(res);
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Inventory entries
|
|
// ---------------------------------------------------------------------------
|
|
|
|
app.get('/api/inventory', (req, res) => {
|
|
ok(res, { entries: list_inventory() });
|
|
});
|
|
|
|
app.post('/api/inventory', (req, res) => {
|
|
const { component_id, location_type, location_ref = '', quantity = '', notes = '' } = req.body;
|
|
if (!component_id) return fail(res, 'component_id is required');
|
|
if (!location_type) return fail(res, 'location_type is required');
|
|
if (!get_component(component_id)) return fail(res, 'component not found', 404);
|
|
const now = Date.now();
|
|
const entry = {
|
|
id: generate_id(),
|
|
component_id,
|
|
location_type,
|
|
location_ref: String(location_ref).trim(),
|
|
quantity: String(quantity).trim(),
|
|
notes: String(notes).trim(),
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
set_inventory_entry(entry);
|
|
ok(res, { entry });
|
|
});
|
|
|
|
app.put('/api/inventory/:id', (req, res) => {
|
|
const existing = get_inventory_entry(req.params.id);
|
|
if (!existing) return fail(res, 'not found', 404);
|
|
const { location_type, location_ref, quantity, notes } = req.body;
|
|
const updated = { ...existing, updated_at: Date.now() };
|
|
if (location_type !== undefined) updated.location_type = location_type;
|
|
if (location_ref !== undefined) updated.location_ref = String(location_ref).trim();
|
|
if (quantity !== undefined) updated.quantity = String(quantity).trim();
|
|
if (notes !== undefined) updated.notes = String(notes).trim();
|
|
set_inventory_entry(updated);
|
|
ok(res, { entry: updated });
|
|
});
|
|
|
|
app.delete('/api/inventory/:id', (req, res) => {
|
|
if (!delete_inventory_entry(req.params.id)) return fail(res, 'not found', 404);
|
|
ok(res);
|
|
});
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`Electronics Inventory running on http://localhost:${PORT}`);
|
|
});
|