managarten/services/cards-server/CLAUDE.md
Till JS a7b62ea8ae feat(cards-server): Phase α — service skeleton + 16-table schema
Lays the foundation for the Cards marketplace + community backend per
apps/cards/docs/MARKETPLACE_PLAN.md. Phase α scope: skeleton, schema,
JWT auth wiring, health endpoint. Routes follow in Phase β.

Stack: Hono + Bun + Drizzle + Postgres + jose-JWKS — mirrors the
mana-credits service template.

Schema: pgSchema('cards') inside mana_platform, 16 tables across six
groups in src/db/schema/:
  - authors.ts: authors, author_follows
  - decks.ts: decks, deck_versions, deck_cards (with cards_card_type
    enum mirroring @mana/cards-core; per-card content_hash for
    smart-merge; CHECK constraint that paid decks must use
    Cards-Pro-Only-1.0 license)
  - tags.ts: tag_definitions (hierarchical), deck_tags
  - engagement.ts: deck_stars, deck_subscriptions, deck_forks
  - discussions.ts: deck_pull_requests (with diff jsonb +
    pr_status enum), card_discussions (bound to card_content_hash
    so threads survive version bumps)
  - moderation.ts: deck_reports (with category/status enums),
    ai_moderation_log
  - credits.ts: deck_purchases (snapshot price + author/mana split),
    author_payouts

Phase λ's co_learn_sessions intentionally not yet here.

Service plumbing:
  - src/index.ts: Hono entry on :3072, /health unauth, /v1 stub
  - src/config.ts: env loader with author-payout BPS knobs
    (defaults 80/20 standard, 90/10 verified-mana) and
    community-verified thresholds
  - src/middleware/jwt-auth.ts + service-auth.ts: JWKS validation
    + X-Service-Key check (mirrors mana-credits)
  - src/lib/errors.ts: HttpError + named subclasses
  - drizzle.config.ts pointing at mana_platform with schemaFilter:cards
  - drizzle/0000_*.sql committed so other devs / prod migration path
    has a reproducible starting point

Validated: tsc --noEmit clean, drizzle-kit generate produces
233-line SQL with all 16 tables + 5 enums + indexes.

Next (Phase α.4): Dockerfile + docker-compose + cloudflare tunnel
route cards-api.mana.how → :3072.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 16:01:08 +02:00

110 lines
3.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# cards-server
Cards Marketplace + Community backend. Owns the published-deck side of
the Cards product (the standalone app at `cards.mana.how` is the
client). Phase α is the data skeleton — schema + bootstrap + JWT auth
in place; routes land progressively in Phase β onwards.
For the full design rationale, phasing, and contract decisions see
**[`apps/cards/docs/MARKETPLACE_PLAN.md`](../../apps/cards/docs/MARKETPLACE_PLAN.md)**.
## Tech Stack
| Layer | Tech |
|-------|------|
| Runtime | Bun |
| Framework | Hono |
| Database | PostgreSQL (`mana_platform.cards.*` schema) + Drizzle ORM |
| Auth | JWT via JWKS from mana-auth (EdDSA, jose) |
| Money | mana-credits — never Stripe directly |
## Port: 3072
## Quick Start
```bash
# Schema push (writes to local mana_platform DB)
bun run db:push
# Dev server with watch
bun run dev
# Type check
bun run type-check
```
## Database
Schema: **`cards`** inside the shared `mana_platform` DB. 17 tables across
six logical groups (matching the source files in `src/db/schema/`):
| File | Tables |
|------|--------|
| `authors.ts` | `cards.authors`, `cards.author_follows` |
| `decks.ts` | `cards.decks`, `cards.deck_versions`, `cards.deck_cards` |
| `tags.ts` | `cards.tag_definitions`, `cards.deck_tags` |
| `engagement.ts` | `cards.deck_stars`, `cards.deck_subscriptions`, `cards.deck_forks` |
| `discussions.ts` | `cards.deck_pull_requests`, `cards.card_discussions` |
| `moderation.ts` | `cards.deck_reports`, `cards.ai_moderation_log` |
| `credits.ts` | `cards.deck_purchases`, `cards.author_payouts` |
`co_learn_sessions` (Phase λ) is intentionally not yet in the schema.
Every table is created via `pgSchema('cards')` per the Mana convention.
## Auth model
Three middleware:
- `jwtAuth(authUrl)` — validates Bearer tokens via JWKS. Sets
`c.set('user', { userId, email, role })`. Used on every user-facing
`/v1/*` route.
- `serviceAuth(serviceKey)``X-Service-Key` check for service-to-
service calls (e.g. mana-credits-webhook → cards-server).
- (planned) `optionalAuth` — for routes that should respond
differently when the caller is signed-in but never reject anonymous.
## Phasing (per MARKETPLACE_PLAN §11)
| Phase | What lands | Where |
|-------|-----------|-------|
| **α** | Skeleton + schema + JWT + health | now |
| β | Author publish flow + AI-mod-first-pass | next |
| γ | Discovery (browse, search, tags, follow) | |
| δ | Subscribe + smart-merge | |
| ε | Pull-requests + discussions | |
| ζ | mana-credits marketplace | |
| η | Moderation + trust | |
| θ | Deep AI (auto-tags, embeddings, audio) | |
| ι | Optimisation + scale | |
## Environment Variables
```env
PORT=3072
DATABASE_URL=postgresql://mana:devpassword@localhost:5432/mana_platform
MANA_AUTH_URL=http://localhost:3001
MANA_CREDITS_URL=http://localhost:3061
MANA_LLM_URL=http://localhost:3025
MANA_MEDIA_URL=http://localhost:3015
MANA_NOTIFY_URL=http://localhost:3040
MANA_SERVICE_KEY=dev-service-key
CORS_ORIGINS=http://localhost:5173,http://localhost:5180
# Author payout splits (basis points). Defaults: 80/20 standard,
# 90/10 verified-mana.
AUTHOR_PAYOUT_STANDARD_BPS=8000
AUTHOR_PAYOUT_VERIFIED_BPS=9000
# Community-verified auto-thresholds.
COMMUNITY_VERIFY_STARS=500
COMMUNITY_VERIFY_FEATURED=3
COMMUNITY_VERIFY_SUBSCRIBERS=200
```
## Critical Rules
- **Never call Stripe directly.** All money flows through mana-credits.
- **`/v1` is the public contract** — additive-only changes within v1, breaking changes go to `/v2`.
- **Content-hash everything.** Per-card and per-version SHA-256s drive smart-merge, cache invalidation, and trust.
- **Subscribed Decks are unidirectional.** Author → Subscriber. Forks for the bidirectional case.
- **Verification is binary, not numeric.** Two flags (`verified_mana`, `verified_community`), the UI shows badges. Never invent a "trust score".