#!/usr/bin/env node /** * Portal-Funktionen (News, Terminplaner): legt die Attribute an der Collection portalFeatures an, die der * Verkauf des Terminplaners braucht (Abschaltgrund, Marker der Abo-Ende-Mail, Marker der Zahlungsmeldung). * Idempotent: Vorhandenes wird uebersprungen. Vor dem Code-Deploy ausfuehren (activate() schreibt die neuen * Felder; ohne Schema antwortet Appwrite mit "Unknown attribute"). * scripts/setup-abo-collections.mjs bleibt unveraendert (Top-Level-Skript ohne Pruefmodus). * Usage: * node scripts/feature-setup.mjs --check (nur melden, nichts anlegen; Exit-Code 1 bei Luecken) * node scripts/feature-setup.mjs (Fehlendes anlegen) * * config.js und der Appwrite-Client werden erst in main() geladen: test/support/schema.js importiert die * Attributliste, bevor die Tests ihre Umgebung (useTestEnv) setzen koennen. */ import { pathToFileURL } from 'node:url' /** * Neue Attribute von portalFeatures (alle optional): * - disabledReason: 'team' | 'abo_ende' | 'abo_ende_vor_einrichtung' | 'kunde_geloescht' (laengster Wert 24 Zeichen) * - aboEndeMailAt: Zeitpunkt der versandten Mail "Terminplaner beendet" (Marker, nach dem Versand gesetzt) * - zahlungMeldung: '' | 'bereits_aktiv' | 'abgeschaltet' | 'abo_ende' | 'gemeldet' (Marker einer Zahlung ohne Ticket) */ export const PORTAL_FEATURE_ATTRIBUTES = [ { type: 'string', key: 'disabledReason', size: 32, required: false }, { type: 'datetime', key: 'aboEndeMailAt', required: false }, { type: 'string', key: 'zahlungMeldung', size: 24, required: false }, ] export async function main({ check = false, client, log = console.log } = {}) { const { config } = await import('../server/config.js') let api = client if (!api) { const { createAppwriteClient } = await import('./analyse-setup.mjs') api = createAppwriteClient() } const featuresId = config.collections.portalFeatures const missing = [] // Attribute einer Collection abgleichen: meldet Vorhandenes bzw. (mit --check) jede Luecke und liefert die fehlenden async function syncAttributes(collectionId, attributes) { const have = new Set((await api.listAttributes(collectionId)).map((a) => a.key)) for (const attr of attributes) { if (have.has(attr.key)) log(`= ${collectionId}.${attr.key} (existiert)`) else if (check) log(`- ${collectionId}.${attr.key} FEHLT`) } return attributes.filter((a) => !have.has(a.key)) } const featureAbsent = await syncAttributes(featuresId, PORTAL_FEATURE_ATTRIBUTES) featureAbsent.forEach((a) => missing.push({ ...a, collection: featuresId })) if (check) { log(missing.length ? `${missing.length} Eintrag/Eintraege fehlen.` : 'Alles vorhanden.') return { missing } } for (const attr of featureAbsent) { await api.createAttribute(featuresId, attr) log(`+ Attribut ${featuresId}.${attr.key}`) } for (const attr of featureAbsent) await api.waitForAttribute(featuresId, attr.key) log('Fertig.') return { missing } } 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 }) => process.exit(check && missing.length ? 1 : 0)) .catch((e) => { console.error(e.message) process.exit(1) }) }