feat: Backend API für training_types + Frontend api.js
Backend (catalogs.py):
- GET /api/training-types (Liste)
- POST /api/training-types (Erstellen)
- PUT /api/training-types/{id} (Bearbeiten)
- DELETE /api/training-types/{id} (Löschen mit CASCADE-Check)
- Cascade-Protection: Fehler wenn Übungen zugeordnet
Frontend (api.js):
- listTrainingTypes(filters)
- createTrainingType(data)
- updateTrainingType(id, data)
- deleteTrainingType(id)
- Export zum api-Objekt hinzugefügt
Pattern: Konsistent mit anderen Katalog-Endpoints
CRUD: Volle Admin-Verwaltung
Version: 0.4.0
This commit is contained in:
parent
62b5b4c2fd
commit
72c927e69e
|
|
@ -359,6 +359,131 @@ def delete_training_character(char_id: int, session=Depends(require_auth)):
|
|||
return {"ok": True}
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════════════════
|
||||
# TRAINING TYPES (Breitensport, Leistungssport, etc.)
|
||||
# ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@router.get("/training-types")
|
||||
def list_training_types(
|
||||
status: Optional[str] = Query(default='active'),
|
||||
session=Depends(require_auth)
|
||||
):
|
||||
"""List all training types."""
|
||||
with get_db() as conn:
|
||||
cur = get_cursor(conn)
|
||||
|
||||
query = "SELECT * FROM training_types"
|
||||
params = []
|
||||
|
||||
if status:
|
||||
query += " WHERE status = %s"
|
||||
params.append(status)
|
||||
|
||||
query += " ORDER BY sort_order, name"
|
||||
|
||||
cur.execute(query, params)
|
||||
rows = cur.fetchall()
|
||||
return [r2d(r) for r in rows]
|
||||
|
||||
|
||||
@router.post("/training-types")
|
||||
def create_training_type(data: dict, session=Depends(require_auth)):
|
||||
"""Create new training type (admin only)."""
|
||||
role = session.get('role')
|
||||
if role not in ['admin', 'superadmin']:
|
||||
raise HTTPException(403, "Nur Admins dürfen Trainingsstile erstellen")
|
||||
|
||||
name = data.get('name')
|
||||
if not name:
|
||||
raise HTTPException(400, "Name ist Pflichtfeld")
|
||||
|
||||
with get_db() as conn:
|
||||
cur = get_cursor(conn)
|
||||
|
||||
cur.execute("""
|
||||
INSERT INTO training_types (name, abbreviation, description, sort_order, status)
|
||||
VALUES (%s, %s, %s, %s, %s)
|
||||
RETURNING id
|
||||
""", (
|
||||
name,
|
||||
data.get('abbreviation'),
|
||||
data.get('description'),
|
||||
data.get('sort_order', 99),
|
||||
data.get('status', 'active')
|
||||
))
|
||||
|
||||
type_id = cur.fetchone()['id']
|
||||
conn.commit()
|
||||
|
||||
cur.execute("SELECT * FROM training_types WHERE id = %s", (type_id,))
|
||||
return r2d(cur.fetchone())
|
||||
|
||||
|
||||
@router.put("/training-types/{type_id}")
|
||||
def update_training_type(type_id: int, data: dict, session=Depends(require_auth)):
|
||||
"""Update training type (admin only)."""
|
||||
role = session.get('role')
|
||||
if role not in ['admin', 'superadmin']:
|
||||
raise HTTPException(403, "Nur Admins dürfen Trainingsstile bearbeiten")
|
||||
|
||||
with get_db() as conn:
|
||||
cur = get_cursor(conn)
|
||||
|
||||
cur.execute("""
|
||||
UPDATE training_types SET
|
||||
name = %s,
|
||||
abbreviation = %s,
|
||||
description = %s,
|
||||
sort_order = %s,
|
||||
status = %s,
|
||||
updated_at = NOW()
|
||||
WHERE id = %s
|
||||
""", (
|
||||
data.get('name'),
|
||||
data.get('abbreviation'),
|
||||
data.get('description'),
|
||||
data.get('sort_order'),
|
||||
data.get('status'),
|
||||
type_id
|
||||
))
|
||||
|
||||
conn.commit()
|
||||
|
||||
cur.execute("SELECT * FROM training_types WHERE id = %s", (type_id,))
|
||||
return r2d(cur.fetchone())
|
||||
|
||||
|
||||
@router.delete("/training-types/{type_id}")
|
||||
def delete_training_type(type_id: int, session=Depends(require_auth)):
|
||||
"""Delete training type (superadmin only)."""
|
||||
role = session.get('role')
|
||||
if role != 'superadmin':
|
||||
raise HTTPException(403, "Nur Superadmins dürfen Trainingsstile löschen")
|
||||
|
||||
with get_db() as conn:
|
||||
cur = get_cursor(conn)
|
||||
|
||||
# Check if assigned to exercises
|
||||
cur.execute("""
|
||||
SELECT COUNT(*) as count
|
||||
FROM exercise_training_types
|
||||
WHERE training_type_id = %s
|
||||
""", (type_id,))
|
||||
ex_count = cur.fetchone()['count']
|
||||
|
||||
if ex_count > 0:
|
||||
raise HTTPException(
|
||||
409,
|
||||
f"Trainingsstil kann nicht gelöscht werden: {ex_count} Übung(en) zugeordnet. "
|
||||
"Bitte zuerst alle Zuordnungen entfernen."
|
||||
)
|
||||
|
||||
cur.execute("DELETE FROM training_types WHERE id = %s", (type_id,))
|
||||
conn.commit()
|
||||
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════════════════
|
||||
# SKILL CATEGORIES
|
||||
# ════════════════════════════════════════════════════════════════════════
|
||||
|
|
|
|||
|
|
@ -305,6 +305,30 @@ export async function deleteTrainingCharacter(id) {
|
|||
return request(`/api/training-characters/${id}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
// Training Types (Breitensport, Leistungssport, etc.)
|
||||
export async function listTrainingTypes(filters = {}) {
|
||||
const query = new URLSearchParams(filters).toString()
|
||||
return request(`/api/training-types${query ? '?' + query : ''}`)
|
||||
}
|
||||
|
||||
export async function createTrainingType(data) {
|
||||
return request('/api/training-types', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
}
|
||||
|
||||
export async function updateTrainingType(id, data) {
|
||||
return request(`/api/training-types/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
}
|
||||
|
||||
export async function deleteTrainingType(id) {
|
||||
return request(`/api/training-types/${id}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
// Skill Categories
|
||||
export async function listSkillCategories(filters = {}) {
|
||||
const query = new URLSearchParams(filters).toString()
|
||||
|
|
@ -512,6 +536,10 @@ export const api = {
|
|||
createTrainingCharacter,
|
||||
updateTrainingCharacter,
|
||||
deleteTrainingCharacter,
|
||||
listTrainingTypes,
|
||||
createTrainingType,
|
||||
updateTrainingType,
|
||||
deleteTrainingType,
|
||||
listSkillCategories,
|
||||
createSkillCategory,
|
||||
updateSkillCategory,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Shinkan Jinkendo Frontend Version
|
||||
|
||||
export const APP_VERSION = "0.3.4"
|
||||
export const APP_VERSION = "0.4.0"
|
||||
export const BUILD_DATE = "2026-04-23"
|
||||
|
||||
export const PAGE_VERSIONS = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user