Refactor codebase

This commit is contained in:
Peter Stockings
2026-02-24 21:23:14 +11:00
parent f3abb4781b
commit 56168a182b
11 changed files with 191 additions and 127 deletions

View File

@@ -1,9 +1,12 @@
from flask import Blueprint, jsonify, request, session
from app import SYDNEY_TZ
from app.auth import login_required
from app.db import query, query_one
from collections import OrderedDict
from datetime import datetime, timezone
from flask import Blueprint, jsonify, request, session
from app.config import SYDNEY_TZ
from app.auth import login_required, privacy_guard
from app.db import query
from app.utils import calculate_weight_change
bp = Blueprint("api", __name__, url_prefix="/api")
# Distinct hues for up to 12 users; cycles if more
@@ -58,7 +61,6 @@ def progress_over_time():
""", params)
# Group rows by user
from collections import OrderedDict
users_map = OrderedDict()
for r in rows:
uid = r["user_id"]
@@ -90,7 +92,6 @@ def progress_over_time():
points = entry["data"]
best_fit = {"slope": 0, "intercept": 0}
if len(points) >= 2:
# Convert dates to day offsets from first point
base = datetime.strptime(points[0]["date"], "%Y-%m-%d")
xs = [(datetime.strptime(p["date"], "%Y-%m-%d") - base).days for p in points]
ys = [p["weight"] for p in points]
@@ -118,14 +119,9 @@ def progress_over_time():
@bp.route("/chart-data/<int:user_id>")
@login_required
@privacy_guard
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
@@ -169,15 +165,10 @@ def comparison():
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
lost, pct = calculate_weight_change(start_w, current_w)
names.append(u["display_name"] or u["username"])
pct_lost.append(pct)
kg_lost.append(round(lost, 1))
kg_lost.append(lost)
return jsonify({
"names": names,
@@ -188,14 +179,9 @@ def comparison():
@bp.route("/weekly-change/<int:user_id>")
@login_required
@privacy_guard
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

View File

@@ -1,6 +1,7 @@
from flask import Blueprint, render_template, request, redirect, url_for, session, flash
from werkzeug.security import generate_password_hash, check_password_hash
from app.db import query_one, execute_returning, execute
from app.db import query_one, execute_returning
from app.utils import parse_profile_fields
bp = Blueprint("auth", __name__)
@@ -10,13 +11,7 @@ def signup():
if request.method == "POST":
username = request.form.get("username", "").strip()
password = request.form.get("password", "")
display_name = request.form.get("display_name", "").strip()
height_cm = request.form.get("height_cm") or None
age = request.form.get("age") or None
gender = request.form.get("gender") or None
goal_weight_kg = request.form.get("goal_weight_kg") or None
starting_weight_kg = request.form.get("starting_weight_kg") or None
is_private = request.form.get("is_private") == "on"
fields = parse_profile_fields(request.form)
# Validation
if not username or not password:
@@ -38,7 +33,11 @@ def signup():
user = execute_returning(
"""INSERT INTO users (username, password_hash, display_name, height_cm, age, gender, goal_weight_kg, starting_weight_kg, is_private)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id""",
(username, password_hash, display_name or username, height_cm, age, gender, goal_weight_kg, starting_weight_kg, is_private),
(username, password_hash,
fields["display_name"] or username,
fields["height_cm"], fields["age"], fields["gender"],
fields["goal_weight_kg"], fields["starting_weight_kg"],
fields["is_private"]),
)
session["user_id"] = user["id"]

View File

@@ -1,57 +1,11 @@
import math
from flask import Blueprint, render_template, request, redirect, url_for, flash
from app.auth import login_required, get_current_user
from app.db import query, query_one, execute, execute_returning
from app.utils import calculate_bmi, check_milestones
bp = Blueprint("checkin", __name__)
def calculate_bmi(weight_kg, height_cm):
"""Calculate BMI from weight (kg) and height (cm)."""
if not weight_kg or not height_cm or float(height_cm) == 0:
return None
h_m = float(height_cm) / 100.0
return round(float(weight_kg) / (h_m * h_m), 1)
def check_milestones(user_id, user):
"""Check and award any new milestones after a check-in."""
checkins = query(
"SELECT weight_kg, checked_in_at FROM checkins WHERE user_id = %s ORDER BY checked_in_at ASC",
(user_id,),
)
if not checkins:
return
starting = float(user.get("starting_weight_kg") or checkins[0]["weight_kg"])
current = float(checkins[-1]["weight_kg"])
total_lost = starting - current
count = len(checkins)
milestone_checks = [
("first_checkin", count >= 1),
("5_checkins", count >= 5),
("10_checkins", count >= 10),
("25_checkins", count >= 25),
("lost_1kg", total_lost >= 1),
("lost_2kg", total_lost >= 2),
("lost_5kg", total_lost >= 5),
("lost_10kg", total_lost >= 10),
("lost_15kg", total_lost >= 15),
("lost_20kg", total_lost >= 20),
]
for key, achieved in milestone_checks:
if achieved:
try:
execute(
"INSERT INTO milestones (user_id, milestone_key) VALUES (%s, %s) ON CONFLICT DO NOTHING",
(user_id, key),
)
except Exception:
pass
@bp.route("/checkin", methods=["GET"])
@login_required
def index():
@@ -88,7 +42,6 @@ def create():
(user["id"], weight_kg, bmi, notes or None),
)
# Check milestones
check_milestones(user["id"], user)
# If HTMX request, return just the new row

View File

@@ -1,7 +1,7 @@
from flask import Blueprint, render_template
from app.auth import login_required, get_current_user
from app.db import query, query_one
from app.utils import calculate_streak
from app.utils import calculate_streak, calculate_weight_change
bp = Blueprint("dashboard", __name__)
@@ -32,11 +32,11 @@ def index():
weight_change = None
weight_change_pct = None
if latest and first_checkin:
start_w = float(first_checkin["weight_kg"])
current_w = float(latest["weight_kg"])
weight_change = round(current_w - start_w, 1)
if start_w > 0:
weight_change_pct = round((weight_change / start_w) * 100, 1)
kg_lost, pct_lost = calculate_weight_change(
first_checkin["weight_kg"], latest["weight_kg"]
)
weight_change = round(-kg_lost, 1) # negative = gained, positive = lost
weight_change_pct = round(-pct_lost, 1)
# Recent check-ins (last 5)
recent_checkins = query(
@@ -75,4 +75,3 @@ def index():
milestones=milestones,
streak=streak,
)

View File

@@ -1,8 +1,8 @@
from flask import Blueprint, render_template
from app.auth import login_required
from app.db import query, query_one
from app import SYDNEY_TZ
from app.utils import calculate_streak
from app.config import SYDNEY_TZ
from app.utils import calculate_streak, calculate_weight_change
from datetime import timezone
bp = Blueprint("leaderboard", __name__)
@@ -33,13 +33,7 @@ def index():
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:
weight_lost = start_w - current_w
pct_lost = round((weight_lost / start_w) * 100, 1)
else:
weight_lost = 0
pct_lost = 0
weight_lost, pct_lost = calculate_weight_change(start_w, current_w)
goal = float(u["goal_weight_kg"]) if u["goal_weight_kg"] else None
goal_progress = None
@@ -50,7 +44,7 @@ def index():
streak = calculate_streak(u["id"])
ranked.append({
**u,
"weight_lost": round(weight_lost, 1),
"weight_lost": weight_lost,
"pct_lost": pct_lost,
"goal_progress": goal_progress,
"streak": streak["current"],

View File

@@ -1,6 +1,7 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash
from app.auth import login_required, get_current_user
from app.db import execute
from app.utils import parse_profile_fields
bp = Blueprint("profile", __name__)
@@ -16,21 +17,17 @@ def index():
@login_required
def update():
user = get_current_user()
display_name = request.form.get("display_name", "").strip()
height_cm = request.form.get("height_cm") or None
age = request.form.get("age") or None
gender = request.form.get("gender") or None
goal_weight_kg = request.form.get("goal_weight_kg") or None
starting_weight_kg = request.form.get("starting_weight_kg") or None
is_private = request.form.get("is_private") == "on"
fields = parse_profile_fields(request.form)
execute(
"""UPDATE users
SET display_name = %s, height_cm = %s, age = %s, gender = %s,
goal_weight_kg = %s, starting_weight_kg = %s, is_private = %s
WHERE id = %s""",
(display_name or user["username"], height_cm, age, gender,
goal_weight_kg, starting_weight_kg, is_private, user["id"]),
(fields["display_name"] or user["username"],
fields["height_cm"], fields["age"], fields["gender"],
fields["goal_weight_kg"], fields["starting_weight_kg"],
fields["is_private"], user["id"]),
)
if request.headers.get("HX-Request"):