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>
195 lines
6.9 KiB
Python
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
|