1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
package web
import (
"log/slog"
"net/http"
"strconv"
"github.com/martinlehoux/biking_home/ride"
"github.com/martinlehoux/kagamigo/kcore"
)
func (s *Server) handleDataQuality(w http.ResponseWriter, r *http.Request) {
items, err := ride.ListUncategorised(s.db)
if err != nil {
slog.Error("Failed to load uncategorised rides", "error", err)
http.Error(w, "failed to load rides", http.StatusInternalServerError)
return
}
views := make([]RideView, 0, len(items))
for _, item := range items {
views = append(views, buildRideView(item))
}
total, err := ride.CountUncategorised(s.db)
if err != nil {
slog.Warn("Failed to count uncategorised rides", "error", err)
total = len(items)
}
slog.Info("Loaded data quality page", "uncategorised", total, "displayed", len(views))
kcore.RenderPage(r.Context(), DataQualityPage(DataQualityPageData{
Items: views,
Total: total,
Notice: dataQualityNotice(r),
}), w)
}
func (s *Server) handleBulkUpdateCategories(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, "invalid form", http.StatusBadRequest)
return
}
bikeType := ride.BikeType(r.FormValue("bike_type"))
rideType := ride.RideType(r.FormValue("ride_type"))
if !ride.IsValidBikeType(string(bikeType)) {
http.Error(w, "invalid bike_type", http.StatusBadRequest)
return
}
if !ride.IsValidRideType(string(rideType)) {
http.Error(w, "invalid ride_type", http.StatusBadRequest)
return
}
rawIDs := r.Form["ride_ids"]
if len(rawIDs) == 0 {
http.Error(w, "no rides selected", http.StatusBadRequest)
return
}
ids := make([]int64, 0, len(rawIDs))
for _, raw := range rawIDs {
id, err := strconv.ParseInt(raw, 10, 64)
if err != nil || id <= 0 {
http.Error(w, "invalid ride id", http.StatusBadRequest)
return
}
ids = append(ids, id)
}
affected, err := ride.BulkUpdateCategories(s.db, ids, bikeType, rideType)
if err != nil {
slog.Error("Failed to bulk update categories", "error", err)
http.Error(w, "failed to update categories", http.StatusInternalServerError)
return
}
slog.Info("Bulk updated ride categories", "count", affected, "bike_type", bikeType, "ride_type", rideType)
http.Redirect(w, r, "/data-quality?bulk=updated&count="+strconv.FormatInt(affected, 10), http.StatusSeeOther)
}
func dataQualityNotice(r *http.Request) string {
if r.URL.Query().Get("bulk") == "updated" {
count := r.URL.Query().Get("count")
if count != "" {
return count + " rides categorised."
}
return "Rides categorised."
}
if r.URL.Query().Get("categories") == "updated" {
return "Ride categories updated."
}
return ""
}
func dataQualityURL() string {
return "/data-quality"
}
type DataQualityPageData struct {
Items []RideView
Total int
Notice string
}
func formatRideID(id int64) string {
return strconv.FormatInt(id, 10)
}
|