shinkan-jinkendo/backend/migrations/014_variant_progression_search_legacy.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

116 lines
4.5 KiB
PL/PgSQL

-- Migration 014: Variant Progression System + Search Vector + Legacy Cleanup
-- Autor: Claude Code
-- Datum: 2026-04-24
-- Zweck: Varianten-Progression, Volltext-Suche, Legacy-Spalten entfernen
DO $$
BEGIN
-- ============================================================================
-- HELPER FUNCTION: update_timestamp (für Triggers)
-- ============================================================================
CREATE OR REPLACE FUNCTION update_timestamp()
RETURNS trigger AS $func$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$func$ LANGUAGE plpgsql;
-- ============================================================================
-- VARIANT PROGRESSION
-- ============================================================================
-- Erweitere exercise_variants Tabelle
ALTER TABLE exercise_variants
ADD COLUMN IF NOT EXISTS progression_level INT DEFAULT 1 CHECK (progression_level BETWEEN 1 AND 10),
ADD COLUMN IF NOT EXISTS sequence_order INT,
ADD COLUMN IF NOT EXISTS prerequisite_variant_id INT REFERENCES exercise_variants(id) ON DELETE SET NULL;
-- Index für Prerequisites
CREATE INDEX IF NOT EXISTS idx_exercise_variants_prerequisite
ON exercise_variants(prerequisite_variant_id);
-- ============================================================================
-- SEARCH VECTOR (Volltext-Suche)
-- ============================================================================
-- Füge search_vector zu exercises hinzu
ALTER TABLE exercises
ADD COLUMN IF NOT EXISTS search_vector tsvector;
-- Index für Volltext-Suche
CREATE INDEX IF NOT EXISTS idx_exercises_search
ON exercises USING gin(search_vector);
-- Funktion für automatisches Update
CREATE OR REPLACE FUNCTION update_exercises_search_vector()
RETURNS trigger AS $func$
BEGIN
NEW.search_vector :=
setweight(to_tsvector('german', COALESCE(NEW.title, '')), 'A') ||
setweight(to_tsvector('german', COALESCE(NEW.summary, '')), 'B') ||
setweight(to_tsvector('german', COALESCE(NEW.execution, '')), 'C') ||
setweight(to_tsvector('german', COALESCE(NEW.trainer_notes, '')), 'D');
RETURN NEW;
END;
$func$ LANGUAGE plpgsql;
-- Trigger
DROP TRIGGER IF EXISTS exercises_search_update ON exercises;
CREATE TRIGGER exercises_search_update
BEFORE INSERT OR UPDATE ON exercises
FOR EACH ROW EXECUTE FUNCTION update_exercises_search_vector();
-- Initiales Befüllen (für existierende Zeilen)
UPDATE exercises SET search_vector = (
setweight(to_tsvector('german', COALESCE(title, '')), 'A') ||
setweight(to_tsvector('german', COALESCE(summary, '')), 'B') ||
setweight(to_tsvector('german', COALESCE(execution, '')), 'C') ||
setweight(to_tsvector('german', COALESCE(trainer_notes, '')), 'D')
) WHERE search_vector IS NULL;
-- ============================================================================
-- LEGACY COLUMN CLEANUP
-- Deprecated Felder aus exercises (ersetzt durch M:N Tabellen in Migration 008+)
-- ============================================================================
-- age_groups JSONB → ersetzt durch exercise_age_groups M:N (seit Migration 008)
ALTER TABLE exercises DROP COLUMN IF EXISTS age_groups;
-- focus_area VARCHAR → ersetzt durch exercise_focus_areas M:N (seit Migration 008)
ALTER TABLE exercises DROP COLUMN IF EXISTS focus_area;
-- secondary_areas JSONB → ersetzt durch exercise_focus_areas M:N
ALTER TABLE exercises DROP COLUMN IF EXISTS secondary_areas;
-- training_character VARCHAR → ersetzt durch exercise_training_characters M:N (seit Migration 012)
ALTER TABLE exercises DROP COLUMN IF EXISTS training_character;
-- ============================================================================
-- ADDITIONAL INDEXES (Performance)
-- ============================================================================
-- Häufige Filter
CREATE INDEX IF NOT EXISTS idx_exercises_visibility ON exercises(visibility);
CREATE INDEX IF NOT EXISTS idx_exercises_status ON exercises(status);
CREATE INDEX IF NOT EXISTS idx_exercises_created_at ON exercises(created_at DESC);
-- M:N Relations (falls noch nicht vorhanden)
CREATE INDEX IF NOT EXISTS idx_exercise_focus_areas_focus
ON exercise_focus_areas(focus_area_id);
CREATE INDEX IF NOT EXISTS idx_exercise_styles_style
ON exercise_training_styles(style_direction_id);
CREATE INDEX IF NOT EXISTS idx_exercise_target_groups_group
ON exercise_target_groups(target_group_id);
CREATE INDEX IF NOT EXISTS idx_exercise_skills_skill
ON exercise_skills(skill_id);
RAISE NOTICE 'Migration 014 completed successfully';
END $$;