R0 (Doku): - Archiv unter docs/marketplace/archive/ aus managarten-Tag cards-decommission-base: MARKETPLACE_PLAN (654 Z., Vollvision mit mana-credits-Flow, Anti-Patterns), COMPETITORS, GUIDELINES, cards-server_CLAUDE. - docs/playbooks/MARKETPLACE_RESTORE.md mit Schema-Naming-Entscheidung (eigenes marketplace-pgSchema), Wellen R0-R6, Cardecky-Skill- Integration, Lizenz-Modell. - CLAUDE.md Invariante 2: Strategie-B gilt nur für Study-/FSRS-/Sync- Schicht; Marketplace-Restore ist explizite Ausnahme. - STATUS.md: Phase 12 R0+R1 durch. R1 (Schema): - 16 Tabellen + 5 Enums im neuen marketplace-pgSchema (authors, decks, deck_versions, deck_cards, tag_definitions, deck_tags, deck_stars, deck_subscriptions, deck_forks, deck_pull_requests, card_discussions, deck_reports, ai_moderation_log, deck_purchases, author_payouts, author_follows). - drizzle.config.ts: schemaFilter ['cards', 'marketplace']. - Greenfield cards-pgSchema unangetastet. - DB-CHECK decks_price_requires_license verifiziert (paid Deck mit CC-BY wirft sauber ab). - type-check + 56 API-Tests grün, drizzle-kit push idempotent. Decks dormant (kein Code-Pfad ruft die Tabellen). R2 (Backend α/β: Author-Profile + Publish + AI-Mod) als nächstes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
312 lines
15 KiB
Markdown
312 lines
15 KiB
Markdown
# Marketplace-Restore — Playbook
|
||
|
||
> **Status:** Plan, R0+R1 in Arbeit. Stand 2026-05-09.
|
||
> **Vorgänger:** das alte `services/cards-server/` aus `managarten/` (mana-
|
||
> monorepo) wurde am 2026-05-08 zusammen mit `apps/cards/` dekommissioniert,
|
||
> weil beides eine Kopplung war — siehe Decommission-Commits
|
||
> `bc158cb0b` (cards-server), `9cd871749` (apps/cards), `dd1bab09d`
|
||
> (cards-core). Rollback-Tag: `cards-decommission-base` im managarten-Repo.
|
||
>
|
||
> Dieser Plan dokumentiert den **Restore** des Marketplace-Backends in
|
||
> die Standalone-Cards-App, additiv zur bestehenden Greenfield-API.
|
||
|
||
## TL;DR
|
||
|
||
- **Strategie-B-Klarstellung:** „Kein Code aus mana-monorepo" galt der
|
||
Study-/FSRS-/Sync-Schicht (Dexie raus, server-authoritative). Marketplace
|
||
war nie davon betroffen — er wurde nur mit-rausgerissen, weil er an
|
||
`apps/cards` gekoppelt war.
|
||
- **Restore vs. Neubau:** Restore. ~13.000 Zeilen reifer Code, 8 Phasen
|
||
in Produktion gelaufen, Plan-Doku in Goldstandard-Qualität (654 Zeilen),
|
||
bekannte Limitierungen sauber dokumentiert. Neubau wäre 4–6 Wochen,
|
||
Restore ~14 Tage.
|
||
- **Schema-Naming-Entscheidung:** **Eigenes `marketplace`-pgSchema**
|
||
in derselben `cards`-DB. Begründung: saubere Read/Write-Trennung,
|
||
Backup-Granularität (`pg_dump --schema=marketplace`), RLS-Policies
|
||
pro Schema möglich, keine Kollisionen mit den existierenden
|
||
`cards.{decks,cards,reviews,…}`-Tabellen.
|
||
- **Service-Topologie:** Single-Service. Marketplace-Routen kommen unter
|
||
`/api/v1/marketplace/*` in den bestehenden `cards/apps/api`. Kein
|
||
zweiter Hono-Prozess, kein zweiter Container. YAGNI bei deinem Volumen.
|
||
- **Frontend:** alte Routes 1:1 nach `cards/apps/web/src/routes/` —
|
||
`/explore`, `/d/[slug]`, `/u/[slug]`, `/me/{published,subscribed,forks,purchases}`,
|
||
`/admin/reports`. Imports auf Verdaccio-Pakete umstellen, Theming-
|
||
Bridge-Aliase greifen automatisch.
|
||
- **Restore-Reihenfolge:** R0 (Doku) → R1 (Schema) → R2 (Auth + Routes
|
||
α/β) → R3 (γ + δ) → R4 (ε) → R5 (Frontend) → R6 (Smoke + erste
|
||
Cardecky-Decks publishen).
|
||
- **Was nicht im ersten Wurf:** Paid Decks (ζ.1) und Moderation-UI
|
||
(η.1). Schema-Tabellen kommen mit, aber Code-Pfade bleiben dormant
|
||
bis nach erstem Live-Test.
|
||
|
||
---
|
||
|
||
## Was die alte Implementation konnte (Phase α–η.1)
|
||
|
||
Inhalt der archivierten Dokumente — **Original-Wahrheit** unter
|
||
`cards/docs/marketplace/archive/`:
|
||
|
||
| Datei | Was drin |
|
||
|---|---|
|
||
| `MARKETPLACE_PLAN_2026-05-07.md` | 654-Zeilen-Vollvision: Datenmodell, mana-credits-Flow, Cold-Start-Strategie, Anti-Patterns, Phasen-Status |
|
||
| `COMPETITORS_2026-05.md` | 353-Zeilen-Konkurrenz-Analyse: Quizlet, AnkiHub, Brainscape, AnkiPro, AnkiApp, RemNote, Mnemosyne |
|
||
| `GUIDELINES.md` | 367 Zeilen Community-Guidelines + Lizenz-Modell (SPDX + Cardecky-Personal-Use-1.0 + Cardecky-Pro-Only-1.0) |
|
||
| `cards-server_CLAUDE.md` | 110 Zeilen Tech-Stack-Doku des Original-Service |
|
||
|
||
Phasen-Status zum Zeitpunkt der Decommission (2026-05-08):
|
||
|
||
| Phase | Status | Was lief produktiv |
|
||
|---|---|---|
|
||
| α — Skelett | ✅ live | 17-Tabellen-Schema, JWT-Auth, Container, Tunnel `cardecky-api.mana.how` |
|
||
| β — Author-Workflow | ✅ live | Profil-Claim, Publish, Lizenz-Picker (SPDX), Preis-Eingabe, AI-Mod-First-Pass |
|
||
| γ — Discovery | ✅ live | `/explore`, Stars, Follows, Author-Profile, Trending, Search (ILIKE) |
|
||
| δ — Subscribe + Smart-Merge | ✅ live | Pull, Diff-View „+N · ~N · −N", FSRS-State erhalten über Karten-Hash-Diff |
|
||
| ε — PRs + Discussions | ✅ live | „✏️ Verbessern" auf jeder Karte, Author-Merge, Inline-Threads, Notify-Mails |
|
||
| ζ.1 — Paid Decks | ✅ live | 4-Schritt-Reserve→Purchase→Commit→Grant, 80/20-Split (90/10 für `verified_mana`) |
|
||
| η.1 — Moderation | ✅ live | Reports, Admin-Inbox, Takedown-Workflow (kaskadiert auf PRs), Author-Ban |
|
||
|
||
Bekannte Limitierungen (siehe `MARKETPLACE_PLAN_2026-05-07.md` §13a):
|
||
PR-Merge-stale-blind, Reconciler-Lücke bei Paid-Pipeline, Mention-System
|
||
fehlt, Discussion-Threading 1-Level, kein Refund-Self-Service. Alles
|
||
dokumentiert, nichts unbekannt.
|
||
|
||
---
|
||
|
||
## Architektur-Anpassungen für den Restore
|
||
|
||
### 1. DB-Topologie: eigenes `marketplace`-pgSchema
|
||
|
||
**Alt** (in `mana_platform`-DB, geteilt mit allen mana-Services):
|
||
```
|
||
mana_platform.cards.{authors, decks, deck_versions, …} — 17 Tabellen
|
||
```
|
||
|
||
**Neu** (in standalone `cards`-DB des Standalone-Repos):
|
||
```
|
||
cards.cards.{decks, cards, reviews, media_files, tags, imports} — Greenfield, bleibt
|
||
cards.marketplace.{authors, decks, deck_versions, deck_cards, …} — Restore, neu
|
||
```
|
||
|
||
Begründung für ein **eigenes pgSchema** statt Tabellen-Prefix
|
||
(`published_decks`, `published_deck_versions`, …):
|
||
- **Sauberer Read-Path.** Public-Endpoints sehen nur das `marketplace`-
|
||
Schema. Greenfield-Code (private Decks/Karten/Reviews) sieht
|
||
ausschließlich `cards`. Kein Risiko, dass ein `SELECT * FROM decks`
|
||
versehentlich beide Welten mischt.
|
||
- **Backup-Granularität.** `pg_dump --schema=marketplace` exportiert
|
||
nur den Marketplace-Stand für Compliance/Recovery. Privater Lern-
|
||
Stand der User bleibt unangetastet.
|
||
- **RLS-Policies pro Schema** — falls wir je Row-Level-Security
|
||
einführen für public-decks-take-down-Workflows, ist das pro Schema
|
||
konfigurierbar.
|
||
- **Drizzle-kit-Push-Disziplin.** `schemaFilter: ['cards', 'marketplace']`
|
||
hält beide Pushes sauber. Schema-Drift fängt sich auf Schema-Ebene.
|
||
|
||
Drizzle-Variablennamen halten den `public`-Prefix aus dem alten Code:
|
||
`publicDecks`, `publicDeckVersions`, `publicDeckCards`. So bleibt das
|
||
intent klar, und Imports kollidieren nicht mit `cards.decks` aus dem
|
||
Greenfield.
|
||
|
||
### 2. Auth-Modell: identisch, kein Refactor
|
||
|
||
Alter cards-server: JWKS-Cache gegen `mana-auth`. Greenfield-cards-api:
|
||
JWKS-Cache gegen `mana-auth`. Identisch. Service-Key-Auth (`X-Service-Key`)
|
||
für Mana-Webhooks ebenfalls 1:1 übernommen.
|
||
|
||
Optional-Auth-Middleware für Public-Endpoints (`/explore`, `/d/:slug`)
|
||
muss aus dem alten Code mitkommen — der erlaubt anonymen Read und
|
||
gibt zugleich personalisierte Daten (z.B. „Bist du Subscriber?")
|
||
wenn ein Bearer mit-übermittelt ist.
|
||
|
||
### 3. Service-Topologie: Single-Service
|
||
|
||
Alle Marketplace-Routen unter `/api/v1/marketplace/*` in
|
||
`cards/apps/api/src/routes/marketplace/`:
|
||
|
||
```
|
||
cards/apps/api/src/routes/marketplace/
|
||
├── authors.ts
|
||
├── decks.ts (publish, list, version reads)
|
||
├── engagement.ts (stars, subscribe, fork)
|
||
├── discussions.ts (card-discussions threads)
|
||
├── pull-requests.ts
|
||
├── moderation.ts (reports + admin)
|
||
├── purchases.ts (paid decks — dormant in R3, aktiv ab späterer Welle)
|
||
└── explore.ts (discovery + search)
|
||
```
|
||
|
||
Hauptserver-Mount in `cards/apps/api/src/index.ts`:
|
||
```ts
|
||
app.route('/api/v1/marketplace/authors', authorsRouter())
|
||
app.route('/api/v1/marketplace/decks', decksRouter())
|
||
// …
|
||
```
|
||
|
||
Vorteile: ein Prozess, ein Container, ein Tunnel-Endpoint
|
||
(`cardecky-api.mana.how`), eine JWT-Validierung, eine Drizzle-DB-Connection.
|
||
|
||
### 4. Frontend: additive Routes, gleicher Stack
|
||
|
||
`cards/apps/web/` ist SvelteKit + Svelte 5 (runes-only). Alter
|
||
`apps/cards/apps/web/` war es auch. Routen werden 1:1 übernommen,
|
||
mit drei Anpassungen:
|
||
|
||
- **Imports:** `@mana/shared-*` kommt heute aus Verdaccio (npm.mana.how).
|
||
`pnpm add @mana/shared-ui@^0.1.1 @mana/shared-share-protocol …`.
|
||
- **Theming:** alte Components nutzten alte Token. Greenfield-Bridge-
|
||
Aliase in `app.css` mappen die meisten alten Token aufs 12er-Mana-
|
||
Vokabular. Test im Browser, ggf. Anpassungen.
|
||
- **Auth-Hook:** `dev-stub.svelte.ts` bleibt für Phase-2-Lücke. Sobald
|
||
echte mana-auth-Login-Flow ausgerollt ist (siehe `STATUS.md`),
|
||
weicht der Stub für `@mana/shared-auth`.
|
||
|
||
### 5. Hash-Implementierung: bestehende `@cards/domain` benutzen
|
||
|
||
Alter `cards-server/src/lib/hash.ts`: eigenständige SHA-256-Implementierung.
|
||
Greenfield: `cardContentHash` in `@cards/domain` (Web-Crypto, deterministisch).
|
||
|
||
Beim Restore: `lib/hash.ts` **nicht** mit-übernehmen, sondern
|
||
Marketplace-Code auf `cardContentHash` aus `@cards/domain` umbiegen.
|
||
Eine Hash-Definition für die ganze App.
|
||
|
||
### 6. mana-Service-Calls: identische Pattern
|
||
|
||
Alter cards-server rief mana-credits, mana-llm, mana-media, mana-notify
|
||
über `MANA_*_URL`-env-Vars. Greenfield-cards-api macht das genauso.
|
||
Code 1:1 übernehmen.
|
||
|
||
---
|
||
|
||
## Wellen-Plan
|
||
|
||
| Welle | Zustand | Was passiert | Blocker |
|
||
|---|---|---|---|
|
||
| **R0** | 🟡 in Arbeit | Doku-Restore: Archive aus cards-decommission-base + dieser Plan + Strategie-B-Klarstellung | — |
|
||
| **R1** | ⏸ pending | Schema-Restore: 7 Schema-Files in `cards/apps/api/src/db/schema/marketplace/`, drizzle-kit push grün gegen lokale `cards`-DB | R0 |
|
||
| **R2** | ⏸ pending | Backend Phase α + β: Author-Profile + Publish + AI-Mod-Stub | R1 |
|
||
| **R3** | ⏸ pending | Backend Phase γ + δ: Discovery + Subscribe + Smart-Merge | R2 |
|
||
| **R4** | ⏸ pending | Backend Phase ε: Pull-Requests + Card-Discussions | R3 |
|
||
| **R5** | ⏸ pending | Frontend-Routes: `/explore`, `/d/[slug]`, `/u/[slug]`, `/me/{published,subscribed,forks}` | R4 |
|
||
| **R6** | ⏸ pending | E2E-Smoke: erstes Cardecky-Deck publishen, von Till's Account subscriben, Smart-Merge testen | R5 |
|
||
|
||
**Aufwand-Schätzung gesamt: ~14 Tage Real-Arbeit.**
|
||
|
||
Bewusst aus dem ersten Restore-Wurf rausgelassen:
|
||
|
||
- **ζ.1 Paid Decks** — Schema-Tabellen kommen in R1 mit, aber Routes/UI
|
||
bleiben dormant. Re-Aktivierung als eigene Welle nach Live-Validation.
|
||
Begründung: mana-credits-Integration ist heikel (4-step-Pipeline mit
|
||
reservation-commit-grant), Author-Erlöse sind ein Verein-Compliance-
|
||
Thema (Steuern, AGB, Refund-Policy), und solange Cardecky synthetic
|
||
Decks publisht, gibt's keinen Need.
|
||
- **η.1 Moderation-UI** — Schema-Tabellen + API-Endpoints kommen mit,
|
||
Admin-Frontend (`/admin/reports`) wird ausgelassen bis erste echte
|
||
User da sind. Take-Down via SQL für die ersten Wochen.
|
||
- **θ Deep AI** (Auto-Tags, Embeddings, TTS) — bleibt explizit später-
|
||
Phase, war im Original-Plan auch nicht für den ersten Wurf vorgesehen.
|
||
|
||
---
|
||
|
||
## Cardecky-Skill-Integration
|
||
|
||
Der `/cards-deck`-Skill (siehe `~/.claude/skills/cards-deck/SKILL.md`)
|
||
produziert Decks unter dem Cardecky-Plattform-User. Beim Marketplace-
|
||
Restore wird Cardecky **automatisch zum Marketplace-Author**:
|
||
|
||
1. Bei R2 wird ein Init-Skript einen `marketplace.authors`-Row für
|
||
Cardecky-User-ID anlegen (`slug='cardecky'`, `display_name='Cardecky'`,
|
||
`pseudonym=false`, `verified_mana=true` — der Verein vergibt das
|
||
Badge an seinen eigenen KI-Author).
|
||
2. Skill-Stufe 5 (Publish) wird erweitert um einen optionalen Schritt:
|
||
nach Anlegen des privaten Decks kann der Skill ein `POST
|
||
/api/v1/marketplace/decks/:id/publish` mit `semver=1.0.0` und einem
|
||
auto-generierten Changelog hinterher schicken — dann ist das Deck
|
||
sofort im `/explore` sichtbar.
|
||
3. Default bleibt aber „nur privat anlegen", weil:
|
||
- das Validate-Stage (Stufe 4) eine menschliche Sichtung verdient,
|
||
bevor 30 Karten öffentlich werden;
|
||
- Reviewer-Stops nach Stufe 3 sind zwingend.
|
||
|
||
Der Skill braucht keine Architektur-Änderung — er addiert nur einen
|
||
optionalen 6. Schritt.
|
||
|
||
---
|
||
|
||
## Lizenz-Modell (aus dem Original übernommen)
|
||
|
||
Aus `MARKETPLACE_PLAN_2026-05-07.md` §3 + `GUIDELINES.md`:
|
||
|
||
| SPDX-ID | Erlaubt | Wann |
|
||
|---|---|---|
|
||
| `CC0-1.0` | alles, kein Attribution-Pflicht | für Public-Domain-fähige Karten |
|
||
| `CC-BY-4.0` | alles, mit Attribution | meist gewählt für Wissens-Decks |
|
||
| `CC-BY-SA-4.0` | alles, ShareAlike + Attribution | für Wikipedia-derivierte Decks |
|
||
| `Cardecky-Personal-Use-1.0` | nur persönlicher Lern-Use, kein Re-Publish | **Default für kostenlose Decks** |
|
||
| `Cardecky-Pro-Only-1.0` | nur via Kauf, kein Re-Publish, kein Fork | **Pflicht für paid Decks** (DB-CHECK enforced) |
|
||
|
||
Der DB-CHECK auf `decks.price_credits = 0 OR license = 'Cardecky-Pro-Only-1.0'`
|
||
ist im Schema beibehalten — Code-Bug kann nicht stillschweigend ein
|
||
Paid-Deck mit CC-Lizenz publishen.
|
||
|
||
---
|
||
|
||
## Cold-Start-Strategie (aus dem Original)
|
||
|
||
Kommt im ersten Restore-Wurf nicht aktiv zum Tragen, aber der Original-
|
||
Plan §9 hat drei Hebel definiert, die bei Re-Launch greifen:
|
||
|
||
1. **Verein-Seed-Decks** — 50 hochwertige Cardecky-published Decks
|
||
(Sprachen, Geschichte, Allgemeinwissen, Programmierung). Der
|
||
`/cards-deck`-Skill ist genau das Werkzeug dafür.
|
||
2. **Anki-Top-100-Import-Service** — populäre CC-BY-Anki-Decks mit
|
||
Original-Author-Attribution importieren, Original-Author bekommt
|
||
`verified_mana`-Badge bei Registrierung.
|
||
3. **Influencer-Outreach** — 10–20 Anki-Power-Authoren (AnKing &
|
||
Konsorten) gezielt ansprechen, sehr Author-freundlicher Cut.
|
||
|
||
Hebel 2 + 3 sind Wachstumsmaßnahmen, nicht Phase-1. Hebel 1 ist
|
||
sofort umsetzbar mit dem bestehenden Skill.
|
||
|
||
---
|
||
|
||
## Anti-Patterns aus dem Original-Plan §13 (gelten weiter)
|
||
|
||
- **Kein 1-5-Sterne-Rating-System.** Stars (Bookmark) ja, Bewertungen nein.
|
||
- **Kein Reddit-Style-Voting** auf Karten/PRs/Discussions. Hacker-News-Effekt.
|
||
- **Kein „Karten der Woche" allein-algorithmisch.** Editorial + Trending-
|
||
Liste, aber niemals reiner Algo-Feed.
|
||
- **Kein Anki-Bashing im Marketing.** Bridge nicht Burning.
|
||
- **Keine Pflicht-Klarnamen.** Pseudonyme bleiben gleichberechtigt.
|
||
- **Kein Marketplace-Cut über 30 %.** Standard 80/20, verified 90/10.
|
||
|
||
---
|
||
|
||
## Offene Punkte
|
||
|
||
- **PR-Merge-stale-blind aus dem Original.** Bekannte Limitierung: wenn
|
||
Author zwischen PR-Open und Merge selbst eine Karte ändert, deren
|
||
`previousContentHash` der PR matched, gewinnt stumm der PR. Im
|
||
ersten Restore-Wurf so übernehmen wie war; späterer Fix via
|
||
optimistic locking auf `baseVersionId` der PR-Row mit Reject bei
|
||
Mismatch.
|
||
- **Reconciler-Cron für Paid-Pipeline-Inkonsistenzen.** Original §13a
|
||
beschreibt das Loch: bei Commit-/Grant-Failure nach Schritt 2 bleibt
|
||
eine Purchase-Row mit `creditsTransaction = null`. Beim Restore
|
||
initial nicht aktiv (Paid-Decks dormant), aber sobald Phase ζ
|
||
reaktiviert wird, ist Reconciler ein muss-haben.
|
||
- **`cards-decommission-base`-Tag im managarten-Repo.** Falls jemand
|
||
managarten löscht oder das Tag nochmal entfernt, geht der Restore-
|
||
Pfad verloren. Empfehlung: Schema-Files + ausgewählte Code-Snippets
|
||
einmal nach `cards/docs/marketplace/archive/` kopieren (Doks sind
|
||
schon drin, Code-Files folgen optional bei R1+R2).
|
||
- **Schema-Migrations-Pfad bei späterer Drizzle-Version.** Greenfield-
|
||
Cards plant Migration auf Drizzle 0.45/zod-4 mit der Plattform mit
|
||
(`mana/docs/MIGRATION_DRIZZLE_ZOD.md`). Marketplace-Schema kommt mit
|
||
Drizzle 0.38 — sollte mit upgradeen, idealerweise atomar.
|
||
- **Karten-Hash-Konsistenz zwischen Greenfield und Marketplace.**
|
||
Greenfield-`@cards/domain` `cardContentHash` ist die SoT. Marketplace-
|
||
`deck_cards.content_hash` muss mit demselben Algorithmus berechnet
|
||
werden — sonst funktioniert Smart-Merge nicht. Beim R2-Port-Pass
|
||
testen, dass `cardContentHash({type,fields})` aus `@cards/domain`
|
||
byte-identisch ist mit dem alten `cards-server/src/lib/hash.ts`. Wenn
|
||
nicht: alten Code anpassen, nicht `@cards/domain` brechen.
|