529 lines
25 KiB
JavaScript
529 lines
25 KiB
JavaScript
/**
|
|
* Dummy backend for the visual testing rig.
|
|
*
|
|
* Serves the frozen fixture snapshot (test/fixtures/snapshot.json) on every
|
|
* GET endpoint the frontend reads during screenshot runs. Always-authed stub:
|
|
* every request is treated as the fixture user, so headless --screenshot runs
|
|
* need no cookie juggling. (There is no logged-out/login screenshot.)
|
|
*
|
|
* Mutating methods (POST/PATCH/PUT/DELETE) return the stored fixture payload
|
|
* (or a minimal echo) so UI actions don't crash mid-run — persistence is
|
|
* in-memory only and resets on restart.
|
|
*
|
|
* Usage: node test/dummyBackend.mjs [--port 4218]
|
|
*/
|
|
import http from 'node:http';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const FIXTURE_DIR = path.join(__dirname, 'fixtures');
|
|
const IMAGE_DIR = path.join(FIXTURE_DIR, 'images');
|
|
|
|
const portArg = process.argv.find((a) => a.startsWith('--port='))?.split('=')[1];
|
|
const PORT = Number(portArg ?? process.env.RIG_BACKEND_PORT ?? 4208);
|
|
|
|
function loadSnapshot() {
|
|
const file = path.join(FIXTURE_DIR, 'snapshot.json');
|
|
if (!fs.existsSync(file)) {
|
|
console.error(
|
|
`[dummy-backend] missing ${file}. Run: npm run rig:collect (live backend at :4208 required)`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
}
|
|
|
|
const snap = loadSnapshot();
|
|
const { auth, company, catalog, parties, documents, misc } = snap;
|
|
const googleClientId = snap.meta?.googleClientId ?? null;
|
|
const user = auth.me.user;
|
|
|
|
function json(res, status, body) {
|
|
const text = JSON.stringify(body);
|
|
res.writeHead(status, {
|
|
'Content-Type': 'application/json',
|
|
'Content-Length': Buffer.byteLength(text),
|
|
'Cache-Control': 'no-store',
|
|
});
|
|
res.end(text);
|
|
}
|
|
|
|
function readBody(req) {
|
|
return new Promise((resolve) => {
|
|
const chunks = [];
|
|
req.on('data', (c) => chunks.push(c));
|
|
req.on('end', () => {
|
|
const raw = Buffer.concat(chunks).toString('utf8');
|
|
if (!raw) return resolve(null);
|
|
try {
|
|
resolve(JSON.parse(raw));
|
|
} catch {
|
|
resolve(raw);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
const server = http.createServer(async (req, res) => {
|
|
const url = new URL(req.url, 'http://localhost');
|
|
const p = url.pathname;
|
|
const method = req.method;
|
|
|
|
// Public / auth bootstrap. The dummy is always-authed: every request is
|
|
// treated as the fixture user, so headless --screenshot runs need no
|
|
// cookie juggling. (There is no logged-out/login screenshot in the rig.)
|
|
if (p === '/api/health') return json(res, 200, { ok: true });
|
|
if (p === '/api/auth/me' && method === 'GET') {
|
|
return json(res, 200, { user, googleClientId });
|
|
}
|
|
if (p === '/api/auth/login' && method === 'POST') {
|
|
await readBody(req);
|
|
res.setHeader('Set-Cookie', 'token=dummy-rig-token; Path=/; HttpOnly');
|
|
return json(res, 200, { message: 'Login successful', user });
|
|
}
|
|
if (p === '/api/auth/logout' && method === 'POST') {
|
|
res.setHeader('Set-Cookie', 'token=; Path=/; HttpOnly; Max-Age=0');
|
|
return json(res, 200, { ok: true });
|
|
}
|
|
if (p === '/api/auth/selected-company' && method === 'PUT') {
|
|
return json(res, 200, { user });
|
|
}
|
|
|
|
// Everything else is served as the fixture user (always-authed stub).
|
|
|
|
// SSE: hold the stream open with heartbeats, never emit (frozen fixtures).
|
|
if (p === '/api/events' && method === 'GET') {
|
|
res.writeHead(200, {
|
|
'Content-Type': 'text/event-stream',
|
|
'Cache-Control': 'no-cache',
|
|
Connection: 'keep-alive',
|
|
});
|
|
res.write(': connected\n\n');
|
|
const beat = setInterval(() => {
|
|
try {
|
|
res.write(': heartbeat\n\n');
|
|
} catch {
|
|
clearInterval(beat);
|
|
}
|
|
}, 20000);
|
|
req.on('close', () => clearInterval(beat));
|
|
return;
|
|
}
|
|
|
|
// Binary images (byte-identical to the live backend at collect time).
|
|
// Backend fullHash is an 8-char alphanumeric string (see dashback pic.js).
|
|
const thumbMatch = p.match(/^\/api\/thumb\/([A-Za-z0-9]{8})$/);
|
|
if (thumbMatch && method === 'GET') {
|
|
const file = path.join(IMAGE_DIR, `${thumbMatch[1]}.avif`);
|
|
if (!fs.existsSync(file)) return json(res, 404, { error: 'Image not found' });
|
|
const buf = fs.readFileSync(file);
|
|
res.writeHead(200, {
|
|
'Content-Type': 'image/avif',
|
|
'Content-Length': buf.length,
|
|
'Cache-Control': 'private, max-age=31536000, immutable',
|
|
ETag: `"${thumbMatch[1]}"`,
|
|
});
|
|
return res.end(buf);
|
|
}
|
|
const imageMatch = p.match(/^\/api\/image\/([A-Za-z0-9]{8})$/);
|
|
if (imageMatch && method === 'GET') {
|
|
const file = path.join(IMAGE_DIR, `${imageMatch[1]}.avif`);
|
|
if (!fs.existsSync(file)) return json(res, 404, { error: 'Image not found' });
|
|
const buf = fs.readFileSync(file);
|
|
res.writeHead(200, {
|
|
'Content-Type': 'image/avif',
|
|
'Content-Length': buf.length,
|
|
'Cache-Control': 'private, max-age=31536000, immutable',
|
|
ETag: `"${imageMatch[1]}"`,
|
|
});
|
|
return res.end(buf);
|
|
}
|
|
|
|
// ── Reads ──────────────────────────────────────────────
|
|
if (method === 'GET') {
|
|
if (p === '/api/company') return json(res, 200, { companies: auth.companies });
|
|
if (p === '/api/category/tree') return json(res, 200, catalog.tree);
|
|
const catArticles = p.match(/^\/api\/category\/(\d+)\/articles$/);
|
|
if (catArticles) {
|
|
return json(res, 200, {
|
|
articles: catalog.articlesByCategory[catArticles[1]] ?? [],
|
|
});
|
|
}
|
|
const articleMatch = p.match(/^\/api\/article\/([^/]+)$/);
|
|
if (articleMatch && !['search', 'history', 'export.zip'].includes(articleMatch[1])) {
|
|
const sku = decodeURIComponent(articleMatch[1]);
|
|
if (sku === catalog.articleDetail?.sku) return json(res, 200, { article: catalog.articleDetail });
|
|
// Any other SKU: clone the known detail so deep links still render.
|
|
if (catalog.articleDetail) {
|
|
return json(res, 200, {
|
|
article: { ...catalog.articleDetail, sku, name: catalog.articleDetail.name },
|
|
});
|
|
}
|
|
const catArticle = Object.values(catalog.articlesByCategory ?? {}).flat().find((a) => a.sku === sku);
|
|
if (catArticle) {
|
|
return json(res, 200, {
|
|
article: {
|
|
id: catArticle.id,
|
|
sku: catArticle.sku,
|
|
name: catArticle.name,
|
|
unitamount: 1,
|
|
unit: { si: 'stk', name: 'Stück' },
|
|
prices: [{ minmultiplier: 1, net: 10, taxRate: 19, customergroupId: 1 }],
|
|
categoryIds: [],
|
|
photos: catArticle.fullHash ? [{ fullHash: catArticle.fullHash, thumbHash: catArticle.thumbHash }] : [],
|
|
children: [],
|
|
},
|
|
});
|
|
}
|
|
return json(res, 404, { error: 'Article not found' });
|
|
}
|
|
if (p === '/api/article/history') return json(res, 200, { history: misc.articleHistoryGrid ?? [] });
|
|
const skuHistory = p.match(/^\/api\/article\/([^/]+)\/history$/);
|
|
if (skuHistory) return json(res, 200, { history: catalog.articleHistory ?? [] });
|
|
const skuSuppliers = p.match(/^\/api\/article\/([^/]+)\/supplier$/);
|
|
if (skuSuppliers) return json(res, 200, { suppliers: catalog.articleSuppliers ?? [] });
|
|
const skuChildren = p.match(/^\/api\/article\/([^/]+)\/children$/);
|
|
if (skuChildren) return json(res, 200, { children: [] });
|
|
const skuAttr = p.match(/^\/api\/article\/([^/]+)\/attributevalue$/);
|
|
if (skuAttr) return json(res, 200, { attributevalues: [] });
|
|
const skuProp = p.match(/^\/api\/article\/([^/]+)\/property$/);
|
|
if (skuProp) return json(res, 200, { properties: [] });
|
|
const skuArticlelist = p.match(/^\/api\/article\/([^/]+)\/articlelist$/);
|
|
if (skuArticlelist) return json(res, 200, { items: [] });
|
|
const skuOptions = p.match(/^\/api\/article\/([^/]+)\/optionslist$/);
|
|
if (skuOptions) return json(res, 200, { options: [] });
|
|
if (p === '/api/brand') return json(res, 200, { brands: catalog.brands ?? [] });
|
|
if (p === '/api/unit') return json(res, 200, { units: catalog.units ?? [] });
|
|
if (p === '/api/warehouse') return json(res, 200, { warehouses: catalog.warehouses ?? [] });
|
|
const warehouseById = p.match(/^\/api\/warehouse\/(\d+)$/);
|
|
if (warehouseById) {
|
|
const found = (catalog.warehouses ?? []).find((w) => String(w.id) === warehouseById[1]);
|
|
if (!found) return json(res, 404, { error: 'Warehouse not found' });
|
|
return json(res, 200, { warehouse: found });
|
|
}
|
|
if (p === '/api/attributename') return json(res, 200, { attributenames: catalog.attributes ?? [] });
|
|
const attrValues = p.match(/^\/api\/attributename\/(\d+)\/value$/);
|
|
if (attrValues) {
|
|
const attr = (catalog.attributes ?? []).find((a) => String(a.id) === attrValues[1]);
|
|
return json(res, 200, { values: attr?.values ?? [] });
|
|
}
|
|
if (p === '/api/propertyname') return json(res, 200, { propertynames: catalog.properties ?? [] });
|
|
if (p === '/api/supplier') return json(res, 200, { suppliers: parties.suppliers ?? [] });
|
|
const supplierArticles = p.match(/^\/api\/supplier\/(\d+)\/articles$/);
|
|
if (supplierArticles) return json(res, 200, { articles: [] });
|
|
if (p === '/api/entity') return json(res, 200, { entities: parties.entities ?? [] });
|
|
if (p === '/api/entity/next-number') return json(res, 200, { number: 'KD-001001' });
|
|
const entityMatch = p.match(/^\/api\/entity\/(\d+)$/);
|
|
if (entityMatch) {
|
|
const found = (parties.entities ?? []).find((e) => String(e.id) === entityMatch[1]);
|
|
if (!found && parties.entityDetail && String(parties.entityDetail.id) === entityMatch[1]) {
|
|
return json(res, 200, { entity: parties.entityDetail });
|
|
}
|
|
if (!found) return json(res, 404, { error: 'Entity not found' });
|
|
return json(res, 200, { entity: found });
|
|
}
|
|
const entityAddr = p.match(/^\/api\/entity\/(\d+)\/address$/);
|
|
if (entityAddr) return json(res, 200, { addresses: parties.entityAddresses ?? [] });
|
|
const entityContact = p.match(/^\/api\/entity\/(\d+)\/contact$/);
|
|
if (entityContact) return json(res, 200, { contacts: parties.entityContacts ?? [] });
|
|
if (p === '/api/customergroup') return json(res, 200, { customergroups: parties.customergroups ?? [] });
|
|
if (p === '/api/delivery') return json(res, 200, { deliveries: documents.deliveries ?? [] });
|
|
const deliveryMatch = p.match(/^\/api\/delivery\/(\d+)$/);
|
|
if (deliveryMatch) {
|
|
const found = (documents.deliveries ?? []).find((d) => String(d.id) === deliveryMatch[1]);
|
|
const detail = documents.deliveryDetail && String(documents.deliveryDetail.id) === deliveryMatch[1]
|
|
? documents.deliveryDetail
|
|
: null;
|
|
if (detail) return json(res, 200, { delivery: detail });
|
|
if (found && !detail) {
|
|
return json(res, 200, {
|
|
delivery: { ...found, lines: [], files: found.files ?? [] },
|
|
});
|
|
}
|
|
return json(res, 404, { error: 'Delivery not found' });
|
|
}
|
|
if (p === '/api/quote') return json(res, 200, { quotes: documents.quotes ?? [] });
|
|
const quoteMatch = p.match(/^\/api\/quote\/(\d+)$/);
|
|
if (quoteMatch) {
|
|
const found = (documents.quotes ?? []).find((q) => String(q.id) === quoteMatch[1]);
|
|
if (!found) return json(res, 404, { error: 'Quote not found' });
|
|
return json(res, 200, { quote: { ...found, lines: found.lines ?? [] } });
|
|
}
|
|
if (p === '/api/order') return json(res, 200, { orders: documents.orders ?? [] });
|
|
const orderMatch = p.match(/^\/api\/order\/(\d+)$/);
|
|
if (orderMatch) {
|
|
const found = (documents.orders ?? []).find((o) => String(o.id) === orderMatch[1]);
|
|
if (!found) return json(res, 404, { error: 'Order not found' });
|
|
return json(res, 200, { order: { ...found, lines: found.lines ?? [] } });
|
|
}
|
|
if (p === '/api/number') return json(res, 200, { numbers: documents.numbers ?? [] });
|
|
if (p === '/api/posterminal') return json(res, 200, { posterminals: misc.posterminals ?? [] });
|
|
if (p === '/api/printer') return json(res, 200, { printers: misc.printers ?? [] });
|
|
const printerById = p.match(/^\/api\/printer\/(\d+)$/);
|
|
if (printerById) {
|
|
const found = (misc.printers ?? []).find((x) => String(x.id) === printerById[1]);
|
|
if (!found) return json(res, 404, { error: 'Printer not found' });
|
|
return json(res, 200, { printer: found });
|
|
}
|
|
if (p === '/api/labeltemplate') return json(res, 200, { labeltemplates: misc.labeltemplates ?? [] });
|
|
const labelById = p.match(/^\/api\/labeltemplate\/(\d+)$/);
|
|
if (labelById) {
|
|
const found = (misc.labeltemplates ?? []).find((x) => String(x.id) === labelById[1]);
|
|
if (!found) return json(res, 404, { error: 'Label template not found' });
|
|
return json(res, 200, { labeltemplate: found });
|
|
}
|
|
if (p === '/api/company/setting') return json(res, 200, { settings: company.settings ?? [] });
|
|
const settingByKey = p.match(/^\/api\/company\/setting\/(.+)$/);
|
|
if (settingByKey) {
|
|
const found = (company.settings ?? []).find((s) => s.key === decodeURIComponent(settingByKey[1]));
|
|
if (!found) return json(res, 404, { error: 'Setting not found' });
|
|
return json(res, 200, { setting: found });
|
|
}
|
|
if (p === '/api/vfs') return json(res, 200, { files: misc.vfs ?? [] });
|
|
if (p === '/api/history') return json(res, 200, { history: misc.globalHistory ?? [], rows: misc.globalHistory ?? [] });
|
|
if (p === '/api/modelconversation') return json(res, 200, { conversations: [] });
|
|
const conversationMsgs = p.match(/^\/api\/modelconversation\/(\d+)\/message$/);
|
|
if (conversationMsgs) return json(res, 200, { messages: [] });
|
|
const ledgerSku = p.match(/^\/api\/ledger\/sku\/(.+)$/);
|
|
if (ledgerSku) return json(res, 200, { ledger: [] });
|
|
const ledgerMa = p.match(/^\/api\/ledger\/manifestarticle\/(\d+)$/);
|
|
if (ledgerMa) return json(res, 200, { ledger: [] });
|
|
if (p === '/api/ledger/history') return json(res, 200, { ledger: misc.ledgerHistory ?? [], total: 0 });
|
|
if (p === '/api/ledger') return json(res, 200, { ledger: [] });
|
|
if (p === '/api/article/search') {
|
|
const q = (url.searchParams.get('target') ?? '').toLowerCase();
|
|
const all = Object.values(catalog.articlesByCategory ?? {}).flat();
|
|
const seen = new Map();
|
|
for (const a of all) seen.set(String(a.sku), a);
|
|
const rows = [...seen.values()].filter(
|
|
(a) => !q || String(a.sku).toLowerCase().includes(q) || String(a.name ?? '').toLowerCase().includes(q),
|
|
);
|
|
return json(res, 200, { articles: rows.slice(0, 50), total: rows.length });
|
|
}
|
|
return json(res, 404, { error: 'Not found' });
|
|
}
|
|
|
|
// ── Mutations: echo fixtures so the UI never crashes mid-run ──
|
|
const body = await readBody(req);
|
|
if (method === 'POST' && p === '/api/article') {
|
|
return json(res, 201, { article: { ...catalog.articleDetail, ...(body ?? {}) } });
|
|
}
|
|
const skuPatch = p.match(/^\/api\/article\/([^/]+)$/);
|
|
if (skuPatch && (method === 'PATCH' || method === 'PUT')) {
|
|
return json(res, 200, { article: { ...catalog.articleDetail, ...(body ?? {}) } });
|
|
}
|
|
if (method === 'DELETE' && p.match(/^\/api\/article\/[^/]+(\/image\/.+)?$/)) {
|
|
res.writeHead(204);
|
|
return res.end();
|
|
}
|
|
if (p.match(/^\/api\/article\/[^/]+\/(prices|weight|brand|eans|categories|images\/order|supplier\/order|attributevalue|property|articlelist|optionslist|parent|children|print)$/) && ['PUT', 'PATCH', 'POST'].includes(method)) {
|
|
if (p.endsWith('/supplier/order')) return json(res, 200, { ids: body?.ids ?? [] });
|
|
if (p.endsWith('/images/order')) return json(res, 200, { fullHashes: body?.fullHashes ?? [] });
|
|
if (p.endsWith('/articlelist')) return json(res, 200, { items: body?.items ?? [] });
|
|
if (p.endsWith('/optionslist')) return json(res, 200, { options: body?.options ?? [] });
|
|
if (p.endsWith('/attributevalue')) return json(res, 200, { attributevalues: [] });
|
|
if (p.endsWith('/property')) return json(res, 200, { properties: [] });
|
|
return json(res, 200, { article: { ...catalog.articleDetail, ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/article\/[^/]+\/supplier(\/\d+)?$/) && ['POST', 'PATCH'].includes(method)) {
|
|
return json(res, method === 'POST' ? 201 : 200, {
|
|
supplier: { id: 1, entityId: 1, ...(body ?? {}) },
|
|
});
|
|
}
|
|
if (method === 'DELETE' && p.match(/^\/api\/article\/[^/]+\/(supplier|property|children)\/.+$/)) {
|
|
return json(res, 200, { ok: true });
|
|
}
|
|
if (p === '/api/category' && method === 'POST') {
|
|
return json(res, 201, { category: { id: 999001, parentId: body?.parentId ?? 1, sort: 0, ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/category\/\d+$/) && ['PATCH', 'PUT'].includes(method)) {
|
|
const id = Number(p.split('/').pop());
|
|
return json(res, 200, { category: { id, ...(body ?? {}) } });
|
|
}
|
|
if (p === '/api/category/order' && method === 'PUT') {
|
|
return json(res, 200, { categoryIds: body?.categoryIds ?? [] });
|
|
}
|
|
if (method === 'DELETE' && p.match(/^\/api\/category\/\d+(\/image)?$/)) {
|
|
return json(res, 200, { category: { id: Number(p.split('/')[3]) } });
|
|
}
|
|
if (p === '/api/brand' && method === 'PUT') {
|
|
return json(res, 200, { brand: { id: 999001, ...(body ?? {}) } });
|
|
}
|
|
if (method === 'DELETE' && p.match(/^\/api\/brand\/[^/]+(\/image)?$/)) {
|
|
return json(res, 200, { brand: { id: 999001 } });
|
|
}
|
|
if (p === '/api/unit' && method === 'PUT') {
|
|
return json(res, 200, { unit: { ...(body ?? {}) } });
|
|
}
|
|
if (method === 'DELETE' && p.startsWith('/api/unit')) {
|
|
res.writeHead(204);
|
|
return res.end();
|
|
}
|
|
if (p === '/api/supplier' && method === 'POST') {
|
|
return json(res, 201, { supplier: { id: 999001, ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/supplier\/\d+$/) && method === 'PATCH') {
|
|
return json(res, 200, { supplier: { id: Number(p.split('/').pop()), ...(body ?? {}) } });
|
|
}
|
|
if (method === 'DELETE' && p.match(/^\/api\/(supplier|warehouse|printer|labeltemplate|customergroup|entity|delivery|quote|order|posterminal)\/[^/]+(\/(address|contact|category|all))?$/)) {
|
|
if (p.includes('/posterminal/') && (p.endsWith('/all') || p.includes('/category/'))) {
|
|
return json(res, 200, { id: Number(p.split('/')[3]), categoryIds: [] });
|
|
}
|
|
res.writeHead(204);
|
|
return res.end();
|
|
}
|
|
if (p === '/api/warehouse' && method === 'POST') {
|
|
return json(res, 201, { warehouse: { id: 999001, ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/warehouse\/\d+$/) && method === 'PATCH') {
|
|
return json(res, 200, { warehouse: { id: Number(p.split('/').pop()), ...(body ?? {}) } });
|
|
}
|
|
if (p === '/api/warehouse/order' && method === 'PUT') {
|
|
return json(res, 200, { warehouseIds: body?.warehouseIds ?? [] });
|
|
}
|
|
if (p === '/api/stock/adjust' && method === 'POST') {
|
|
return json(res, 200, { ok: true });
|
|
}
|
|
if (p === '/api/delivery' && method === 'POST') {
|
|
return json(res, 201, { delivery: { ...(documents.deliveryDetail ?? {}), ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/delivery\/\d+$/) && method === 'PATCH') {
|
|
return json(res, 200, { delivery: { ...(documents.deliveryDetail ?? {}), ...(body ?? {}) } });
|
|
}
|
|
if (p === '/api/quote' && method === 'POST') {
|
|
return json(res, 201, { quote: { id: 999001, ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/quote\/\d+$/) && method === 'PATCH') {
|
|
return json(res, 200, { quote: { id: Number(p.split('/').pop()), ...(body ?? {}) } });
|
|
}
|
|
if (p === '/api/order' && method === 'POST') {
|
|
return json(res, 201, { order: { id: 999001, ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/order\/\d+$/) && method === 'PATCH') {
|
|
return json(res, 200, { order: { id: Number(p.split('/').pop()), ...(body ?? {}) } });
|
|
}
|
|
if (p === '/api/entity' && method === 'POST') {
|
|
return json(res, 201, { entity: { id: 999001, ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/entity\/\d+$/) && method === 'PATCH') {
|
|
return json(res, 200, { entity: { id: Number(p.split('/').pop()), ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/entity\/\d+\/(address|contact)$/) && method === 'POST') {
|
|
const kind = p.endsWith('address') ? 'address' : 'contact';
|
|
return json(res, 201, { [kind]: { id: 999001, ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/entity\/\d+\/(address|contact)\/\d+$/) && method === 'PATCH') {
|
|
const kind = p.includes('/address/') ? 'address' : 'contact';
|
|
return json(res, 200, { [kind]: { id: Number(p.split('/').pop()), ...(body ?? {}) } });
|
|
}
|
|
if (p === '/api/customergroup' && method === 'POST') {
|
|
return json(res, 201, { customergroup: { id: 999001, ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/customergroup\/\d+$/) && method === 'PATCH') {
|
|
return json(res, 200, { customergroup: { id: Number(p.split('/').pop()), ...(body ?? {}) } });
|
|
}
|
|
if (p === '/api/customergroup/order' && method === 'PUT') {
|
|
return json(res, 200, { customergroupIds: body?.customergroupIds ?? [] });
|
|
}
|
|
if (p === '/api/company' && method === 'POST') {
|
|
return json(res, 201, { company: { id: 999001, ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/company\/\d+$/) && method === 'PUT') {
|
|
return json(res, 200, { company: { id: Number(p.split('/').pop()), ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/company\/setting\/.+$/) && method === 'PUT') {
|
|
return json(res, 200, { setting: { key: decodeURIComponent(p.split('/').pop()), ...(body ?? {}) } });
|
|
}
|
|
if (p === '/api/company/import' && method === 'POST') {
|
|
res.writeHead(200, {
|
|
'Content-Type': 'text/event-stream',
|
|
'Cache-Control': 'no-cache',
|
|
Connection: 'keep-alive',
|
|
});
|
|
res.write('event: status\ndata: {"message": "Import gestartet. Daten werden verarbeitet..."}\n\n');
|
|
res.write('event: progress\ndata: {"phase": "categories", "current": 1, "total": 1, "item": "Katalog"}\n\n');
|
|
res.write('event: done\ndata: {"ok": true, "message": "Import erfolgreich abgeschlossen.", "stats": {"categories": 1, "categoriesCreated": 1, "categoryPictures": 0, "articlesTotal": 1, "articlesImported": 1, "articlesUpdated": 0, "variantsLinked": 0, "articlePictures": 1}}\n\n');
|
|
res.end();
|
|
return;
|
|
}
|
|
if (p.match(/^\/api\/number\/\d+$/) && method === 'PATCH') {
|
|
return json(res, 200, { number: { id: Number(p.split('/').pop()), ...(body ?? {}) } });
|
|
}
|
|
if (p === '/api/posterminal' && method === 'PUT') {
|
|
return json(res, 200, { posterminal: { id: 999001, ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/posterminal\/\d+$/) && method === 'PATCH') {
|
|
return json(res, 200, { posterminal: { id: Number(p.split('/').pop()), ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/posterminal\/\d+\/(all|category\/\d+)$/) && ['PUT', 'DELETE'].includes(method)) {
|
|
return json(res, 200, { id: Number(p.split('/')[3]), categoryIds: [] });
|
|
}
|
|
if (p === '/api/printer' && method === 'POST') {
|
|
return json(res, 201, { printer: { id: 999001, ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/printer\/\d+$/) && method === 'PATCH') {
|
|
return json(res, 200, { printer: { id: Number(p.split('/').pop()), ...(body ?? {}) } });
|
|
}
|
|
if (p === '/api/printer/order' && method === 'PUT') {
|
|
return json(res, 200, { printerIds: body?.printerIds ?? [] });
|
|
}
|
|
if (p === '/api/labeltemplate' && method === 'POST') {
|
|
return json(res, 201, { labeltemplate: { id: 999001, ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/labeltemplate\/.+$/) && method === 'PATCH') {
|
|
return json(res, 200, { labeltemplate: { id: p.split('/').pop(), ...(body ?? {}) } });
|
|
}
|
|
if (p === '/api/ledger' && method === 'POST') {
|
|
return json(res, 201, { ledger: { id: 999001, ...(body ?? {}) } });
|
|
}
|
|
if (p === '/api/attributename' && method === 'POST') {
|
|
return json(res, 201, { attributename: { id: 999001, ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/attributename\/.+$/) && ['PATCH', 'PUT'].includes(method)) {
|
|
return json(res, 200, { attributename: { id: p.split('/').pop(), ...(body ?? {}) } });
|
|
}
|
|
if (method === 'DELETE' && p.match(/^\/api\/(attributename|attributevalue|propertyname)\/.+$/)) {
|
|
return json(res, 200, { ok: true });
|
|
}
|
|
if (p === '/api/propertyname' && method === 'POST') {
|
|
return json(res, 201, { propertyname: { id: 999001, ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/propertyname\/.+$/) && ['PATCH', 'PUT'].includes(method)) {
|
|
return json(res, 200, { propertyname: { id: p.split('/').pop(), ...(body ?? {}) } });
|
|
}
|
|
if (p === '/api/modelconversation' && method === 'POST') {
|
|
return json(res, 201, { conversation: { id: 999001, ...(body ?? {}) } });
|
|
}
|
|
if (p.match(/^\/api\/modelconversation\/\d+\/message$/) && method === 'POST') {
|
|
return json(res, 201, { message: { id: 999001, ...(body ?? {}) } });
|
|
}
|
|
if (method === 'DELETE' && p.match(/^\/api\/modelconversation\/\d+$/)) {
|
|
res.writeHead(204);
|
|
return res.end();
|
|
}
|
|
if (p === '/api/vfs/upload' && method === 'POST') {
|
|
return json(res, 200, { files: [] });
|
|
}
|
|
if (p === '/api/vfs/skills/restore' && method === 'POST') {
|
|
return json(res, 200, { success: true, restored: ['system.SKILL'], count: 1 });
|
|
}
|
|
if (p === '/api/vfs/applets/restore' && method === 'POST') {
|
|
return json(res, 200, { success: true, restored: ['example.applet.js'], count: 1 });
|
|
}
|
|
if (p.startsWith('/api/vfs/') && method === 'PUT') {
|
|
return json(res, 200, { ok: true });
|
|
}
|
|
if (method === 'DELETE' && p.startsWith('/api/vfs/')) {
|
|
res.writeHead(204);
|
|
return res.end();
|
|
}
|
|
|
|
return json(res, 404, { error: 'Not found' });
|
|
});
|
|
|
|
server.listen(PORT, () => {
|
|
console.log(`[dummy-backend] fixtures on http://localhost:${PORT} (${Object.keys(catalog.articlesByCategory ?? {}).length} categories)`);
|
|
});
|