- Introduced a new section detailing the Single Source of Truth for various components, linking to their respective CLAUDE.md files. - Enhanced clarity on documentation organization across different areas of the project.
5.3 KiB
convex/ — Backend-Schicht
Convex ist das vollständige Backend von LemonSpace: Datenbank, Realtime-Subscriptions, File Storage und Background-Job-Scheduler in einem. Kein separates API-Layer notwendig.
Dateien im Überblick
| Datei | Zweck |
|---|---|
schema.ts |
Einzige Wahrheitsquelle für alle Tabellen und Typen |
ai.ts |
KI-Bildgenerierungs-Pipeline |
credits.ts |
Credit-System: Balance, Reservation+Commit, Tier-Config |
nodes.ts |
CRUD für Canvas-Nodes |
edges.ts |
CRUD für Canvas-Edges |
canvases.ts |
CRUD für Canvases |
openrouter.ts |
OpenRouter-HTTP-Client + Modell-Config (Backend) |
auth.ts |
Better Auth Integration |
helpers.ts |
requireAuth() — von allen Queries/Mutations genutzt |
polar.ts |
Polar.sh Webhook-Handler (Subscriptions) |
pexels.ts |
Pexels Stock-Bilder API |
freepik.ts |
Freepik Asset-Browser API |
storage.ts |
Convex File Storage Helpers |
export.ts |
Canvas-Export-Logik |
http.ts |
HTTP-Endpunkte (Webhooks) |
Schema (schema.ts)
Alle Node-Typen sind in zwei Validators definiert: phase1NodeTypes (aktiv) und nodeType (alle Phasen). Phase-2/3-Typen werden im Schema vordeklariert, um spätere Schema-Migrationen zu vermeiden — die UI filtert nach Phase.
Tabellen
canvases — Canvas pro User. Index: by_owner, by_owner_updated.
nodes — Alle Nodes eines Canvas. Felder: type, positionX/Y, width, height, status (idle|analyzing|clarifying|executing|done|error), statusMessage, retryCount, data (v.any()), parentId, zIndex. Index: by_canvas, by_canvas_type, by_parent.
dataistv.any()— Typ-Safety läuft über dentype-Discriminator + Zod im Frontend. Die Node-Data-Shapes sind inschema.tsdokumentiert (imageNodeData,promptNodeData, etc.).
edges — Verbindungen zwischen Nodes. Index: by_canvas, by_source, by_target.
creditBalances — Pro User: balance, reserved, monthlyAllocation. available = balance - reserved (computed, nicht gespeichert).
creditTransactions — Jede Credit-Bewegung. Types: subscription | topup | usage | reservation | refund. Status: committed | reserved | released | failed.
subscriptions — Aktive Subscription. Tier: free | starter | pro | max | business.
dailyUsage — Täglicher Zähler pro User für Abuse-Prevention. Key: userId + date (ISO).
AI-Pipeline (ai.ts)
generateImage (action, public)
→ checkAbuseLimits (internalMutation)
→ reserve (mutation, wenn INTERNAL_CREDITS_ENABLED=true)
→ markNodeExecuting (internalMutation)
→ scheduler.runAfter(0, processImageGeneration) ← Background-Job
processImageGeneration (internalAction)
→ generateAndStoreImage (internalAction)
→ generateImageWithAutoRetry (lokal, max 2 Retries)
→ generateImageViaOpenRouter
→ ctx.storage.store(blob)
→ finalizeImageSuccess (internalMutation)
→ commitInternal (credits)
[Fehler] → releaseInternal (credits)
→ finalizeImageFailure (internalMutation)
[finally] → decrementConcurrency
Wichtig: generateImage gibt { queued: true } zurück, sobald der Background-Job geplant ist. Der Node wechselt sofort auf status: "executing". Der eigentliche API-Call läuft asynchron.
Retry-Logik: Max. 2 Retries (MAX_IMAGE_RETRIES = 2). Backoff: min(1500ms, 400ms * retryCount). Retryable: provider (5xx, 408, 429), timeout, transient. Nicht retryable: credits, policy, unbekannte 4xx.
env-Flags:
INTERNAL_CREDITS_ENABLED=true— Aktiviert Reservation+Commit. Beifalsewird nur Abuse-Prevention geprüft.OPENROUTER_API_KEY— Pflicht.
Credit-System (credits.ts)
Tier-Konfiguration (TIER_CONFIG)
| Tier | Credits/Monat | Daily Cap | Concurrency | Premium-Modelle |
|---|---|---|---|---|
| free | 50 | 10 | 1 | nein |
| starter | 400 | 50 | 2 | ja |
| pro | 3.300 | 200 | 2 | ja |
| max | 6.700 | 500 | 2 | ja |
1 Credit = €0,01 (Euro-Cent-Einheit durchgängig in DB und UI).
Reservation+Commit-Flow
reserve()— Prüftavailable >= estimatedCost, Daily Cap, Concurrency. Erhöhtreserved+dailyUsage. GibttransactionIdzurück.- Job läuft...
commitInternal()— ZiehtactualCostvonbalanceab, gibtestimatedCostausreservedfrei. Schreibttype: "usage".- Bei Fehler:
releaseInternal()— GibtestimatedCostausreservedfrei.generationCountbleibt erhöht (Versuch zählt).
env-Flag: ALLOW_TEST_CREDIT_GRANT=true — Aktiviert grantTestCredits Mutation (nur Dev/Staging).
Auth (helpers.ts)
requireAuth(ctx) // → { userId: string }
Wirft bei unauthentifiziertem Zugriff. Wird von allen Queries und Mutations genutzt, die User-Daten berühren.
Konventionen
internalMutation/internalAction— Nur von anderen Convex-Funktionen aufrufbar, nicht direkt vom Client.mutation/query/action— Öffentlich, direkt vom Frontend nutzbar.- Alle User-Daten sind über
userId(Better Auth User ID, kein Convex-eigenes User-Dokument) indiziert. - Schema-Migrationen:
npx convex deverkennt Breaking Changes automatisch. Beiv.any()-Feldern gibt es keine automatische Migration — Datensätze müssen manuell/scriptmäßig migriert werden.