Initial electronics inventory webapp

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>
This commit is contained in:
2026-03-20 19:11:13 +00:00
commit ef2e53ea18
13 changed files with 2768 additions and 0 deletions

152
server.mjs Normal file
View File

@@ -0,0 +1,152 @@
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}`);
});