Production Release: RestDays Widget + Trainingstyp Fix #16

Merged
Lars merged 10 commits from develop into main 2026-03-23 09:24:17 +01:00
2 changed files with 67 additions and 13 deletions
Showing only changes of commit f87b93ce2f - Show all commits

View File

@ -0,0 +1,34 @@
-- Migration 012: Unique constraint on (profile_id, date, focus)
-- v9d Phase 2a: Prevent duplicate rest day types per date
-- Date: 2026-03-22
-- Add focus column (extracted from rest_config for performance + constraints)
ALTER TABLE rest_days
ADD COLUMN IF NOT EXISTS focus VARCHAR(20);
-- Populate from existing JSONB data
UPDATE rest_days
SET focus = rest_config->>'focus'
WHERE focus IS NULL;
-- Make NOT NULL (safe because we just populated all rows)
ALTER TABLE rest_days
ALTER COLUMN focus SET NOT NULL;
-- Add CHECK constraint for valid focus values
ALTER TABLE rest_days
ADD CONSTRAINT valid_focus CHECK (
focus IN ('muscle_recovery', 'cardio_recovery', 'mental_rest', 'deload', 'injury')
);
-- Add UNIQUE constraint: Same profile + date + focus = duplicate
ALTER TABLE rest_days
ADD CONSTRAINT unique_rest_day_per_focus
UNIQUE (profile_id, date, focus);
-- Add index for efficient queries by focus
CREATE INDEX IF NOT EXISTS idx_rest_days_focus
ON rest_days(focus);
-- Comment for documentation
COMMENT ON COLUMN rest_days.focus IS 'Extracted from rest_config.focus for performance and constraints. Prevents duplicate rest day types per date.';

View File

@ -10,6 +10,7 @@ from datetime import datetime, timedelta
from fastapi import APIRouter, HTTPException, Depends, Header
from pydantic import BaseModel, Field
from psycopg2.extras import Json
from psycopg2.errors import UniqueViolation
from db import get_db, get_cursor, r2d
from auth import require_auth
@ -89,23 +90,39 @@ def create_rest_day(
# Convert RestConfig to dict for JSONB storage
config_dict = data.rest_config.model_dump()
focus = data.rest_config.focus
with get_db() as conn:
cur = get_cursor(conn)
try:
with get_db() as conn:
cur = get_cursor(conn)
# Insert (multiple entries per date allowed)
cur.execute(
"""
INSERT INTO rest_days (profile_id, date, rest_config, note, created_at)
VALUES (%s, %s, %s, %s, CURRENT_TIMESTAMP)
RETURNING id, profile_id, date, rest_config, note, created_at
""",
(pid, data.date, Json(config_dict), data.note)
# Insert (multiple entries per date allowed, but not same focus)
cur.execute(
"""
INSERT INTO rest_days (profile_id, date, focus, rest_config, note, created_at)
VALUES (%s, %s, %s, %s, %s, CURRENT_TIMESTAMP)
RETURNING id, profile_id, date, focus, rest_config, note, created_at
""",
(pid, data.date, focus, Json(config_dict), data.note)
)
result = cur.fetchone()
return r2d(result)
except UniqueViolation:
# User-friendly error for duplicate focus
focus_labels = {
'muscle_recovery': 'Muskelregeneration',
'cardio_recovery': 'Cardio-Erholung',
'mental_rest': 'Mentale Erholung',
'deload': 'Deload',
'injury': 'Verletzungspause',
}
focus_label = focus_labels.get(focus, focus)
raise HTTPException(
400,
f"Du hast bereits einen Ruhetag '{focus_label}' für {data.date}. Bitte wähle einen anderen Typ oder lösche den bestehenden Eintrag."
)
result = cur.fetchone()
return r2d(result)
@router.get("/{rest_day_id}")
def get_rest_day(
@ -159,6 +176,9 @@ def update_rest_day(
if data.rest_config:
updates.append("rest_config = %s")
values.append(Json(data.rest_config.model_dump()))
# Also update focus column if config changed
updates.append("focus = %s")
values.append(data.rest_config.focus)
if data.note is not None:
updates.append("note = %s")