Add fields and contents to bins; fields to bin types
- bins: fields:{} and contents:[] on all new records
- bin types: fields:{} on all new records
- PUT /api/bins/:id accepts fields
- PUT /api/bin-types/:id accepts fields
- POST/PUT/DELETE /api/bins/:id/contents for content items
(type: 'component'|'item', component_id or name, quantity, notes)
- api.mjs: add_bin_content, update_bin_content, delete_bin_content
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -73,6 +73,9 @@ export const create_bin_from_source = (source_id, name) => req('POST', '/api/bin
|
||||
export const get_bin = (id) => req('GET', `/api/bins/${id}`);
|
||||
export const update_bin = (id, body) => req('PUT', `/api/bins/${id}`, body);
|
||||
export const update_bin_corners = (id, corners, phys_w, phys_h) => req('PUT', `/api/bins/${id}/corners`, { corners, phys_w, phys_h });
|
||||
export const add_bin_content = (id, body) => req('POST', `/api/bins/${id}/contents`, body);
|
||||
export const update_bin_content = (id, cid, body) => req('PUT', `/api/bins/${id}/contents/${cid}`, body);
|
||||
export const delete_bin_content = (id, cid) => req('DELETE', `/api/bins/${id}/contents/${cid}`);
|
||||
export const delete_bin = (id) => req('DELETE', `/api/bins/${id}`);
|
||||
|
||||
export async function upload_bin(file, name) {
|
||||
|
||||
66
server.mjs
66
server.mjs
@@ -670,7 +670,7 @@ app.get('/api/bin-types', (req, res) => {
|
||||
});
|
||||
|
||||
app.post('/api/bin-types', (req, res) => {
|
||||
const { name, phys_w, phys_h, description = '' } = req.body;
|
||||
const { name, phys_w, phys_h, description = '', fields = {} } = req.body;
|
||||
if (!name?.trim()) return fail(res, 'name is required');
|
||||
if (!(phys_w > 0)) return fail(res, 'phys_w must be a positive number');
|
||||
if (!(phys_h > 0)) return fail(res, 'phys_h must be a positive number');
|
||||
@@ -680,6 +680,7 @@ app.post('/api/bin-types', (req, res) => {
|
||||
phys_w: Number(phys_w),
|
||||
phys_h: Number(phys_h),
|
||||
description: description.trim(),
|
||||
fields: typeof fields === 'object' && fields !== null ? fields : {},
|
||||
created_at: Date.now(),
|
||||
updated_at: Date.now(),
|
||||
};
|
||||
@@ -690,7 +691,7 @@ app.post('/api/bin-types', (req, res) => {
|
||||
app.put('/api/bin-types/:id', (req, res) => {
|
||||
const existing = get_bin_type(req.params.id);
|
||||
if (!existing) return fail(res, 'not found', 404);
|
||||
const { name, phys_w, phys_h, description } = req.body;
|
||||
const { name, phys_w, phys_h, description, fields } = req.body;
|
||||
const updated = { ...existing, updated_at: Date.now() };
|
||||
if (name !== undefined) updated.name = name.trim();
|
||||
if (phys_w !== undefined) {
|
||||
@@ -702,6 +703,7 @@ app.put('/api/bin-types/:id', (req, res) => {
|
||||
updated.phys_h = Number(phys_h);
|
||||
}
|
||||
if (description !== undefined) updated.description = description.trim();
|
||||
if (fields !== undefined && typeof fields === 'object' && fields !== null) updated.fields = fields;
|
||||
set_bin_type(updated);
|
||||
ok(res, { bin_type: updated });
|
||||
});
|
||||
@@ -759,6 +761,8 @@ app.post('/api/bins/from-source', (req, res) => {
|
||||
phys_w: bt?.phys_w ?? null,
|
||||
phys_h: bt?.phys_h ?? null,
|
||||
image_filename: null,
|
||||
fields: {},
|
||||
contents: [],
|
||||
created_at: Date.now(),
|
||||
updated_at: Date.now(),
|
||||
};
|
||||
@@ -802,6 +806,8 @@ app.post('/api/bins', upload.single('image'), async (req, res) => {
|
||||
phys_w: bt?.phys_w ?? null,
|
||||
phys_h: bt?.phys_h ?? null,
|
||||
image_filename: null,
|
||||
fields: {},
|
||||
contents: [],
|
||||
created_at: Date.now(),
|
||||
updated_at: Date.now(),
|
||||
};
|
||||
@@ -849,11 +855,11 @@ app.put('/api/bins/:id/corners', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Update name / type
|
||||
// Update name / type / fields
|
||||
app.put('/api/bins/:id', (req, res) => {
|
||||
const bin = get_bin(req.params.id);
|
||||
if (!bin) return fail(res, 'not found', 404);
|
||||
const { name, type_id } = req.body;
|
||||
const { name, type_id, fields } = req.body;
|
||||
if (type_id !== undefined && type_id !== null && !get_bin_type(type_id)) {
|
||||
return fail(res, 'bin type not found', 404);
|
||||
}
|
||||
@@ -864,10 +870,62 @@ app.put('/api/bins/:id', (req, res) => {
|
||||
const bt = type_id ? get_bin_type(type_id) : null;
|
||||
if (bt) { updated.phys_w = bt.phys_w; updated.phys_h = bt.phys_h; }
|
||||
}
|
||||
if (fields !== undefined && typeof fields === 'object' && fields !== null) updated.fields = fields;
|
||||
set_bin(updated);
|
||||
ok(res, { bin: updated });
|
||||
});
|
||||
|
||||
// Add a content item to a bin
|
||||
app.post('/api/bins/:id/contents', (req, res) => {
|
||||
const bin = get_bin(req.params.id);
|
||||
if (!bin) return fail(res, 'not found', 404);
|
||||
const { type, component_id, name, quantity = '', notes = '' } = req.body;
|
||||
if (type !== 'component' && type !== 'item') return fail(res, 'type must be "component" or "item"');
|
||||
if (type === 'component' && !component_id) return fail(res, 'component_id is required for type component');
|
||||
if (type === 'item' && !name?.trim()) return fail(res, 'name is required for type item');
|
||||
const item = {
|
||||
id: generate_id(),
|
||||
type,
|
||||
component_id: type === 'component' ? component_id : null,
|
||||
name: type === 'item' ? name.trim() : null,
|
||||
quantity: String(quantity).trim(),
|
||||
notes: String(notes).trim(),
|
||||
created_at: Date.now(),
|
||||
};
|
||||
const updated = { ...bin, contents: [...(bin.contents ?? []), item], updated_at: Date.now() };
|
||||
set_bin(updated);
|
||||
ok(res, { bin: updated, item });
|
||||
});
|
||||
|
||||
// Update a content item
|
||||
app.put('/api/bins/:id/contents/:cid', (req, res) => {
|
||||
const bin = get_bin(req.params.id);
|
||||
if (!bin) return fail(res, 'not found', 404);
|
||||
const idx = (bin.contents ?? []).findIndex(c => c.id === req.params.cid);
|
||||
if (idx === -1) return fail(res, 'content item not found', 404);
|
||||
const item = bin.contents[idx];
|
||||
const { quantity, notes, name } = req.body;
|
||||
const updated_item = { ...item };
|
||||
if (quantity !== undefined) updated_item.quantity = String(quantity).trim();
|
||||
if (notes !== undefined) updated_item.notes = String(notes).trim();
|
||||
if (name !== undefined && item.type === 'item') updated_item.name = name.trim();
|
||||
const new_contents = bin.contents.map((c, i) => i === idx ? updated_item : c);
|
||||
const updated = { ...bin, contents: new_contents, updated_at: Date.now() };
|
||||
set_bin(updated);
|
||||
ok(res, { bin: updated, item: updated_item });
|
||||
});
|
||||
|
||||
// Remove a content item
|
||||
app.delete('/api/bins/:id/contents/:cid', (req, res) => {
|
||||
const bin = get_bin(req.params.id);
|
||||
if (!bin) return fail(res, 'not found', 404);
|
||||
const exists = (bin.contents ?? []).some(c => c.id === req.params.cid);
|
||||
if (!exists) return fail(res, 'content item not found', 404);
|
||||
const updated = { ...bin, contents: bin.contents.filter(c => c.id !== req.params.cid), updated_at: Date.now() };
|
||||
set_bin(updated);
|
||||
ok(res);
|
||||
});
|
||||
|
||||
app.delete('/api/bins/:id', (req, res) => {
|
||||
const bin = get_bin(req.params.id);
|
||||
if (!bin) return fail(res, 'not found', 404);
|
||||
|
||||
Reference in New Issue
Block a user