From f3abb4781bf6fc0bdc89ef221a17a98cc9ae4c57 Mon Sep 17 00:00:00 2001 From: Peter Stockings Date: Tue, 24 Feb 2026 21:12:45 +1100 Subject: [PATCH] Show daily streak count on leaderboard as well --- app/routes/dashboard.py | 42 +--------------------------------- app/routes/leaderboard.py | 3 +++ app/templates/leaderboard.html | 3 +++ app/utils.py | 42 ++++++++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 41 deletions(-) create mode 100644 app/utils.py diff --git a/app/routes/dashboard.py b/app/routes/dashboard.py index 9c5f0d3..b09c6a7 100644 --- a/app/routes/dashboard.py +++ b/app/routes/dashboard.py @@ -1,51 +1,11 @@ from flask import Blueprint, render_template from app.auth import login_required, get_current_user from app.db import query, query_one -from app import SYDNEY_TZ -from datetime import datetime, timezone, timedelta +from app.utils import calculate_streak bp = Blueprint("dashboard", __name__) -def calculate_streak(user_id): - """Calculate current and best consecutive-day check-in streaks.""" - rows = query( - """SELECT DISTINCT (checked_in_at AT TIME ZONE 'UTC' AT TIME ZONE 'Australia/Sydney')::date AS d - FROM checkins WHERE user_id = %s ORDER BY d DESC""", - (user_id,), - ) - if not rows: - return {"current": 0, "best": 0} - - days = [r["d"] for r in rows] - today = datetime.now(SYDNEY_TZ).date() - - # Current streak: must include today or yesterday to count - current = 0 - expected = today - if days[0] == today or days[0] == today - timedelta(days=1): - expected = days[0] - for d in days: - if d == expected: - current += 1 - expected -= timedelta(days=1) - else: - break - - # Best streak - best = 1 - run = 1 - for i in range(1, len(days)): - if days[i] == days[i - 1] - timedelta(days=1): - run += 1 - best = max(best, run) - else: - run = 1 - - best = max(best, current) - return {"current": current, "best": best} - - @bp.route("/") @login_required def index(): diff --git a/app/routes/leaderboard.py b/app/routes/leaderboard.py index af4f412..ef6c177 100644 --- a/app/routes/leaderboard.py +++ b/app/routes/leaderboard.py @@ -2,6 +2,7 @@ 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 datetime import timezone bp = Blueprint("leaderboard", __name__) @@ -46,11 +47,13 @@ def index(): total_to_lose = start_w - goal goal_progress = min(100, round((weight_lost / total_to_lose) * 100, 1)) if total_to_lose > 0 else 0 + streak = calculate_streak(u["id"]) ranked.append({ **u, "weight_lost": round(weight_lost, 1), "pct_lost": pct_lost, "goal_progress": goal_progress, + "streak": streak["current"], }) # Sort by % lost (descending) diff --git a/app/templates/leaderboard.html b/app/templates/leaderboard.html index 430851f..3d08de2 100644 --- a/app/templates/leaderboard.html +++ b/app/templates/leaderboard.html @@ -56,6 +56,7 @@ Lost (%) Goal Progress Check-ins + Streak @@ -96,6 +97,8 @@ {% endif %} {{ u.total_checkins }} + {% if u.streak > 0 %}🔥 {{ u.streak }}d{% else + %}—{% endif %} {% endfor %} diff --git a/app/utils.py b/app/utils.py new file mode 100644 index 0000000..729a186 --- /dev/null +++ b/app/utils.py @@ -0,0 +1,42 @@ +from app.db import query +from app import SYDNEY_TZ +from datetime import datetime, timedelta + + +def calculate_streak(user_id): + """Calculate current and best consecutive-day check-in streaks.""" + rows = query( + """SELECT DISTINCT (checked_in_at AT TIME ZONE 'UTC' AT TIME ZONE 'Australia/Sydney')::date AS d + FROM checkins WHERE user_id = %s ORDER BY d DESC""", + (user_id,), + ) + if not rows: + return {"current": 0, "best": 0} + + days = [r["d"] for r in rows] + today = datetime.now(SYDNEY_TZ).date() + + # Current streak: must include today or yesterday to count + current = 0 + expected = today + if days[0] == today or days[0] == today - timedelta(days=1): + expected = days[0] + for d in days: + if d == expected: + current += 1 + expected -= timedelta(days=1) + else: + break + + # Best streak + best = 1 + run = 1 + for i in range(1, len(days)): + if days[i] == days[i - 1] - timedelta(days=1): + run += 1 + best = max(best, run) + else: + run = 1 + + best = max(best, current) + return {"current": current, "best": best}