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:
152
server.mjs
Normal file
152
server.mjs
Normal 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}`);
|
||||
});
|
||||
Reference in New Issue
Block a user