#!/usr/bin/env node /** * Oeffnungszeiten live: legt die Collections oeffnungszeiten und zeitenErinnerungen mit Attributen und Indizes in * Appwrite an (nur Server-Key, keine Client-Rechte) und ergaenzt portalFeatures um die Felder unlockedBy, * aboEndeAt, einbau, behaltenSessionId und k24At (Abo-Freischaltung, Behalten, Selbstheilung). Das Schema steht in * server/services/zeiten/schema.js. Idempotent: Vorhandenes wird uebersprungen, fehlende Attribute und Indizes * auch in schon vorhandenen Collections nachgezogen. portalFeatures selbst legt setup-abo-collections.mjs an * (fehlt sie, bricht das Skript vor jeder Aenderung ab). Vor dem Einschalten (ZEITEN_ENABLED) und vor dem * Code-Deploy ausfuehren: der Code schreibt die neuen Felder, ohne Schema antwortet Appwrite mit "Unknown attribute". * Usage: * node scripts/zeiten-setup.mjs --check (nur melden, nichts anlegen; Exit 1 bei Fehlendem) * node scripts/zeiten-setup.mjs (Fehlendes anlegen) */ import { pathToFileURL } from 'node:url' import { config } from '../server/config.js' import { ZEITEN_COLLECTIONS, ZEITEN_FEATURE_ATTRIBUTES } from '../server/services/zeiten/schema.js' import { createAppwriteClient } from './analyse-setup.mjs' 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 vorhandene portalFeatures (nur zusaetzliche Attribute, keine Indizes) const targets = [ ...Object.entries(ZEITEN_COLLECTIONS).map(([name, { attributes, indexes }]) => ({ id: config.collections[name], attributes, indexes, own: true })), { id: config.collections.portalFeatures, attributes: ZEITEN_FEATURE_ATTRIBUTES, indexes: [], own: false }, ] // Erst alles pruefen, dann (ohne --check) anlegen, damit --check garantiert nichts veraendert for (const { id, attributes, indexes, own } of targets) { const exists = await api.collectionExists(id) if (!exists && !own) throw new Error(`Collection ${id} (portalFeatures) fehlt – zuerst scripts/setup-abo-collections.mjs ausfuehren`) 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) }) }