Files
Webklar-Kundenbereich/server/services/shop/schema.js
T
ClaudeandClaude Opus 5.5 e9ab15bdb5 Webshop A1/A2: Konfiguration, Schalter, Katalog shopSetup, Schema und Setup-Skript
config.shop (SHOP_ENABLED, SHOP_PILOT_EMAILS, SHOP_UPLOAD_DIR, STRIPE_CONNECT_WEBHOOK_SECRET), config.shopSetup (299 € einmalig),
Collection-IDs shopConfig/shopProducts/shopOrders/shopWiderrufe, services/shop/schalter.js, Katalogschluessel shopSetup in
stripeBilling und stripe-products.mjs, services/shop/schema.js, scripts/shop-setup.mjs (--check), schemaKeys fuer die Test-Mocks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 08:30:21 +00:00

109 lines
4.2 KiB
JavaScript

/**
* Webshop: Appwrite-Schema der vier Collections (Spezifikation 2026-10-05-webshop-design.md).
* Reine Daten, absichtlich ohne Import von server/config.js: test/support/schema.js liest sie vor useTestEnv(),
* ein frueh geladenes config.js wuerde den Endpunkt des Appwrite-Nachbaus verfehlen. Die Collection-IDs stehen in
* config.collections.<Name>; scripts/shop-setup.mjs legt alles an. Kein Feld heisst data (appwriteAdmin.formatRequestBody
* haelt Bodies mit data fuer fertig verpackt), daher heissen die JSON-Felder einstellungen, rechtstexte, produkt, bestellung.
*
* Attribut: { key, type: 'string' | 'integer' | 'boolean' | 'datetime', size (nur Text), required: false }
* Index: { key, type: 'key', attributes: [...] }
* Datumswerte sind UTC (toISOString()); Appwrite liefert sie als "+00:00" zurueck, daher immer mit new Date() lesen.
*/
const str = (key, size) => ({ key, type: 'string', size, required: false })
const int = (key) => ({ key, type: 'integer', required: false })
const bool = (key) => ({ key, type: 'boolean', required: false })
const dt = (key) => ({ key, type: 'datetime', required: false })
const index = (key, ...attributes) => ({ key, type: 'key', attributes })
export const SHOP_COLLECTIONS = {
// Ein Shop je Funktion; Dokument-ID = ID des portalFeatures-Dokuments. Anfrage, Stripe-Konto, Einstellungen und Rechtstexte
// als JSON-Text; die grossen Spalten werden in Appwrite Textspalten und zaehlen nicht zur Zeilenbreite.
shopConfig: {
attributes: [
str('customerId', 36),
str('projectId', 36),
str('siteKey', 16),
str('anfrage', 4000),
str('anfrageStatus', 16),
dt('anfrageAt'),
dt('anfrageEntschiedenAt'),
str('anfrageEntschiedenVon', 255),
str('anfrageBegruendung', 1000),
str('stripeAccountId', 64),
str('stripeStatus', 4000),
str('einstellungen', 20000),
str('rechtstexte', 100000),
dt('rechtstexteBestaetigtAt'),
str('rechtstexteBestaetigtVon', 255),
str('rechtstexteHash', 64),
bool('geoeffnet'),
dt('geoeffnetAt'),
int('version'),
],
indexes: [
index('idx_customer', 'customerId'),
index('idx_sitekey', 'siteKey'),
index('idx_anfrage', 'anfrageStatus'),
index('idx_account', 'stripeAccountId'),
],
},
// Ein Artikel je Dokument (ID randomUUID); Name und Sortierung stehen zusaetzlich als Spalten, der Rest im JSON-Text produkt
shopProducts: {
attributes: [str('shopId', 36), str('customerId', 36), bool('aktiv'), int('sort'), str('name', 120), str('produkt', 30000)],
indexes: [index('idx_shop', 'shopId', 'sort')],
},
// Eine Bestellung je Dokument; enthaelt personenbezogene Daten des Kaeufers (Aufbewahrung und Anonymisierung: Block J)
shopOrders: {
attributes: [
str('shopId', 36),
str('customerId', 36),
str('bestellnummer', 16),
str('status', 16),
str('tokenHash', 64),
str('stripeSessionId', 128),
str('paymentIntentId', 64),
int('summeCent'),
dt('erstelltAt'),
dt('ablaufAt'),
dt('bezahltAt'),
str('kaeuferEmail', 320),
str('bestellung', 60000),
dt('anonymisiertAt'),
],
indexes: [
index('idx_shop_status', 'shopId', 'status'),
index('idx_nummer', 'shopId', 'bestellnummer'),
index('idx_session', 'stripeSessionId'),
index('idx_erstellt', 'shopId', 'erstelltAt'),
],
},
// Widerruf von Vertraegen (§ 356a BGB) ueber die oeffentliche Widerrufsseite
shopWiderrufe: {
attributes: [
str('shopId', 36),
str('customerId', 36),
str('bestellnummer', 32),
str('name', 200),
str('email', 320),
str('nachricht', 2000),
dt('eingangAt'),
dt('bestaetigungAt'),
dt('bearbeitetAt'),
str('orderId', 36),
],
indexes: [index('idx_shop', 'shopId', 'eingangAt')],
},
}
/** Datumsfelder je Collection, damit der Appwrite-Nachbau der Tests sie wie Appwrite formatieren kann. */
export const SHOP_DATETIMES = Object.fromEntries(
Object.entries(SHOP_COLLECTIONS).map(([name, { attributes }]) => [
name,
attributes.filter((attribute) => attribute.type === 'datetime').map((attribute) => attribute.key),
]),
)