Migration 012:
- exercise_training_characters (M:N junction table)
- trainer_contexts (Fokussierte Trainer-Ansichten)
- Indizes für Performance
- Example seed data
Backend (catalogs.py):
- GET /api/trainer-contexts (list own contexts)
- POST /api/trainer-contexts (create context)
- PUT /api/trainer-contexts/{id} (update own context)
- DELETE /api/trainer-contexts/{id} (delete own context)
- Enriched responses mit focus_area_name, style_direction_name, training_type_name
- Ownership-Validation (nur eigene Kontexte)
Frontend:
- TrainerContextsPage.jsx (vollständige CRUD-UI)
- Kaskadierende Dropdowns (Fokusbereich → Stilrichtung)
- is_style_independent Flag für stilunabhängige Kontexte
- api.js erweitert (listTrainerContexts, create, update, delete)
Architektur:
- Flat Catalogs mit M:N überall
- NULL = 'für alles geeignet'
- Trainer-Kontexte für fokussierte Ansichten
- Vorbereitung für 1000+ Übungen mit flexibler KI-Filterung
version: 0.5.0 (backend + frontend)
module: exercises 0.5.0, catalogs 1.5.0
page: TrainerContextsPage 1.0.0
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
121 lines
4.6 KiB
SQL
121 lines
4.6 KiB
SQL
-- Migration 012: Exercise Training Characters (M:N) + Trainer Contexts
|
|
-- Author: Claude Code
|
|
-- Date: 2026-04-23
|
|
-- Purpose: Add M:N relationship for training characters and trainer profile system
|
|
|
|
DO $$
|
|
BEGIN
|
|
|
|
-- ============================================================================
|
|
-- EXERCISE TRAINING CHARACTERS (M:N)
|
|
-- ============================================================================
|
|
|
|
-- Create junction table for exercise ↔ training_characters
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM information_schema.tables
|
|
WHERE table_schema = 'public'
|
|
AND table_name = 'exercise_training_characters'
|
|
) THEN
|
|
CREATE TABLE exercise_training_characters (
|
|
id SERIAL PRIMARY KEY,
|
|
exercise_id INT NOT NULL REFERENCES exercises(id) ON DELETE CASCADE,
|
|
training_character_id INT NOT NULL REFERENCES training_characters(id) ON DELETE RESTRICT,
|
|
is_primary BOOLEAN DEFAULT false,
|
|
created_at TIMESTAMP DEFAULT NOW(),
|
|
UNIQUE(exercise_id, training_character_id)
|
|
);
|
|
|
|
CREATE INDEX idx_exercise_training_characters_exercise
|
|
ON exercise_training_characters(exercise_id);
|
|
CREATE INDEX idx_exercise_training_characters_character
|
|
ON exercise_training_characters(training_character_id);
|
|
|
|
RAISE NOTICE 'Created table: exercise_training_characters';
|
|
END IF;
|
|
|
|
|
|
-- ============================================================================
|
|
-- TRAINER CONTEXTS (Fokussierte Trainer-Ansichten)
|
|
-- ============================================================================
|
|
|
|
-- Create trainer_contexts table
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM information_schema.tables
|
|
WHERE table_schema = 'public'
|
|
AND table_name = 'trainer_contexts'
|
|
) THEN
|
|
CREATE TABLE trainer_contexts (
|
|
id SERIAL PRIMARY KEY,
|
|
profile_id INT NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
|
|
|
|
-- Context definition
|
|
name VARCHAR(100) NOT NULL, -- "Karate Goju-Ryu Breitensport", "Gewaltschutz"
|
|
|
|
-- Hierarchical filters (all optional)
|
|
focus_area_id INT REFERENCES focus_areas(id) ON DELETE CASCADE,
|
|
style_direction_id INT REFERENCES style_directions(id) ON DELETE CASCADE,
|
|
training_type_id INT REFERENCES training_types(id) ON DELETE SET NULL,
|
|
|
|
-- Flags
|
|
is_style_independent BOOLEAN DEFAULT false, -- true = "Leistungssport stilunabhängig"
|
|
is_active BOOLEAN DEFAULT true,
|
|
|
|
-- Metadata
|
|
description TEXT,
|
|
sort_order INT DEFAULT 99,
|
|
created_at TIMESTAMP DEFAULT NOW(),
|
|
updated_at TIMESTAMP DEFAULT NOW(),
|
|
|
|
-- Prevent duplicates
|
|
UNIQUE(profile_id, focus_area_id, style_direction_id, training_type_id)
|
|
);
|
|
|
|
CREATE INDEX idx_trainer_contexts_profile ON trainer_contexts(profile_id);
|
|
CREATE INDEX idx_trainer_contexts_focus ON trainer_contexts(focus_area_id);
|
|
CREATE INDEX idx_trainer_contexts_style ON trainer_contexts(style_direction_id);
|
|
CREATE INDEX idx_trainer_contexts_type ON trainer_contexts(training_type_id);
|
|
|
|
RAISE NOTICE 'Created table: trainer_contexts';
|
|
END IF;
|
|
|
|
|
|
-- ============================================================================
|
|
-- SEED DATA: Example Trainer Contexts
|
|
-- ============================================================================
|
|
|
|
-- Insert example contexts for profile_id = 1 (if exists)
|
|
DO $seed$
|
|
BEGIN
|
|
IF EXISTS (SELECT 1 FROM profiles WHERE id = 1) THEN
|
|
-- Only insert if not already present
|
|
IF NOT EXISTS (SELECT 1 FROM trainer_contexts WHERE profile_id = 1) THEN
|
|
INSERT INTO trainer_contexts (profile_id, name, focus_area_id, style_direction_id, training_type_id, is_style_independent, description, sort_order)
|
|
VALUES
|
|
-- Karate Breitensport (if focus_area and style exist)
|
|
(1, 'Karate Breitensport',
|
|
(SELECT id FROM focus_areas WHERE name = 'Karate' LIMIT 1),
|
|
NULL, -- all styles
|
|
(SELECT id FROM training_types WHERE name = 'Breitensport' LIMIT 1),
|
|
false,
|
|
'Breitensport für alle Karate-Stilrichtungen',
|
|
1
|
|
);
|
|
|
|
RAISE NOTICE 'Inserted example trainer contexts for profile_id = 1';
|
|
END IF;
|
|
END IF;
|
|
END $seed$;
|
|
|
|
|
|
-- ============================================================================
|
|
-- MIGRATION TRACKING
|
|
-- ============================================================================
|
|
|
|
INSERT INTO schema_migrations (version, description)
|
|
VALUES (12, 'exercise_training_characters + trainer_contexts')
|
|
ON CONFLICT (version) DO NOTHING;
|
|
|
|
RAISE NOTICE 'Migration 012 completed successfully';
|
|
|
|
END $$;
|