Files
ClaudeandClaude Opus 5.5 c7cd436ec2 Öffnungszeiten: Mails K24–K27 heißen jetzt K27–K30, Feld k24At heißt einschaltMailAt
K24–K26 sind seit dem Webshop-Livegang (main) vergeben; der Mail-Lader sucht die Vorlage über den
Dateianfang, doppelte Nummern hätten die falsche Mail geschickt. Das Feld ist noch nicht in der
Produktion angelegt (zeiten-setup.mjs lief noch nicht), daher jetzt ein sprechender Name.
Spec und Plan behalten die alten Nummern (Hinweis oben im Dokument).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 06:00:17 +00:00

95 lines
4.5 KiB
JavaScript
Raw Permalink 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 einschaltMailAt (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)
})
}