From 85bee3190cc75c947490a0d3a9983e832d6fa5d2 Mon Sep 17 00:00:00 2001 From: knso Date: Wed, 30 Sep 2026 16:24:21 +0000 Subject: [PATCH] =?UTF-8?q?Schema:=20portalFeatures-Zust=C3=A4nde=20und=20?= =?UTF-8?q?newsPosts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- scripts/setup-abo-collections.mjs | 61 ++++++++++++++++++++++++++++++- server/config.js | 1 + test/support.test.js | 12 ++++++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/scripts/setup-abo-collections.mjs b/scripts/setup-abo-collections.mjs index 1dcdef0..f5d3b24 100644 --- a/scripts/setup-abo-collections.mjs +++ b/scripts/setup-abo-collections.mjs @@ -1,7 +1,8 @@ #!/usr/bin/env node /** * Wartungs-Abo: legt die Collections subscriptions, hourCredits, takeoverCredits - * (Uebernahmegutschriften) und aboOffers (individuelle Angebote) in woms-database an und + * (Uebernahmegutschriften), aboOffers (individuelle Angebote), portalFeatures (Portal-Funktionen je + * Projekt) und newsPosts (News-Pinnwand) in woms-database an und * ergaenzt worksheets um das Flag countsToPlan ("zaehlt aufs Abo-Stundenkonto"). * Idempotent: existierende Collection/Attribute/Indexe werden uebersprungen. * Usage (im Container): docker exec -w /app webklar-kundenbereich-server node scripts/setup-abo-collections.mjs @@ -244,6 +245,64 @@ await ensureCollection( ] ) +// Portal-Funktionen je Projekt (Zustand, Kauf, Zustimmungsnachweis). Die Collection existiert in +// Produktion teils schon; ensureCollection ueberspringt Vorhandenes (409) und ergaenzt Fehlendes. +await ensureCollection( + 'portalFeatures', + 'Portal-Funktionen', + [ + { type: 'string', key: 'customerId', size: 64 }, + { type: 'string', key: 'projectId', size: 64 }, + { type: 'string', key: 'featureKey', size: 64 }, + { type: 'boolean', key: 'enabled' }, + { type: 'boolean', key: 'unlockedByPurchase' }, + { type: 'string', key: 'purchaseStatus', size: 64 }, + { type: 'string', key: 'state', size: 16 }, + { type: 'string', key: 'siteKey', size: 16 }, + { type: 'string', key: 'ticketId', size: 64 }, + { type: 'string', key: 'stripeSessionId', size: 64 }, + { type: 'string', key: 'stripeInvoiceId', size: 64 }, + { type: 'string', key: 'invoiceUrl', size: 500 }, + { type: 'datetime', key: 'orderedAt' }, + { type: 'datetime', key: 'activatedAt' }, + { type: 'datetime', key: 'disabledAt' }, + { type: 'string', key: 'consentText', size: 1000 }, + { type: 'datetime', key: 'consentAt' }, + { type: 'string', key: 'consentIp', size: 64 }, + { type: 'string', key: 'consentUserAgent', size: 300 }, + { type: 'string', key: 'adminNote', size: 300 }, + ], + [ + { key: 'idx_customer', type: 'key', attributes: ['customerId'] }, + { key: 'idx_state', type: 'key', attributes: ['state'] }, + { key: 'idx_sitekey', type: 'unique', attributes: ['siteKey'] }, + ] +) + +// News-Pinnwand: Beitraege je Projekt +await ensureCollection( + 'newsPosts', + 'News-Beiträge', + [ + { type: 'string', key: 'customerId', size: 64 }, + { type: 'string', key: 'projectId', size: 64 }, + { type: 'string', key: 'type', size: 8 }, + { type: 'string', key: 'title', size: 60 }, + { type: 'string', key: 'text', size: 400 }, + { type: 'string', key: 'imageId', size: 64 }, + { type: 'string', key: 'paper', size: 8 }, + { type: 'string', key: 'duration', size: 8 }, + { type: 'datetime', key: 'visibleFrom' }, + { type: 'datetime', key: 'visibleUntil' }, + { type: 'datetime', key: 'takenDownAt' }, + { type: 'datetime', key: 'createdAt' }, + { type: 'datetime', key: 'updatedAt' }, + ], + [ + { key: 'idx_project_from', type: 'key', attributes: ['projectId', 'visibleFrom'] }, + ] +) + // Worksheet-Flag: nur markierte Arbeitszeit wird vom Abo-Guthaben abgezogen await ensure('worksheets.countsToPlan', () => aw(`${col('worksheets')}/attributes/boolean`, { diff --git a/server/config.js b/server/config.js index 35e9c7e..d4efdcb 100644 --- a/server/config.js +++ b/server/config.js @@ -118,6 +118,7 @@ export const config = { takeoverCredits: process.env.APPWRITE_COLLECTION_TAKEOVER_CREDITS || 'takeoverCredits', aboOffers: process.env.APPWRITE_COLLECTION_ABO_OFFERS || 'aboOffers', contractEvents: process.env.APPWRITE_COLLECTION_CONTRACT_EVENTS || 'contractEvents', + newsPosts: process.env.APPWRITE_COLLECTION_NEWS_POSTS || 'newsPosts', }, publicBaseUrl: (process.env.PORTAL_PUBLIC_URL || 'https://project.webklar.com').replace(/\/$/, ''), diff --git a/test/support.test.js b/test/support.test.js index 5123168..02a5282 100644 --- a/test/support.test.js +++ b/test/support.test.js @@ -287,3 +287,15 @@ test('Stripe-Nachbau: failNext lässt den nächsten passenden Aufruf scheitern', await assert.rejects(stripe.subscriptions.update('sub_f', { metadata: { a: 'd' } }), (e) => e.statusCode === 402) assert.equal((await stripe.subscriptions.retrieve('sub_f')).metadata.a, 'c') }) + +test('schema_funktionen_news', async () => { + const { schemaKeys } = await import('./support/schema.js') + const features = schemaKeys('portalFeatures') + for (const k of ['customerId', 'projectId', 'featureKey', 'enabled', 'unlockedByPurchase', 'purchaseStatus', 'state', 'siteKey', 'ticketId', 'stripeSessionId', 'stripeInvoiceId', 'invoiceUrl', 'orderedAt', 'activatedAt', 'disabledAt', 'consentText', 'consentAt', 'consentIp', 'consentUserAgent', 'adminNote']) { + assert.ok(features.includes(k), `portalFeatures.${k}`) + } + const news = schemaKeys('newsPosts') + for (const k of ['customerId', 'projectId', 'type', 'title', 'text', 'imageId', 'paper', 'duration', 'visibleFrom', 'visibleUntil', 'takenDownAt', 'createdAt', 'updatedAt']) { + assert.ok(news.includes(k), `newsPosts.${k}`) + } +})