split segment efforts and leaderboards by activity type

This commit is contained in:
Vincent van der Wal
2026-07-22 17:19:52 +02:00
parent 23a2b4b374
commit 512860c863
3 changed files with 117 additions and 29 deletions
+52 -19
View File
@@ -424,33 +424,44 @@ export function getSegment(id: number): SegmentRow | undefined {
return db.prepare('SELECT * FROM segments WHERE id = ?').get(id) as SegmentRow | undefined;
}
/** Per-user bests split by activity type - a run doesn't compete with a ride. */
export function segmentLeaderboard(segmentId: number): {
user_id: number;
username: string;
type: string;
best_s: number;
attempts: number;
date: string | null;
}[] {
return db
.prepare(
`SELECT e.user_id, u.username, min(e.elapsed_s) best_s, count(*) attempts,
(SELECT date FROM segment_efforts b
`SELECT e.user_id, u.username, a.type, min(e.elapsed_s) best_s, count(*) attempts,
(SELECT b.date FROM segment_efforts b
JOIN activities ab ON ab.id = b.activity_id
WHERE b.segment_id = e.segment_id AND b.user_id = e.user_id
AND ab.type = a.type AND b.elapsed_s IS NOT NULL
ORDER BY b.elapsed_s ASC LIMIT 1) date
FROM segment_efforts e JOIN users u ON u.id = e.user_id
FROM segment_efforts e
JOIN users u ON u.id = e.user_id
JOIN activities a ON a.id = e.activity_id
WHERE e.segment_id = ? AND e.elapsed_s IS NOT NULL
GROUP BY e.user_id ORDER BY best_s ASC`
GROUP BY e.user_id, a.type ORDER BY best_s ASC`
)
.all(segmentId) as {
user_id: number;
username: string;
type: string;
best_s: number;
attempts: number;
date: string | null;
}[];
}
/** All segments a user has completed, with their best time, attempts, and rank. */
/**
* All segments a user has completed, with their best time, attempts, and
* rank. Rank is computed within the activity type of the user's best effort,
* since leaderboards are split by type.
*/
export function userSegments(userId: number): {
id: number;
name: string;
@@ -458,20 +469,12 @@ export function userSegments(userId: number): {
elev_gain_m: number;
attempts: number;
best_s: number | null;
best_type: string | null;
rank: number | null;
}[] {
return db
const base = db
.prepare(
`SELECT s.id, s.name, s.distance_m, s.elev_gain_m,
count(e.id) attempts,
min(e.elapsed_s) best_s,
CASE WHEN min(e.elapsed_s) IS NULL THEN NULL ELSE (
SELECT count(*) + 1 FROM (
SELECT min(e2.elapsed_s) b FROM segment_efforts e2
WHERE e2.segment_id = s.id AND e2.elapsed_s IS NOT NULL AND e2.user_id != @userId
GROUP BY e2.user_id
) others WHERE others.b < min(e.elapsed_s)
) END rank
`SELECT s.id, s.name, s.distance_m, s.elev_gain_m, count(e.id) attempts
FROM segments s JOIN segment_efforts e ON e.segment_id = s.id AND e.user_id = @userId
GROUP BY s.id ORDER BY attempts DESC, s.name`
)
@@ -481,9 +484,37 @@ export function userSegments(userId: number): {
distance_m: number;
elev_gain_m: number;
attempts: number;
best_s: number | null;
rank: number | null;
}[];
const bestStmt = db.prepare(
`SELECT e.elapsed_s, a.type FROM segment_efforts e
JOIN activities a ON a.id = e.activity_id
WHERE e.segment_id = ? AND e.user_id = ? AND e.elapsed_s IS NOT NULL
ORDER BY e.elapsed_s ASC LIMIT 1`
);
const rankStmt = db.prepare(
`SELECT count(*) + 1 n FROM (
SELECT min(e2.elapsed_s) b FROM segment_efforts e2
JOIN activities a2 ON a2.id = e2.activity_id
WHERE e2.segment_id = ? AND a2.type = ? AND e2.elapsed_s IS NOT NULL AND e2.user_id != ?
GROUP BY e2.user_id
) others WHERE others.b < ?`
);
return base.map((segment) => {
const best = bestStmt.get(segment.id, userId) as
| { elapsed_s: number; type: string }
| undefined;
const rank = best
? (rankStmt.get(segment.id, best.type, userId, best.elapsed_s) as { n: number }).n
: null;
return {
...segment,
best_s: best?.elapsed_s ?? null,
best_type: best?.type ?? null,
rank
};
});
}
export function deleteSegment(id: number, userId: number): boolean {
@@ -502,12 +533,13 @@ export function segmentAllEfforts(segmentId: number): {
username: string;
activity_id: number;
activity_name: string;
type: string;
elapsed_s: number | null;
date: string | null;
}[] {
return db
.prepare(
`SELECT e.id, e.user_id, u.username, e.activity_id, a.name activity_name,
`SELECT e.id, e.user_id, u.username, e.activity_id, a.name activity_name, a.type,
e.elapsed_s, e.date
FROM segment_efforts e
JOIN users u ON u.id = e.user_id
@@ -521,6 +553,7 @@ export function segmentAllEfforts(segmentId: number): {
username: string;
activity_id: number;
activity_name: string;
type: string;
elapsed_s: number | null;
date: string | null;
}[];