peak search, denser catalog, profiles, filename parsing, themed map controls

This commit is contained in:
Vincent van der Wal
2026-07-22 12:07:01 +02:00
parent b288daf251
commit b89de57967
17 changed files with 767 additions and 64 deletions
+63 -3
View File
@@ -72,6 +72,14 @@ db.exec(`
CREATE INDEX IF NOT EXISTS idx_ascents_user ON ascents(user_id);
`);
// lightweight migrations for columns added after the initial schema
const userCols = (db.prepare('PRAGMA table_info(users)').all() as { name: string }[]).map(
(c) => c.name
);
for (const col of ['bio', 'location', 'avatar']) {
if (!userCols.includes(col)) db.exec(`ALTER TABLE users ADD COLUMN ${col} TEXT`);
}
// Seed the catalog with the curated list while no OSM import has run.
// Seed rows use osm_id "seed:<name>"; scripts/import-peaks.js upgrades them
// to real OSM nodes (preserving ascents) and fills in the rest of the Alps.
@@ -181,6 +189,58 @@ export function listAllActivities(): (Omit<ActivityRow, 'points'> & { username:
.all() as (Omit<ActivityRow, 'points'> & { username: string })[];
}
export interface ProfileRow {
id: number;
username: string;
created_at: string;
bio: string | null;
location: string | null;
avatar: string | null;
}
export function getProfile(username: string): ProfileRow | undefined {
return db
.prepare('SELECT id, username, created_at, bio, location, avatar FROM users WHERE username = ?')
.get(username) as ProfileRow | undefined;
}
export function updateProfile(
userId: number,
fields: { bio: string | null; location: string | null; avatar?: string }
): void {
if (fields.avatar !== undefined) {
db.prepare('UPDATE users SET bio = ?, location = ?, avatar = ? WHERE id = ?').run(
fields.bio,
fields.location,
fields.avatar,
userId
);
} else {
db.prepare('UPDATE users SET bio = ?, location = ? WHERE id = ?').run(
fields.bio,
fields.location,
userId
);
}
}
export function userStats(userId: number): {
activities: number;
distance_m: number;
elev_gain_m: number;
moving_s: number;
} {
return db
.prepare(
`SELECT count(*) activities,
coalesce(sum(distance_m), 0) distance_m,
coalesce(sum(elev_gain_m), 0) elev_gain_m,
coalesce(sum(coalesce(moving_s, duration_s, 0)), 0) moving_s
FROM activities WHERE user_id = ?`
)
.get(userId) as { activities: number; distance_m: number; elev_gain_m: number; moving_s: number };
}
export function communityTotals(): {
users: number;
activities: number;
@@ -207,17 +267,17 @@ export function peaksInView(
userId: number,
bbox: { minLat: number; minLon: number; maxLat: number; maxLon: number },
zoom: number,
limit = 300
limit = 1000
): (PeakRow & { climbed_at: string | null; ascent_activity_id: number | null })[] {
return db
.prepare(
`SELECT p.*, a.climbed_at, a.activity_id ascent_activity_id
FROM peaks p
LEFT JOIN ascents a ON a.peak_id = p.id AND a.user_id = @userId
WHERE p.minzoom <= @zoom
WHERE (p.minzoom <= @zoom OR a.climbed_at IS NOT NULL)
AND p.lat BETWEEN @minLat AND @maxLat
AND p.lon BETWEEN @minLon AND @maxLon
ORDER BY p.score DESC
ORDER BY (a.climbed_at IS NOT NULL) DESC, p.score DESC
LIMIT @limit`
)
.all({ userId, zoom: Math.floor(zoom), ...bbox, limit }) as (PeakRow & {