feat: Exercises-Router M:N Zuordnungen
Backend API erweitert um M:N Katalog-Zuordnungen:
- GET /exercises/{id}: Liefert focus_areas[], training_styles[], target_groups[], age_groups_catalog[]
- POST /exercises: Akzeptiert focus_areas_multi[], training_styles_multi[], target_groups_multi[], age_groups_catalog[]
- PUT /exercises/{id}: DELETE+INSERT Pattern für M:N Updates (konsistent mit skills)
Rückwärtskompatibilität:
- Legacy FK-Felder (focus_area_id, training_style_id, training_character_id) bleiben erhalten
- Alte Aufrufe funktionieren unverändert
- Neue M:N Felder sind optional
version: 0.3.1
modules: exercises 0.4.0
This commit is contained in:
parent
c7444ecaec
commit
d67f659e97
|
|
@ -143,6 +143,45 @@ def get_exercise(exercise_id: int, session=Depends(require_auth)):
|
||||||
""", (exercise_id,))
|
""", (exercise_id,))
|
||||||
exercise['media'] = [r2d(r) for r in cur.fetchall()]
|
exercise['media'] = [r2d(r) for r in cur.fetchall()]
|
||||||
|
|
||||||
|
# Get M:N catalog assignments
|
||||||
|
# Focus Areas
|
||||||
|
cur.execute("""
|
||||||
|
SELECT efa.*, fa.name, fa.abbreviation, fa.color
|
||||||
|
FROM exercise_focus_areas efa
|
||||||
|
JOIN focus_areas fa ON efa.focus_area_id = fa.id
|
||||||
|
WHERE efa.exercise_id = %s
|
||||||
|
ORDER BY efa.is_primary DESC, fa.name
|
||||||
|
""", (exercise_id,))
|
||||||
|
exercise['focus_areas'] = [r2d(r) for r in cur.fetchall()]
|
||||||
|
|
||||||
|
# Training Styles
|
||||||
|
cur.execute("""
|
||||||
|
SELECT es.*, ts.name, ts.abbreviation
|
||||||
|
FROM exercise_styles es
|
||||||
|
JOIN training_styles ts ON es.training_style_id = ts.id
|
||||||
|
WHERE es.exercise_id = %s
|
||||||
|
ORDER BY es.is_primary DESC, ts.name
|
||||||
|
""", (exercise_id,))
|
||||||
|
exercise['training_styles'] = [r2d(r) for r in cur.fetchall()]
|
||||||
|
|
||||||
|
# Target Groups
|
||||||
|
cur.execute("""
|
||||||
|
SELECT etg.*, tg.name, tg.description
|
||||||
|
FROM exercise_target_groups etg
|
||||||
|
JOIN target_groups tg ON etg.target_group_id = tg.id
|
||||||
|
WHERE etg.exercise_id = %s
|
||||||
|
ORDER BY etg.is_primary DESC, tg.name
|
||||||
|
""", (exercise_id,))
|
||||||
|
exercise['target_groups'] = [r2d(r) for r in cur.fetchall()]
|
||||||
|
|
||||||
|
# Age Groups
|
||||||
|
cur.execute("""
|
||||||
|
SELECT age_group FROM exercise_age_groups
|
||||||
|
WHERE exercise_id = %s
|
||||||
|
ORDER BY age_group
|
||||||
|
""", (exercise_id,))
|
||||||
|
exercise['age_groups_catalog'] = [r['age_group'] for r in cur.fetchall()]
|
||||||
|
|
||||||
return exercise
|
return exercise
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -227,6 +266,39 @@ def create_exercise(data: dict, session=Depends(require_auth)):
|
||||||
skill.get('target_level')
|
skill.get('target_level')
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# Add M:N catalog assignments if provided
|
||||||
|
# Focus Areas
|
||||||
|
if data.get('focus_areas_multi'):
|
||||||
|
for fa in data['focus_areas_multi']:
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO exercise_focus_areas (exercise_id, focus_area_id, is_primary)
|
||||||
|
VALUES (%s, %s, %s)
|
||||||
|
""", (exercise_id, fa['focus_area_id'], fa.get('is_primary', False)))
|
||||||
|
|
||||||
|
# Training Styles
|
||||||
|
if data.get('training_styles_multi'):
|
||||||
|
for ts in data['training_styles_multi']:
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO exercise_styles (exercise_id, training_style_id, is_primary)
|
||||||
|
VALUES (%s, %s, %s)
|
||||||
|
""", (exercise_id, ts['training_style_id'], ts.get('is_primary', False)))
|
||||||
|
|
||||||
|
# Target Groups
|
||||||
|
if data.get('target_groups_multi'):
|
||||||
|
for tg in data['target_groups_multi']:
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO exercise_target_groups (exercise_id, target_group_id, is_primary)
|
||||||
|
VALUES (%s, %s, %s)
|
||||||
|
""", (exercise_id, tg['target_group_id'], tg.get('is_primary', False)))
|
||||||
|
|
||||||
|
# Age Groups
|
||||||
|
if data.get('age_groups_catalog'):
|
||||||
|
for age_group in data['age_groups_catalog']:
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO exercise_age_groups (exercise_id, age_group)
|
||||||
|
VALUES (%s, %s)
|
||||||
|
""", (exercise_id, age_group))
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
return get_exercise(exercise_id, session)
|
return get_exercise(exercise_id, session)
|
||||||
|
|
@ -313,6 +385,43 @@ def update_exercise(exercise_id: int, data: dict, session=Depends(require_auth))
|
||||||
skill.get('target_level')
|
skill.get('target_level')
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# Update M:N catalog assignments if provided
|
||||||
|
# Focus Areas
|
||||||
|
if 'focus_areas_multi' in data:
|
||||||
|
cur.execute("DELETE FROM exercise_focus_areas WHERE exercise_id = %s", (exercise_id,))
|
||||||
|
for fa in data['focus_areas_multi']:
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO exercise_focus_areas (exercise_id, focus_area_id, is_primary)
|
||||||
|
VALUES (%s, %s, %s)
|
||||||
|
""", (exercise_id, fa['focus_area_id'], fa.get('is_primary', False)))
|
||||||
|
|
||||||
|
# Training Styles
|
||||||
|
if 'training_styles_multi' in data:
|
||||||
|
cur.execute("DELETE FROM exercise_styles WHERE exercise_id = %s", (exercise_id,))
|
||||||
|
for ts in data['training_styles_multi']:
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO exercise_styles (exercise_id, training_style_id, is_primary)
|
||||||
|
VALUES (%s, %s, %s)
|
||||||
|
""", (exercise_id, ts['training_style_id'], ts.get('is_primary', False)))
|
||||||
|
|
||||||
|
# Target Groups
|
||||||
|
if 'target_groups_multi' in data:
|
||||||
|
cur.execute("DELETE FROM exercise_target_groups WHERE exercise_id = %s", (exercise_id,))
|
||||||
|
for tg in data['target_groups_multi']:
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO exercise_target_groups (exercise_id, target_group_id, is_primary)
|
||||||
|
VALUES (%s, %s, %s)
|
||||||
|
""", (exercise_id, tg['target_group_id'], tg.get('is_primary', False)))
|
||||||
|
|
||||||
|
# Age Groups
|
||||||
|
if 'age_groups_catalog' in data:
|
||||||
|
cur.execute("DELETE FROM exercise_age_groups WHERE exercise_id = %s", (exercise_id,))
|
||||||
|
for age_group in data['age_groups_catalog']:
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO exercise_age_groups (exercise_id, age_group)
|
||||||
|
VALUES (%s, %s)
|
||||||
|
""", (exercise_id, age_group))
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
return get_exercise(exercise_id, session)
|
return get_exercise(exercise_id, session)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Shinkan Jinkendo Version Information
|
# Shinkan Jinkendo Version Information
|
||||||
|
|
||||||
APP_VERSION = "0.3.0"
|
APP_VERSION = "0.3.1"
|
||||||
BUILD_DATE = "2026-04-23"
|
BUILD_DATE = "2026-04-23"
|
||||||
DB_SCHEMA_VERSION = "20260423"
|
DB_SCHEMA_VERSION = "20260423"
|
||||||
|
|
||||||
|
|
@ -11,7 +11,7 @@ MODULE_VERSIONS = {
|
||||||
"groups": "0.1.0",
|
"groups": "0.1.0",
|
||||||
"skills": "0.1.0",
|
"skills": "0.1.0",
|
||||||
"methods": "0.1.0",
|
"methods": "0.1.0",
|
||||||
"exercises": "0.3.0", # Updated: M:N Beziehungen
|
"exercises": "0.4.0", # Updated: M:N API-Integration
|
||||||
"training_units": "0.1.0",
|
"training_units": "0.1.0",
|
||||||
"training_programs": "0.1.0",
|
"training_programs": "0.1.0",
|
||||||
"planning": "0.1.0",
|
"planning": "0.1.0",
|
||||||
|
|
@ -22,6 +22,17 @@ MODULE_VERSIONS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
CHANGELOG = [
|
CHANGELOG = [
|
||||||
|
{
|
||||||
|
"version": "0.3.1",
|
||||||
|
"date": "2026-04-23",
|
||||||
|
"changes": [
|
||||||
|
"Feature: Exercises-Router unterstützt M:N Zuordnungen",
|
||||||
|
"API: GET /exercises/{id} liefert focus_areas[], training_styles[], target_groups[], age_groups_catalog[]",
|
||||||
|
"API: POST/PUT /exercises akzeptiert focus_areas_multi[], training_styles_multi[], target_groups_multi[], age_groups_catalog[]",
|
||||||
|
"Pattern: DELETE+INSERT für M:N Updates (konsistent mit skills)",
|
||||||
|
"Backward-Compatible: Legacy FK-Felder (focus_area_id, training_style_id) bleiben erhalten",
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"date": "2026-04-23",
|
"date": "2026-04-23",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Shinkan Jinkendo Frontend Version
|
// Shinkan Jinkendo Frontend Version
|
||||||
|
|
||||||
export const APP_VERSION = "0.3.0"
|
export const APP_VERSION = "0.3.1"
|
||||||
export const BUILD_DATE = "2026-04-23"
|
export const BUILD_DATE = "2026-04-23"
|
||||||
|
|
||||||
export const PAGE_VERSIONS = {
|
export const PAGE_VERSIONS = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user