feat(tg-fitness-bot): soft delete, edit workouts, notes via API

Deleted workouts are marked with deleted_at instead of being removed.
All queries filter on deleted_at IS NULL. New update_workout() does
soft-delete + recreate preserving the original timestamp. PUT endpoint
at /api/workouts/{id}. POST /api/workouts now accepts a note field.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Danny 2026-04-13 20:41:37 +02:00
parent e7ac2b174f
commit 6fb6207041
2 changed files with 88 additions and 39 deletions

View file

@ -13,7 +13,7 @@ from urllib.parse import parse_qs
from aiohttp import web
from db import init_db, get_db, save_workout, get_workouts, get_workout_count, get_stats_sql, delete_workout, export_workouts
from db import init_db, get_db, save_workout, get_workouts, get_workout_count, get_stats_sql, delete_workout, update_workout, export_workouts
from parser import parse_workout, format_workout
logging.basicConfig(
@ -112,6 +112,7 @@ async def api_save_workout(request: web.Request):
body = await request.json()
raw_text = body.get("raw_text", "")
superset_groups = body.get("superset_groups")
note = body.get("note") or None
if superset_groups:
# Structured input from the Mini App UI
@ -121,6 +122,7 @@ async def api_save_workout(request: web.Request):
timestamp=datetime.now(timezone.utc),
superset_groups=superset_groups,
raw_text=raw_text or None,
note=note,
)
elif raw_text:
# Text-based input (same format as sending a message to the bot)
@ -138,6 +140,7 @@ async def api_save_workout(request: web.Request):
timestamp=datetime.now(timezone.utc),
superset_groups=superset_dicts,
raw_text=raw_text,
note=note,
)
else:
return web.json_response(
@ -147,9 +150,26 @@ async def api_save_workout(request: web.Request):
return web.json_response({"workout_id": workout_id}, status=201)
@require_auth
async def api_update_workout(request: web.Request):
"""Update a workout — soft-deletes old, creates new with same timestamp."""
workout_id = int(request.match_info["workout_id"])
body = await request.json()
superset_groups = body.get("superset_groups")
note = body.get("note") or None
if not superset_groups:
return web.json_response({"error": "Provide superset_groups"}, status=400)
new_id = update_workout(request["user_id"], workout_id, superset_groups, note=note)
if new_id is None:
return web.json_response({"error": "Not found"}, status=404)
return web.json_response({"workout_id": new_id})
@require_auth
async def api_delete_workout(request: web.Request):
"""Delete a workout by ID."""
"""Soft-delete a workout by ID."""
workout_id = int(request.match_info["workout_id"])
if delete_workout(request["user_id"], workout_id):
return web.json_response({"deleted": True})
@ -213,6 +233,7 @@ def create_app() -> web.Application:
app.router.add_get("/api/workouts", api_get_workouts)
app.router.add_post("/api/workouts", api_save_workout)
app.router.add_put("/api/workouts/{workout_id}", api_update_workout)
app.router.add_delete("/api/workouts/{workout_id}", api_delete_workout)
app.router.add_get("/api/exercises", api_get_exercise_names)
app.router.add_get("/api/stats", api_get_stats)