Files
Webklar-Kundenbereich/server/services/shop/schema.js
T
ClaudeandClaude Opus 5.5 08d837d690 Shop Review B I-6, I-7: Archiv der bestaetigten Rechtstexte, G09 ohne Link-Ersatz
I-6: Neue Collection shopRechtstexteArchiv (ID r + 35 Hex aus sha256(shopId + hash)); das
Bestaetigen legt die Fassung samt Versand-/Zahlungstext und Gewaehrleistungstext ab, G09 zeigt
die Fassung zum rechtstexteHash der Bestellung (Rueckfall mit Warnung auf die aktuell
bestaetigte), loescheShopDaten loescht das Archiv mit.

I-7: Grenzen AGB 20000, Belehrung 12000, Formular 3000 plus Pruefung der maskierten Laenge beim
Speichern; G09 ersetzt die AGB nie mehr durch einen Link, sondern scheitert (Marker bleibt
offen, wird wiederholt). Test mit Texten in Hoechstlaenge gegen die Grenzen der Warteschlange.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 09:58:31 +00:00

117 lines
4.7 KiB
JavaScript

/**
* Webshop: Appwrite-Schema der fuenf 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')],
},
// Archiv der bestaetigten Rechtstexte (eine Fassung je Dokument, ID 'r' + 35 Hex-Zeichen aus sha256(shopId + hash)): G09 zeigt dem
// Kaeufer die Fassung zum Zeitpunkt seiner Bestellung, und der Haendler kann belegen, welche Belehrung galt. Keine personenbezogenen
// Daten ausser der Person, die bestaetigt hat; wird mit dem Shop geloescht (loeschen.js).
shopRechtstexteArchiv: {
attributes: [str('shopId', 36), str('customerId', 36), str('hash', 64), str('texte', 100000), dt('bestaetigtAt'), str('bestaetigtVon', 255)],
indexes: [index('idx_shop_hash', 'shopId', 'hash')],
},
}
/** 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),
]),
)