fix: Make migration 007 idempotent (IF NOT EXISTS + ON CONFLICT)
Some checks failed
Deploy Development / deploy (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 5s
Test Suite / playwright-tests (push) Failing after 1m54s

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.
This commit is contained in:
Lars 2026-04-23 08:10:44 +02:00
parent 0c7caea847
commit d0e9b9b764

View File

@ -3,7 +3,7 @@
-- Beschreibung: Dynamische Kataloge statt Hard-Coding -- Beschreibung: Dynamische Kataloge statt Hard-Coding
-- Fokusbereiche (statt hard-coded) -- Fokusbereiche (statt hard-coded)
CREATE TABLE focus_areas ( CREATE TABLE IF NOT EXISTS focus_areas (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE, name VARCHAR(100) NOT NULL UNIQUE,
abbreviation VARCHAR(20), abbreviation VARCHAR(20),
@ -16,10 +16,10 @@ CREATE TABLE focus_areas (
updated_at TIMESTAMP DEFAULT NOW() 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.) -- Trainingsstile (NEU: Shotokan, Goju-Ryu, etc.)
CREATE TABLE training_styles ( CREATE TABLE IF NOT EXISTS training_styles (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE, name VARCHAR(100) NOT NULL UNIQUE,
abbreviation VARCHAR(20), abbreviation VARCHAR(20),
@ -31,11 +31,11 @@ CREATE TABLE training_styles (
updated_at TIMESTAMP DEFAULT NOW() updated_at TIMESTAMP DEFAULT NOW()
); );
CREATE INDEX idx_training_styles_status ON training_styles(status); CREATE INDEX IF NOT EXISTS 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_parent ON training_styles(parent_style_id);
-- Trainingscharaktere (statt hard-coded) -- Trainingscharaktere (statt hard-coded)
CREATE TABLE training_characters ( CREATE TABLE IF NOT EXISTS training_characters (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE, name VARCHAR(100) NOT NULL UNIQUE,
description TEXT, description TEXT,
@ -45,10 +45,10 @@ CREATE TABLE training_characters (
updated_at TIMESTAMP DEFAULT NOW() 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) -- Fähigkeitsbereiche (Kategorien für Skills)
CREATE TABLE skill_categories ( CREATE TABLE IF NOT EXISTS skill_categories (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE, name VARCHAR(100) NOT NULL UNIQUE,
description TEXT, description TEXT,
@ -59,35 +59,53 @@ CREATE TABLE skill_categories (
updated_at TIMESTAMP DEFAULT NOW() updated_at TIMESTAMP DEFAULT NOW()
); );
CREATE INDEX idx_skill_categories_status ON skill_categories(status); CREATE INDEX IF NOT EXISTS 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_parent ON skill_categories(parent_category_id);
-- Skills erweitern (Verknüpfung mit Kategorien) -- Skills erweitern (Verknüpfung mit Kategorien)
ALTER TABLE skills DO $$
ADD COLUMN category_id INT REFERENCES skill_categories(id), BEGIN
ADD COLUMN parent_skill_id INT REFERENCES skills(id), -- Hierarchie: Tsuki → Mae-Zuki IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='skills' AND column_name='category_id') THEN
ADD COLUMN level INT CHECK (level BETWEEN 1 AND 10), -- Schwierigkeitsgrad ALTER TABLE skills ADD COLUMN category_id INT REFERENCES skill_categories(id);
ADD COLUMN sort_order INT; 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 IF NOT EXISTS idx_skills_category ON skills(category_id);
CREATE INDEX idx_skills_parent ON skills(parent_skill_id); CREATE INDEX IF NOT EXISTS idx_skills_parent ON skills(parent_skill_id);
-- Exercises erweitern -- Exercises erweitern
ALTER TABLE exercises DO $$
ADD COLUMN training_style_id INT REFERENCES training_styles(id), BEGIN
ADD COLUMN training_character_id INT REFERENCES training_characters(id), IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='exercises' AND column_name='training_style_id') THEN
ADD COLUMN focus_area_id INT REFERENCES focus_areas(id); 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 -- 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 focus_area; -- später, nach Migration
-- ALTER TABLE exercises DROP COLUMN training_character; -- 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 IF NOT EXISTS idx_exercises_style ON exercises(training_style_id);
CREATE INDEX idx_exercises_character ON exercises(training_character_id); CREATE INDEX IF NOT EXISTS 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_focus_area ON exercises(focus_area_id);
-- Trainer-Fokusbereich-Zuordnung (Welcher Trainer arbeitet in welchen Fokusbereichen?) -- 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, id SERIAL PRIMARY KEY,
profile_id INT REFERENCES profiles(id) ON DELETE CASCADE, profile_id INT REFERENCES profiles(id) ON DELETE CASCADE,
focus_area_id INT REFERENCES focus_areas(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) UNIQUE(profile_id, focus_area_id)
); );
CREATE INDEX idx_trainer_focus_areas_profile ON trainer_focus_areas(profile_id); CREATE INDEX IF NOT EXISTS 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_focus ON trainer_focus_areas(focus_area_id);
-- Basis-Daten einfügen -- Basis-Daten einfügen
INSERT INTO focus_areas (name, abbreviation, description, color, icon, sort_order) VALUES INSERT INTO focus_areas (name, abbreviation, description, color, icon, sort_order) VALUES
('Karate', 'KAR', 'Traditionelles Karate', '#1D9E75', '🥋', 1), ('Karate', 'KAR', 'Traditionelles Karate', '#1D9E75', '🥋', 1),
('Selbstverteidigung', 'SV', 'Praktische Selbstverteidigung', '#D85A30', '🛡️', 2), ('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 INSERT INTO training_styles (name, abbreviation, description, sort_order) VALUES
('Shotokan', 'SHO', 'Shotokan-Karate', 1), ('Shotokan', 'SHO', 'Shotokan-Karate', 1),
('Goju-Ryu', 'GJR', 'Goju-Ryu-Karate', 2), ('Goju-Ryu', 'GJR', 'Goju-Ryu-Karate', 2),
('Wado-Ryu', 'WAD', 'Wado-Ryu-Karate', 3), ('Wado-Ryu', 'WAD', 'Wado-Ryu-Karate', 3),
('Shito-Ryu', 'SHI', 'Shito-Ryu-Karate', 4), ('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 INSERT INTO training_characters (name, description, sort_order) VALUES
('Grundlage', 'Einführung und Basisvermittlung', 1), ('Grundlage', 'Einführung und Basisvermittlung', 1),
@ -118,7 +138,8 @@ INSERT INTO training_characters (name, description, sort_order) VALUES
('Vertiefung', 'Vertiefung und Spezialisierung', 3), ('Vertiefung', 'Vertiefung und Spezialisierung', 3),
('Festigung', 'Wiederholung und Festigung', 4), ('Festigung', 'Wiederholung und Festigung', 4),
('Diagnose', 'Leistungsdiagnose und Test', 5), ('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 INSERT INTO skill_categories (name, description, sort_order) VALUES
('Kihon', 'Grundschultechniken', 1), ('Kihon', 'Grundschultechniken', 1),
@ -126,4 +147,5 @@ INSERT INTO skill_categories (name, description, sort_order) VALUES
('Kata', 'Formen', 3), ('Kata', 'Formen', 3),
('Selbstverteidigung', 'SV-Techniken', 4), ('Selbstverteidigung', 'SV-Techniken', 4),
('Fitness', 'Kondition und Athletik', 5), ('Fitness', 'Kondition und Athletik', 5),
('Mental', 'Mentale Fähigkeiten', 6); ('Mental', 'Mentale Fähigkeiten', 6)
ON CONFLICT (name) DO NOTHING;