chore: seed-test-decks browser-console snippet
Some checks are pending
CI / validate (push) Waiting to run
Some checks are pending
CI / validate (push) Waiting to run
Lokales Test-Daten-Tool: 7 Decks gemischt (verschiedene Farben + Karten-Counts inkl. Empty-Stack für Empty-State-Test) anlegbar via Browser-Console-Paste. Auth liest TOKEN/STUB-User-ID aus localStorage, hits cards-api auf localhost:3081 (lokal) oder cardecky-api.mana.how (live). Wiederholtes Ausführen erstellt zusätzliche Decks — keine Unique-Constraint auf Deck-Name. Sauberer Reset über die UI oder docker exec ... TRUNCATE. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
870e2aea85
commit
9626200616
1 changed files with 246 additions and 0 deletions
246
scripts/seed-test-decks.js
Normal file
246
scripts/seed-test-decks.js
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
/**
|
||||
* 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',
|
||||
);
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue