From d0e9b9b7640cf645fb3307297b31e6092b2938c0 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 23 Apr 2026 08:10:44 +0200 Subject: [PATCH] fix: Make migration 007 idempotent (IF NOT EXISTS + ON CONFLICT) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CRITICAL FIX: Migration 007 failed beim Startup wegen existierendem Index Problem: - Migration 007 wurde teilweise angewendet (Spalten + Indexes erstellt) - Bei erneutem Versuch: 'idx_skills_category already exists' → Abbruch - Tabellen (training_styles, training_characters, etc.) nicht erstellt - Katalog-Endpoints → 500 Error (UndefinedTable) Lösung: - Alle CREATE TABLE → CREATE TABLE IF NOT EXISTS - Alle CREATE INDEX → CREATE INDEX IF NOT EXISTS - ALTER TABLE → DO $$ BEGIN IF NOT EXISTS ... END $$ - Alle INSERT → ON CONFLICT (name) DO NOTHING Migration kann jetzt beliebig oft ausgeführt werden ohne Fehler. --- backend/migrations/007_exercise_catalogs.sql | 84 ++++++++++++-------- 1 file changed, 53 insertions(+), 31 deletions(-) diff --git a/backend/migrations/007_exercise_catalogs.sql b/backend/migrations/007_exercise_catalogs.sql index c99ea2b..608575c 100644 --- a/backend/migrations/007_exercise_catalogs.sql +++ b/backend/migrations/007_exercise_catalogs.sql @@ -3,7 +3,7 @@ -- Beschreibung: Dynamische Kataloge statt Hard-Coding -- Fokusbereiche (statt hard-coded) -CREATE TABLE focus_areas ( +CREATE TABLE IF NOT EXISTS focus_areas ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL UNIQUE, abbreviation VARCHAR(20), @@ -16,10 +16,10 @@ CREATE TABLE focus_areas ( updated_at TIMESTAMP DEFAULT NOW() ); -CREATE INDEX idx_focus_areas_status ON focus_areas(status); +CREATE INDEX IF NOT EXISTS idx_focus_areas_status ON focus_areas(status); -- Trainingsstile (NEU: Shotokan, Goju-Ryu, etc.) -CREATE TABLE training_styles ( +CREATE TABLE IF NOT EXISTS training_styles ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL UNIQUE, abbreviation VARCHAR(20), @@ -31,11 +31,11 @@ CREATE TABLE training_styles ( updated_at TIMESTAMP DEFAULT NOW() ); -CREATE INDEX idx_training_styles_status ON training_styles(status); -CREATE INDEX idx_training_styles_parent ON training_styles(parent_style_id); +CREATE INDEX IF NOT EXISTS idx_training_styles_status ON training_styles(status); +CREATE INDEX IF NOT EXISTS idx_training_styles_parent ON training_styles(parent_style_id); -- Trainingscharaktere (statt hard-coded) -CREATE TABLE training_characters ( +CREATE TABLE IF NOT EXISTS training_characters ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL UNIQUE, description TEXT, @@ -45,10 +45,10 @@ CREATE TABLE training_characters ( updated_at TIMESTAMP DEFAULT NOW() ); -CREATE INDEX idx_training_characters_status ON training_characters(status); +CREATE INDEX IF NOT EXISTS idx_training_characters_status ON training_characters(status); -- Fähigkeitsbereiche (Kategorien für Skills) -CREATE TABLE skill_categories ( +CREATE TABLE IF NOT EXISTS skill_categories ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL UNIQUE, description TEXT, @@ -59,35 +59,53 @@ CREATE TABLE skill_categories ( updated_at TIMESTAMP DEFAULT NOW() ); -CREATE INDEX idx_skill_categories_status ON skill_categories(status); -CREATE INDEX idx_skill_categories_parent ON skill_categories(parent_category_id); +CREATE INDEX IF NOT EXISTS idx_skill_categories_status ON skill_categories(status); +CREATE INDEX IF NOT EXISTS idx_skill_categories_parent ON skill_categories(parent_category_id); -- Skills erweitern (Verknüpfung mit Kategorien) -ALTER TABLE skills -ADD COLUMN category_id INT REFERENCES skill_categories(id), -ADD COLUMN parent_skill_id INT REFERENCES skills(id), -- Hierarchie: Tsuki → Mae-Zuki -ADD COLUMN level INT CHECK (level BETWEEN 1 AND 10), -- Schwierigkeitsgrad -ADD COLUMN sort_order INT; +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='skills' AND column_name='category_id') THEN + ALTER TABLE skills ADD COLUMN category_id INT REFERENCES skill_categories(id); + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='skills' AND column_name='parent_skill_id') THEN + ALTER TABLE skills ADD COLUMN parent_skill_id INT REFERENCES skills(id); + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='skills' AND column_name='level') THEN + ALTER TABLE skills ADD COLUMN level INT CHECK (level BETWEEN 1 AND 10); + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='skills' AND column_name='sort_order') THEN + ALTER TABLE skills ADD COLUMN sort_order INT; + END IF; +END $$; -CREATE INDEX idx_skills_category ON skills(category_id); -CREATE INDEX idx_skills_parent ON skills(parent_skill_id); +CREATE INDEX IF NOT EXISTS idx_skills_category ON skills(category_id); +CREATE INDEX IF NOT EXISTS idx_skills_parent ON skills(parent_skill_id); -- Exercises erweitern -ALTER TABLE exercises -ADD COLUMN training_style_id INT REFERENCES training_styles(id), -ADD COLUMN training_character_id INT REFERENCES training_characters(id), -ADD COLUMN focus_area_id INT REFERENCES focus_areas(id); +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='exercises' AND column_name='training_style_id') THEN + ALTER TABLE exercises ADD COLUMN training_style_id INT REFERENCES training_styles(id); + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='exercises' AND column_name='training_character_id') THEN + ALTER TABLE exercises ADD COLUMN training_character_id INT REFERENCES training_characters(id); + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='exercises' AND column_name='focus_area_id') THEN + ALTER TABLE exercises ADD COLUMN focus_area_id INT REFERENCES focus_areas(id); + END IF; +END $$; -- Alte text-basierte Spalten können später deprecated werden -- ALTER TABLE exercises DROP COLUMN focus_area; -- später, nach Migration -- ALTER TABLE exercises DROP COLUMN training_character; -- später, nach Migration -CREATE INDEX idx_exercises_style ON exercises(training_style_id); -CREATE INDEX idx_exercises_character ON exercises(training_character_id); -CREATE INDEX idx_exercises_focus_area ON exercises(focus_area_id); +CREATE INDEX IF NOT EXISTS idx_exercises_style ON exercises(training_style_id); +CREATE INDEX IF NOT EXISTS idx_exercises_character ON exercises(training_character_id); +CREATE INDEX IF NOT EXISTS idx_exercises_focus_area ON exercises(focus_area_id); -- Trainer-Fokusbereich-Zuordnung (Welcher Trainer arbeitet in welchen Fokusbereichen?) -CREATE TABLE trainer_focus_areas ( +CREATE TABLE IF NOT EXISTS trainer_focus_areas ( id SERIAL PRIMARY KEY, profile_id INT REFERENCES profiles(id) ON DELETE CASCADE, focus_area_id INT REFERENCES focus_areas(id) ON DELETE CASCADE, @@ -96,21 +114,23 @@ CREATE TABLE trainer_focus_areas ( UNIQUE(profile_id, focus_area_id) ); -CREATE INDEX idx_trainer_focus_areas_profile ON trainer_focus_areas(profile_id); -CREATE INDEX idx_trainer_focus_areas_focus ON trainer_focus_areas(focus_area_id); +CREATE INDEX IF NOT EXISTS idx_trainer_focus_areas_profile ON trainer_focus_areas(profile_id); +CREATE INDEX IF NOT EXISTS idx_trainer_focus_areas_focus ON trainer_focus_areas(focus_area_id); -- Basis-Daten einfügen INSERT INTO focus_areas (name, abbreviation, description, color, icon, sort_order) VALUES ('Karate', 'KAR', 'Traditionelles Karate', '#1D9E75', '🥋', 1), ('Selbstverteidigung', 'SV', 'Praktische Selbstverteidigung', '#D85A30', '🛡️', 2), -('Gewaltschutz', 'GS', 'Gewaltprävention und Deeskalation', '#EF9F27', '🤝', 3); +('Gewaltschutz', 'GS', 'Gewaltprävention und Deeskalation', '#EF9F27', '🤝', 3) +ON CONFLICT (name) DO NOTHING; INSERT INTO training_styles (name, abbreviation, description, sort_order) VALUES ('Shotokan', 'SHO', 'Shotokan-Karate', 1), ('Goju-Ryu', 'GJR', 'Goju-Ryu-Karate', 2), ('Wado-Ryu', 'WAD', 'Wado-Ryu-Karate', 3), ('Shito-Ryu', 'SHI', 'Shito-Ryu-Karate', 4), -('Kyokushin', 'KYO', 'Kyokushin-Karate', 5); +('Kyokushin', 'KYO', 'Kyokushin-Karate', 5) +ON CONFLICT (name) DO NOTHING; INSERT INTO training_characters (name, description, sort_order) VALUES ('Grundlage', 'Einführung und Basisvermittlung', 1), @@ -118,7 +138,8 @@ INSERT INTO training_characters (name, description, sort_order) VALUES ('Vertiefung', 'Vertiefung und Spezialisierung', 3), ('Festigung', 'Wiederholung und Festigung', 4), ('Diagnose', 'Leistungsdiagnose und Test', 5), -('Wettkampf', 'Wettkampfvorbereitung', 6); +('Wettkampf', 'Wettkampfvorbereitung', 6) +ON CONFLICT (name) DO NOTHING; INSERT INTO skill_categories (name, description, sort_order) VALUES ('Kihon', 'Grundschultechniken', 1), @@ -126,4 +147,5 @@ INSERT INTO skill_categories (name, description, sort_order) VALUES ('Kata', 'Formen', 3), ('Selbstverteidigung', 'SV-Techniken', 4), ('Fitness', 'Kondition und Athletik', 5), -('Mental', 'Mentale Fähigkeiten', 6); +('Mental', 'Mentale Fähigkeiten', 6) +ON CONFLICT (name) DO NOTHING;