refactor(big-bang): cards → wordeck im gesamten Code-Layer
Some checks are pending
CI / validate (push) Waiting to run
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:
parent
c77100e85a
commit
372832d266
90 changed files with 213 additions and 3716 deletions
|
|
@ -340,7 +340,7 @@ sub_index 1 = back→front). Cloze hat 1 Review pro Cluster-Index.
|
|||
Beim Card-Insert werden alle initialen Reviews in **einer Transaktion**
|
||||
mit angelegt — siehe `apps/api/src/routes/cards.ts` POST-Handler.
|
||||
|
||||
`subIndexCount(type)` in `@cards/domain` ist die SoT für statische
|
||||
`subIndexCount(type)` in `@wordeck/domain` ist die SoT für statische
|
||||
Typen. Für Cloze siehe Subtilität #6 — `subIndexCountForCloze(text)` ist
|
||||
die SoT, weil die Anzahl text-abhängig ist.
|
||||
|
||||
|
|
@ -391,14 +391,14 @@ Swap auf mana-auth:
|
|||
### 6. Cloze-Karten haben N Reviews — sub_index pro Cluster
|
||||
|
||||
`subIndexCount('cloze')` wirft bewusst, weil die Anzahl text-abhängig
|
||||
ist. Caller müssen `subIndexCountForCloze(text)` aus `@cards/domain`
|
||||
ist. Caller müssen `subIndexCountForCloze(text)` aus `@wordeck/domain`
|
||||
nutzen. Cluster werden nach numerischer ID aufsteigend sortiert
|
||||
(`{{c1::…}}` = sub_index 0, `{{c2::…}}` = 1, …). Der Card-POST-Handler
|
||||
lehnt `type=cloze` ohne mindestens ein Cluster mit 422 ab — eine Cloze
|
||||
ohne `{{cN::…}}`-Markup ist sinnlos.
|
||||
|
||||
Render-Helpers (`renderClozePrompt` / `renderClozeAnswer`) leben in
|
||||
`@cards/domain/src/cloze.ts`, sind 12-fach unit-getestet und werden
|
||||
`@wordeck/domain/src/cloze.ts`, sind 12-fach unit-getestet und werden
|
||||
vom Study-View dünn konsumiert. Hint-Markup (`{{c1::answer::hint}}`)
|
||||
wird MVP-stumm gedroppt — Hint-Anzeige ist Phase-9-Polish.
|
||||
|
||||
|
|
@ -470,7 +470,7 @@ Jede neue Karte bekommt einen SHA-256-`content_hash` über
|
|||
genutzt vom Anki-Re-Import-Dedupe (`/api/v1/cards/hashes` lädt
|
||||
nur die Hash-Liste, der Importer dedupliziert clientseitig).
|
||||
|
||||
`cardContentHash()` in `@cards/domain` ist deterministisch und
|
||||
`cardContentHash()` in `@wordeck/domain` ist deterministisch und
|
||||
field-order-invariant. Cluster-Markup, Whitespace und Hint-
|
||||
Annotationen zählen mit — bewusst, weil zwei Cards mit dem Text
|
||||
`Paris ist Hauptstadt` und `{{c1::Paris}} ist Hauptstadt` sind
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"name": "@cards/api",
|
||||
"name": "@wordeck/api",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Cards-API — Hono+Bun-Backend für die Greenfield-Cards-App. Spricht mana-Plattform-Services über HTTP.",
|
||||
"description": "Cards-API \u2014 Hono+Bun-Backend f\u00fcr die Greenfield-Cards-App. Spricht mana-Plattform-Services \u00fcber HTTP.",
|
||||
"scripts": {
|
||||
"dev": "bun run --hot src/index.ts",
|
||||
"start": "bun run src/index.ts",
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
"drizzle:bootstrap-tracking": "bun run scripts/bootstrap-drizzle-tracking.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cards/domain": "workspace:*",
|
||||
"@wordeck/domain": "workspace:*",
|
||||
"drizzle-orm": "0.38",
|
||||
"hono": "^4.6.0",
|
||||
"jose": "^6.2.3",
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@
|
|||
|
||||
import { pgSchema } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const cardsSchema = pgSchema('cards');
|
||||
export const cardsSchema = pgSchema('wordeck');
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { newReview } from '@cards/domain';
|
||||
import { newReview } from '@wordeck/domain';
|
||||
|
||||
export function makeInitialReviewRows(params: {
|
||||
userId: string;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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`).
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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' };
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -4,18 +4,18 @@ import { Hono } from 'hono';
|
|||
import { dsgvoRouter } from '../src/routes/dsgvo.ts';
|
||||
import type { CardsDb } from '../src/db/connection.ts';
|
||||
|
||||
const ORIG_ENV = process.env.CARDS_DSGVO_SERVICE_KEY;
|
||||
const ORIG_ENV = process.env.WORDECK_DSGVO_SERVICE_KEY;
|
||||
const TEST_KEY = 'msk_test_dsgvo_key_42';
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.CARDS_DSGVO_SERVICE_KEY = TEST_KEY;
|
||||
process.env.WORDECK_DSGVO_SERVICE_KEY = TEST_KEY;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (ORIG_ENV === undefined) {
|
||||
delete process.env.CARDS_DSGVO_SERVICE_KEY;
|
||||
delete process.env.WORDECK_DSGVO_SERVICE_KEY;
|
||||
} else {
|
||||
process.env.CARDS_DSGVO_SERVICE_KEY = ORIG_ENV;
|
||||
process.env.WORDECK_DSGVO_SERVICE_KEY = ORIG_ENV;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ describe('dsgvoRouter — Service-Key-Gate', () => {
|
|||
|
||||
describe('dsgvoRouter — Key-not-configured Pfad', () => {
|
||||
it('Wenn ENV fehlt → 500 service_key_not_configured', async () => {
|
||||
delete process.env.CARDS_DSGVO_SERVICE_KEY;
|
||||
delete process.env.WORDECK_DSGVO_SERVICE_KEY;
|
||||
const { app } = buildApp();
|
||||
const res = await app.request('/api/v1/dsgvo/export?user_id=u-1', {
|
||||
headers: { 'X-Service-Key': 'whatever' },
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { cardContentHash } from '@cards/domain';
|
||||
import { cardContentHash } from '@wordeck/domain';
|
||||
|
||||
import { hashVersionCards } from '../src/lib/marketplace/version-hash.ts';
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ describe('hashVersionCards', () => {
|
|||
expect(inOrder).toBe(reversedInput);
|
||||
});
|
||||
|
||||
it('uses cardContentHash for per-card identity (consumes @cards/domain SoT)', async () => {
|
||||
it('uses cardContentHash for per-card identity (consumes @wordeck/domain SoT)', async () => {
|
||||
// Smoke: changing fields without changing the type must change the
|
||||
// per-card hash (cardContentHash semantics) and therefore the
|
||||
// version hash.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@
|
|||
// Aktiviert den X-User-Id-Dev-Stub für Tests, weil die Suiten ohne
|
||||
// echten mana-auth-JWKS laufen. In Produktion ist der Stub fail-secure
|
||||
// AUS (siehe apps/api/src/middleware/auth.ts).
|
||||
if (process.env.CARDS_AUTH_DEV_STUB === undefined) {
|
||||
process.env.CARDS_AUTH_DEV_STUB = 'true';
|
||||
if (process.env.WORDECK_AUTH_DEV_STUB === undefined) {
|
||||
process.env.WORDECK_AUTH_DEV_STUB = 'true';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +1,41 @@
|
|||
{
|
||||
"name": "@cards/web",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Cards-Web — SvelteKit 2 + Svelte 5 Frontend für cardecky.mana.how.",
|
||||
"scripts": {
|
||||
"dev": "vite dev --port 3082 --host",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"type-check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"test": "vitest run --passWithNoTests",
|
||||
"lint": "echo 'lint configured later (eslint flat-config)'",
|
||||
"clean": "rm -rf .svelte-kit build .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cards/domain": "workspace:*",
|
||||
"dompurify": "^3.4.2",
|
||||
"jszip": "^3.10.1",
|
||||
"marked": "^18.0.3",
|
||||
"sql.js": "^1.14.1",
|
||||
"@mana/themes": "^0.1.0",
|
||||
"@mana/shared-ui-2": "^0.1.0",
|
||||
"@mana/shared-icons": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-node": "^5.2.0",
|
||||
"@sveltejs/kit": "^2.8.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||
"@tailwindcss/vite": "^4.2.4",
|
||||
"@types/dompurify": "^3.2.0",
|
||||
"@types/jszip": "^3.4.1",
|
||||
"@types/sql.js": "^1.4.11",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"tailwindcss": "^4.2.4",
|
||||
"vite": "^5.4.0",
|
||||
"vitest": "^2.1.0"
|
||||
}
|
||||
"name": "@wordeck/web",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Cards-Web \u2014 SvelteKit 2 + Svelte 5 Frontend f\u00fcr cardecky.mana.how.",
|
||||
"scripts": {
|
||||
"dev": "vite dev --port 3082 --host",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"type-check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"test": "vitest run --passWithNoTests",
|
||||
"lint": "echo 'lint configured later (eslint flat-config)'",
|
||||
"clean": "rm -rf .svelte-kit build .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wordeck/domain": "workspace:*",
|
||||
"dompurify": "^3.4.2",
|
||||
"jszip": "^3.10.1",
|
||||
"marked": "^18.0.3",
|
||||
"sql.js": "^1.14.1",
|
||||
"@mana/themes": "^0.1.0",
|
||||
"@mana/shared-ui-2": "^0.1.0",
|
||||
"@mana/shared-icons": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-node": "^5.2.0",
|
||||
"@sveltejs/kit": "^2.8.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||
"@tailwindcss/vite": "^4.2.4",
|
||||
"@types/dompurify": "^3.2.0",
|
||||
"@types/jszip": "^3.4.1",
|
||||
"@types/sql.js": "^1.4.11",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"tailwindcss": "^4.2.4",
|
||||
"vite": "^5.4.0",
|
||||
"vitest": "^2.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|||
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||||
|
||||
const cspHeader =
|
||||
process.env.CARDS_CSP_ENFORCE === 'true'
|
||||
process.env.WORDECK_CSP_ENFORCE === 'true'
|
||||
? 'Content-Security-Policy'
|
||||
: 'Content-Security-Policy-Report-Only';
|
||||
response.headers.set(cspHeader, CSP_POLICY);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* geladen, Duplikate werden gezählt und übersprungen.
|
||||
*/
|
||||
|
||||
import { cardContentHash } from '@cards/domain';
|
||||
import { cardContentHash } from '@wordeck/domain';
|
||||
import { createDeck } from '$lib/api/decks.ts';
|
||||
import { createCard, listCardHashes } from '$lib/api/cards.ts';
|
||||
import { sanitizeAnkiHtml, type ParsedAnki } from './parse.ts';
|
||||
|
|
|
|||
|
|
@ -18,14 +18,14 @@
|
|||
* mana-monorepo/apps/cards/apps/web/src/lib/anki/parse.ts (commit
|
||||
* ~Mai 2026). Anki-Format-Logik ist standalone Parser-Code ohne
|
||||
* Architektur-Übernahme — die Kopie spart 2-3 Tage Re-Implementierung
|
||||
* bei null Strategy-Risiko. CardType-Import auf @cards/domain
|
||||
* bei null Strategy-Risiko. CardType-Import auf @wordeck/domain
|
||||
* umgestellt, Doc-Kommentar an Phase-8-Scope angepasst.
|
||||
* --------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import JSZip, { type JSZipObject } from 'jszip';
|
||||
import initSqlJs, { type Database } from 'sql.js';
|
||||
import type { CardType } from '@cards/domain';
|
||||
import type { CardType } from '@wordeck/domain';
|
||||
|
||||
export interface ParsedDeck {
|
||||
ankiId: string; // Anki's numeric deck id, stringified
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Card, CardCreate, CardUpdate } from '@cards/domain';
|
||||
import type { Card, CardCreate, CardUpdate } from '@wordeck/domain';
|
||||
import { api } from './client.ts';
|
||||
|
||||
export function listCards(deckId?: string) {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export const API_BASE = (() => {
|
|||
const fromPublic = publicEnv.PUBLIC_CARDS_API_URL;
|
||||
if (fromPublic) return fromPublic;
|
||||
if (typeof window !== 'undefined') return 'http://localhost:3081';
|
||||
return process.env.CARDS_API_URL ?? 'http://localhost:3081';
|
||||
return process.env.WORDECK_API_URL ?? 'http://localhost:3081';
|
||||
})();
|
||||
|
||||
export class ApiError extends Error {
|
||||
|
|
@ -29,7 +29,7 @@ export class ApiError extends Error {
|
|||
readonly body: unknown,
|
||||
message?: string
|
||||
) {
|
||||
super(message ?? `cards-api ${status}`);
|
||||
super(message ?? `wordeck-api ${status}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Deck, DeckCreate, DeckUpdate } from '@cards/domain';
|
||||
import type { Deck, DeckCreate, DeckUpdate } from '@wordeck/domain';
|
||||
import { api, apiForm } from './client.ts';
|
||||
|
||||
export function listDecks(opts: { forkedFromMarketplace?: boolean; archived?: boolean } = {}) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Deck } from '@cards/domain';
|
||||
import type { Deck } from '@wordeck/domain';
|
||||
import { listDecks } from './decks.ts';
|
||||
import { listCards } from './cards.ts';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Card, Rating, Review } from '@cards/domain';
|
||||
import type { Card, Rating, Review } from '@wordeck/domain';
|
||||
import { api } from './client.ts';
|
||||
|
||||
export type DueReview = Review & {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Auth-Session für cards-web.
|
||||
* Auth-Session für wordeck-web.
|
||||
*
|
||||
* Login-Flow: cards-web redirectet zu auth.mana.how (mana-auth-web),
|
||||
* Login-Flow: wordeck-web redirectet zu auth.mana.how (mana-auth-web),
|
||||
* dort setzt mana-auth den SSO-Cookie, dann Redirect zurück zu
|
||||
* /auth/callback, welcher via `tryRefresh()` + `loadUserFromToken()`
|
||||
* den JWT holt und in localStorage speichert.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import type { DeckCategoryId } from '@cards/domain';
|
||||
import type { DeckCategoryId } from '@wordeck/domain';
|
||||
import {
|
||||
Globe,
|
||||
Heartbeat,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import type { Card, Deck } from '@cards/domain';
|
||||
import type { Card, Deck } from '@wordeck/domain';
|
||||
import { t, tn } from '$lib/i18n/index.svelte.ts';
|
||||
import { stackLayers } from '$lib/utils/deck-tilt';
|
||||
import CardSurface from './CardSurface.svelte';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import type { Deck } from '@cards/domain';
|
||||
import type { Deck } from '@wordeck/domain';
|
||||
import DeckStack from './DeckStack.svelte';
|
||||
import NewDeckCard from './NewDeckCard.svelte';
|
||||
import { t } from '$lib/i18n/index.svelte.ts';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { Deck } from '@cards/domain';
|
||||
import { DECK_CATEGORY_LABELS } from '@cards/domain';
|
||||
import type { Deck } from '@wordeck/domain';
|
||||
import { DECK_CATEGORY_LABELS } from '@wordeck/domain';
|
||||
import { stackLayers } from '$lib/utils/deck-tilt';
|
||||
import { t, tn } from '$lib/i18n/index.svelte.ts';
|
||||
import { archiveDeck, unarchiveDeck } from '$lib/api/decks.ts';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import type { Rating } from '@cards/domain';
|
||||
import type { Rating } from '@wordeck/domain';
|
||||
import { fetchDistractors } from '$lib/api/decks.ts';
|
||||
|
||||
let {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { type DeckCategoryId, DECK_CATEGORY_IDS, DECK_CATEGORY_LABELS } from '@cards/domain';
|
||||
import { type DeckCategoryId, DECK_CATEGORY_IDS, DECK_CATEGORY_LABELS } from '@wordeck/domain';
|
||||
import { createDeck, generateDeck, generateDeckFromImage } from '$lib/api/decks.ts';
|
||||
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
||||
import { toasts } from '$lib/stores/toasts.svelte.ts';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { checkTypingAnswer, type TypingMatchResult } from '@cards/domain';
|
||||
import type { Rating } from '@cards/domain';
|
||||
import { checkTypingAnswer, type TypingMatchResult } from '@wordeck/domain';
|
||||
import type { Rating } from '@wordeck/domain';
|
||||
|
||||
let {
|
||||
promptHtml,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import type { DeckListEntry } from '$lib/api/marketplace.ts';
|
||||
import type { DeckCategoryId } from '@cards/domain';
|
||||
import { DECK_CATEGORY_IDS } from '@cards/domain';
|
||||
import type { DeckCategoryId } from '@wordeck/domain';
|
||||
import { DECK_CATEGORY_IDS } from '@wordeck/domain';
|
||||
import { stackLayers, deterministicRandoms } from '$lib/utils/deck-tilt';
|
||||
import { Star } from '@mana/shared-icons';
|
||||
import CardSurface from '$lib/components/CardSurface.svelte';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import type { Card, Deck } from '@cards/domain';
|
||||
import type { Card, Deck } from '@wordeck/domain';
|
||||
import {
|
||||
getMyAuthorProfile,
|
||||
getMarketplaceDeck,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Card } from '@cards/domain';
|
||||
import type { Card } from '@wordeck/domain';
|
||||
|
||||
export function exportToCsv(cards: Card[]): string {
|
||||
const rows: string[] = ['type,front,back'];
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { cardContentHash } from '@cards/domain';
|
||||
import { cardContentHash } from '@wordeck/domain';
|
||||
import { createDeck } from '$lib/api/decks.ts';
|
||||
import { createCard, listCardHashes } from '$lib/api/cards.ts';
|
||||
import type { ParsedCsvCard } from './parse.ts';
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
renderClozePrompt,
|
||||
type Card,
|
||||
type CardType,
|
||||
} from '@cards/domain';
|
||||
} from '@wordeck/domain';
|
||||
import { apiErrorMessage } from '$lib/api/error.ts';
|
||||
import { getCard, updateCard, deleteCard } from '$lib/api/cards.ts';
|
||||
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
extractClusterIds,
|
||||
renderClozePrompt,
|
||||
type CardType,
|
||||
} from '@cards/domain';
|
||||
} from '@wordeck/domain';
|
||||
import { createCard } from '$lib/api/cards.ts';
|
||||
import { apiErrorMessage } from '$lib/api/error.ts';
|
||||
import { listDecks, getDeck } from '$lib/api/decks.ts';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import type { Deck } from '@cards/domain';
|
||||
import type { Deck } from '@wordeck/domain';
|
||||
import { listDecks } from '$lib/api/decks.ts';
|
||||
import { listCards } from '$lib/api/cards.ts';
|
||||
import { listDueReviews } from '$lib/api/reviews.ts';
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/state';
|
||||
import { goto } from '$app/navigation';
|
||||
import type { Card, Deck } from '@cards/domain';
|
||||
import { type DeckCategoryId, DECK_CATEGORY_IDS, DECK_CATEGORY_LABELS } from '@cards/domain';
|
||||
import type { Card, Deck } from '@wordeck/domain';
|
||||
import { type DeckCategoryId, DECK_CATEGORY_IDS, DECK_CATEGORY_LABELS } from '@wordeck/domain';
|
||||
import { getDeck, updateDeck } from '$lib/api/decks.ts';
|
||||
import { listCards, deleteCard } from '$lib/api/cards.ts';
|
||||
import { listDueReviews } from '$lib/api/reviews.ts';
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/state';
|
||||
import { goto } from '$app/navigation';
|
||||
import type { Deck } from '@cards/domain';
|
||||
import { type DeckCategoryId, DECK_CATEGORY_IDS, DECK_CATEGORY_LABELS } from '@cards/domain';
|
||||
import type { Deck } from '@wordeck/domain';
|
||||
import { type DeckCategoryId, DECK_CATEGORY_IDS, DECK_CATEGORY_LABELS } from '@wordeck/domain';
|
||||
import {
|
||||
getDeck,
|
||||
updateDeck,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/state';
|
||||
import { goto } from '$app/navigation';
|
||||
import type { Card, Deck } from '@cards/domain';
|
||||
import type { Card, Deck } from '@wordeck/domain';
|
||||
import { getDeck } from '$lib/api/decks.ts';
|
||||
import { listCards } from '$lib/api/cards.ts';
|
||||
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
import { type DeckCategoryId, DECK_CATEGORY_IDS, DECK_CATEGORY_LABELS } from '@cards/domain';
|
||||
import { type DeckCategoryId, DECK_CATEGORY_IDS, DECK_CATEGORY_LABELS } from '@wordeck/domain';
|
||||
import { createDeck, generateDeck, generateDeckFromImage } from '$lib/api/decks.ts';
|
||||
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
||||
import { toasts } from '$lib/stores/toasts.svelte.ts';
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { listDecks } from '$lib/api/decks.ts';
|
||||
import { pullUpdate } from '$lib/api/marketplace.ts';
|
||||
import type { Deck } from '@cards/domain';
|
||||
import type { Deck } from '@wordeck/domain';
|
||||
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
||||
import EmptyState from '$lib/components/marketplace/EmptyState.svelte';
|
||||
import { toasts } from '$lib/stores/toasts.svelte.ts';
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
renderClozePrompt,
|
||||
renderClozeAnswer,
|
||||
type Rating,
|
||||
} from '@cards/domain';
|
||||
} from '@wordeck/domain';
|
||||
import { apiErrorMessage } from '$lib/api/error.ts';
|
||||
import { getDeck } from '$lib/api/decks.ts';
|
||||
import { listDueReviews, gradeReview, undoReview, type DueReview } from '$lib/api/reviews.ts';
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { defineConfig } from 'vite';
|
|||
export default defineConfig({
|
||||
plugins: [tailwindcss(), sveltekit()],
|
||||
server: {
|
||||
port: Number(process.env.CARDS_WEB_PORT ?? 3082),
|
||||
port: Number(process.env.WORDECK_WEB_PORT ?? 3082),
|
||||
host: true,
|
||||
},
|
||||
// @mana/* aus Verdaccio publishen Raw-.ts (main: src/index.ts).
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
## TL;DR
|
||||
|
||||
- **Domain-Library separieren** (`@cards/domain`, framework-agnostic)
|
||||
- **Domain-Library separieren** (`@wordeck/domain`, framework-agnostic)
|
||||
- **Card-Type als discriminated union** mit `fields: Record<string,string>` + `subIndex`-Granularität
|
||||
- **FSRS via `ts-fsrs` v5.3.2** in dünnem Adapter, Reviews bleiben PLAINTEXT
|
||||
- **Markdown-Editor**, kein Rich-Text/WYSIWYG
|
||||
|
|
@ -24,7 +24,7 @@ mana-monorepo hat `packages/cards-core/` mit Pure-TS:
|
|||
- Keine Dexie-, Sync-, oder UI-Abhängigkeiten
|
||||
- Konsumiert von App-UI-Modul UND von `services/cards-server`
|
||||
|
||||
→ **Bei uns:** `packages/cards-domain/` analog. Bewusst kein `@cards/core` (Name-Konflikt), sondern `@cards/domain`. Steht.
|
||||
→ **Bei uns:** `packages/cards-domain/` analog. Bewusst kein `@cards/core` (Name-Konflikt), sondern `@wordeck/domain`. Steht.
|
||||
|
||||
## 2. Card-Type-Modell
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ mana-monorepos `(app)/cards/`-Routen:
|
|||
|
||||
mana-monorepo nutzt `@mana/shared-privacy` mit `VisibilityLevel = 'private' | 'space' | 'public'`. Bei Änderungen wird `visibilityChangedAt` + `visibilityChangedBy` getrackt.
|
||||
|
||||
→ **Bei uns:** Gleiches Enum als TypeScript-Type in `@cards/domain`. Audit-Felder optional ab Phase 9 (DSGVO-Polish).
|
||||
→ **Bei uns:** Gleiches Enum als TypeScript-Type in `@wordeck/domain`. Audit-Felder optional ab Phase 9 (DSGVO-Polish).
|
||||
|
||||
## 13. Tests mit fake-indexeddb
|
||||
|
||||
|
|
@ -185,7 +185,7 @@ mana-monorepos Marketplace hat content-hash auf jeder Karte + jedem Deck-Version
|
|||
|
||||
## 5 Kern-Entscheidungen für unser Greenfield
|
||||
|
||||
1. **`@cards/domain` als Pure-TS-Workspace-Package** — keine Framework-Bindings
|
||||
1. **`@wordeck/domain` als Pure-TS-Workspace-Package** — keine Framework-Bindings
|
||||
2. **Card-Type discriminated union mit `fields`-Slot + `subIndex`-Granularität** — auch wenn MVP nur basic-Karten hat
|
||||
3. **`ts-fsrs` v5.3.2 hinter dünnem Adapter** — Reviews plaintext, indiziert auf `due`
|
||||
4. **Cloze als Token-Parser, wenn implementiert** — Anki-kompatible Syntax
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ export class PullRequestService {
|
|||
}
|
||||
|
||||
private deckUrl(slug: string): string {
|
||||
const base = process.env.CARDS_WEB_URL || 'https://cardecky.mana.how';
|
||||
const base = process.env.WORDECK_WEB_URL || 'https://cardecky.mana.how';
|
||||
return `${base}/d/${slug}`;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -160,13 +160,13 @@ mit drei Anpassungen:
|
|||
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
|
||||
### 5. Hash-Implementierung: bestehende `@wordeck/domain` benutzen
|
||||
|
||||
Alter `cards-server/src/lib/hash.ts`: eigenständige SHA-256-Implementierung.
|
||||
Greenfield: `cardContentHash` in `@cards/domain` (Web-Crypto, deterministisch).
|
||||
Greenfield: `cardContentHash` in `@wordeck/domain` (Web-Crypto, deterministisch).
|
||||
|
||||
Beim Restore: `lib/hash.ts` **nicht** mit-übernehmen, sondern
|
||||
Marketplace-Code auf `cardContentHash` aus `@cards/domain` umbiegen.
|
||||
Marketplace-Code auf `cardContentHash` aus `@wordeck/domain` umbiegen.
|
||||
Eine Hash-Definition für die ganze App.
|
||||
|
||||
### 6. mana-Service-Calls: identische Pattern
|
||||
|
|
@ -304,9 +304,9 @@ sofort umsetzbar mit dem bestehenden Skill.
|
|||
(`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-
|
||||
Greenfield-`@wordeck/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`
|
||||
testen, dass `cardContentHash({type,fields})` aus `@wordeck/domain`
|
||||
byte-identisch ist mit dem alten `cards-server/src/lib/hash.ts`. Wenn
|
||||
nicht: alten Code anpassen, nicht `@cards/domain` brechen.
|
||||
nicht: alten Code anpassen, nicht `@wordeck/domain` brechen.
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
# Cards-Production-Env-Skeleton. Auf dem Mac Mini liegt die echte
|
||||
# Wordeck-Production-Env-Skeleton. Auf dem Mac Mini liegt die echte
|
||||
# `.env.production` mit den richtigen Secrets — diese Datei ist nur
|
||||
# das Template (committet, ohne Secrets).
|
||||
#
|
||||
# Generiere die Secrets z.B. mit `openssl rand -hex 32`.
|
||||
|
||||
CARDS_DB_PASSWORD=change-me-strong-random
|
||||
CARDS_S3_SECRET_KEY=change-me-32-bytes-base64
|
||||
CARDS_DSGVO_SERVICE_KEY=change-me-msk-prefix
|
||||
CARDS_API_VERSION=1.0.0
|
||||
WORDECK_DB_PASSWORD=change-me-strong-random
|
||||
WORDECK_DSGVO_SERVICE_KEY=change-me-msk-prefix
|
||||
WORDECK_API_VERSION=1.0.0
|
||||
|
||||
# Fail-secure: in Produktion MUSS dieser Wert 'false' bleiben (oder die
|
||||
# Variable ganz weggelassen werden — der Compose-Default ist 'false').
|
||||
# Nur für gezielte Diagnose temporär auf 'true' setzen und sofort
|
||||
# wieder zurückstellen. Wenn 'true' aktiv ist, akzeptiert die API
|
||||
# `X-User-Id`-Header als vollwertige Auth (founder-Tier).
|
||||
CARDS_AUTH_DEV_STUB=false
|
||||
WORDECK_AUTH_DEV_STUB=false
|
||||
|
||||
# Verdaccio-Token für @mana/* — nutze denselben claudebot-Token, den
|
||||
# auch die Plattform verwendet (siehe ~/.cloudflared/.npmrc oder
|
||||
|
|
|
|||
|
|
@ -1,23 +1,19 @@
|
|||
# Production-Stack für Cards auf dem Mac Mini.
|
||||
# Production-Stack für Wordeck auf dem Mac Mini.
|
||||
#
|
||||
# Lebt unter ~/projects/cards/ auf mana-server (Forgejo-Klon von
|
||||
# git.mana.how/till/cards). Build-Contexte zeigen relativ in den
|
||||
# Repo, kein externes Image-Registry — Cards ist Greenfield-eigenständig
|
||||
# Lebt unter ~/projects/wordeck/ auf mana-server (Forgejo-Klon von
|
||||
# git.mana.how/till/wordeck). Build-Contexte zeigen relativ in den
|
||||
# Repo, kein externes Image-Registry — Wordeck ist Greenfield-eigenständig
|
||||
# (Strategie B), kein Plattform-Coupling.
|
||||
#
|
||||
# Ports auf dem Mac Mini:
|
||||
# cards-postgres: 5436 (Plattform 5432, Dev 5435 sind belegt)
|
||||
# cards-minio S3: 9210 (cadvisor hat 9110 belegt)
|
||||
# cards-minio UI: 9211
|
||||
# cards-api: 3191 (alt war 3072 → cards-api.mana.how)
|
||||
# cards-web: 5181 (alt war 5180 → cards.mana.how)
|
||||
# Ports auf dem Mac Mini (unverändert seit Cards-Greenfield):
|
||||
# wordeck-postgres: 5436 (Plattform 5432, Dev 5435 sind belegt)
|
||||
# wordeck-api: 3191 (cardecky-api.mana.how alt → api.wordeck.com neu)
|
||||
# wordeck-web: 5181 (cardecky.mana.how alt → wordeck.com neu)
|
||||
#
|
||||
# Cutover (2026-05-08): cardecky.mana.how + cardecky-api.mana.how
|
||||
# zeigen via Cloudflare-Tunnel auf diese Container. Alte Hostnames
|
||||
# cards.mana.how / cards-api.mana.how → nginx :4400 → 301 zu
|
||||
# cardecky.* (User-Bookmark-Erhalt).
|
||||
# Cutover (2026-05-17): cards → wordeck Big-Bang über DB+Volume+Container+
|
||||
# Verzeichnis (analog Manaspur→Viadocu-Rebrand vom selben Tag).
|
||||
#
|
||||
# Start (von ~/projects/cards/ auf mana-server):
|
||||
# Start (von ~/projects/wordeck/ auf mana-server):
|
||||
# docker compose -f infrastructure/docker-compose.production.yml \
|
||||
# --env-file infrastructure/.env.production up -d --build
|
||||
#
|
||||
|
|
@ -25,27 +21,27 @@
|
|||
# docker compose -f infrastructure/docker-compose.production.yml down
|
||||
|
||||
services:
|
||||
cards-postgres:
|
||||
wordeck-postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: cards-postgres
|
||||
container_name: wordeck-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: cards
|
||||
POSTGRES_PASSWORD: ${CARDS_DB_PASSWORD:?missing CARDS_DB_PASSWORD}
|
||||
POSTGRES_DB: cards
|
||||
POSTGRES_USER: wordeck
|
||||
POSTGRES_PASSWORD: ${WORDECK_DB_PASSWORD:?missing WORDECK_DB_PASSWORD}
|
||||
POSTGRES_DB: wordeck
|
||||
ports:
|
||||
- '127.0.0.1:5436:5432'
|
||||
volumes:
|
||||
- /Volumes/ManaData/cards/postgres:/var/lib/postgresql/data
|
||||
- /Volumes/ManaData/wordeck/postgres:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'pg_isready -U cards -d cards']
|
||||
test: ['CMD-SHELL', 'pg_isready -U wordeck -d wordeck']
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 20
|
||||
|
||||
cards-api:
|
||||
image: cards-api:local
|
||||
container_name: cards-api
|
||||
wordeck-api:
|
||||
image: wordeck-api:local
|
||||
container_name: wordeck-api
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: apps/api/Dockerfile
|
||||
|
|
@ -53,28 +49,28 @@ services:
|
|||
NPM_AUTH_TOKEN: ${NPM_AUTH_TOKEN:?missing NPM_AUTH_TOKEN}
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
cards-postgres:
|
||||
wordeck-postgres:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
DATABASE_URL: 'postgresql://cards:${CARDS_DB_PASSWORD}@cards-postgres:5432/cards'
|
||||
CARDS_API_PORT: 3081
|
||||
CARDS_API_VERSION: ${CARDS_API_VERSION:-1.0.0}
|
||||
CARDS_PUBLIC_URL: https://wordeck.com
|
||||
CARDS_DSGVO_SERVICE_KEY: ${CARDS_DSGVO_SERVICE_KEY:?missing CARDS_DSGVO_SERVICE_KEY}
|
||||
DATABASE_URL: 'postgresql://wordeck:${WORDECK_DB_PASSWORD}@wordeck-postgres:5432/wordeck'
|
||||
WORDECK_API_PORT: 3081
|
||||
WORDECK_API_VERSION: ${WORDECK_API_VERSION:-1.0.0}
|
||||
WORDECK_PUBLIC_URL: https://wordeck.com
|
||||
WORDECK_DSGVO_SERVICE_KEY: ${WORDECK_DSGVO_SERVICE_KEY:?missing WORDECK_DSGVO_SERVICE_KEY}
|
||||
MANA_AUTH_URL: https://auth.mana.how
|
||||
MANA_CREDITS_URL: https://credits.mana.how
|
||||
CARDS_MANA_SERVICE_KEY: ${CARDS_MANA_SERVICE_KEY:-}
|
||||
WORDECK_MANA_SERVICE_KEY: ${WORDECK_MANA_SERVICE_KEY:-}
|
||||
# Fail-secure: opt-in. Auf der Prod-Box gar nicht setzen
|
||||
# ⇒ Bypass AUS. Nur für gezielte lokale Diagnose temporär
|
||||
# auf 'true' setzen (und sofort wieder rausnehmen).
|
||||
CARDS_AUTH_DEV_STUB: ${CARDS_AUTH_DEV_STUB:-false}
|
||||
WORDECK_AUTH_DEV_STUB: ${WORDECK_AUTH_DEV_STUB:-false}
|
||||
NODE_ENV: production
|
||||
ports:
|
||||
- '127.0.0.1:3191:3081'
|
||||
|
||||
cards-web:
|
||||
image: cards-web:local
|
||||
container_name: cards-web
|
||||
wordeck-web:
|
||||
image: wordeck-web:local
|
||||
container_name: wordeck-web
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: apps/web/Dockerfile
|
||||
|
|
@ -82,19 +78,19 @@ services:
|
|||
NPM_AUTH_TOKEN: ${NPM_AUTH_TOKEN:?missing NPM_AUTH_TOKEN}
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- cards-api
|
||||
- wordeck-api
|
||||
environment:
|
||||
# SvelteKit `$env/dynamic/public` liest zur Runtime — daher
|
||||
# hier statt als Build-Arg. Wert landet im SSR-Init-Snapshot
|
||||
# und in client-fetches.
|
||||
PUBLIC_CARDS_API_URL: https://api.wordeck.com
|
||||
PUBLIC_WORDECK_API_URL: https://api.wordeck.com
|
||||
PUBLIC_MANA_AUTH_URL: https://auth.mana.how
|
||||
PUBLIC_AUTH_WEB_URL: https://auth.mana.how
|
||||
# mana e.V. Apple-Developer-Team-ID. Wird ausgeliefert in
|
||||
# /.well-known/apple-app-site-association für die cards-native
|
||||
# Universal-Links (applinks:cardecky.mana.how).
|
||||
# /.well-known/apple-app-site-association für die Wordeck-Native
|
||||
# Universal-Links (applinks:wordeck.com).
|
||||
PUBLIC_APPLE_TEAM_ID: QP3GLU8PH3
|
||||
CARDS_API_URL: https://api.wordeck.com
|
||||
WORDECK_API_URL: https://api.wordeck.com
|
||||
NODE_ENV: production
|
||||
ports:
|
||||
- '127.0.0.1:5181:3000'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
# Lokales Cards-Dev-Setup. Postgres-Container auf 5435 + MinIO auf
|
||||
# 9100/9101 (Plattform nutzt 9000/9001 — wir bleiben isoliert).
|
||||
# Lokales Wordeck-Dev-Setup. Postgres-Container auf 5435.
|
||||
#
|
||||
# Start: pnpm docker:up (vom Repo-Root)
|
||||
# Logs: pnpm docker:logs
|
||||
|
|
@ -8,39 +7,20 @@
|
|||
# Daten persistieren in `infrastructure/.volumes/`, git-ignored.
|
||||
|
||||
services:
|
||||
cards-postgres:
|
||||
wordeck-postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: cards-postgres
|
||||
container_name: wordeck-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: cards
|
||||
POSTGRES_PASSWORD: cards
|
||||
POSTGRES_DB: cards
|
||||
POSTGRES_USER: wordeck
|
||||
POSTGRES_PASSWORD: wordeck
|
||||
POSTGRES_DB: wordeck
|
||||
ports:
|
||||
- '5435:5432'
|
||||
volumes:
|
||||
- ./.volumes/cards-postgres:/var/lib/postgresql/data
|
||||
- ./.volumes/wordeck-postgres:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'pg_isready -U cards -d cards']
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
|
||||
cards-minio:
|
||||
image: minio/minio:latest
|
||||
container_name: cards-minio
|
||||
restart: unless-stopped
|
||||
command: server /data --console-address ':9001'
|
||||
environment:
|
||||
MINIO_ROOT_USER: cardsadmin
|
||||
MINIO_ROOT_PASSWORD: cardsadmin
|
||||
ports:
|
||||
- '9100:9000' # S3-API
|
||||
- '9101:9001' # Web-Console
|
||||
volumes:
|
||||
- ./.volumes/cards-minio:/data
|
||||
healthcheck:
|
||||
test: ['CMD', 'mc', 'ready', 'local']
|
||||
test: ['CMD-SHELL', 'pg_isready -U wordeck -d wordeck']
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"name": "cards",
|
||||
"name": "wordeck",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Cards — eigenständige föderierte Spaced-Repetition-App des mana e.V. Greenfield-Build (Strategie B), redet über mana-share/-mcp/-search/-events mit den anderen Verein-Apps.",
|
||||
"description": "Cards \u2014 eigenst\u00e4ndige f\u00f6derierte Spaced-Repetition-App des mana e.V. Greenfield-Build (Strategie B), redet \u00fcber mana-share/-mcp/-search/-events mit den anderen Verein-Apps.",
|
||||
"packageManager": "pnpm@9.15.9",
|
||||
"engines": {
|
||||
"node": ">=20",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"name": "@cards/domain",
|
||||
"name": "@wordeck/domain",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Cards-Domain — Pure-TS-Modell: Card-Types, FSRS-Adapter, Cloze-Parser, Anki-Import-Helpers, zod-Schemas. Keine DB-, Framework- oder Hono/SvelteKit-Abhängigkeiten.",
|
||||
"description": "Cards-Domain \u2014 Pure-TS-Modell: Card-Types, FSRS-Adapter, Cloze-Parser, Anki-Import-Helpers, zod-Schemas. Keine DB-, Framework- oder Hono/SvelteKit-Abh\u00e4ngigkeiten.",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
|
|
@ -149,7 +149,7 @@ export function subIndexCount(type: string): number {
|
|||
return 1;
|
||||
case 'cloze':
|
||||
throw new Error(
|
||||
'subIndexCount("cloze") not supported — use subIndexCountForCloze(text) from @cards/domain'
|
||||
'subIndexCount("cloze") not supported — use subIndexCountForCloze(text) from @wordeck/domain'
|
||||
);
|
||||
default:
|
||||
return 1;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// @cards/domain — public API
|
||||
// @wordeck/domain — public API
|
||||
//
|
||||
// Pure-TS, keine DB/Framework-Abhängigkeiten. Wird vom apps/api
|
||||
// (Drizzle-Schemas) und apps/web (UI) gleichermaßen konsumiert.
|
||||
3496
pnpm-lock.yaml
generated
3496
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue