aboutsummaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/ride-filters.go4
-rw-r--r--web/ride-filters_test.go10
-rw-r--r--web/server.go4
-rw-r--r--web/server_test.go21
-rw-r--r--web/templates.templ4
-rw-r--r--web/templates_templ.go14
-rw-r--r--web/views.go1
7 files changed, 39 insertions, 19 deletions
diff --git a/web/ride-filters.go b/web/ride-filters.go
index 189a72e..3b567f5 100644
--- a/web/ride-filters.go
+++ b/web/ride-filters.go
@@ -271,10 +271,10 @@ func rideMatchesFilters(item rides.Ride, filters RideFilters) bool {
if filters.DistanceMaxKm != nil && item.DistanceM > float64(*filters.DistanceMaxKm)*1000 {
return false
}
- if filters.DurationMinMinutes != nil && item.MovingTimeS < int64(*filters.DurationMinMinutes)*60 {
+ if filters.DurationMinMinutes != nil && item.EffectiveMovingTimeS() < int64(*filters.DurationMinMinutes)*60 {
return false
}
- if filters.DurationMaxMinutes != nil && item.MovingTimeS > int64(*filters.DurationMaxMinutes)*60 {
+ if filters.DurationMaxMinutes != nil && item.EffectiveMovingTimeS() > int64(*filters.DurationMaxMinutes)*60 {
return false
}
startDate := item.StartDate.UTC()
diff --git a/web/ride-filters_test.go b/web/ride-filters_test.go
index c7b9133..8b470b6 100644
--- a/web/ride-filters_test.go
+++ b/web/ride-filters_test.go
@@ -89,11 +89,11 @@ func TestParseRideFiltersTreatsFullRangesAsInactive(t *testing.T) {
func TestFilterRidesUsesInclusiveBoundsAndMovingTime(t *testing.T) {
items := []rides.Ride{
- {Name: "lower boundary", DistanceM: 20_000, MovingTimeS: 1_800, StartDate: time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC), GPXPath: "lower.gpx"},
- {Name: "upper boundary", DistanceM: 100_000, MovingTimeS: 10_800, StartDate: time.Date(2026, 8, 31, 12, 0, 0, 0, time.UTC), GPXPath: "upper.gpx"},
- {Name: "outside distance", DistanceM: 110_000, MovingTimeS: 10_800, StartDate: time.Date(2026, 8, 31, 12, 0, 0, 0, time.UTC), GPXPath: "outside-distance.gpx"},
- {Name: "outside moving time", DistanceM: 100_000, MovingTimeS: 10_801, StartDate: time.Date(2026, 8, 31, 12, 0, 0, 0, time.UTC), GPXPath: "outside-duration.gpx"},
- {Name: "outside date", DistanceM: 100_000, MovingTimeS: 10_800, StartDate: time.Date(2026, 9, 1, 12, 0, 0, 0, time.UTC), GPXPath: "outside-date.gpx"},
+ {Name: "lower boundary", DistanceM: 20_000, ExternalMovingTimeS: 1_800, StartDate: time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC), GPXPath: "lower.gpx"},
+ {Name: "upper boundary", DistanceM: 100_000, ExternalMovingTimeS: 10_800, StartDate: time.Date(2026, 8, 31, 12, 0, 0, 0, time.UTC), GPXPath: "upper.gpx"},
+ {Name: "outside distance", DistanceM: 110_000, ExternalMovingTimeS: 10_800, StartDate: time.Date(2026, 8, 31, 12, 0, 0, 0, time.UTC), GPXPath: "outside-distance.gpx"},
+ {Name: "outside moving time", DistanceM: 100_000, ExternalMovingTimeS: 10_801, StartDate: time.Date(2026, 8, 31, 12, 0, 0, 0, time.UTC), GPXPath: "outside-duration.gpx"},
+ {Name: "outside date", DistanceM: 100_000, ExternalMovingTimeS: 10_800, StartDate: time.Date(2026, 9, 1, 12, 0, 0, 0, time.UTC), GPXPath: "outside-date.gpx"},
}
filters := parseRideFilters(url.Values{
rideFilterDistanceMinParam: {"20"},
diff --git a/web/server.go b/web/server.go
index 4bc74e3..06ee425 100644
--- a/web/server.go
+++ b/web/server.go
@@ -228,7 +228,7 @@ func buildRideViews(items []rides.Ride) []RideView {
}
func buildRideView(item rides.Ride) RideView {
- view := RideView{Ride: item, Cotacol: "-", CotacolPer100Km: "-"}
+ view := RideView{Ride: item, Cotacol: "-", CotacolPer100Km: "-", MovingTime: formatDuration(item.EffectiveMovingTimeS())}
cotacol, ready := item.CotacolScore()
if ready && item.CotacolAlgorithmVersion() == ride.CotacolAlgorithmVersion && item.DistanceM > 0 {
view.Cotacol = formatCotacol(cotacol)
@@ -459,7 +459,7 @@ func rideFromActivity(externalID, gpxPath string, activity strava.Activity) ride
Type: activityType,
StartDate: activity.StartDate,
DistanceM: activity.DistanceM,
- MovingTimeS: activity.MovingTimeS,
+ ExternalMovingTimeS: activity.MovingTimeS,
ElapsedTimeS: activity.ElapsedTimeS,
TotalElevationGainM: activity.TotalElevationGainM,
AverageSpeedMps: activity.AverageSpeedMps,
diff --git a/web/server_test.go b/web/server_test.go
index 823e4a8..68928fe 100644
--- a/web/server_test.go
+++ b/web/server_test.go
@@ -361,7 +361,7 @@ func TestHandlerReturnsNotFoundForUnknownRide(t *testing.T) {
func TestHandlerShowsErrorForUnavailableRideRoute(t *testing.T) {
server, db := newWebTestServer(t)
_, err := db.Exec(`
- INSERT INTO rides (external_id, gpx_path, name, type, start_date, distance_m, moving_time_s, elapsed_time_s, total_elevation_gain_m, average_speed_mps)
+ INSERT INTO rides (external_id, gpx_path, name, type, start_date, distance_m, external_moving_time_s, elapsed_time_s, total_elevation_gain_m, average_speed_mps)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, "strava:6ba7b810-9dad-41d1-80b4-00c04fd430c8", "missing.gpx", "Unavailable Route", "Ride", "2026-08-01T07:00:00Z", 20_000, 0, 0, 0, 0)
require.NoError(t, err)
@@ -648,6 +648,25 @@ func TestBuildRideViewsUsesStoredCotacol(t *testing.T) {
assert.NotEqual(t, "-", views[0].Cotacol)
}
+func TestBuildRideViewMovingTimePrefersCanonical(t *testing.T) {
+ _, db := newWebTestServer(t)
+ ride := rides.Ride{
+ ExternalID: "strava:550e8400-e29b-41d4-a716-446655440000",
+ GPXPath: testGPXPath(t, "moving.gpx"),
+ Name: "Moving Ride",
+ Type: "Ride",
+ StartDate: time.Date(2026, 8, 1, 7, 0, 0, 0, time.UTC),
+ DistanceM: 20_000,
+ ExternalMovingTimeS: 7_200,
+ }
+ require.NoError(t, rides.Save(db, ride))
+ items, err := rides.List(db)
+ require.NoError(t, err)
+ require.Len(t, items, 1)
+
+ assert.NotEqual(t, "2h 00m", buildRideView(items[0]).MovingTime)
+}
+
func TestHandlerSortsRidesByDistance(t *testing.T) {
server, db := newWebTestServer(t)
require.NoError(t, rides.Save(db, rides.Ride{
diff --git a/web/templates.templ b/web/templates.templ
index 8a987fc..88f17d1 100644
--- a/web/templates.templ
+++ b/web/templates.templ
@@ -108,7 +108,7 @@ templ RidesContent(data RidesPageData) {
</tr></thead>
<tbody>
for _, item := range data.Items {
- <tr><td><strong><a href={ templ.URL(rideDetailURL(item.ID)) }>{ item.Name }</a></strong><br/><small>{ item.Type }</small></td><td>{ formatRideDate(item.StartDate) }</td><td class="numeric">{ formatDistance(item.DistanceM) }</td><td class="numeric">{ formatDuration(item.MovingTimeS) }</td><td class="numeric">{ formatElevation(item.TotalElevationGainM) }</td><td class="numeric">{ item.Cotacol }</td><td class="numeric">{ item.CotacolPer100Km }</td></tr>
+ <tr><td><strong><a href={ templ.URL(rideDetailURL(item.ID)) }>{ item.Name }</a></strong><br/><small>{ item.Type }</small></td><td>{ formatRideDate(item.StartDate) }</td><td class="numeric">{ formatDistance(item.DistanceM) }</td><td class="numeric">{ item.MovingTime }</td><td class="numeric">{ formatElevation(item.TotalElevationGainM) }</td><td class="numeric">{ item.Cotacol }</td><td class="numeric">{ item.CotacolPer100Km }</td></tr>
}
</tbody>
</table>
@@ -139,7 +139,7 @@ templ RideDetailContent(data RideDetailView) {
<section class="panel summary-panel">
<dl>
<div><dt>Distance</dt><dd>{ formatDistance(data.Ride.DistanceM) }</dd></div>
- <div><dt>Moving time</dt><dd>{ formatDuration(data.Ride.MovingTimeS) }</dd></div>
+ <div><dt>Moving time</dt><dd>{ data.MovingTime }</dd></div>
<div><dt>Elevation</dt><dd>{ formatElevation(data.Ride.TotalElevationGainM) }</dd></div>
</dl>
</section>
diff --git a/web/templates_templ.go b/web/templates_templ.go
index 05ae20a..a1cf867 100644
--- a/web/templates_templ.go
+++ b/web/templates_templ.go
@@ -821,9 +821,9 @@ func RidesContent(data RidesPageData) templ.Component {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var56 string
- templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(formatDuration(item.MovingTimeS))
+ templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(item.MovingTime)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 111, Col: 288}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 111, Col: 271}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56))
if templ_7745c5c3_Err != nil {
@@ -836,7 +836,7 @@ func RidesContent(data RidesPageData) templ.Component {
var templ_7745c5c3_Var57 string
templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(formatElevation(item.TotalElevationGainM))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 111, Col: 358}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 111, Col: 341}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57))
if templ_7745c5c3_Err != nil {
@@ -849,7 +849,7 @@ func RidesContent(data RidesPageData) templ.Component {
var templ_7745c5c3_Var58 string
templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(item.Cotacol)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 111, Col: 399}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 111, Col: 382}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58))
if templ_7745c5c3_Err != nil {
@@ -862,7 +862,7 @@ func RidesContent(data RidesPageData) templ.Component {
var templ_7745c5c3_Var59 string
templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(item.CotacolPer100Km)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 111, Col: 448}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 111, Col: 431}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59))
if templ_7745c5c3_Err != nil {
@@ -1054,9 +1054,9 @@ func RideDetailContent(data RideDetailView) templ.Component {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var69 string
- templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(formatDuration(data.Ride.MovingTimeS))
+ templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(data.MovingTime)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 142, Col: 72}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 142, Col: 50}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var69))
if templ_7745c5c3_Err != nil {
diff --git a/web/views.go b/web/views.go
index de4d38f..1bf1782 100644
--- a/web/views.go
+++ b/web/views.go
@@ -16,6 +16,7 @@ type RideView struct {
rides.Ride
Cotacol string
CotacolPer100Km string
+ MovingTime string
}
type RidesPageData struct {