-- Migration 017: Exercise Blocks + Template Blocks -- Autor: Claude Code -- Datum: 2026-04-24 -- Zweck: Gruppierung verschiedener Übungen in Blöcken -- Series = Varianten-Progression (via exercise_variants, KEINE eigene Tabelle) -- Blocks = Verschiedene Übungen in Reihenfolge (DIESE Migration) DO $$ BEGIN -- ============================================================================ -- EXERCISE BLOCKS -- Eine Sammlung verschiedener Übungen in definierter Reihenfolge -- ============================================================================ CREATE TABLE IF NOT EXISTS exercise_blocks ( id SERIAL PRIMARY KEY, name VARCHAR(200) NOT NULL, description TEXT, goal TEXT, -- Template-Modus: Block mit Platzhaltern für flexible Planung is_template BOOLEAN DEFAULT false, -- Ownership & Sichtbarkeit club_id INT REFERENCES clubs(id) ON DELETE SET NULL, created_by INT REFERENCES profiles(id) ON DELETE SET NULL, visibility VARCHAR(20) DEFAULT 'private' CHECK (visibility IN ('private', 'club', 'official')), -- Timestamps created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- ============================================================================ -- EXERCISE BLOCK ITEMS -- Einzelne Positionen innerhalb eines Blocks -- ============================================================================ CREATE TABLE IF NOT EXISTS exercise_block_items ( id SERIAL PRIMARY KEY, block_id INT NOT NULL REFERENCES exercise_blocks(id) ON DELETE CASCADE, -- Konkrete Übung (NULL wenn is_placeholder = true) exercise_id INT REFERENCES exercises(id) ON DELETE RESTRICT, -- Optionale Variante (kann NULL sein → Haupt-Übung wird genutzt) variant_id INT REFERENCES exercise_variants(id) ON DELETE SET NULL, -- Reihenfolge innerhalb des Blocks sequence_order INT NOT NULL, -- Template-Modus: Platzhalter statt konkreter Übung is_placeholder BOOLEAN DEFAULT false, -- Kriterien für Platzhalter-Auflösung (nur relevant wenn is_placeholder = true) -- Schema: {"focus_area_id": 1, "max_duration": 10, "skill_ids": [3, 7], "difficulty": "easier"} -- Alle Felder optional, werden als AND-Filter bei der Übungssuche genutzt placeholder_criteria JSONB, -- Platzhalter-Beschriftung (für UX im Template-Modus) placeholder_label VARCHAR(100), -- z.B. "Aufwärmübung Schlag", "Hauptübung Kumite" -- Zusätzliche Notizen für diese Position notes TEXT, -- Timestamps created_at TIMESTAMP DEFAULT NOW(), -- Constraints UNIQUE(block_id, sequence_order), -- Entweder exercise_id ODER is_placeholder=true CHECK ( (is_placeholder = false AND exercise_id IS NOT NULL) OR (is_placeholder = true AND exercise_id IS NULL) ) ); -- ============================================================================ -- INDEXES -- ============================================================================ CREATE INDEX IF NOT EXISTS idx_exercise_blocks_club ON exercise_blocks(club_id); CREATE INDEX IF NOT EXISTS idx_exercise_blocks_creator ON exercise_blocks(created_by); CREATE INDEX IF NOT EXISTS idx_exercise_blocks_visibility ON exercise_blocks(visibility); CREATE INDEX IF NOT EXISTS idx_exercise_blocks_template ON exercise_blocks(is_template) WHERE is_template = true; CREATE INDEX IF NOT EXISTS idx_exercise_block_items_block ON exercise_block_items(block_id); CREATE INDEX IF NOT EXISTS idx_exercise_block_items_exercise ON exercise_block_items(exercise_id); CREATE INDEX IF NOT EXISTS idx_exercise_block_items_placeholder ON exercise_block_items(is_placeholder) WHERE is_placeholder = true; -- ============================================================================ -- UPDATED_AT TRIGGER -- ============================================================================ DROP TRIGGER IF EXISTS exercise_blocks_update ON exercise_blocks; CREATE TRIGGER exercise_blocks_update BEFORE UPDATE ON exercise_blocks FOR EACH ROW EXECUTE FUNCTION update_timestamp(); RAISE NOTICE 'Migration 017 completed successfully (Exercise Blocks)'; END $$;