refactor(big-bang): cards → wordeck im gesamten Code-Layer
Some checks are pending
CI / validate (push) Waiting to run

Phase 2 des cards→wordeck Big-Bang-Rebrand:

- 4 package.json: @cards/* → @wordeck/*
- packages/cards-domain/ → packages/wordeck-domain/
- 41+12 Files: from '@cards/domain' → '@wordeck/domain'
- pgSchema('cards') → pgSchema('wordeck') (Drizzle-Schema)
- 17 Files: process.env.CARDS_* → process.env.WORDECK_*
- docker-compose Service-Names: cards-* → wordeck-*
- docker-compose Volume: /Volumes/ManaData/cards → wordeck
- env-vars in compose: CARDS_DB_PASSWORD/_API_VERSION/_DSGVO_SERVICE_KEY etc. → WORDECK_*
- Log-Prefixes + Error-Strings + manifest-id 'cards' → 'wordeck'
- CORS-Origin cardecky.mana.how → wordeck.com
- .env.production.example umbenannt + S3-Key entfernt (kein MinIO mehr)

Type-Check 0 Errors in api+domain+web, 51/51 Domain-Tests grün.

DB-Rename + Container/Volume-Rename auf mana-server folgen in nächstem
Commit nach Verzeichnis-Rename.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-05-17 22:39:42 +02:00
parent c77100e85a
commit 372832d266
90 changed files with 213 additions and 3716 deletions

View file

@ -4,4 +4,4 @@
import { pgSchema } from 'drizzle-orm/pg-core';
export const cardsSchema = pgSchema('cards');
export const cardsSchema = pgSchema('wordeck');

View file

@ -6,7 +6,7 @@ import { cardsSchema } from './_schema.ts';
/**
* Decks Sammlungen von Karten. Eine Karte gehört zu genau einem Deck.
* `fsrs_settings` ist ein JSONB-Slot für per-Deck-Overrides der globalen
* FSRS-Defaults (siehe @cards/domain `FsrsSettings`).
* FSRS-Defaults (siehe @wordeck/domain `FsrsSettings`).
*/
export const decks = cardsSchema.table(
'decks',

View file

@ -29,7 +29,7 @@ import { marketplaceSchema } from './_schema.ts';
import { authors } from './authors.ts';
/**
* Spiegelt `CardType` aus `@cards/domain`. Enum lebt im
* Spiegelt `CardType` aus `@wordeck/domain`. Enum lebt im
* `marketplace`-pgSchema (nicht im default `public`), damit
* drizzle-kit-Push mit `schemaFilter: ['cards', 'marketplace']`
* keine spurious CREATE-TYPE-Versuche macht.
@ -125,7 +125,7 @@ export const publicDeckCards = marketplaceSchema.table(
versionId: uuid('version_id')
.notNull()
.references(() => publicDeckVersions.id, { onDelete: 'cascade' }),
// Spiegelt @cards/domain CardType.
// Spiegelt @wordeck/domain CardType.
type: cardTypeEnum('type').notNull(),
// Free-form key/value bag.
// basic / basic-reverse / type-in: { front, back }
@ -135,7 +135,7 @@ export const publicDeckCards = marketplaceSchema.table(
// SHA-256 über canonical(type, fields). Subscriber nutzen den
// Hash für per-Karten-Smart-Merge — unveränderte Karten behalten
// ihren FSRS-State über Versions-Pulls hinweg. **Identisch zur
// Berechnung in `@cards/domain` `cardContentHash`** — das ist der
// Berechnung in `@wordeck/domain` `cardContentHash`** — das ist der
// SoT, dieser Hash ist nur die persistierte Form.
contentHash: text('content_hash').notNull(),
},

View file

@ -43,7 +43,7 @@ app.use(
if (/^https?:\/\/localhost(:\d+)?$/.test(origin)) return origin;
if (/^https?:\/\/127\.0\.0\.1(:\d+)?$/.test(origin)) return origin;
}
if (origin === 'https://cardecky.mana.how') return origin;
if (origin === 'https://wordeck.com') return origin;
return null;
},
allowHeaders: ['Content-Type', 'X-User-Id', 'Authorization', 'X-Service-Key'],
@ -83,15 +83,15 @@ app.route('/api/v1/marketplace/decks', marketplaceDecksRouter());
app.get('/', (c) =>
c.json({
app: 'cards',
version: process.env.CARDS_API_VERSION ?? '0.0.0',
app: 'wordeck',
version: process.env.WORDECK_API_VERSION ?? '0.0.0',
see: '/.well-known/mana-app.json',
})
);
const port = Number(process.env.CARDS_API_PORT ?? 3081);
const port = Number(process.env.WORDECK_API_PORT ?? 3081);
console.log(`[cards-api] listening on http://localhost:${port}`);
console.log(`[wordeck-api] listening on http://localhost:${port}`);
export default {
port,

View file

@ -1,6 +1,6 @@
/**
* Per-Version-Content-Hash. Pro Karte wird `cardContentHash` aus
* `@cards/domain` benutzt (gemeinsame Source-of-Truth zwischen privatem
* `@wordeck/domain` benutzt (gemeinsame Source-of-Truth zwischen privatem
* Lern-Stack und Marketplace) pro Version hashen wir die geordnete
* Liste von Karten-Hashes plus deren Ordinal-Position.
*
@ -17,7 +17,7 @@
* privaten `cards.cards.content_hash` matchen können soll.
*/
import { cardContentHash } from '@cards/domain';
import { cardContentHash } from '@wordeck/domain';
export interface OrderedCardForHash {
type: string;

View file

@ -1,4 +1,4 @@
import { newReview } from '@cards/domain';
import { newReview } from '@wordeck/domain';
export function makeInitialReviewRows(params: {
userId: string;

View file

@ -3,7 +3,7 @@ import { and, eq, sql } from 'drizzle-orm';
import type { CardsDb } from '../db/connection.ts';
import { cards, decks } from '../db/schema/index.ts';
const APP_BASE_URL = process.env.CARDS_PUBLIC_URL ?? 'https://cardecky.mana.how';
const APP_BASE_URL = process.env.WORDECK_PUBLIC_URL ?? 'https://cardecky.mana.how';
export type SearchHit = {
id: string;

View file

@ -38,7 +38,7 @@ export function tierAtLeast(have: Tier, need: Tier): boolean {
const MANA_AUTH_URL = process.env.MANA_AUTH_URL ?? 'https://auth.mana.how';
// Fail-secure: opt-in, nicht opt-out. Vergessene env-var ⇒ Bypass AUS.
// Tests setzen die Variable via vitest.config.ts → tests/setup.ts.
const ALLOW_DEV_STUB = process.env.CARDS_AUTH_DEV_STUB === 'true';
const ALLOW_DEV_STUB = process.env.WORDECK_AUTH_DEV_STUB === 'true';
let jwksCache: ReturnType<typeof createRemoteJWKSet> | null = null;
function getJwks() {
@ -118,7 +118,7 @@ export function getUserId(c: Context<{ Variables: AuthVars }>): string {
* Tier-Gate für Premium-Features. Nutze als Hono-Middleware:
* r.post('/premium-thing', requireTier('beta'), async (c) => )
* Liefert 403 wenn der eingeloggte User nicht mindestens den
* verlangten Tier hat. Aktuell auf cards-api nicht aktiv genutzt
* verlangten Tier hat. Aktuell auf wordeck-api nicht aktiv genutzt
* (Cards-MVP ist tier-frei für authentifizierte User), aber das
* Plumbing ist da für künftige Premium-Pfade (z.B. AI-Bulk-Convert).
*/

View file

@ -5,7 +5,7 @@ import type { MiddlewareHandler } from 'hono';
* service-zu-service-Calls.
*
* Heute (Phase 5): vergleicht `X-Service-Key`-Header per
* constant-time-Compare gegen `process.env.CARDS_DSGVO_SERVICE_KEY`.
* constant-time-Compare gegen `process.env.WORDECK_DSGVO_SERVICE_KEY`.
*
* Phase F-1: ersetzt durch Verifikation gegen mana-auth's
* `apps.app_service_keys` Tabelle (caller-App = `mana-admin`).

View file

@ -7,7 +7,7 @@ import {
cardContentHash,
subIndexCount,
subIndexCountForCloze,
} from '@cards/domain';
} from '@wordeck/domain';
import { makeInitialReviewRows } from '../lib/reviews.ts';
import { toCardDto } from '../lib/dto.ts';

View file

@ -2,7 +2,7 @@ import { eq } from 'drizzle-orm';
import { Hono } from 'hono';
import { z } from 'zod';
import { cardContentHash, subIndexCount } from '@cards/domain';
import { cardContentHash, subIndexCount } from '@wordeck/domain';
import { makeInitialReviewRows } from '../lib/reviews.ts';

View file

@ -2,7 +2,7 @@ import { and, eq, isNotNull, isNull, ne } from 'drizzle-orm';
import { sql } from 'drizzle-orm';
import { Hono } from 'hono';
import { DeckCreateSchema, DeckUpdateSchema } from '@cards/domain';
import { DeckCreateSchema, DeckUpdateSchema } from '@wordeck/domain';
import { getDb, type CardsDb } from '../db/connection.ts';
import { cards, decks, publicDecks } from '../db/schema/index.ts';

View file

@ -42,7 +42,7 @@ export async function buildUserExport(db: CardsDb, userId: string) {
user_id: userId,
exported_at: new Date().toISOString(),
app: 'wordeck',
app_version: process.env.CARDS_API_VERSION ?? '0.0.0',
app_version: process.env.WORDECK_API_VERSION ?? '0.0.0',
data: {
decks: decksRows.map((d) => ({
...d,

View file

@ -25,7 +25,7 @@ healthRoute.get('/healthz/details', async (c) => {
{
status: allOk ? 'ok' : 'degraded',
app: 'wordeck',
version: process.env.CARDS_API_VERSION ?? '0.0.0',
version: process.env.WORDECK_API_VERSION ?? '0.0.0',
uptime_s: Math.floor(process.uptime()),
checks: {
db: dbProbe,
@ -38,8 +38,8 @@ healthRoute.get('/healthz/details', async (c) => {
healthRoute.get('/version', (c) =>
c.json({
app: 'wordeck',
version: process.env.CARDS_API_VERSION ?? '0.0.0',
build: process.env.CARDS_BUILD_SHA ?? 'dev',
version: process.env.WORDECK_API_VERSION ?? '0.0.0',
build: process.env.WORDECK_BUILD_SHA ?? 'dev',
})
);

View file

@ -2,7 +2,7 @@ import { and, eq } from 'drizzle-orm';
import { Hono } from 'hono';
import { z } from 'zod';
import { cardContentHash } from '@cards/domain';
import { cardContentHash } from '@wordeck/domain';
import { getDb, type CardsDb } from '../../db/connection.ts';
import {
@ -34,7 +34,7 @@ import { hashVersionCards } from '../../lib/marketplace/version-hash.ts';
*
* Geschichte: ported aus
* `cards-decommission-base:services/cards-server/src/{routes,services}/decks.ts`,
* mit Greenfield-Anpassungen (Hashing über `@cards/domain`,
* mit Greenfield-Anpassungen (Hashing über `@wordeck/domain`,
* AI-Mod-Stub statt mana-llm-Call, Auth-Vars-Shape).
*/

View file

@ -7,7 +7,7 @@ import {
newReview,
subIndexCount,
subIndexCountForCloze,
} from '@cards/domain';
} from '@wordeck/domain';
import { getDb, type CardsDb } from '../../db/connection.ts';
import { cards, decks, reviews } from '../../db/schema/index.ts';
@ -42,7 +42,7 @@ import { computeDiff } from '../../lib/marketplace/diff.ts';
* Architektur-Begründung:
* - **content_hash-basierter Dedupe** ist die Smart-Merge-Magic:
* `cards.cards.content_hash` matcht `marketplace.deck_cards.content_hash`,
* identisch berechnet via `@cards/domain.cardContentHash`.
* identisch berechnet via `@wordeck/domain.cardContentHash`.
* Unveränderte Karten = identisches Hash schon da Insert
* übersprungen FSRS bleibt.
* - **Removed-Cards bleiben lokal**: anders als das alte Dexie-Sync
@ -305,7 +305,7 @@ export function forkRouter(deps: MarketplaceForkDeps = {}): Hono<{ Variables: Au
// Marketplace-Karte). Unveränderte Karten existieren schon
// privat — wir können den Insert sicher überspringen, weil
// (deck_id, content_hash) im privaten cards.cards einzigartig
// ist (gleiche Hash-Funktion via @cards/domain).
// ist (gleiche Hash-Funktion via @wordeck/domain).
// Removed-Cards bleiben lokal (server-authoritative User-Choice).
const now = new Date();

View file

@ -2,7 +2,7 @@ import { and, asc, desc, eq } from 'drizzle-orm';
import { Hono } from 'hono';
import { z } from 'zod';
import { cardContentHash } from '@cards/domain';
import { cardContentHash } from '@wordeck/domain';
import { getDb, type CardsDb } from '../../db/connection.ts';
import {

View file

@ -5,7 +5,7 @@ import {
GradeReviewInputSchema,
gradeReview,
type Review as DomainReview,
} from '@cards/domain';
} from '@wordeck/domain';
import { getDb, type CardsDb } from '../db/connection.ts';
import { cards, decks, reviews } from '../db/schema/index.ts';

View file

@ -3,13 +3,13 @@ import { Hono } from 'hono';
import {
SEARCH_ENVELOPE_VERSION,
type SearchResultEnvelope,
} from '@cards/domain';
} from '@wordeck/domain';
import { getDb, type CardsDb } from '../db/connection.ts';
import { authMiddleware, type AuthVars } from '../middleware/auth.ts';
import { searchUserCards } from '../lib/search.ts';
const APP_VERSION = process.env.CARDS_API_VERSION ?? '0.0.0';
const APP_VERSION = process.env.WORDECK_API_VERSION ?? '0.0.0';
export type SearchDeps = { db?: CardsDb };
@ -43,7 +43,7 @@ export function searchRouter(deps: SearchDeps = {}): Hono<{ Variables: AuthVars
const envelope: SearchResultEnvelope = {
envelope_version: SEARCH_ENVELOPE_VERSION,
query: q,
app: 'cards',
app: 'wordeck',
app_version: APP_VERSION,
results: hits.map((h) => ({
id: h.id,

View file

@ -1,6 +1,6 @@
import { Hono } from 'hono';
import { parseEnvelope, validatePayloadForType } from '@cards/domain';
import { parseEnvelope, validatePayloadForType } from '@wordeck/domain';
import manifest from '../../../../app-manifest.json' with { type: 'json' };

View file

@ -7,7 +7,7 @@ import {
cardContentHash,
subIndexCount,
subIndexCountForCloze,
} from '@cards/domain';
} from '@wordeck/domain';
import { makeInitialReviewRows } from '../lib/reviews.ts';
@ -17,8 +17,8 @@ import { authMiddleware, type AuthVars } from '../middleware/auth.ts';
import { ulid } from '../lib/ulid.ts';
import { searchUserCards } from '../lib/search.ts';
const APP_BASE_URL = process.env.CARDS_PUBLIC_URL ?? 'https://wordeck.com';
const APP_VERSION = process.env.CARDS_API_VERSION ?? '0.0.0';
const APP_BASE_URL = process.env.WORDECK_PUBLIC_URL ?? 'https://wordeck.com';
const APP_VERSION = process.env.WORDECK_API_VERSION ?? '0.0.0';
export type ToolsDeps = { db?: CardsDb };
@ -132,7 +132,7 @@ export function toolsRouter(deps: ToolsDeps = {}): Hono<{ Variables: AuthVars }>
})),
total: hits.length,
took_ms: tookMs,
app: 'cards',
app: 'wordeck',
app_version: APP_VERSION,
base_url: APP_BASE_URL,
});

View file

@ -17,7 +17,7 @@
const CREDITS_URL = process.env.MANA_CREDITS_URL ?? 'https://credits.mana.how';
function authHeader(): Record<string, string> {
const key = process.env.CARDS_MANA_SERVICE_KEY;
const key = process.env.WORDECK_MANA_SERVICE_KEY;
if (!key) return {};
return { 'X-Service-Key': key };
}

View file

@ -13,7 +13,7 @@
*/
const LLM_URL = process.env.MANA_LLM_URL ?? 'https://llm.mana.how';
const LLM_API_KEY = process.env.CARDS_LLM_API_KEY ?? '';
const LLM_API_KEY = process.env.WORDECK_LLM_API_KEY ?? '';
export interface ChatMessage {
role: 'system' | 'user' | 'assistant';

View file

@ -1,5 +1,5 @@
import { newReview, subIndexCount } from '@cards/domain';
import type { QuotePayload, TextPayload, LinkPayload } from '@cards/domain';
import { newReview, subIndexCount } from '@wordeck/domain';
import type { QuotePayload, TextPayload, LinkPayload } from '@wordeck/domain';
import type { CardsDb } from '../db/connection.ts';
import { cards, reviews } from '../db/schema/index.ts';
@ -15,7 +15,7 @@ export type HandlerResult = {
resulting_id: string;
};
const APP_BASE_URL = process.env.CARDS_PUBLIC_URL ?? 'https://wordeck.com';
const APP_BASE_URL = process.env.WORDECK_PUBLIC_URL ?? 'https://wordeck.com';
/**
* Legt eine basic-Karte mit (front,back) im Inbox-Deck an, inkl.