Add delivery and supplier HTTP APIs with expanded response docs.

Wire store-backed CRUD for goods receipts and suppliers, and teach the API doc generator to expand $ref/allOf/array samples so route markdown shows full JSON shapes.
This commit is contained in:
seb
2026-08-06 05:32:48 +02:00
parent a748bd5f5c
commit 1860f38640
52 changed files with 2515 additions and 66 deletions

View File

@@ -36,7 +36,10 @@ const options = {
{ name: 'Company', description: 'Company CRUD' },
{ name: 'Unit', description: 'Company-scoped units of measure' },
{ name: 'Brand', description: 'Company-scoped brands' },
{ name: 'Supplier', description: 'Company-scoped suppliers (Lieferanten)' },
{ name: 'Delivery', description: 'Company-scoped goods receipts (Lieferungen)' },
{ name: 'Image', description: 'Content-addressed AVIF by fullHash' },
{ name: 'Posterminal', description: 'Company-scoped POS terminals' },
],
},
apis: [
@@ -64,41 +67,68 @@ function schemaRefName(schema) {
return null;
}
function formatSchema(schema, components, indent = 0) {
if (!schema) return '_none_';
/**
* Build a sample JSON value from an OpenAPI schema (expands $ref / allOf / array items).
* @param {object} schema
* @param {object} [components]
* @param {number} [depth]
* @returns {*}
*/
function toExample(schema, components, depth = 0) {
if (!schema || depth > 8) return null;
if (schema.example !== undefined) return schema.example;
if (schema.$ref) {
const name = schemaRefName(schema);
const resolved = components?.schemas?.[name];
if (resolved) {
return formatSchema({ ...resolved, title: name }, components, indent);
const resolved = components?.schemas?.[schemaRefName(schema)];
return resolved ? toExample(resolved, components, depth) : {};
}
if (Array.isArray(schema.allOf) && schema.allOf.length) {
const out = {};
for (const part of schema.allOf) {
const sample = toExample(part, components, depth + 1);
if (sample && typeof sample === 'object' && !Array.isArray(sample)) {
Object.assign(out, sample);
}
}
return `\`${name}\``;
}
if (schema.example !== undefined) {
return '```json\n' + JSON.stringify(schema.example, null, 2) + '\n```';
}
if (schema.type === 'object' && schema.properties) {
const lines = ['```json', '{'];
const keys = Object.keys(schema.properties);
keys.forEach((key, i) => {
const prop = schema.properties[key];
const comma = i < keys.length - 1 ? ',' : '';
let value = '…';
if (prop.type === 'string') value = prop.format === 'email' ? '"user@example.com"' : '"…"';
else if (prop.type === 'integer' || prop.type === 'number') value = prop.nullable ? 'null' : '1';
else if (prop.type === 'boolean') value = 'true';
else if (prop.type === 'array') value = '[]';
else if (prop.type === 'object' || prop.$ref) value = '{ … }';
if (prop.example !== undefined) value = JSON.stringify(prop.example);
lines.push(` "${key}": ${value}${comma}`);
});
lines.push('}', '```');
return lines.join('\n');
return out;
}
if (schema.type === 'array') {
return '```json\n[]\n```';
if (schema.items) {
return [toExample(schema.items, components, depth + 1)];
}
return [];
}
return '```json\n' + JSON.stringify(schema, null, 2) + '\n```';
if (schema.type === 'object' || schema.properties) {
const out = {};
for (const [key, prop] of Object.entries(schema.properties || {})) {
out[key] = toExample(prop, components, depth + 1);
}
return out;
}
if (schema.type === 'string') {
if (schema.enum?.length) return schema.enum[0];
if (schema.format === 'email') return 'user@example.com';
if (schema.format === 'date-time') return '2026-08-10T00:00:00.000Z';
if (schema.format === 'date') return '2026-08-10';
return '…';
}
if (schema.type === 'integer') return 1;
if (schema.type === 'number') return 1;
if (schema.type === 'boolean') return true;
if (schema.nullable) return null;
return '…';
}
function formatSchema(schema, components) {
if (!schema) return '_none_';
const example = toExample(schema, components);
return '```json\n' + JSON.stringify(example, null, 2) + '\n```';
}
function contentSchema(content) {