Paginate activity history and aggregate dashboard totals in SQL

This commit is contained in:
Peter Stockings
2026-10-02 14:36:35 +10:00
parent a300ea007d
commit 5830976e31
11 changed files with 386 additions and 65 deletions
+25 -58
View File
@@ -1,10 +1,9 @@
import hashlib
import re
import secrets
from collections import defaultdict
from datetime import UTC, datetime, timedelta
from datetime import UTC, datetime
from pathlib import Path
from zoneinfo import ZoneInfo, available_timezones
from zoneinfo import available_timezones
from flask import (
Blueprint,
@@ -27,8 +26,8 @@ from garmin_sync.service import SyncError, atomic_write
from .analytics import pace_text
from .display import stored_analysis
from .extensions import db, limiter
from .history import activity_page, latest_mapped_run, overview, records_for
from .models import Activity, Friendship, GarminConnection, User
from .records import record_book
from .services import activity_data, import_cache, import_upload, route_data
web = Blueprint("web", __name__)
@@ -75,36 +74,6 @@ def are_friends(first, second):
)
def latest_mapped_run(rows):
return next(
(row for row in rows if row.sport == "running" and row.has_route),
None,
)
def overview(rows, timezone="Australia/Sydney"):
total_distance = sum(r.distance_m for r in rows)
total_time = sum(r.duration_s for r in rows)
latest = max((r.started_at for r in rows), default=datetime.now(UTC))
latest_day = latest.replace(tzinfo=UTC).astimezone(ZoneInfo(timezone)).date()
monday = latest_day - timedelta(days=latest_day.weekday())
weeks = [monday - timedelta(weeks=n) for n in reversed(range(8))]
distances = defaultdict(float)
for row in rows:
day = row.started_at.replace(tzinfo=UTC).astimezone(ZoneInfo(timezone)).date()
distances[day - timedelta(days=day.weekday())] += row.distance_km
seconds = round(total_time / (total_distance / 1000)) if total_distance else None
return {
"distance": total_distance / 1000,
"duration": total_time,
"count": len(rows),
"elevation": sum(r.elevation_m for r in rows),
"pace": f"{seconds // 60}:{seconds % 60:02d}" if seconds is not None else "—",
"weeks": [w.strftime("%d %b") for w in weeks],
"weekly_km": [round(distances[w], 2) for w in weeks],
}
@web.get("/health")
def health():
db.session.execute(db.text("SELECT 1"))
@@ -208,17 +177,18 @@ def logout():
@web.get("/dashboard")
@login_required
def dashboard():
rows = db.session.scalars(
db.select(Activity).filter_by(owner_id=current_user.id).order_by(Activity.started_at.desc())
).all()
filters = [Activity.owner_id == current_user.id]
stats = overview(filters, current_user.timezone)
pagination = activity_page(filters, total=stats["count"])
return render_template(
"dashboard.html",
rows=rows,
stats=overview(rows, current_user.timezone),
rows=pagination.items,
pagination=pagination,
stats=stats,
profile=current_user,
personal=True,
record_book=record_book(rows),
mapped_run=latest_mapped_run(rows),
record_book=records_for(current_user.id, {row.id for row in pagination.items}),
mapped_run=latest_mapped_run(filters),
)
@@ -257,13 +227,12 @@ def friends():
for link in links
]
accepted_ids = [p["person"].id for p in people if p["link"].status == "accepted"]
rows = db.session.scalars(
db.select(Activity)
.where(Activity.owner_id.in_(accepted_ids), Activity.visibility == "friends")
.order_by(Activity.started_at.desc())
.limit(50)
).all()
return render_template("friends.html", people=people, rows=rows)
pagination = activity_page(
[Activity.owner_id.in_(accepted_ids), Activity.visibility == "friends"]
)
return render_template(
"friends.html", people=people, rows=pagination.items, pagination=pagination
)
@web.post("/friends/<int:friendship_id>/<action>")
@@ -290,18 +259,17 @@ def profile(username):
user = db.first_or_404(db.select(User).filter_by(username=username))
if user.id != current_user.id and not are_friends(current_user.id, user.id):
abort(404)
rows = db.session.scalars(
db.select(Activity)
.filter_by(owner_id=user.id, visibility="friends")
.order_by(Activity.started_at.desc())
).all()
filters = [Activity.owner_id == user.id, Activity.visibility == "friends"]
stats = overview(filters, user.timezone)
pagination = activity_page(filters, total=stats["count"])
return render_template(
"dashboard.html",
rows=rows,
stats=overview(rows, user.timezone),
rows=pagination.items,
pagination=pagination,
stats=stats,
profile=user,
personal=False,
mapped_run=latest_mapped_run(rows),
mapped_run=latest_mapped_run(filters),
)
@@ -311,8 +279,7 @@ def activity(activity_id):
analysis = stored_analysis(row)
efforts = []
if owner:
history = db.session.scalars(db.select(Activity).filter_by(owner_id=row.owner_id)).all()
efforts = record_book(history)["runs"].get(row.id, [])
efforts = records_for(row.owner_id, {row.id})["runs"].get(row.id, [])
return render_template(
"activity.html",
activity=row,