refactor(go): remove duplicate mileage history api
This commit is contained in:
@@ -79,28 +79,6 @@ type LocationRow struct {
|
||||
VIN string `json:"vin"`
|
||||
}
|
||||
|
||||
type MileagePointQuery struct {
|
||||
Protocol string
|
||||
VIN string
|
||||
DateFrom string
|
||||
DateTo string
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
|
||||
type MileagePointRow struct {
|
||||
TS string `json:"ts"`
|
||||
EventID string `json:"event_id"`
|
||||
FrameID string `json:"frame_id"`
|
||||
ReceivedAt string `json:"received_at"`
|
||||
TotalMileageKM float64 `json:"total_mileage_km"`
|
||||
SpeedKMH *float64 `json:"speed_kmh,omitempty"`
|
||||
Longitude *float64 `json:"longitude,omitempty"`
|
||||
Latitude *float64 `json:"latitude,omitempty"`
|
||||
Protocol string `json:"protocol"`
|
||||
VIN string `json:"vin"`
|
||||
}
|
||||
|
||||
type RawFrameRepository struct {
|
||||
db Queryer
|
||||
database string
|
||||
@@ -122,11 +100,6 @@ type LocationRepository struct {
|
||||
database string
|
||||
}
|
||||
|
||||
type MileagePointRepository struct {
|
||||
db Queryer
|
||||
database string
|
||||
}
|
||||
|
||||
func NewLocationRepository(db Queryer, database string) *LocationRepository {
|
||||
if db == nil {
|
||||
panic("location query db must not be nil")
|
||||
@@ -138,17 +111,6 @@ func NewLocationRepository(db Queryer, database string) *LocationRepository {
|
||||
return &LocationRepository{db: db, database: database}
|
||||
}
|
||||
|
||||
func NewMileagePointRepository(db Queryer, database string) *MileagePointRepository {
|
||||
if db == nil {
|
||||
panic("mileage point query db must not be nil")
|
||||
}
|
||||
database = strings.TrimSpace(database)
|
||||
if database != "" && !safeIdentifier(database) {
|
||||
database = ""
|
||||
}
|
||||
return &MileagePointRepository{db: db, database: database}
|
||||
}
|
||||
|
||||
func (r *RawFrameRepository) Query(ctx context.Context, query RawFrameQuery) ([]RawFrameRow, error) {
|
||||
query = normalizeRawFrameQuery(query)
|
||||
sqlText, args := buildRawFrameSQL(r.tableName(), query)
|
||||
@@ -264,53 +226,6 @@ func (r *LocationRepository) Count(ctx context.Context, query LocationQuery) (in
|
||||
return countRows(ctx, r.db, sqlText, args...)
|
||||
}
|
||||
|
||||
func (r *MileagePointRepository) Query(ctx context.Context, query MileagePointQuery) ([]MileagePointRow, error) {
|
||||
query = normalizeMileagePointQuery(query)
|
||||
sqlText, args := buildMileagePointSQL(r.tableName(), query)
|
||||
rows, err := r.db.QueryContext(ctx, sqlText, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
out := make([]MileagePointRow, 0)
|
||||
for rows.Next() {
|
||||
var row MileagePointRow
|
||||
var ts scanDateTime
|
||||
var receivedAt scanDateTime
|
||||
var speed sql.NullFloat64
|
||||
var longitude sql.NullFloat64
|
||||
var latitude sql.NullFloat64
|
||||
if err := rows.Scan(
|
||||
&ts,
|
||||
&row.EventID,
|
||||
&row.FrameID,
|
||||
&receivedAt,
|
||||
&row.TotalMileageKM,
|
||||
&speed,
|
||||
&longitude,
|
||||
&latitude,
|
||||
&row.Protocol,
|
||||
&row.VIN,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
row.TS = ts.String
|
||||
row.ReceivedAt = receivedAt.String
|
||||
row.SpeedKMH = nullableFloat(speed)
|
||||
row.Longitude = nullableFloat(longitude)
|
||||
row.Latitude = nullableFloat(latitude)
|
||||
out = append(out, row)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (r *MileagePointRepository) Count(ctx context.Context, query MileagePointQuery) (int64, error) {
|
||||
query = normalizeMileagePointQuery(query)
|
||||
sqlText, args := buildMileagePointCountSQL(r.tableName(), query)
|
||||
return countRows(ctx, r.db, sqlText, args...)
|
||||
}
|
||||
|
||||
func countRows(ctx context.Context, db Queryer, sqlText string, args ...any) (int64, error) {
|
||||
rows, err := db.QueryContext(ctx, sqlText, args...)
|
||||
if err != nil {
|
||||
@@ -347,13 +262,6 @@ func (r *LocationRepository) tableName() string {
|
||||
return r.database + ".vehicle_locations"
|
||||
}
|
||||
|
||||
func (r *MileagePointRepository) tableName() string {
|
||||
if r.database == "" {
|
||||
return "vehicle_locations"
|
||||
}
|
||||
return r.database + ".vehicle_locations"
|
||||
}
|
||||
|
||||
func normalizeRawFrameQuery(query RawFrameQuery) RawFrameQuery {
|
||||
query.Protocol = strings.ToUpper(strings.TrimSpace(query.Protocol))
|
||||
query.VehicleKey = strings.TrimSpace(query.VehicleKey)
|
||||
@@ -381,17 +289,6 @@ func normalizeLocationQuery(query LocationQuery) LocationQuery {
|
||||
return query
|
||||
}
|
||||
|
||||
func normalizeMileagePointQuery(query MileagePointQuery) MileagePointQuery {
|
||||
query.Protocol = strings.ToUpper(strings.TrimSpace(query.Protocol))
|
||||
query.VIN = strings.TrimSpace(query.VIN)
|
||||
query.DateFrom = normalizeDateTimeLiteral(query.DateFrom)
|
||||
query.DateTo = normalizeDateTimeLiteral(query.DateTo)
|
||||
if query.Limit <= 0 {
|
||||
query.Limit = 20
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
func buildRawFrameSQL(table string, query RawFrameQuery) (string, []any) {
|
||||
where := rawFrameWhere(query)
|
||||
sqlText := `SELECT ts, frame_id, event_id, message_id, event_time, received_at, raw_size_bytes, raw_hex, raw_text, parsed_json, parse_status, parse_error, source_endpoint, protocol, vehicle_key, vin, phone, device_id FROM ` + table
|
||||
@@ -548,24 +445,6 @@ func buildLocationCountSQL(table string, query LocationQuery) (string, []any) {
|
||||
return sqlText, nil
|
||||
}
|
||||
|
||||
func buildMileagePointSQL(table string, query MileagePointQuery) (string, []any) {
|
||||
where := mileagePointWhere(query)
|
||||
sqlText := `SELECT ts, event_id, frame_id, received_at, total_mileage_km, speed_kmh, longitude, latitude, protocol, vin FROM ` + table
|
||||
if len(where) > 0 {
|
||||
sqlText += " WHERE " + strings.Join(where, " AND ")
|
||||
}
|
||||
sqlText += " ORDER BY ts DESC LIMIT " + strconv.Itoa(query.Limit) + " OFFSET " + strconv.Itoa(query.Offset)
|
||||
return sqlText, nil
|
||||
}
|
||||
|
||||
func buildMileagePointCountSQL(table string, query MileagePointQuery) (string, []any) {
|
||||
sqlText := `SELECT COUNT(*) FROM ` + table
|
||||
if where := mileagePointWhere(query); len(where) > 0 {
|
||||
sqlText += " WHERE " + strings.Join(where, " AND ")
|
||||
}
|
||||
return sqlText, nil
|
||||
}
|
||||
|
||||
func rawFrameWhere(query RawFrameQuery) []string {
|
||||
var where []string
|
||||
add := func(clause string) {
|
||||
@@ -637,26 +516,6 @@ func locationWhere(query LocationQuery) []string {
|
||||
return where
|
||||
}
|
||||
|
||||
func mileagePointWhere(query MileagePointQuery) []string {
|
||||
where := []string{"total_mileage_km IS NOT NULL"}
|
||||
add := func(clause string) {
|
||||
where = append(where, clause)
|
||||
}
|
||||
if query.Protocol != "" {
|
||||
add("protocol = '" + quote(query.Protocol) + "'")
|
||||
}
|
||||
if query.VIN != "" {
|
||||
add("vin = '" + quote(query.VIN) + "'")
|
||||
}
|
||||
if query.DateFrom != "" {
|
||||
add("ts >= '" + quote(normalizeDateTimeLiteral(query.DateFrom)) + "'")
|
||||
}
|
||||
if query.DateTo != "" {
|
||||
add("ts <= '" + quote(normalizeDateTimeLiteral(query.DateTo)) + "'")
|
||||
}
|
||||
return where
|
||||
}
|
||||
|
||||
type RawFrameHandler struct {
|
||||
repository *RawFrameRepository
|
||||
}
|
||||
@@ -665,10 +524,6 @@ type LocationHandler struct {
|
||||
repository *LocationRepository
|
||||
}
|
||||
|
||||
type MileagePointHandler struct {
|
||||
repository *MileagePointRepository
|
||||
}
|
||||
|
||||
func NewRawFrameHandler(repository *RawFrameRepository) *RawFrameHandler {
|
||||
if repository == nil {
|
||||
panic("raw frame repository must not be nil")
|
||||
@@ -683,13 +538,6 @@ func NewLocationHandler(repository *LocationRepository) *LocationHandler {
|
||||
return &LocationHandler{repository: repository}
|
||||
}
|
||||
|
||||
func NewMileagePointHandler(repository *MileagePointRepository) *MileagePointHandler {
|
||||
if repository == nil {
|
||||
panic("mileage point repository must not be nil")
|
||||
}
|
||||
return &MileagePointHandler{repository: repository}
|
||||
}
|
||||
|
||||
func (h *RawFrameHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
writeHistoryError(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||
@@ -762,39 +610,6 @@ func (h *LocationHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (h *MileagePointHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
writeHistoryError(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||
return
|
||||
}
|
||||
if strings.Trim(r.URL.Path, "/") != "api/history/mileage-points" {
|
||||
writeHistoryError(w, http.StatusNotFound, "route not found")
|
||||
return
|
||||
}
|
||||
query, err := parseMileagePointQuery(r)
|
||||
if err != nil {
|
||||
writeHistoryError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
total, err := h.repository.Count(r.Context(), query)
|
||||
if err != nil {
|
||||
writeHistoryError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
rows, err := h.repository.Query(r.Context(), query)
|
||||
if err != nil {
|
||||
writeHistoryError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"items": rows,
|
||||
"total": total,
|
||||
"limit": query.Limit,
|
||||
"offset": query.Offset,
|
||||
})
|
||||
}
|
||||
|
||||
func parseRawFrameQuery(r *http.Request) (RawFrameQuery, error) {
|
||||
values := r.URL.Query()
|
||||
limit, err := parseBoundedInt(values.Get("limit"), 20, 1, 500, "limit")
|
||||
@@ -830,30 +645,6 @@ func parseRawFrameQuery(r *http.Request) (RawFrameQuery, error) {
|
||||
return normalizeRawFrameQuery(query), nil
|
||||
}
|
||||
|
||||
func parseMileagePointQuery(r *http.Request) (MileagePointQuery, error) {
|
||||
values := r.URL.Query()
|
||||
limit, err := parseBoundedInt(values.Get("limit"), 20, 1, 500, "limit")
|
||||
if err != nil {
|
||||
return MileagePointQuery{}, err
|
||||
}
|
||||
offset, err := parseBoundedInt(values.Get("offset"), 0, 0, 1_000_000, "offset")
|
||||
if err != nil {
|
||||
return MileagePointQuery{}, err
|
||||
}
|
||||
query := MileagePointQuery{
|
||||
Protocol: values.Get("protocol"),
|
||||
VIN: values.Get("vin"),
|
||||
DateFrom: values.Get("dateFrom"),
|
||||
DateTo: values.Get("dateTo"),
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
}
|
||||
if !validDateTime(query.DateFrom) || !validDateTime(query.DateTo) {
|
||||
return MileagePointQuery{}, errors.New("dateFrom/dateTo must use YYYY-MM-DD or YYYY-MM-DD HH:mm:ss")
|
||||
}
|
||||
return normalizeMileagePointQuery(query), nil
|
||||
}
|
||||
|
||||
func parseLocationQuery(r *http.Request) (LocationQuery, error) {
|
||||
values := r.URL.Query()
|
||||
limit, err := parseBoundedInt(values.Get("limit"), 20, 1, 500, "limit")
|
||||
|
||||
Reference in New Issue
Block a user