aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-08-28 10:19:15 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-08-28 10:19:15 +0200
commit10806900a46b4eba197af9fc768928c57bd33982 (patch)
treec4e69e242831dcdce962eb6e227a41557e126f14
parent843fd11774531a4fd22456093b112506cbffbc18 (diff)
Add touring ride type for cyclotourisme
-rw-r--r--db/migrations/20260829000000_add_touring_ride_type.sql55
-rw-r--r--db/schema.sql5
-rw-r--r--ride/category.go5
-rw-r--r--web/categories.go3
-rw-r--r--web/frontend/analytics-logic.test.ts2
-rw-r--r--web/frontend/analytics-logic.ts3
-rw-r--r--web/ride-filters.go1
-rw-r--r--web/ride-filters_test.go3
8 files changed, 69 insertions, 8 deletions
diff --git a/db/migrations/20260829000000_add_touring_ride_type.sql b/db/migrations/20260829000000_add_touring_ride_type.sql
new file mode 100644
index 0000000..dc1555d
--- /dev/null
+++ b/db/migrations/20260829000000_add_touring_ride_type.sql
@@ -0,0 +1,55 @@
+-- migrate:up
+CREATE TABLE rides_new (
+ id integer primary key,
+ external_id text unique not null,
+ gpx_path text not null,
+ name text not null,
+ type text not null,
+ start_date text not null,
+ distance_m real not null,
+ external_moving_time_s integer not null,
+ elapsed_time_s integer not null,
+ total_elevation_gain_m real not null,
+ average_speed_mps real not null,
+ created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
+ updated_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
+ cotacol_score real,
+ cotacol_algo_version text,
+ moving_time_s integer,
+ moving_time_algo_version text,
+ bike_type TEXT NOT NULL DEFAULT '' CHECK (bike_type IN ('', 'road', 'mtb')),
+ ride_type TEXT NOT NULL DEFAULT '' CHECK (ride_type IN ('', 'training', 'indoor', 'race', 'transport', 'touring')),
+ fit_path TEXT NOT NULL DEFAULT ''
+);
+INSERT INTO rides_new (id, external_id, gpx_path, name, type, start_date, distance_m, external_moving_time_s, elapsed_time_s, total_elevation_gain_m, average_speed_mps, created_at, updated_at, cotacol_score, cotacol_algo_version, moving_time_s, moving_time_algo_version, bike_type, ride_type, fit_path)
+SELECT id, external_id, gpx_path, name, type, start_date, distance_m, external_moving_time_s, elapsed_time_s, total_elevation_gain_m, average_speed_mps, created_at, updated_at, cotacol_score, cotacol_algo_version, moving_time_s, moving_time_algo_version, bike_type, ride_type, fit_path FROM rides;
+DROP TABLE rides;
+ALTER TABLE rides_new RENAME TO rides;
+
+-- migrate:down
+CREATE TABLE rides_new (
+ id integer primary key,
+ external_id text unique not null,
+ gpx_path text not null,
+ name text not null,
+ type text not null,
+ start_date text not null,
+ distance_m real not null,
+ external_moving_time_s integer not null,
+ elapsed_time_s integer not null,
+ total_elevation_gain_m real not null,
+ average_speed_mps real not null,
+ created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
+ updated_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
+ cotacol_score real,
+ cotacol_algo_version text,
+ moving_time_s integer,
+ moving_time_algo_version text,
+ bike_type TEXT NOT NULL DEFAULT '' CHECK (bike_type IN ('', 'road', 'mtb')),
+ ride_type TEXT NOT NULL DEFAULT '' CHECK (ride_type IN ('', 'training', 'indoor', 'race', 'transport')),
+ fit_path TEXT NOT NULL DEFAULT ''
+);
+INSERT INTO rides_new (id, external_id, gpx_path, name, type, start_date, distance_m, external_moving_time_s, elapsed_time_s, total_elevation_gain_m, average_speed_mps, created_at, updated_at, cotacol_score, cotacol_algo_version, moving_time_s, moving_time_algo_version, bike_type, ride_type, fit_path)
+SELECT id, external_id, gpx_path, name, type, start_date, distance_m, external_moving_time_s, elapsed_time_s, total_elevation_gain_m, average_speed_mps, created_at, updated_at, cotacol_score, cotacol_algo_version, moving_time_s, moving_time_algo_version, bike_type, ride_type, fit_path FROM rides WHERE ride_type IN ('', 'training', 'indoor', 'race', 'transport');
+DROP TABLE rides;
+ALTER TABLE rides_new RENAME TO rides;
diff --git a/db/schema.sql b/db/schema.sql
index a22e98f..79f4c62 100644
--- a/db/schema.sql
+++ b/db/schema.sql
@@ -13,7 +13,7 @@ CREATE TABLE rides (
average_speed_mps real not null,
created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
updated_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
-, cotacol_score real, cotacol_algo_version text, moving_time_s integer, moving_time_algo_version text, bike_type TEXT NOT NULL DEFAULT '' CHECK (bike_type IN ('', 'road', 'mtb')), ride_type TEXT NOT NULL DEFAULT '' CHECK (ride_type IN ('', 'training', 'indoor', 'race', 'transport')), fit_path TEXT NOT NULL DEFAULT '');
+, cotacol_score real, cotacol_algo_version text, moving_time_s integer, moving_time_algo_version text, bike_type TEXT NOT NULL DEFAULT '' CHECK (bike_type IN ('', 'road', 'mtb')), ride_type TEXT NOT NULL DEFAULT '' CHECK (ride_type IN ('', 'training', 'indoor', 'race', 'transport', 'touring')), fit_path TEXT NOT NULL DEFAULT '');
CREATE TABLE official_climbs (
id integer primary key,
name text not null,
@@ -43,4 +43,5 @@ INSERT INTO "schema_migrations" (version) VALUES
('20260821000000'),
('20260823000000'),
('20260827000000'),
- ('20260828000000');
+ ('20260828000000'),
+ ('20260829000000');
diff --git a/ride/category.go b/ride/category.go
index 0eafcc0..6a3431c 100644
--- a/ride/category.go
+++ b/ride/category.go
@@ -14,6 +14,7 @@ const (
RideTypeIndoor RideType = "indoor"
RideTypeRace RideType = "race"
RideTypeTransport RideType = "transport"
+ RideTypeTouring RideType = "touring"
)
var validBikeTypes = map[BikeType]struct{}{
@@ -26,10 +27,10 @@ var validRideTypes = map[RideType]struct{}{
RideTypeIndoor: {},
RideTypeRace: {},
RideTypeTransport: {},
+ RideTypeTouring: {},
}
-var AllBikeTypes = []BikeType{BikeTypeRoad, BikeTypeMTB}
-var AllRideTypes = []RideType{RideTypeTraining, RideTypeIndoor, RideTypeRace, RideTypeTransport}
+var AllRideTypes = []RideType{RideTypeTraining, RideTypeIndoor, RideTypeRace, RideTypeTransport, RideTypeTouring}
func IsValidBikeType(value string) bool {
_, ok := validBikeTypes[BikeType(value)]
diff --git a/web/categories.go b/web/categories.go
index a76fa1c..74d871c 100644
--- a/web/categories.go
+++ b/web/categories.go
@@ -53,7 +53,7 @@ func rideCategoriesUpdateURL(id int64) string {
}
var bikeTypeOptions = []ride.BikeType{ride.BikeTypeRoad, ride.BikeTypeMTB}
-var rideTypeOptions = []ride.RideType{ride.RideTypeTraining, ride.RideTypeIndoor, ride.RideTypeRace, ride.RideTypeTransport}
+var rideTypeOptions = []ride.RideType{ride.RideTypeTraining, ride.RideTypeIndoor, ride.RideTypeRace, ride.RideTypeTransport, ride.RideTypeTouring}
var bikeTypeLabels = map[ride.BikeType]string{
ride.BikeTypeRoad: "Road",
@@ -65,6 +65,7 @@ var rideTypeLabels = map[ride.RideType]string{
ride.RideTypeIndoor: "Indoor",
ride.RideTypeRace: "Race",
ride.RideTypeTransport: "Transport",
+ ride.RideTypeTouring: "Touring",
}
func formatCategoryOrEmpty(value string) string {
diff --git a/web/frontend/analytics-logic.test.ts b/web/frontend/analytics-logic.test.ts
index 0d1f789..e5c6caf 100644
--- a/web/frontend/analytics-logic.test.ts
+++ b/web/frontend/analytics-logic.test.ts
@@ -71,7 +71,7 @@ test("empty ride and bike types land in an Unknown group", () => {
const points = [makePoint({ rideType: "" }), makePoint({ rideType: "race" })];
assert.deepEqual(groupRidePoints(points, "rideType"), [
{ label: "Race", count: 1, colorIndex: 2 },
- { label: "Unknown", count: 1, colorIndex: 4 },
+ { label: "Unknown", count: 1, colorIndex: 5 },
]);
const bikes = [makePoint({ bikeType: "" }), makePoint({ bikeType: "road" })];
assert.deepEqual(groupRidePoints(bikes, "bikeType"), [
diff --git a/web/frontend/analytics-logic.ts b/web/frontend/analytics-logic.ts
index 5da8b80..fe1a9f9 100644
--- a/web/frontend/analytics-logic.ts
+++ b/web/frontend/analytics-logic.ts
@@ -65,6 +65,7 @@ const rideTypeLabels: Record<string, string> = {
indoor: "Indoor",
race: "Race",
transport: "Transport",
+ touring: "Touring",
};
const bikeTypeLabels: Record<string, string> = {
@@ -73,7 +74,7 @@ const bikeTypeLabels: Record<string, string> = {
mtb: "MTB",
};
-const rideTypeOrder = ["training", "indoor", "race", "transport"];
+const rideTypeOrder = ["training", "indoor", "race", "transport", "touring"];
const bikeTypeOrder = ["road", "mtb"];
export const analyticsRideTypeLabel = (value: string): string => rideTypeLabels[value] ?? value;
diff --git a/web/ride-filters.go b/web/ride-filters.go
index 8535756..db925d9 100644
--- a/web/ride-filters.go
+++ b/web/ride-filters.go
@@ -239,6 +239,7 @@ func defaultRideTypes() []ride.RideType {
ride.RideTypeTraining,
ride.RideTypeIndoor,
ride.RideTypeRace,
+ ride.RideTypeTouring,
}
}
diff --git a/web/ride-filters_test.go b/web/ride-filters_test.go
index 84c6417..b7abcf4 100644
--- a/web/ride-filters_test.go
+++ b/web/ride-filters_test.go
@@ -121,11 +121,12 @@ func TestFilterRidesDefaultsToAllRideTypesExceptTransport(t *testing.T) {
{Name: "race", BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeRace, GPXPath: "b.gpx"},
{Name: "indoor", BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeIndoor, GPXPath: "c.gpx"},
{Name: "transport", BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTransport, GPXPath: "d.gpx"},
+ {Name: "touring", BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTouring, GPXPath: "e.gpx"},
}
filtered := filterRides(items, parseRideFilters(url.Values{}))
- require.Len(t, filtered, 3)
+ require.Len(t, filtered, 4)
assert.False(t, parseRideFilters(url.Values{}).Active())
for _, item := range filtered {
assert.NotEqual(t, ride.RideTypeTransport, item.RideType)