Files
Synculous-2/api/routes/adaptive_meds.py
chelsea 6850abf7d2 fix partial-update overwrite bug in snitch and adaptive meds PUT handlers
Both handlers were building update_data with defaults for every field,
so a partial save (e.g. toggling one toggle) would silently reset all
other settings back to their defaults. Now only fields explicitly
present in the request body are written to the DB.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 19:05:09 -06:00

195 lines
6.9 KiB
Python

"""
api/routes/adaptive_meds.py - API endpoints for adaptive medication settings
"""
import logging
import uuid
import flask
import jwt
import os
import core.postgres as postgres
import core.adaptive_meds as adaptive_meds
logger = logging.getLogger(__name__)
JWT_SECRET = os.getenv("JWT_SECRET")
def _get_user_uuid(request):
"""Extract and validate user UUID from JWT token."""
auth_header = request.headers.get("Authorization", "")
if not auth_header.startswith("Bearer "):
return None
token = auth_header[7:]
try:
payload = jwt.decode(token, JWT_SECRET, algorithms=["HS256"])
return payload.get("sub")
except jwt.ExpiredSignatureError:
return None
except jwt.InvalidTokenError:
return None
def register(app):
@app.route("/api/adaptive-meds/settings", methods=["GET"])
def get_adaptive_settings():
"""Get user's adaptive medication settings."""
user_uuid = _get_user_uuid(flask.request)
if not user_uuid:
return flask.jsonify({"error": "Unauthorized"}), 401
settings = adaptive_meds.get_adaptive_settings(user_uuid)
if not settings:
# Return defaults
return flask.jsonify(
{
"adaptive_timing_enabled": False,
"adaptive_mode": "shift_all",
"presence_tracking_enabled": False,
"nagging_enabled": True,
"nag_interval_minutes": 15,
"max_nag_count": 4,
"quiet_hours_start": None,
"quiet_hours_end": None,
}
), 200
return flask.jsonify(
{
"adaptive_timing_enabled": settings.get(
"adaptive_timing_enabled", False
),
"adaptive_mode": settings.get("adaptive_mode", "shift_all"),
"presence_tracking_enabled": settings.get(
"presence_tracking_enabled", False
),
"nagging_enabled": settings.get("nagging_enabled", True),
"nag_interval_minutes": settings.get("nag_interval_minutes", 15),
"max_nag_count": settings.get("max_nag_count", 4),
"quiet_hours_start": settings.get("quiet_hours_start"),
"quiet_hours_end": settings.get("quiet_hours_end"),
}
), 200
@app.route("/api/adaptive-meds/settings", methods=["PUT"])
def update_adaptive_settings():
"""Update user's adaptive medication settings."""
user_uuid = _get_user_uuid(flask.request)
if not user_uuid:
return flask.jsonify({"error": "Unauthorized"}), 401
data = flask.request.get_json()
if not data:
return flask.jsonify({"error": "No data provided"}), 400
# Validate required fields if enabling adaptive timing
if data.get("adaptive_timing_enabled"):
if not data.get("adaptive_mode"):
return flask.jsonify(
{"error": "adaptive_mode is required when enabling adaptive timing"}
), 400
# Only update fields explicitly provided in the request — never overwrite with defaults
allowed_fields = [
"adaptive_timing_enabled", "adaptive_mode", "presence_tracking_enabled",
"nagging_enabled", "nag_interval_minutes", "max_nag_count",
"quiet_hours_start", "quiet_hours_end",
]
update_data = {field: data[field] for field in allowed_fields if field in data}
if not update_data:
return flask.jsonify({"success": True}), 200
try:
existing = adaptive_meds.get_adaptive_settings(user_uuid)
if existing:
postgres.update(
"adaptive_med_settings", update_data, {"user_uuid": user_uuid}
)
else:
update_data["id"] = str(uuid.uuid4())
update_data["user_uuid"] = user_uuid
postgres.insert("adaptive_med_settings", update_data)
return flask.jsonify({"success": True}), 200
except Exception as e:
logger.error(f"Failed to save adaptive settings: {e}")
return flask.jsonify({
"error": "Failed to save settings",
"details": str(e)
}), 500
@app.route("/api/adaptive-meds/presence", methods=["GET"])
def get_presence_status():
"""Get user's Discord presence status."""
user_uuid = _get_user_uuid(flask.request)
if not user_uuid:
return flask.jsonify({"error": "Unauthorized"}), 401
presence = adaptive_meds.get_user_presence(user_uuid)
if not presence:
return flask.jsonify(
{"is_online": False, "last_online_at": None, "typical_wake_time": None}
), 200
typical_wake = adaptive_meds.calculate_typical_wake_time(user_uuid)
return flask.jsonify(
{
"is_online": presence.get("is_currently_online", False),
"last_online_at": presence.get("last_online_at").isoformat()
if presence.get("last_online_at")
else None,
"last_offline_at": presence.get("last_offline_at").isoformat()
if presence.get("last_offline_at")
else None,
"typical_wake_time": typical_wake.strftime("%H:%M")
if typical_wake
else None,
}
), 200
@app.route("/api/adaptive-meds/schedule", methods=["GET"])
def get_today_schedule():
"""Get today's adaptive medication schedule."""
user_uuid = _get_user_uuid(flask.request)
if not user_uuid:
return flask.jsonify({"error": "Unauthorized"}), 401
from datetime import date
today = date.today()
# Get all medications for user
meds = postgres.select("medications", {"user_uuid": user_uuid, "active": True})
schedule_data = []
for med in meds:
med_id = med.get("id")
med_schedules = postgres.select(
"medication_schedules",
{
"user_uuid": user_uuid,
"medication_id": med_id,
"adjustment_date": today,
},
)
for sched in med_schedules:
schedule_data.append(
{
"medication_id": med_id,
"medication_name": med.get("name"),
"base_time": sched.get("base_time"),
"adjusted_time": sched.get("adjusted_time"),
"adjustment_minutes": sched.get("adjustment_minutes", 0),
"status": sched.get("status", "pending"),
"nag_count": sched.get("nag_count", 0),
}
)
return flask.jsonify(schedule_data), 200