segments with leaderboards, auto-matching, and popular-stretch suggestions
This commit is contained in:
@@ -66,6 +66,28 @@ db.exec(`
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_peaks_lat_zoom ON peaks(minzoom, lat, lon);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS segments (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
creator_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
points TEXT NOT NULL,
|
||||
bounds TEXT NOT NULL,
|
||||
distance_m REAL NOT NULL,
|
||||
elev_gain_m REAL NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS segment_efforts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
segment_id INTEGER NOT NULL REFERENCES segments(id) ON DELETE CASCADE,
|
||||
activity_id INTEGER NOT NULL REFERENCES activities(id) ON DELETE CASCADE,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
elapsed_s REAL,
|
||||
date TEXT,
|
||||
UNIQUE (segment_id, activity_id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_efforts_segment ON segment_efforts(segment_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ascents (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
@@ -366,6 +388,78 @@ export function recordAscent(
|
||||
return result.changes > 0;
|
||||
}
|
||||
|
||||
export interface SegmentRow {
|
||||
id: number;
|
||||
name: string;
|
||||
creator_id: number | null;
|
||||
points: string;
|
||||
bounds: string;
|
||||
distance_m: number;
|
||||
elev_gain_m: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export function listSegments(): (Omit<SegmentRow, 'points'> & {
|
||||
efforts: number;
|
||||
athletes: number;
|
||||
best_s: number | null;
|
||||
})[] {
|
||||
return db
|
||||
.prepare(
|
||||
`SELECT s.id, s.name, s.creator_id, s.bounds, s.distance_m, s.elev_gain_m, s.created_at,
|
||||
count(e.id) efforts,
|
||||
count(DISTINCT e.user_id) athletes,
|
||||
min(e.elapsed_s) best_s
|
||||
FROM segments s LEFT JOIN segment_efforts e ON e.segment_id = s.id
|
||||
GROUP BY s.id ORDER BY efforts DESC, s.id DESC`
|
||||
)
|
||||
.all() as (Omit<SegmentRow, 'points'> & {
|
||||
efforts: number;
|
||||
athletes: number;
|
||||
best_s: number | null;
|
||||
})[];
|
||||
}
|
||||
|
||||
export function getSegment(id: number): SegmentRow | undefined {
|
||||
return db.prepare('SELECT * FROM segments WHERE id = ?').get(id) as SegmentRow | undefined;
|
||||
}
|
||||
|
||||
export function segmentLeaderboard(segmentId: number): {
|
||||
user_id: number;
|
||||
username: 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
|
||||
WHERE b.segment_id = e.segment_id AND b.user_id = e.user_id
|
||||
ORDER BY b.elapsed_s ASC LIMIT 1) date
|
||||
FROM segment_efforts e JOIN users u ON u.id = e.user_id
|
||||
WHERE e.segment_id = ? AND e.elapsed_s IS NOT NULL
|
||||
GROUP BY e.user_id ORDER BY best_s ASC`
|
||||
)
|
||||
.all(segmentId) as {
|
||||
user_id: number;
|
||||
username: string;
|
||||
best_s: number;
|
||||
attempts: number;
|
||||
date: string | null;
|
||||
}[];
|
||||
}
|
||||
|
||||
export function segmentEffortsForActivity(activityId: number): { segment_id: number; name: string; elapsed_s: number | null }[] {
|
||||
return db
|
||||
.prepare(
|
||||
`SELECT e.segment_id, s.name, e.elapsed_s
|
||||
FROM segment_efforts e JOIN segments s ON s.id = e.segment_id
|
||||
WHERE e.activity_id = ? ORDER BY s.name`
|
||||
)
|
||||
.all(activityId) as { segment_id: number; name: string; elapsed_s: number | null }[];
|
||||
}
|
||||
|
||||
export function reachedPeaks(activityId: number, userId: number): (PeakRow & { climbed_at: string | null })[] {
|
||||
return db
|
||||
.prepare(
|
||||
|
||||
Reference in New Issue
Block a user