Schema: Zustimmungsnachweis featureConsents im Skript feature-setup
scripts/feature-setup.mjs legt neben den portalFeatures-Attributen die Collection
featureConsents (nur Server-Key, ohne Rechte) mit 11 Attributen und den Indizes
idx_session und idx_project_feature an; --check meldet je fehlende Collection,
Attribut und Index eine Zeile "- <id>[.<key>] FEHLT", schreibt nichts und liefert
Exit-Code 1 bei Luecken. config.collections.featureConsents (Env
APPWRITE_COLLECTION_FEATURE_CONSENTS) und schemaKeys('featureConsents') in
test/support/schema.js ziehen mit. setup-abo-collections.mjs bleibt unveraendert.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
98fa03aad7
commit
d8e4d79891
4 files changed
+268
-23
No files matched your search
+70
-11
@@ -1,16 +1,17 @@
|
||||
#!/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").
|
||||
* Verkauf des Terminplaners braucht (Abschaltgrund, Marker der Abo-Ende-Mail, Marker der Zahlungsmeldung),
|
||||
* und die Collection featureConsents (Zustimmungsnachweis zur Bestellung, nur Server-Key) samt Attributen und
|
||||
* Indizes. 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.
|
||||
* Attributlisten, bevor die Tests ihre Umgebung (useTestEnv) setzen koennen.
|
||||
*/
|
||||
import { pathToFileURL } from 'node:url'
|
||||
|
||||
@@ -26,6 +27,39 @@ export const PORTAL_FEATURE_ATTRIBUTES = [
|
||||
{ type: 'string', key: 'zahlungMeldung', size: 24, required: false },
|
||||
]
|
||||
|
||||
const str = (key, size) => ({ key, type: 'string', size, required: false })
|
||||
|
||||
/**
|
||||
* Zustimmungsnachweis zur Bestellung einer Funktion (Collection featureConsents, nur Server-Key, wie analyseConsents):
|
||||
* - fassungen: JSON mit den Fassungen der gezeigten Texte; texte: JSON mit den gezeigten Texten (Appwrite macht aus
|
||||
* dieser Groesse eine TEXT-Spalte; 20000 Zeichen genuegen fuer Zustimmung, Haken und Belehrung)
|
||||
* - textHash: SHA-256 (hex) ueber die gespeicherten Texte; stripeSessionId: Stripe-Sitzung, zu der der Nachweis gehoert
|
||||
* - ip, userAgent, konto: Herkunft der Zustimmung; acceptedAt: Zeitpunkt des Klicks (Anker der Widerrufsfrist)
|
||||
*/
|
||||
export const CONSENT_COLLECTION_NAME = 'featureConsents'
|
||||
export const FEATURE_CONSENT_ATTRIBUTES = [
|
||||
str('customerId', 36),
|
||||
str('projectId', 36),
|
||||
str('featureKey', 16),
|
||||
str('stripeSessionId', 128),
|
||||
{ key: 'acceptedAt', type: 'datetime', required: false },
|
||||
str('fassungen', 1000),
|
||||
str('texte', 20000),
|
||||
str('textHash', 64),
|
||||
str('ip', 64),
|
||||
str('userAgent', 300),
|
||||
str('konto', 255),
|
||||
]
|
||||
export const FEATURE_CONSENT_INDEXES = [
|
||||
{ key: 'idx_session', type: 'key', attributes: ['stripeSessionId'] },
|
||||
{ key: 'idx_project_feature', type: 'key', attributes: ['projectId', 'featureKey', 'acceptedAt'] },
|
||||
]
|
||||
|
||||
/** true, wenn das Ergebnis von main() Luecken meldet (Exit-Code 1 im Pruefmodus) */
|
||||
export function hatLuecken({ missing = [], missingCollection = false, missingIndexes = [] } = {}) {
|
||||
return missing.length > 0 || missingCollection || missingIndexes.length > 0
|
||||
}
|
||||
|
||||
export async function main({ check = false, client, log = console.log } = {}) {
|
||||
const { config } = await import('../server/config.js')
|
||||
let api = client
|
||||
@@ -34,11 +68,12 @@ export async function main({ check = false, client, log = console.log } = {}) {
|
||||
api = createAppwriteClient()
|
||||
}
|
||||
const featuresId = config.collections.portalFeatures
|
||||
const consentsId = config.collections.featureConsents
|
||||
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))
|
||||
// Attribute einer Collection abgleichen; `exists` false = Collection fehlt (alle Attribute fehlen)
|
||||
async function syncAttributes(collectionId, attributes, exists = true) {
|
||||
const have = exists ? new Set((await api.listAttributes(collectionId)).map((a) => a.key)) : new Set()
|
||||
for (const attr of attributes) {
|
||||
if (have.has(attr.key)) log(`= ${collectionId}.${attr.key} (existiert)`)
|
||||
else if (check) log(`- ${collectionId}.${attr.key} FEHLT`)
|
||||
@@ -49,25 +84,49 @@ export async function main({ check = false, client, log = console.log } = {}) {
|
||||
const featureAbsent = await syncAttributes(featuresId, PORTAL_FEATURE_ATTRIBUTES)
|
||||
featureAbsent.forEach((a) => missing.push({ ...a, collection: featuresId }))
|
||||
|
||||
const consentsExist = await api.collectionExists(consentsId)
|
||||
if (!consentsExist && check) log(`- ${consentsId} FEHLT`)
|
||||
const consentAbsent = await syncAttributes(consentsId, FEATURE_CONSENT_ATTRIBUTES, consentsExist)
|
||||
consentAbsent.forEach((a) => missing.push({ ...a, collection: consentsId }))
|
||||
const haveIndexes = consentsExist ? new Set((await api.listIndexes(consentsId)).map((i) => i.key)) : new Set()
|
||||
const indexAbsent = FEATURE_CONSENT_INDEXES.filter((i) => !haveIndexes.has(i.key))
|
||||
if (check) indexAbsent.forEach((i) => log(`- ${consentsId}.${i.key} FEHLT`))
|
||||
|
||||
const result = { missing, missingCollection: !consentsExist, missingIndexes: indexAbsent }
|
||||
if (check) {
|
||||
log(missing.length ? `${missing.length} Eintrag/Eintraege fehlen.` : 'Alles vorhanden.')
|
||||
return { missing }
|
||||
const open = missing.length + indexAbsent.length + (consentsExist ? 0 : 1)
|
||||
log(open ? `${open} Eintrag/Eintraege fehlen.` : 'Alles vorhanden.')
|
||||
return result
|
||||
}
|
||||
|
||||
for (const attr of featureAbsent) {
|
||||
await api.createAttribute(featuresId, attr)
|
||||
log(`+ Attribut ${featuresId}.${attr.key}`)
|
||||
}
|
||||
if (!consentsExist) {
|
||||
await api.ensureCollection(consentsId, CONSENT_COLLECTION_NAME)
|
||||
log(`+ Collection ${consentsId}`)
|
||||
}
|
||||
for (const attr of consentAbsent) {
|
||||
await api.createAttribute(consentsId, attr)
|
||||
log(`+ Attribut ${consentsId}.${attr.key}`)
|
||||
}
|
||||
for (const attr of featureAbsent) await api.waitForAttribute(featuresId, attr.key)
|
||||
for (const attr of consentAbsent) await api.waitForAttribute(consentsId, attr.key)
|
||||
// Indizes erst, wenn ihre Attribute verfuegbar sind
|
||||
for (const index of indexAbsent) {
|
||||
await api.createIndex(consentsId, index)
|
||||
log(`+ Index ${consentsId}.${index.key}`)
|
||||
}
|
||||
log('Fertig.')
|
||||
return { missing }
|
||||
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 }) => process.exit(check && missing.length ? 1 : 0))
|
||||
.then((result) => process.exit(check && hatLuecken(result) ? 1 : 0))
|
||||
.catch((e) => {
|
||||
console.error(e.message)
|
||||
process.exit(1)
|
||||
|
||||
Reference in new issue
Block a user