/** * Test-Decks-Seed via Browser-Console. * * Einfachste Nutzung — keine Token-Konfiguration, keine ENV-Vars: * * 1. Cards-App im Browser öffnen (lokal http://localhost:3082 oder * live https://cardecky.mana.how) und sicherstellen dass du * eingeloggt bist (Decks-Seite ist erreichbar). * 2. F12 → Console-Tab. * 3. Den GESAMTEN Inhalt dieser Datei kopieren und einfügen. * Browser fragt evtl. einmal "Wirklich Code einfügen?" → "Allow * pasting" tippen. * 4. Enter. * * Das Skript liest die Auth-Daten aus dem Browser (echtes JWT oder * Dev-Stub-User-ID), zeigt einen Fortschritts-Log und legt 7 * Test-Decks mit gemischten Größen + Farben an. Lauf dauert ~3-5s. * * Wiederholtes Ausführen erstellt zusätzliche Decks (gleiche Namen * dürfen mehrfach vorkommen). Wenn du sauber starten willst: vorher * vorhandene Test-Decks löschen oder ein frisches DB-Reset machen. */ (async () => { const TOKEN = localStorage.getItem('cards.auth.accessToken'); const STUB = localStorage.getItem('cards.dev.userId'); const headers = { 'Content-Type': 'application/json' }; if (TOKEN) headers['Authorization'] = `Bearer ${TOKEN}`; else if (STUB) headers['X-User-Id'] = STUB; else { console.error('Kein Auth-Token gefunden. Bitte erst auf /einloggen.'); return; } const origin = location.origin; const API = origin.includes('localhost:3082') ? 'http://localhost:3081' : origin.includes('cardecky.mana.how') ? 'https://cardecky-api.mana.how' : origin.replace(/cardecky/, 'cardecky-api'); console.log(`%cSeed-Skript läuft gegen ${API}`, 'color:#16a34a; font-weight:600'); async function api(path, body, method = 'POST') { const r = await fetch(API + path, { method, headers, body: body ? JSON.stringify(body) : undefined, }); if (!r.ok) { const text = await r.text(); throw new Error(`${method} ${path} → ${r.status}: ${text}`); } return r.json(); } // Verschiedene Größen + Farben → Stack-Hint, Fan-Spread, Empty-State alle testbar. const DECKS = [ { name: 'Spanisch — Alltagsvokabular', description: 'Wörter und kurze Wendungen für die ersten Reisetage.', color: '#16a34a', cards: makeBasic([ ['hola', 'hallo'], ['gracias', 'danke'], ['por favor', 'bitte'], ['¿dónde está…?', 'wo ist …?'], ['la cuenta, por favor', 'die Rechnung, bitte'], ['no hablo bien español', 'ich spreche nicht gut Spanisch'], ['¿habla inglés?', 'sprechen Sie Englisch?'], ['una mesa para dos', 'einen Tisch für zwei'], ['agua sin gas', 'stilles Wasser'], ['una cerveza', 'ein Bier'], ['¿cuánto cuesta?', 'wie viel kostet das?'], ['demasiado caro', 'zu teuer'], ['está bien', 'ist gut'], ['perdón', 'entschuldigung'], ['lo siento', 'tut mir leid'], ['ayuda', 'Hilfe'], ['estoy perdido', 'ich habe mich verlaufen'], ['la estación', 'der Bahnhof'], ['el aeropuerto', 'der Flughafen'], ['el hotel', 'das Hotel'], ['la habitación', 'das Zimmer'], ['la llave', 'der Schlüssel'], ['mañana', 'morgen'], ['ayer', 'gestern'], ]), }, { name: 'Deutsch ↔ Niederländisch', description: 'Falsche Freunde und überraschend nahe Verwandte.', color: '#FF6600', cards: makeBasicReverse([ ['Bahnhof', 'station'], ['Käse', 'kaas'], ['Fenster', 'raam'], ['Tisch', 'tafel'], ['Brot', 'brood'], ['Buch', 'boek'], ['Auto', 'auto'], ['Haus', 'huis'], ['Frau', 'vrouw'], ['Mann', 'man'], ['Kind', 'kind'], ['Hund', 'hond'], ]), }, { name: 'JavaScript ES2026', description: 'Neue Features — Pattern-Matching, Decorators, Pipe.', color: '#6366F1', cards: [ ...makeBasic([ ['Optional Chaining', '?.'], ['Nullish Coalescing', '??'], ['Logical Assignment', '||=, &&=, ??='], ['Top-Level Await', 'await außerhalb von async-Funktion in ES-Modules'], ['Pipeline Operator (Stage 2)', 'value |> fn()'], ]), ...makeCloze([ 'Der `using`-Keyword aus Stage 3 ruft automatisch `Symbol.{{c1::dispose}}` auf, wenn der Scope verlassen wird.', 'Mit Pattern-Matching schreibst du `{{c1::match}} (value) { when … -> … }` statt verschachtelter `if`-Ketten.', '`Array.prototype.{{c1::with}}(index, value)` gibt eine neue Kopie zurück — immutable Update.', ]), ], }, { name: 'Geschichte 19. Jh.', description: 'Schlüsselereignisse 1815–1900.', color: '#DC2626', cards: makeBasic([ ['1815', 'Wiener Kongress — Neuordnung Europas nach Napoleon'], ['1848', 'Märzrevolution + Frankfurter Nationalversammlung'], ['1861–1865', 'Amerikanischer Bürgerkrieg'], ['1871', 'Deutsche Reichsgründung in Versailles'], ['1884–1885', 'Berliner Kongokonferenz — Aufteilung Afrikas'], ['1889', 'Pariser Weltausstellung + Eiffelturm'], ['1895', 'Erste Filmvorführung der Brüder Lumière'], ]), }, { name: 'Gitarre — Akkord-Grundlagen', description: 'Vier Akkorde, mit denen 80 % der Pop-Songs gehen.', color: '#07D6FF', cards: makeBasic([ ['G-Dur', 'Tonika in vielen Singer-Songwriter-Stücken'], ['D-Dur', 'helle Dominante zu G'], ['Em', 'parallele Moll zu G — melancholische Färbung'], ['C-Dur', 'Subdominante in G — auflösend'], ]), }, { name: 'Pflanzenkunde — heimische Bäume', description: 'Erkennungsmerkmale, Standort, Holz-Eigenschaften. Großer Stapel zum Testen.', color: '#F8D62B', cards: makeBasic( [ ['Eiche', 'Stieleiche oder Traubeneiche, gelappte Blätter, Eicheln'], ['Buche', 'Rotbuche, ovale Blätter mit gewelltem Rand, Bucheckern'], ['Linde', 'herzförmige Blätter, duftende Blüten im Juni'], ['Birke', 'weiße Rinde, kleine dreieckige Blätter'], ['Fichte', 'kurze Nadeln einzeln am Zweig, hängende Zapfen'], ['Kiefer', 'lange Nadeln paarweise, rotbraune Rinde'], ['Tanne', 'flache Nadeln mit zwei weißen Streifen unten, stehende Zapfen'], ['Lärche', 'einziger heimischer Nadelbaum, der im Herbst Nadeln verliert'], ['Esche', 'unpaarig gefiederte Blätter, schwarze Knospen'], ['Ahorn', 'Spitzahorn, Bergahorn, Feldahorn — gelappte Blätter, Flügelfrüchte'], ['Erle', 'eiförmige Blätter, Standort an Bachläufen'], ['Hainbuche', 'doppelt gesägte Blätter, glatte graue Rinde'], ['Pappel', 'dreieckige Blätter, sehr schnellwüchsig'], ['Weide', 'lanzettliche Blätter, Standort an Wasserläufen'], ['Ulme', 'asymmetrischer Blattgrund — Erkennungsmerkmal'], ['Robinie', 'gefiederte Blätter, weiße Schmetterlingsblüten, sehr hartes Holz'], ['Walnuss', 'gefiederte Blätter mit aromatischem Geruch'], ['Edelkastanie', 'lange gesägte Blätter, stachelige Fruchthülle'], ['Rosskastanie', 'handförmig gefiederte Blätter, glatte Kastanien'], ['Vogelbeere', 'gefiederte Blätter, leuchtend rote Beeren-Doldentrauben'], ['Holunder', 'gefiederte Blätter, weiße Doldenblüten, schwarze Beeren'], ['Hasel', 'rundliche Blätter mit Spitze, Haselnüsse'], ['Schlehe', 'dornig, weiße Blüten vor dem Laubaustrieb'], ['Weißdorn', 'tief gelappte Blätter, weiße Blüten, rote Apfelfrüchte'], ['Eberesche', 'andere Bezeichnung für Vogelbeere'], ['Kirschbaum', 'glänzende Rinde mit horizontalen Streifen'], ['Apfelbaum', 'eiförmige Blätter, kugelige Frucht'], ['Birnbaum', 'eiförmige Blätter, längliche Frucht'], ['Eibe', 'flache Nadeln, rote fleischige Samenmäntel — sonst alles giftig'], ['Wacholder', 'spitze Nadeln, schwarzblaue Beerenzapfen für Gin'], ].slice(0, 30), ), }, { name: 'Architekten der Moderne', description: 'Leerer Stapel zum Testen des Empty-States.', color: '#6B7280', cards: [], }, ]; function makeBasic(pairs) { return pairs.map(([front, back]) => ({ type: 'basic', fields: { front, back }, })); } function makeBasicReverse(pairs) { return pairs.map(([front, back]) => ({ type: 'basic-reverse', fields: { front, back }, })); } function makeCloze(texts) { return texts.map((text) => ({ type: 'cloze', fields: { text }, })); } let totalDecks = 0; let totalCards = 0; for (const spec of DECKS) { const deck = await api('/api/v1/decks', { name: spec.name, description: spec.description, color: spec.color, }); totalDecks++; console.log(`%c✓ Deck: ${deck.name}`, 'color:#16a34a', `(${deck.id.slice(0, 8)})`); for (const card of spec.cards) { await api('/api/v1/cards', { deck_id: deck.id, ...card }); totalCards++; } if (spec.cards.length > 0) { console.log(` + ${spec.cards.length} Karten`); } } console.log( `%c✓ Fertig — ${totalDecks} Decks, ${totalCards} Karten angelegt. Jetzt /decks öffnen.`, 'color:#16a34a; font-weight:600; font-size:1.1em', ); })();