Paginate activity history and aggregate dashboard totals in SQL
This commit is contained in:
+25
-58
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user