111 lines
3.6 KiB
Python
111 lines
3.6 KiB
Python
from flask import Blueprint, jsonify, session
|
|
from app import SYDNEY_TZ
|
|
from app.auth import login_required
|
|
from app.db import query, query_one
|
|
from datetime import timezone
|
|
|
|
bp = Blueprint("api", __name__, url_prefix="/api")
|
|
|
|
|
|
@bp.route("/chart-data/<int:user_id>")
|
|
@login_required
|
|
def chart_data(user_id):
|
|
"""Return weight & BMI over time for Chart.js."""
|
|
# Privacy guard: don't expose private user data to others
|
|
if user_id != session.get("user_id"):
|
|
target = query_one("SELECT is_private FROM users WHERE id = %s", (user_id,))
|
|
if target and target["is_private"]:
|
|
return jsonify({"labels": [], "weights": [], "bmis": []})
|
|
|
|
checkins = query(
|
|
"""SELECT weight_kg, bmi, checked_in_at
|
|
FROM checkins WHERE user_id = %s
|
|
ORDER BY checked_in_at ASC""",
|
|
(user_id,),
|
|
)
|
|
|
|
labels = [c["checked_in_at"].replace(tzinfo=timezone.utc).astimezone(SYDNEY_TZ).strftime("%d %b") for c in checkins]
|
|
weights = [float(c["weight_kg"]) for c in checkins]
|
|
bmis = [float(c["bmi"]) if c["bmi"] else None for c in checkins]
|
|
|
|
return jsonify({
|
|
"labels": labels,
|
|
"weights": weights,
|
|
"bmis": bmis,
|
|
})
|
|
|
|
|
|
@bp.route("/comparison")
|
|
@login_required
|
|
def comparison():
|
|
"""Return all-user comparison data for bar charts."""
|
|
users = query("""
|
|
SELECT
|
|
u.id,
|
|
u.display_name,
|
|
u.username,
|
|
u.starting_weight_kg,
|
|
(SELECT weight_kg FROM checkins WHERE user_id = u.id ORDER BY checked_in_at ASC LIMIT 1) as first_weight,
|
|
(SELECT weight_kg FROM checkins WHERE user_id = u.id ORDER BY checked_in_at DESC LIMIT 1) as current_weight
|
|
FROM users u
|
|
WHERE (SELECT COUNT(*) FROM checkins WHERE user_id = u.id) > 0
|
|
AND u.is_private = FALSE
|
|
ORDER BY u.display_name
|
|
""")
|
|
|
|
names = []
|
|
pct_lost = []
|
|
kg_lost = []
|
|
|
|
for u in users:
|
|
start_w = float(u["starting_weight_kg"] or u["first_weight"] or 0)
|
|
current_w = float(u["current_weight"] or start_w)
|
|
if start_w > 0:
|
|
lost = start_w - current_w
|
|
pct = round((lost / start_w) * 100, 1)
|
|
else:
|
|
lost = 0
|
|
pct = 0
|
|
names.append(u["display_name"] or u["username"])
|
|
pct_lost.append(pct)
|
|
kg_lost.append(round(lost, 1))
|
|
|
|
return jsonify({
|
|
"names": names,
|
|
"pct_lost": pct_lost,
|
|
"kg_lost": kg_lost,
|
|
})
|
|
|
|
|
|
@bp.route("/weekly-change/<int:user_id>")
|
|
@login_required
|
|
def weekly_change(user_id):
|
|
"""Return weekly weight changes for bar chart."""
|
|
# Privacy guard: don't expose private user data to others
|
|
if user_id != session.get("user_id"):
|
|
target = query_one("SELECT is_private FROM users WHERE id = %s", (user_id,))
|
|
if target and target["is_private"]:
|
|
return jsonify({"labels": [], "changes": []})
|
|
|
|
checkins = query(
|
|
"""SELECT weight_kg, checked_in_at
|
|
FROM checkins WHERE user_id = %s
|
|
ORDER BY checked_in_at ASC""",
|
|
(user_id,),
|
|
)
|
|
|
|
if len(checkins) < 2:
|
|
return jsonify({"labels": [], "changes": []})
|
|
|
|
labels = []
|
|
changes = []
|
|
for i in range(1, len(checkins)):
|
|
prev_w = float(checkins[i - 1]["weight_kg"])
|
|
curr_w = float(checkins[i]["weight_kg"])
|
|
change = round(curr_w - prev_w, 1)
|
|
label = checkins[i]["checked_in_at"].replace(tzinfo=timezone.utc).astimezone(SYDNEY_TZ).strftime("%d %b")
|
|
labels.append(label)
|
|
changes.append(change)
|
|
|
|
return jsonify({"labels": labels, "changes": changes})
|