Directus als Daten-/Auth-Schicht + Backend-Anbindung

Directus (Postgres, hinter NPM) wird die zentrale Datenquelle für Kunden,
Aufträge, Produkte, Preise und Gutscheine; das skrift-backend bleibt reine
Generierungs-Engine.

- Docker/directus: docker-compose (Directus 11 + Postgres), Bootstrap-Skript
  legt Collections, Rollen/Policies/Rechte und Seed idempotent an.
- Extension skrift-auth: passwortloser Login per E-Mail-Code (Codes nur als
  Hash, Ablauf, Versuchslimit) sowie Kontoanlage aus dem Checkout.
- Extension skrift-orders: kontrollierter Bestell-Endpunkt für Gäste und
  eingeloggte Kunden; Preis wird immer serverseitig berechnet (editierbare
  Formel via sicherem Interpreter, Werte aus price_items).
- skrift-backend: neuer Endpunkt /api/order/from-directus erzeugt SVGs aus
  einem Directus-Auftrag und schreibt Status + artifact_path zurück. Dafür
  wurde die Kernlogik aus generateOrder als runGeneration herausgelöst
  (Verhalten unverändert); generateOrder ist jetzt ein HTTP-Wrapper.
- compose: NPM-Netz ergänzt, API_TOKEN/DIRECTUS_* werden durchgereicht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Lucas Orth
2026-07-20 19:13:35 +02:00
parent 0341c4eaea
commit 2dbbc4611a
17 changed files with 1571 additions and 24 deletions

View File

@@ -138,23 +138,19 @@ async function finalizeOrder(req, res, next) {
* POST /api/order/generate
* Generate order from scratch without using cache
*/
async function generateOrder(req, res, next) {
try {
const { orderNumber, letters } = req.body;
async function runGeneration({ orderNumber, letters, metadata }) {
{
// Validation
if (!orderNumber) {
return res.status(400).json({
error: 'Invalid request',
message: 'orderNumber is required'
});
const e = new Error('orderNumber is required');
e.status = 400;
throw e;
}
if (!letters || !Array.isArray(letters) || letters.length === 0) {
return res.status(400).json({
error: 'Invalid request',
message: 'Letters array is required and must not be empty'
});
const e = new Error('Letters array is required and must not be empty');
e.status = 400;
throw e;
}
console.log(`[Order] Generating order: ${orderNumber}`);
@@ -255,10 +251,10 @@ async function generateOrder(req, res, next) {
}
} catch (err) {
console.error(`[Order] Scriptalization failed for font ${font}:`, err.message);
return res.status(500).json({
error: 'Scriptalization failed',
message: `Failed to scriptalize texts with font ${font}: ${err.message}`
});
const e = new Error(`Failed to scriptalize texts with font ${font}: ${err.message}`);
e.status = 500;
e.label = 'Scriptalization failed';
throw e;
}
}
@@ -290,10 +286,10 @@ async function generateOrder(req, res, next) {
}
} catch (err) {
console.error(`[Order] SVG generation failed for letter ${i}:`, err.message);
return res.status(500).json({
error: 'SVG generation failed',
message: `Failed to generate SVG for letter ${i}: ${err.message}`
});
const e = new Error(`Failed to generate SVG for letter ${i}: ${err.message}`);
e.status = 500;
e.label = 'SVG generation failed';
throw e;
}
// Save SVG file - unterscheide zwischen Briefen und Umschlägen
@@ -352,7 +348,7 @@ async function generateOrder(req, res, next) {
}
// Step 6: Create order metadata
const meta = req.body.metadata || {};
const meta = metadata || {};
const orderMetadata = {
orderNumber,
generatedAt: new Date().toISOString(),
@@ -373,8 +369,7 @@ async function generateOrder(req, res, next) {
console.log(`[Order] Order ${orderNumber} generated successfully`);
// Response
res.status(200).json({
return {
orderNumber,
outputPath: outputDir,
letters: generatedLetters,
@@ -382,9 +377,26 @@ async function generateOrder(req, res, next) {
csvFiles,
totalFiles: generatedLetters.length + generatedEnvelopes.length + csvFiles.length,
timestamp: orderMetadata.generatedAt
});
};
}
}
/**
* POST /api/order/generate
* HTTP-Wrapper um runGeneration – Verhalten unverändert.
*/
async function generateOrder(req, res, next) {
try {
const { orderNumber, letters, metadata } = req.body;
const result = await runGeneration({ orderNumber, letters, metadata });
res.status(200).json(result);
} catch (err) {
if (err.status) {
return res.status(err.status).json({
error: err.label || 'Invalid request',
message: err.message
});
}
console.error('[Order] Error generating order:', err);
next(err);
}
@@ -535,6 +547,7 @@ async function registerOrder(req, res, next) {
module.exports = {
finalizeOrder,
generateOrder,
runGeneration,
uploadMotif,
uploadOrderFile,
registerOrder,