Files

171 lines
4.7 KiB
Go

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"`
}