BREAKING CHANGES:
- exercises.py komplett neu gebaut (kein Legacy-Code)
- Legacy-Felder entfernt: age_groups, focus_area, secondary_areas, training_character
- Nur M:N Relations, keine JSONB-Kataloge
Migrations:
- Migration 014: Variant Progression + Search Vector + Legacy DROP
- exercise_variants: progression_level, sequence_order, prerequisite_variant_id
- exercises: search_vector (tsvector für Volltext-Suche)
- DROP age_groups, focus_area, secondary_areas, training_character
- Helper: update_timestamp() Funktion für Triggers
- Migration 016: Saved Exercise Searches
- saved_exercise_searches (profile_id, name, filters JSONB)
- Migration 017: Exercise Blocks + Template Blocks
- exercise_blocks (name, description, goal, is_template)
- exercise_block_items (exercise_id, variant_id, sequence_order, is_placeholder, placeholder_criteria)
Backend (exercises.py v2.0):
- GET /exercises: Volltext-Suche via tsvector, M:N Joins
- GET /exercises/{id}: enrich_exercise_detail() mit allen M:N Relations
- POST /exercises: M:N Relations (focus_areas_multi, training_styles_multi, target_groups_multi, age_groups, skills)
- PUT /exercises: Partial Update + M:N Relations
- DELETE /exercises: Cascade-Check für exercise_block_items
Architecture:
- Issue #53 konform: Import = Feld-Zuordnung, keine fachliche Interpretation
- Helper: enrich_exercise_detail() für vollständige Objekte
- Helper: assign_exercise_relations() für M:N Management (DELETE+INSERT Pattern)
Docs:
- SMW_IMPORTER_GAP_ANALYSIS.md: Vollständige Gap-Analyse + Umsetzungsplan
Version: 0.7.0
Module: exercises 2.0.0
Schema: 20260424002
104 lines
4.1 KiB
SQL
104 lines
4.1 KiB
SQL
-- 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 $$;
|