feat: Profil-Kategorien + Begrüßung in Zielsprache
languages.greeting (de/en/sv geseedet), neue pair_categories-Tabelle (abgeleitet aus statement- und objektverknüpften Wörtern via word_categories) inkl. Backfill für bereits veröffentlichte Pairs. derivePairCategories() wird beim Publish (pairs + pipeline) aufgerufen. /auth/me liefert language_target_greeting, /auth/stats liefert categories[] mit Punkten je Kategorie fürs Profil. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -331,6 +331,41 @@ async function migrate() {
|
||||
)
|
||||
`);
|
||||
|
||||
// M2M: pairs <-> categories — abgeleitet aus den verknüpften Wörtern (Statements + Objekte).
|
||||
// Wird beim Publish materialisiert (src/lib/pairCategories.js). Basis für die Kategorie-Punkte im Profil.
|
||||
await query(`
|
||||
CREATE TABLE IF NOT EXISTS pair_categories (
|
||||
pair_id UUID NOT NULL REFERENCES pairs(id) ON DELETE CASCADE,
|
||||
category_id UUID NOT NULL REFERENCES categories(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (pair_id, category_id)
|
||||
)
|
||||
`);
|
||||
|
||||
// Backfill: Kategorien für bereits veröffentlichte Pairs ableiten. Idempotent (ON CONFLICT DO NOTHING),
|
||||
// nach dem Erstlauf praktisch leer, da neue Pairs ihre Kategorien beim Publish selbst materialisieren.
|
||||
await query(`
|
||||
INSERT INTO pair_categories (pair_id, category_id)
|
||||
SELECT DISTINCT pid, category_id FROM (
|
||||
SELECT p.id AS pid, wc.category_id
|
||||
FROM pairs p
|
||||
JOIN (
|
||||
SELECT statement_id, word_id FROM statement_positive_words
|
||||
UNION
|
||||
SELECT statement_id, word_id FROM statement_negative_words
|
||||
) sw ON sw.statement_id IN (p.positive_statement_id, p.negative_statement_id)
|
||||
JOIN word_categories wc ON wc.word_id = sw.word_id
|
||||
WHERE p.status = 'published'
|
||||
UNION
|
||||
SELECT op.pair_id AS pid, wc.category_id
|
||||
FROM object_pairs op
|
||||
JOIN pairs p2 ON p2.id = op.pair_id AND p2.status = 'published'
|
||||
JOIN object_words ow ON ow.object_id = op.object_id
|
||||
JOIN word_categories wc ON wc.word_id = ow.word_id
|
||||
) src
|
||||
WHERE category_id IS NOT NULL
|
||||
ON CONFLICT (pair_id, category_id) DO NOTHING
|
||||
`).catch(() => {});
|
||||
|
||||
// pairs.answer_type → single TEXT (was TEXT[], now back to single value + new 'question' type)
|
||||
await query(`ALTER TABLE pairs DROP CONSTRAINT IF EXISTS pairs_answer_type_check`).catch(() => {});
|
||||
await query(`
|
||||
@@ -444,6 +479,9 @@ async function migrate() {
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at()
|
||||
`);
|
||||
|
||||
// Begrüßung pro Sprache (in der Sprache selbst, z. B. sv = "Hej") — für die persönliche Profil-Anrede
|
||||
await query(`ALTER TABLE languages ADD COLUMN IF NOT EXISTS greeting TEXT`).catch(() => {});
|
||||
|
||||
// user_names
|
||||
await query(`
|
||||
CREATE TABLE IF NOT EXISTS user_names (
|
||||
@@ -489,12 +527,17 @@ async function migrate() {
|
||||
// Full unique constraint (not partial) so ON CONFLICT works cleanly
|
||||
await query(`CREATE UNIQUE INDEX IF NOT EXISTS languages_short_en_idx ON languages (short_en)`).catch(() => {});
|
||||
await query(`
|
||||
INSERT INTO languages (short_en, titel_de, titel_en, titel_sv, status, published_at)
|
||||
INSERT INTO languages (short_en, titel_de, titel_en, titel_sv, greeting, status, published_at)
|
||||
VALUES
|
||||
('en', 'Englisch', 'English', 'Engelska', 'published', NOW()),
|
||||
('sv', 'Schwedisch', 'Swedish', 'Svenska', 'published', NOW())
|
||||
ON CONFLICT (short_en) DO UPDATE SET status = EXCLUDED.status, published_at = COALESCE(languages.published_at, EXCLUDED.published_at)
|
||||
('en', 'Englisch', 'English', 'Engelska', 'Hi', 'published', NOW()),
|
||||
('sv', 'Schwedisch', 'Swedish', 'Svenska', 'Hej', 'published', NOW())
|
||||
ON CONFLICT (short_en) DO UPDATE SET
|
||||
status = EXCLUDED.status,
|
||||
published_at = COALESCE(languages.published_at, EXCLUDED.published_at),
|
||||
greeting = COALESCE(languages.greeting, EXCLUDED.greeting)
|
||||
`).catch(() => {});
|
||||
// Deutsch wird separat angelegt → Begrüßung nachtragen
|
||||
await query(`UPDATE languages SET greeting = 'Hallo' WHERE short_en = 'de' AND greeting IS NULL`).catch(() => {});
|
||||
|
||||
// Seed bbox for watermelon test object (only if bbox_x is still NULL)
|
||||
await query(`
|
||||
|
||||
Reference in New Issue
Block a user