#!/usr/bin/env node /** * Aufträge und Rechnungen: legt die Collections auftraege und projektRechnungen mit Attributen und Indizes in * Appwrite an (nur Server-Key, keine Client-Rechte) und ergänzt die vorhandenen Collections customers (Feld * kundenart) und financeSettings (Feld steuernummer). Das Schema steht in server/services/auftraege/schema.js. * Idempotent: Vorhandenes wird übersprungen, fehlende Attribute und Indizes auch in schon vorhandenen Collections * nachgezogen. customers (gehört dem Ticketsystem) und financeSettings (scripts/setup-finanzen.mjs) legt dieses * Skript nie an; fehlen sie, bricht es vor jeder Änderung ab. Vor dem Code-Deploy und vor dem Einschalten * (AUFTRAEGE_ENABLED) ausführen: der Code schreibt die neuen Felder, ohne Schema antwortet Appwrite mit * "Unknown attribute". * Usage: * docker exec -w /app webklar-kundenbereich-server node scripts/auftraege-setup.mjs * node scripts/auftraege-setup.mjs --check (nur melden, nichts anlegen; Exit 1 bei Fehlendem) * node scripts/auftraege-setup.mjs (Fehlendes anlegen) */ import { pathToFileURL } from 'node:url' import { config } from '../server/config.js' import { AUFTRAEGE_COLLECTIONS, AUFTRAEGE_ZUSATZ_ATTRIBUTE } from '../server/services/auftraege/schema.js' import { createAppwriteClient } from './analyse-setup.mjs' // Hinweis, falls eine nicht eigene Collection fehlt const HINWEIS = { financeSettings: 'zuerst scripts/setup-finanzen.mjs ausfuehren', customers: 'die Kunden-Collection gehört dem Ticketsystem (WOMS)', } export async function main({ check = false, client, log = console.log } = {}) { const api = client || createAppwriteClient() const missing = [] const missingCollections = [] const missingIndexes = [] const plan = [] // Eigene Collections plus die vorhandenen customers und financeSettings (nur zusätzliche Attribute, keine Indizes) const targets = [ ...Object.entries(AUFTRAEGE_COLLECTIONS).map(([name, { attributes, indexes }]) => ({ name, id: config.collections[name], attributes, indexes, own: true })), ...Object.entries(AUFTRAEGE_ZUSATZ_ATTRIBUTE).map(([name, attributes]) => ({ name, id: config.collections[name], attributes, indexes: [], own: false })), ] // Erst alles pruefen, dann (ohne --check) anlegen, damit --check garantiert nichts veraendert for (const { name, id, attributes, indexes, own } of targets) { const exists = await api.collectionExists(id) if (!exists && !own) throw new Error(`Collection ${id} fehlt – ${HINWEIS[name]}`) const haveAttributes = exists ? new Set((await api.listAttributes(id)).map((a) => a.key)) : new Set() const haveIndexes = exists ? new Set((await api.listIndexes(id)).map((i) => i.key)) : new Set() const absentAttributes = attributes.filter((a) => !haveAttributes.has(a.key)) const absentIndexes = indexes.filter((i) => !haveIndexes.has(i.key)) if (!exists) { log(`${check ? '-' : '+'} Collection ${id}${check ? ' FEHLT' : ''}`) missingCollections.push(id) } else { log(`= ${id}: ${haveAttributes.size} Attribute, ${haveIndexes.size} Indizes vorhanden`) } if (check) { absentAttributes.forEach((a) => log(`- ${id}.${a.key} FEHLT`)) absentIndexes.forEach((i) => log(`- Index ${id}.${i.key} FEHLT`)) } absentAttributes.forEach((a) => missing.push({ ...a, collection: id })) absentIndexes.forEach((i) => missingIndexes.push({ ...i, collection: id })) plan.push({ id, exists, absentAttributes, absentIndexes }) } const result = { missing, missingCollections, missingIndexes } if (check) { const open = missing.length + missingCollections.length + missingIndexes.length log(open ? `${open} Eintrag/Eintraege fehlen.` : 'Alles vorhanden.') return result } for (const { id, exists, absentAttributes } of plan) { if (!exists) await api.ensureCollection(id, id) for (const attr of absentAttributes) { await api.createAttribute(id, attr) log(`+ Attribut ${id}.${attr.key}`) } } // Indizes brauchen verfuegbare Attribute: erst warten, dann anlegen for (const { id, absentAttributes } of plan) { for (const attr of absentAttributes) await api.waitForAttribute(id, attr.key) } for (const { id, absentIndexes } of plan) { for (const index of absentIndexes) { await api.createIndex(id, index) log(`+ Index ${id}.${index.key}`) } } log('Fertig.') return result } const invokedDirectly = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href if (invokedDirectly) { const check = process.argv.includes('--check') main({ check }) .then(({ missing, missingCollections, missingIndexes }) => process.exit(check && (missing.length || missingCollections.length || missingIndexes.length) ? 1 : 0)) .catch((e) => { console.error(e.message) process.exit(1) }) }