Files
Webklar-Kundenbereich/scripts/auftraege-setup.mjs
T
2026-10-07 14:04:51 +00:00

103 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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)
})
}