perf: parallelize DB queries across routes, clean up fitness UI

Parallelize sequential DB queries in 11 API routes and page loaders
using Promise.all — measurements/latest, stats/overview, goal streak,
exercises, sessions, task stats, monthly expenses, icon page, offline-db.

Move calorie tracking out of /fitness/measure (now under /fitness/nutrition
only). Remove fade-in entrance animations from nutrition page.

Progressive streak computation: scan 3 months first, widen only if needed.

Bump versions to 1.1.1 / 0.2.1.
This commit is contained in:
2026-04-06 13:12:02 +02:00
parent 09cd410eaa
commit 201847400e
18 changed files with 147 additions and 254 deletions

View File

@@ -16,31 +16,13 @@ export const GET: RequestHandler = async ({ locals }) => {
const tenWeeksAgo = new Date();
tenWeeksAgo.setDate(tenWeeksAgo.getDate() - 70);
const totalWorkouts = await WorkoutSession.countDocuments({ createdBy: user.nickname });
const weeklyAgg = await WorkoutSession.aggregate([
{
$match: {
createdBy: user.nickname,
startTime: { $gte: tenWeeksAgo }
}
},
{
$group: {
_id: {
year: { $isoWeekYear: '$startTime' },
week: { $isoWeek: '$startTime' }
},
count: { $sum: 1 }
}
},
{
$sort: { '_id.year': 1, '_id.week': 1 }
}
]);
// Fetch user demographics for kcal estimation
const [goal, latestMeasurement] = await Promise.all([
const [totalWorkouts, weeklyAgg, goal, latestMeasurement] = await Promise.all([
WorkoutSession.countDocuments({ createdBy: user.nickname }),
WorkoutSession.aggregate([
{ $match: { createdBy: user.nickname, startTime: { $gte: tenWeeksAgo } } },
{ $group: { _id: { year: { $isoWeekYear: '$startTime' }, week: { $isoWeek: '$startTime' } }, count: { $sum: 1 } } },
{ $sort: { '_id.year': 1, '_id.week': 1 } }
]),
FitnessGoal.findOne({ username: user.nickname }).lean() as any,
BodyMeasurement.findOne(
{ createdBy: user.nickname, weight: { $ne: null } },
@@ -57,10 +39,19 @@ export const GET: RequestHandler = async ({ locals }) => {
// Lifetime totals: tonnage lifted + cardio km + kcal estimate
// Use stored kcalEstimate when available; fall back to on-the-fly for legacy sessions
const allSessions = await WorkoutSession.find(
{ createdBy: user.nickname },
{ 'exercises.exerciseId': 1, 'exercises.sets': 1, 'exercises.totalDistance': 1, kcalEstimate: 1 }
).lean();
const DISPLAY_LIMIT = 30;
const SMA_LOOKBACK = 6; // w - 1 where w = 7 max
const [allSessions, weightMeasurements] = await Promise.all([
WorkoutSession.find(
{ createdBy: user.nickname },
{ 'exercises.exerciseId': 1, 'exercises.sets': 1, 'exercises.totalDistance': 1, kcalEstimate: 1 }
).lean(),
BodyMeasurement.find(
{ createdBy: user.nickname, weight: { $ne: null } },
{ date: 1, weight: 1, _id: 0 }
).sort({ date: -1 }).limit(DISPLAY_LIMIT + SMA_LOOKBACK).lean()
]);
weightMeasurements.reverse(); // back to chronological order
let totalTonnage = 0;
let totalCardioKm = 0;
@@ -146,18 +137,6 @@ export const GET: RequestHandler = async ({ locals }) => {
upper: totalKcal + combinedMargin,
};
// Fetch extra measurements beyond the display limit to fill the SMA lookback window
const DISPLAY_LIMIT = 30;
const SMA_LOOKBACK = 6; // w - 1 where w = 7 max
const weightMeasurements = await BodyMeasurement.find(
{ createdBy: user.nickname, weight: { $ne: null } },
{ date: 1, weight: 1, _id: 0 }
)
.sort({ date: -1 })
.limit(DISPLAY_LIMIT + SMA_LOOKBACK)
.lean();
weightMeasurements.reverse(); // back to chronological order
// Split into lookback-only (not displayed) and display portions
const displayStart = Math.max(0, weightMeasurements.length - DISPLAY_LIMIT);