Files
Webklar-Kundenbereich/scripts/zeiten-setup.mjs
T
ClaudeandClaude Opus 5.5 4b4cd1058f Öffnungszeiten: Schalter, Collections und Felder
ZEITEN_ENABLED/ZEITEN_PILOT_EMAILS/ZEITEN_KEIN_NEUBAU als config.zeiten (neben config.termine),
zeitenSetup (30 € einmalig), Collection-IDs oeffnungszeiten und zeitenErinnerungen, Schema der beiden
Collections plus fünf neue portalFeatures-Felder, scripts/zeiten-setup.mjs (idempotent, --check),
schemaKeys kennt die neuen Collections und Felder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:25:37 +00:00

95 lines
4.4 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
/**
* 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)
})
}