shinkan-jinkendo/backend/migrations/017_exercise_blocks.sql
Lars 01ed5509f8
Some checks failed
Deploy Development / deploy (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 6s
Test Suite / playwright-tests (push) Failing after 1m54s
feat: Exercises v2.0 + Migrations 014/016/017 (Clean-Room Rebuild)
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
2026-04-24 15:04:27 +02:00

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 $$;