Add depot, attendant, jetfire, optimus, ota services with kustomize overlays
This commit is contained in:
170
services/ota_update_go/handlers/vehicle_path.go
Normal file
170
services/ota_update_go/handlers/vehicle_path.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"otaupdate/services"
|
||||
"strings"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/clickhouse"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
"github.com/gorilla/schema"
|
||||
"github.com/intel-go/fastjson"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
// HandleVehiclePathsPost godoc
|
||||
// @Summary Gets paths of vehicles
|
||||
// @Description Returns vehicle paths
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param vins body vehicleBodyParams true "List of VINs"
|
||||
// @Param lookback_hours query int false "Data lookback window in hours. Default is 24 if not set"
|
||||
// @Success 200 {object} common.GpsPaths
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /vehicle_paths [post]
|
||||
func HandleVehiclePathsPost(w http.ResponseWriter, r *http.Request) {
|
||||
query, body, err := parseVehiclePathsParams(r)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := services.GetClickhouseClient()
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
logger.Error().Err(err).Msg("cannot get clickhouse client")
|
||||
return
|
||||
}
|
||||
|
||||
paths, err := selectVehiclePaths(conn, body.VINs, query.LookbackHours)
|
||||
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
logger.Error().Err(err).Msg("failed to select vehicle paths from feature_table")
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, paths)
|
||||
}
|
||||
|
||||
func parseVehiclePathsParams(r *http.Request) (vehiclePathQuery, vehicleBodyParams, error) {
|
||||
sch := schema.NewDecoder()
|
||||
sch.SetAliasTag("json")
|
||||
queryParams := vehiclePathQuery{}
|
||||
bodyParams := vehicleBodyParams{}
|
||||
|
||||
//process query params
|
||||
err := sch.Decode(&queryParams, r.URL.Query())
|
||||
if err != nil {
|
||||
return vehiclePathQuery{}, vehicleBodyParams{}, errors.WithStack(err)
|
||||
}
|
||||
|
||||
err = validator.GetValidator().Struct(queryParams)
|
||||
if err != nil {
|
||||
return vehiclePathQuery{}, vehicleBodyParams{}, errors.WithStack(err)
|
||||
}
|
||||
|
||||
//process body params
|
||||
err = fastjson.NewDecoder(r.Body).Decode(&bodyParams)
|
||||
|
||||
if err != nil && err != io.EOF {
|
||||
return vehiclePathQuery{}, vehicleBodyParams{}, errors.WithStack(err)
|
||||
}
|
||||
|
||||
err = validator.GetValidator().Struct(bodyParams)
|
||||
if err != nil {
|
||||
return vehiclePathQuery{}, vehicleBodyParams{}, errors.WithStack(err)
|
||||
}
|
||||
|
||||
//set default value for queryParams
|
||||
if queryParams.LookbackHours == 0 {
|
||||
queryParams.LookbackHours = 24
|
||||
}
|
||||
|
||||
return queryParams, bodyParams, nil
|
||||
}
|
||||
|
||||
func selectVehiclePaths(conn clickhouse.ClientInterface, vinList []string, lookbackHours int64) (common.GpsPaths, error) {
|
||||
var vinListSb strings.Builder
|
||||
//serialize vinList for query
|
||||
vinListSb.WriteRune('[')
|
||||
for i, vin := range vinList {
|
||||
vinListSb.WriteRune('\'')
|
||||
vinListSb.WriteString(vin)
|
||||
vinListSb.WriteRune('\'')
|
||||
|
||||
if i < len(vinList)-1 {
|
||||
vinListSb.WriteRune(',')
|
||||
}
|
||||
}
|
||||
vinListSb.WriteRune(']')
|
||||
|
||||
//query gps routes
|
||||
queryText := fmt.Sprintf("SELECT VIN, TBOX_GPSLati, TBOX_GPSLongi FROM feature_table WHERE Timestamp > now() - (interval %d hour) AND VIN in %s ORDER BY Timestamp ASC",
|
||||
lookbackHours, vinListSb.String())
|
||||
|
||||
logger.Info().Msgf("query: %s", queryText)
|
||||
|
||||
var results []ChGpsRow
|
||||
err := conn.Select(&results, queryText)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//transform query results
|
||||
var pathsMap common.GpsPaths = make(common.GpsPaths, len(vinList))
|
||||
|
||||
//populate pathsMap
|
||||
for _, vin := range vinList {
|
||||
pathsMap[vin] = common.GpsPath{}
|
||||
}
|
||||
|
||||
for _, row := range results {
|
||||
_, ok := pathsMap[row.VIN]
|
||||
if !ok {
|
||||
pathsMap[row.VIN] = common.GpsPath{}
|
||||
}
|
||||
|
||||
//ignore invalid GPS points
|
||||
if math.IsNaN(row.TBOX_GPSLati) || math.IsNaN(row.TBOX_GPSLongi) {
|
||||
continue
|
||||
}
|
||||
if row.TBOX_GPSLati == 0 && row.TBOX_GPSLongi == 0 {
|
||||
continue
|
||||
}
|
||||
if row.TBOX_GPSLati > 90 || row.TBOX_GPSLati < -90 {
|
||||
continue
|
||||
}
|
||||
if row.TBOX_GPSLongi > 180 || row.TBOX_GPSLongi < -180 {
|
||||
continue
|
||||
}
|
||||
|
||||
pathsMap[row.VIN] = append(pathsMap[row.VIN],
|
||||
common.GpsPoint{row.TBOX_GPSLati, row.TBOX_GPSLongi},
|
||||
)
|
||||
}
|
||||
|
||||
return pathsMap, nil
|
||||
}
|
||||
|
||||
type ChGpsRow struct {
|
||||
VIN string `ch:"VIN" json:"VIN"`
|
||||
TBOX_GPSLati float64 `ch:"TBOX_GPSLati" json:"TBOX_GPSLati"`
|
||||
TBOX_GPSLongi float64 `ch:"TBOX_GPSLongi" json:"TBOX_GPSLongi"`
|
||||
}
|
||||
|
||||
type vehiclePathQuery struct {
|
||||
LookbackHours int64 `json:"lookback_hours"`
|
||||
}
|
||||
|
||||
type vehicleBodyParams struct {
|
||||
VINs []string `json:"vins" validate:"required"`
|
||||
}
|
||||
Reference in New Issue
Block a user