Add depot, attendant, jetfire, optimus, ota services with kustomize overlays
This commit is contained in:
26
services/ota_update_go/Dockerfile
Normal file
26
services/ota_update_go/Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
ARG BASE_IMAGE=cloud_base_go
|
||||
FROM ${BASE_IMAGE} as builder-go
|
||||
|
||||
WORKDIR /build/ota_update_go
|
||||
COPY ./ota_update_go/go.mod ./ota_update_go/go.sum ./
|
||||
RUN go mod edit -replace fiskerinc.com/modules=../fiskerinc.com/modules \
|
||||
&& go install github.com/swaggo/swag/cmd/swag@v1.7.8 \
|
||||
&& go mod download
|
||||
|
||||
COPY ./ota_update_go ./
|
||||
RUN go mod edit -replace fiskerinc.com/modules=../fiskerinc.com/modules \
|
||||
&& swag init --parseDependency --parseInternal --parseDepth 5 -g main.go \
|
||||
&& go build -tags musl
|
||||
|
||||
FROM alpine:3.17
|
||||
|
||||
RUN apk add --no-cache librdkafka --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community \
|
||||
&& apk add --no-cache ca-certificates
|
||||
|
||||
COPY ./modules_go/logger/log_config .
|
||||
COPY --from=builder-go /build/ota_update_go/otaupdate .
|
||||
|
||||
ENV LOG_CONFIG=log_config
|
||||
EXPOSE 8077
|
||||
|
||||
CMD ./otaupdate
|
||||
193
services/ota_update_go/background/carImmobilizer.go
Normal file
193
services/ota_update_go/background/carImmobilizer.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package background
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"maps"
|
||||
"otaupdate/services"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/cache"
|
||||
"github.com/fiskerinc/cloud-services/pkg/carcommand"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||
)
|
||||
|
||||
// Anywhere a comment says lock, its probably actually lock/unlock
|
||||
|
||||
// Have a local list we check and run against
|
||||
// and have a redis cache list that we will modify
|
||||
// keep track of time started, last updated time, number of times sent, and if its a lock or unlock
|
||||
|
||||
var (
|
||||
immobilizerOnce sync.Once
|
||||
immobilizer *Immobilizer
|
||||
)
|
||||
|
||||
// Message will stay alive in redis for an hour before it is removed
|
||||
const CAR_CHECK_INTERVAL time.Duration = time.Duration(5 * time.Minute) // Minutes between checking car status and sending command
|
||||
const REDIS_UPDATE_INTERVAL time.Duration = time.Duration(time.Hour * 2) // Minutes between updating redis with the current cache list. // need to keep track of last updated incase our service goes down
|
||||
const REDIS_EXPIRATION_DURATION time.Duration = time.Duration(24 * time.Hour) // how old a time should be before we pick it up for us to process
|
||||
|
||||
const CAR_LOCK_ATTEMPT_COUNT = 15 // how many times to send the lock command
|
||||
|
||||
type CarTrack struct {
|
||||
TimesModified int `json:"times_modified"`
|
||||
Immobilize bool `json:"immobilize"` // 0: unlock, 1: lock
|
||||
}
|
||||
|
||||
type Immobilizer struct {
|
||||
VINs map[string]*CarTrack // By having a pointer here, should be able to modify timesModified without write lock
|
||||
sync.RWMutex // mutex for handling the reading and writing of the VIN's map
|
||||
}
|
||||
|
||||
func GetImmobilizer() *Immobilizer {
|
||||
immobilizerOnce.Do(func() {
|
||||
if immobilizer != nil {
|
||||
return
|
||||
}
|
||||
logger.Info().Msg("Init Immobilizer instance")
|
||||
immobilizer = InitiateImmobilizer()
|
||||
})
|
||||
return immobilizer
|
||||
}
|
||||
|
||||
func InitiateImmobilizer() *Immobilizer {
|
||||
imm := &Immobilizer{}
|
||||
imm.VINs = make(map[string]*CarTrack)
|
||||
go time.AfterFunc(CAR_CHECK_INTERVAL, imm.CheckCars)
|
||||
// go time.AfterFunc(REDIS_UPDATE_INTERVAL, imm.UpdateRedis)
|
||||
// Have a random offset so multiple ota's starting at same time don't look and claim at the same time
|
||||
// go time.AfterFunc(REDIS_EXPIRATION_DURATION+(time.Duration(rand.IntN(20))*time.Minute), imm.ClaimRedis)
|
||||
return imm
|
||||
}
|
||||
|
||||
|
||||
func (imm *Immobilizer) GetVINList() (vinList []string) {
|
||||
imm.RLock()
|
||||
defer imm.RUnlock()
|
||||
for vin := range imm.VINs {
|
||||
vinList = append(vinList, vin)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (imm *Immobilizer) CheckCars() {
|
||||
clientPool := services.RedisClientPool()
|
||||
twins, _ := cache.GetVINListDigitalTwin(imm.GetVINList(), clientPool)
|
||||
parkedVINs := []string{} // parkedVINs are the cars we are going to send the lock command to
|
||||
for vin, twin := range twins {
|
||||
gear := twin.Gear
|
||||
if gear == nil {
|
||||
continue
|
||||
}
|
||||
if gear.InPark {
|
||||
parkedVINs = append(parkedVINs, vin)
|
||||
}
|
||||
}
|
||||
// Have a list of vins we can now modify
|
||||
// Send the lock/unlock command, send wake up command
|
||||
hopefulImmob := imm.sendRemoteCommands(parkedVINs)
|
||||
// Not on hopeful immob, we do not check if the car ever wakes up from its sms, so its possible if parked deep in a garage it will not
|
||||
// Possible fix: remove redis timeout for car lock commands
|
||||
go imm.RemoveVINs(hopefulImmob)
|
||||
time.AfterFunc(CAR_CHECK_INTERVAL, imm.CheckCars)
|
||||
}
|
||||
|
||||
func (imm *Immobilizer) sendRemoteCommands(parkedVINs []string) (removableVins []string) {
|
||||
// First send wake up message
|
||||
|
||||
// Send lock or unlock command
|
||||
|
||||
// for _, vin := range request.VINs {
|
||||
// Action logger should get added to when the user adds car to the list
|
||||
// go func() {
|
||||
// actionLog := actionlogger.ActionLog{
|
||||
// VIN: vin,
|
||||
// Action: actionlogger.RemoteCommand,
|
||||
// UserIdentifier: httphandlers.GetClientID(r),
|
||||
// CallLocation: "github.com/fiskerinc/cloud-services/services/ota_update_go/handlers/vehicle_command.go",
|
||||
// Description: string(description),
|
||||
// }
|
||||
// err = alDB.Insert(actionLog)
|
||||
// if err != nil {
|
||||
// logger.Err(err).Msg("failed to insert action log inside HandleVehicleCommand")
|
||||
// }
|
||||
// }()
|
||||
// vehicle_command.go has an extremely convoluted way to get remote commands to car. It pushed it to a kafka queue to to be picked up
|
||||
// then kafka does some delivery re-trying stuff in some way, wakes up the car and then puts the message into redis.
|
||||
// we are going straight to the redis section
|
||||
|
||||
// remoteCommands := make([]string, 0, len(parkedVINs))
|
||||
imm.RLock()
|
||||
batch := redis.NewRedisBatchCommands()
|
||||
smsClient := services.GetSMSClient()
|
||||
wake := carcommand.NewCarWakeUp(services.GetDB().GetCars(), smsClient)
|
||||
for _, vin := range parkedVINs {
|
||||
temp := imm.VINs[vin]
|
||||
temp.TimesModified -= 1
|
||||
if temp.TimesModified < 0 {
|
||||
removableVins = append(removableVins, vin)
|
||||
continue
|
||||
}
|
||||
// probably faster to create the list of things to push and batch, but fine for now
|
||||
msg := common.RemoteCommandSource{}
|
||||
if temp.Immobilize {
|
||||
msg.Command = "doors_lock"
|
||||
} else {
|
||||
msg.Command = "doors_unlock"
|
||||
}
|
||||
// SafeQueueMessage auto deletes after one hour, which I do not want with this lock command, rather it sat around indefinitely
|
||||
data, _ := json.Marshal(common.Message{
|
||||
Handler: "remote_command",
|
||||
Data: msg,
|
||||
})
|
||||
batch.Add("RPUSH", redis.QueueKey(common.TRex.Key(vin)), data)
|
||||
|
||||
// try to wake up car
|
||||
wake.WakeUp(vin, false)
|
||||
}
|
||||
imm.RUnlock()
|
||||
|
||||
redisClient := services.RedisClientPool().GetFromPool()
|
||||
defer redisClient.Close()
|
||||
_, err := redisClient.ExecuteBatch(batch)
|
||||
if err != nil {
|
||||
logger.Err(err).Msg("failed to push car immobilizer commands to redis")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (imm *Immobilizer) RemoveVINs(vins []string) {
|
||||
imm.Lock()
|
||||
defer imm.Unlock()
|
||||
for _, v := range vins {
|
||||
delete(imm.VINs, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (imm *Immobilizer) AddVINs(vins []string, immobilize bool) {
|
||||
imm.Lock()
|
||||
defer imm.Unlock()
|
||||
for _, v := range vins {
|
||||
imm.VINs[v] = &CarTrack{TimesModified: CAR_LOCK_ATTEMPT_COUNT, Immobilize: immobilize}
|
||||
}
|
||||
}
|
||||
|
||||
// Just returns the information about the vins we are currently tracking
|
||||
// not sure about memory safety on this
|
||||
func (imm *Immobilizer) GetVINInformation() (vinInfo map[string]*CarTrack) {
|
||||
imm.RLock()
|
||||
defer imm.RUnlock()
|
||||
vinInfo = maps.Clone(imm.VINs)
|
||||
return
|
||||
}
|
||||
|
||||
func (imm *Immobilizer) UpdateRedis() {
|
||||
|
||||
}
|
||||
|
||||
func (imm *Immobilizer) ClaimRedis() {
|
||||
|
||||
}
|
||||
8
services/ota_update_go/controllers/can_signals.go
Normal file
8
services/ota_update_go/controllers/can_signals.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package controllers
|
||||
|
||||
import "time"
|
||||
|
||||
type CarCANSignal struct {
|
||||
VIN string
|
||||
Last time.Time
|
||||
}
|
||||
7
services/ota_update_go/controllers/errors.go
Normal file
7
services/ota_update_go/controllers/errors.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package controllers
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
var ErrorUnableToConvert = errors.New("unable to convert struct")
|
||||
var ErrorPKRequired = errors.New("primary key required")
|
||||
var ErrorNotFound = errors.New("no object found")
|
||||
38
services/ota_update_go/controllers/handle_create.go
Normal file
38
services/ota_update_go/controllers/handle_create.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/go-pg/pg/v10/orm"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
func NewCreate(helper CreateHelperInterface) *HandleCreate {
|
||||
return &HandleCreate{Helper: helper}
|
||||
}
|
||||
|
||||
type CreateHelperInterface interface {
|
||||
ParseRequest(r *http.Request, model interface{}) error
|
||||
QueryInsert(model interface{}) (orm.Result, error)
|
||||
NewModel() interface{}
|
||||
}
|
||||
|
||||
type HandleCreate struct {
|
||||
Helper CreateHelperInterface
|
||||
}
|
||||
|
||||
func (h *HandleCreate) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
model := h.Helper.NewModel()
|
||||
err := h.Helper.ParseRequest(r, model)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = h.Helper.QueryInsert(model)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, model)
|
||||
}
|
||||
47
services/ota_update_go/controllers/handle_delete.go
Normal file
47
services/ota_update_go/controllers/handle_delete.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/go-pg/pg/v10/orm"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
func NewDelete(helper DeleteHelperInterface) *HandleDelete {
|
||||
return &HandleDelete{Helper: helper}
|
||||
}
|
||||
|
||||
type DeleteHelperInterface interface {
|
||||
ParseDeleteQueryParams(r *http.Request) interface{}
|
||||
QueryDelete(req interface{}) (orm.Result, error)
|
||||
ValidatePK(model interface{}) error
|
||||
}
|
||||
|
||||
type HandleDelete struct {
|
||||
Helper DeleteHelperInterface
|
||||
}
|
||||
|
||||
func (h *HandleDelete) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
filter := h.Helper.ParseDeleteQueryParams(r)
|
||||
err := h.Helper.ValidatePK(filter)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.Helper.QueryDelete(filter)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable, loggerdataresp.PostgresNoRowsErrorCheck) {
|
||||
return
|
||||
}
|
||||
|
||||
if result != nil && result.RowsAffected() == 0 {
|
||||
loggerdataresp.BadDataErrorResp(w, errors.New("Nothing deleted"), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONMessage{
|
||||
Message: "Deleted",
|
||||
})
|
||||
}
|
||||
61
services/ota_update_go/controllers/handle_get_list.go
Normal file
61
services/ota_update_go/controllers/handle_get_list.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
func NewGetList(helper GetListHelperInterface) *HandleGetList {
|
||||
return &HandleGetList{Helper: helper}
|
||||
}
|
||||
|
||||
type GetListHelperInterface interface {
|
||||
ParseGetListQueryParams(r *http.Request) interface{}
|
||||
QueryCount(filter interface{}) (int, error)
|
||||
QuerySelect(filter interface{}, options *queries.PageQueryOptions) (interface{}, error)
|
||||
HasPK(filter interface{}) bool
|
||||
}
|
||||
|
||||
type HandleGetList struct {
|
||||
Helper GetListHelperInterface
|
||||
}
|
||||
|
||||
func (h *HandleGetList) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
var total int
|
||||
|
||||
filter := h.Helper.ParseGetListQueryParams(r)
|
||||
err := validator.ValidateNonRequired(filter)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
options, err := queries.ParsePageQuery(r)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
if options.Order == "" {
|
||||
options.Order = "created_at DESC"
|
||||
}
|
||||
|
||||
items, err := h.Helper.QuerySelect(filter, options)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable, loggerdataresp.PostgresNoRowsErrorCheck) {
|
||||
return
|
||||
}
|
||||
|
||||
if options.Offset == 0 && !h.Helper.HasPK(filter) {
|
||||
total, err = h.Helper.QueryCount(filter)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable, loggerdataresp.PostgresNoRowsErrorCheck) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{
|
||||
Data: items,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
38
services/ota_update_go/controllers/handle_get_model.go
Normal file
38
services/ota_update_go/controllers/handle_get_model.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
func NewGetModel(helper GetModelHelperInterface) *HandleGetModel {
|
||||
return &HandleGetModel{Helper: helper}
|
||||
}
|
||||
|
||||
type GetModelHelperInterface interface {
|
||||
ParseGetModelParams(r *http.Request) interface{}
|
||||
QueryLoad(model interface{}) error
|
||||
HasPK(model interface{}) bool
|
||||
}
|
||||
|
||||
type HandleGetModel struct {
|
||||
Helper GetModelHelperInterface
|
||||
}
|
||||
|
||||
func (h *HandleGetModel) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
item := h.Helper.ParseGetModelParams(r)
|
||||
hasPK := h.Helper.HasPK(item)
|
||||
if !hasPK {
|
||||
loggerdataresp.BadDataErrorResp(w, ErrorPKRequired, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err := h.Helper.QueryLoad(item)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable, loggerdataresp.PostgresNoRowsErrorCheck) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, item)
|
||||
}
|
||||
48
services/ota_update_go/controllers/handle_update.go
Normal file
48
services/ota_update_go/controllers/handle_update.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/go-pg/pg/v10/orm"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
func NewUpdate(helper UpdateHelperInterface) *HandleUpdate {
|
||||
return &HandleUpdate{Helper: helper}
|
||||
}
|
||||
|
||||
type UpdateHelperInterface interface {
|
||||
ParseRequest(r *http.Request, model interface{}) error
|
||||
QueryUpdate(model interface{}) (orm.Result, error)
|
||||
NewModel() interface{}
|
||||
ValidatePK(model interface{}) error
|
||||
}
|
||||
|
||||
type HandleUpdate struct {
|
||||
Helper UpdateHelperInterface
|
||||
}
|
||||
|
||||
func (h *HandleUpdate) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
model := h.Helper.NewModel()
|
||||
err := h.ParseRequest(r, model)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = h.Helper.QueryUpdate(model)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable, loggerdataresp.PostgresNoRowsErrorCheck) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, model)
|
||||
}
|
||||
|
||||
func (h *HandleUpdate) ParseRequest(r *http.Request, model interface{}) error {
|
||||
err := h.Helper.ParseRequest(r, model)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return h.Helper.ValidatePK(model)
|
||||
}
|
||||
46
services/ota_update_go/controllers/health_check.go
Normal file
46
services/ota_update_go/controllers/health_check.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"otaupdate/services"
|
||||
"time"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/health"
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
)
|
||||
|
||||
func HealthCheck() {
|
||||
redis := health.NewRedisHealth(services.RedisClientPool())
|
||||
server := health.HealthCheckServer{}
|
||||
|
||||
err := server.Serve([]health.Config{
|
||||
{
|
||||
Name: "db",
|
||||
Check: health.NewPostgresCheck(services.GetDB().GetDBClient().GetConn()),
|
||||
Timeout: time.Second * 1,
|
||||
},
|
||||
{
|
||||
Name: "redis",
|
||||
Check: redis.Check,
|
||||
Timeout: time.Second * 1,
|
||||
Info: redis.RedisStatus,
|
||||
},
|
||||
{
|
||||
Name: "mongodb",
|
||||
Check: health.NewMongoDBCheck(getMongoClient),
|
||||
Timeout: time.Second * 1,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
}
|
||||
}
|
||||
|
||||
func getMongoClient() (health.MongoConnCheckInterface, error) {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn := client.(health.MongoConnCheckInterface)
|
||||
return conn, nil
|
||||
}
|
||||
14
services/ota_update_go/controllers/helper_base.go
Normal file
14
services/ota_update_go/controllers/helper_base.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/httphandlers"
|
||||
)
|
||||
|
||||
type HelperBase struct {
|
||||
}
|
||||
|
||||
func (h *HelperBase) ParseRequest(r *http.Request, data interface{}) error {
|
||||
return httphandlers.ParseRequest(r, data)
|
||||
}
|
||||
43
services/ota_update_go/controllers/mongo_handle_create.go
Normal file
43
services/ota_update_go/controllers/mongo_handle_create.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/httphandlers"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
func NewMongoCreate(helper MongoCreateHelperInterface) *MongoHandleCreate {
|
||||
return &MongoHandleCreate{Helper: helper}
|
||||
}
|
||||
|
||||
type MongoCreateHelperInterface interface {
|
||||
QueryInsert(model interface{}) error
|
||||
NewModel() interface{}
|
||||
ValidatePK(model interface{}) error
|
||||
}
|
||||
|
||||
type MongoHandleCreate struct {
|
||||
Helper MongoCreateHelperInterface
|
||||
}
|
||||
|
||||
func (h *MongoHandleCreate) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
model := h.Helper.NewModel()
|
||||
err := httphandlers.ParseRequest(r, model)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
err = h.Helper.ValidatePK(model)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
err = h.Helper.QueryInsert(model)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, model)
|
||||
}
|
||||
41
services/ota_update_go/controllers/mongo_handle_delete.go
Normal file
41
services/ota_update_go/controllers/mongo_handle_delete.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
|
||||
)
|
||||
|
||||
func NewMongoDelete(helper MongoDeleteHelperInterface) *MongoHandleDelete {
|
||||
return &MongoHandleDelete{Helper: helper}
|
||||
}
|
||||
|
||||
type MongoDeleteHelperInterface interface {
|
||||
ParseDeleteURLParams(r *http.Request) interface{}
|
||||
ValidateFields(model interface{}) error
|
||||
QueryDelete(req interface{}) error
|
||||
}
|
||||
|
||||
type MongoHandleDelete struct {
|
||||
Helper MongoDeleteHelperInterface
|
||||
}
|
||||
|
||||
func (h *MongoHandleDelete) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
filter := h.Helper.ParseDeleteURLParams(r)
|
||||
err := h.Helper.ValidateFields(filter)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
err = h.Helper.QueryDelete(filter)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusNotFound) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONMessage{
|
||||
Message: "Deleted",
|
||||
})
|
||||
}
|
||||
60
services/ota_update_go/controllers/mongo_handle_get_list.go
Normal file
60
services/ota_update_go/controllers/mongo_handle_get_list.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
func NewMongoGetList(helper MongoGetListHelperInterface) *MongoHandleGetList {
|
||||
return &MongoHandleGetList{Helper: helper}
|
||||
}
|
||||
|
||||
type MongoGetListHelperInterface interface {
|
||||
NewModel() interface{}
|
||||
ParseGetListURLParams(r *http.Request, model interface{})
|
||||
ParseGetListQueryParams(r *http.Request, model interface{})
|
||||
ValidateStruct(model interface{}) error
|
||||
QueryCount(filter interface{}) (int64, error)
|
||||
QuerySelect(filter interface{}, options *queries.PageQueryOptions) (interface{}, error)
|
||||
}
|
||||
|
||||
type MongoHandleGetList struct {
|
||||
Helper MongoGetListHelperInterface
|
||||
}
|
||||
|
||||
func (h *MongoHandleGetList) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
filter := h.Helper.NewModel()
|
||||
h.Helper.ParseGetListURLParams(r, filter)
|
||||
h.Helper.ParseGetListQueryParams(r, filter)
|
||||
err := h.Helper.ValidateStruct(filter)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
options, err := queries.ParsePageQuery(r)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
items, err := h.Helper.QuerySelect(filter, options)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
var total int64
|
||||
if options.Offset == 0 && options.Limit != 0 {
|
||||
total, err = h.Helper.QueryCount(filter)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{
|
||||
Data: items,
|
||||
Total: int(total),
|
||||
})
|
||||
}
|
||||
43
services/ota_update_go/controllers/mongo_handle_get_model.go
Normal file
43
services/ota_update_go/controllers/mongo_handle_get_model.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
)
|
||||
|
||||
func NewMongoGetModel(helper MongoGetModelHelperInterface) *MongoHandleGetModel {
|
||||
return &MongoHandleGetModel{Helper: helper}
|
||||
}
|
||||
|
||||
type MongoGetModelHelperInterface interface {
|
||||
ParseGetURLParams(r *http.Request) interface{}
|
||||
ValidatePK(model interface{}) error
|
||||
Query(filter interface{}) (interface{}, error)
|
||||
}
|
||||
|
||||
type MongoHandleGetModel struct {
|
||||
Helper MongoGetModelHelperInterface
|
||||
}
|
||||
|
||||
func (h *MongoHandleGetModel) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
filter := h.Helper.ParseGetURLParams(r)
|
||||
err := h.Helper.ValidatePK(filter)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
item, err := h.Helper.Query(filter)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
} else if item == nil {
|
||||
loggerdataresp.BadDataErrorResp(w, err, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info().Msgf("%+v", item)
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, item)
|
||||
}
|
||||
53
services/ota_update_go/controllers/mongo_handle_update.go
Normal file
53
services/ota_update_go/controllers/mongo_handle_update.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
func NewMongoUpdate(helper MongoUpdateHelperInterface) *MongoHandleUpdate {
|
||||
return &MongoHandleUpdate{Helper: helper}
|
||||
}
|
||||
|
||||
type MongoUpdateHelperInterface interface {
|
||||
ParseUpdateURLParams(r *http.Request) interface{}
|
||||
ValidateFields(model interface{}) error
|
||||
NewModel() interface{}
|
||||
ParseRequestBody(r *http.Request, model interface{}) error
|
||||
QueryUpdate(filter interface{}, model interface{}) error
|
||||
}
|
||||
|
||||
type MongoHandleUpdate struct {
|
||||
Helper MongoUpdateHelperInterface
|
||||
}
|
||||
|
||||
func (h *MongoHandleUpdate) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
filter := h.Helper.ParseUpdateURLParams(r)
|
||||
err := h.Helper.ValidateFields(filter)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
model := h.Helper.NewModel()
|
||||
err = h.Helper.ParseRequestBody(r, model)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
err = h.Helper.ValidateFields(model)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
logger.Warn().Err(err).Send()
|
||||
return
|
||||
}
|
||||
|
||||
err = h.Helper.QueryUpdate(filter, model)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest, loggerdataresp.MongoUpdateErrorCheck) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info().Msgf("%+v", model)
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, model)
|
||||
}
|
||||
68
services/ota_update_go/docs/docs.go
Normal file
68
services/ota_update_go/docs/docs.go
Normal file
@@ -0,0 +1,68 @@
|
||||
// Package docs GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||
// This file was generated by swaggo/swag
|
||||
package docs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/swaggo/swag"
|
||||
)
|
||||
|
||||
var doc = `{}`
|
||||
|
||||
type swaggerInfo struct {
|
||||
Version string
|
||||
Host string
|
||||
BasePath string
|
||||
Schemes []string
|
||||
Title string
|
||||
Description string
|
||||
}
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = swaggerInfo{
|
||||
Version: "",
|
||||
Host: "",
|
||||
BasePath: "",
|
||||
Schemes: []string{},
|
||||
Title: "",
|
||||
Description: "",
|
||||
}
|
||||
|
||||
type s struct{}
|
||||
|
||||
func (s *s) ReadDoc() string {
|
||||
sInfo := SwaggerInfo
|
||||
sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1)
|
||||
|
||||
t, err := template.New("swagger_info").Funcs(template.FuncMap{
|
||||
"marshal": func(v interface{}) string {
|
||||
a, _ := json.Marshal(v)
|
||||
return string(a)
|
||||
},
|
||||
"escape": func(v interface{}) string {
|
||||
// escape tabs
|
||||
str := strings.Replace(v.(string), "\t", "\\t", -1)
|
||||
// replace " with \", and if that results in \\", replace that with \\\"
|
||||
str = strings.Replace(str, "\"", "\\\"", -1)
|
||||
return strings.Replace(str, "\\\\\"", "\\\\\\\"", -1)
|
||||
},
|
||||
}).Parse(doc)
|
||||
if err != nil {
|
||||
return doc
|
||||
}
|
||||
|
||||
var tpl bytes.Buffer
|
||||
if err := t.Execute(&tpl, sInfo); err != nil {
|
||||
return doc
|
||||
}
|
||||
|
||||
return tpl.String()
|
||||
}
|
||||
|
||||
func init() {
|
||||
swag.Register("swagger", &s{})
|
||||
}
|
||||
151
services/ota_update_go/go.mod
Normal file
151
services/ota_update_go/go.mod
Normal file
@@ -0,0 +1,151 @@
|
||||
module otaupdate
|
||||
|
||||
go 1.25
|
||||
|
||||
toolchain go1.25.0
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-storage-blob-go v0.15.0
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.6.0
|
||||
github.com/fiskerinc/cloud-services/pkg v0.0.0-00010101000000-000000000000
|
||||
github.com/go-pg/pg/v10 v10.11.1
|
||||
github.com/gomodule/redigo v1.8.9
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/schema v1.2.0
|
||||
github.com/intel-go/fastjson v0.0.0-20170329170629-f846ae58a1ab
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/swaggo/swag v1.8.8
|
||||
go.mongodb.org/mongo-driver v1.14.0
|
||||
google.golang.org/grpc v1.67.3
|
||||
google.golang.org/protobuf v1.36.1
|
||||
gopkg.in/DataDog/dd-trace-go.v1 v1.60.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-pipeline-go v0.2.3 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 // indirect
|
||||
github.com/ClickHouse/ch-go v0.58.2 // indirect
|
||||
github.com/DataDog/appsec-internal-go v1.4.0 // indirect
|
||||
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 // indirect
|
||||
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 // indirect
|
||||
github.com/DataDog/datadog-go/v5 v5.3.0 // indirect
|
||||
github.com/DataDog/go-libddwaf/v2 v2.2.3 // indirect
|
||||
github.com/DataDog/go-tuf v1.0.2-0.5.2 // indirect
|
||||
github.com/DataDog/sketches-go v1.4.2 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/ReneKroon/ttlcache/v2 v2.11.0 // indirect
|
||||
github.com/albenik/bcd v0.0.0-20170831201648-635201416bc7 // indirect
|
||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||
github.com/apache/arrow/go/arrow v0.0.0-20211013220434-5962184e7a30 // indirect
|
||||
github.com/apache/thrift v0.16.0 // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.327 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/confluentinc/confluent-kafka-go/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/ebitengine/purego v0.5.2 // indirect
|
||||
github.com/elliotchance/orderedmap/v2 v2.2.0 // indirect
|
||||
github.com/fiskerinc/cloud-services/pkg/can-go v0.0.0-00010101000000-000000000000 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/go-faster/city v1.0.1 // indirect
|
||||
github.com/go-faster/errors v0.6.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-pg/zerochecker v0.2.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.15.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang/mock v1.7.0-rc.1 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/iancoleman/strcase v0.3.0 // indirect
|
||||
github.com/jeremywohl/flatten v1.0.1 // indirect
|
||||
github.com/jinzhu/copier v0.3.5 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
github.com/lestrrat-go/iter v1.0.2 // indirect
|
||||
github.com/lestrrat-go/jwx v1.2.25 // indirect
|
||||
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-ieproxy v0.0.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/onsi/gomega v1.25.0 // indirect
|
||||
github.com/outcaste-io/ristretto v0.2.3 // indirect
|
||||
github.com/paulmach/orb v0.8.0 // indirect
|
||||
github.com/philhofer/fwd v1.1.2 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/redis/go-redis/v9 v9.5.1 // indirect
|
||||
github.com/rs/zerolog v1.29.1 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f // indirect
|
||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a // indirect
|
||||
github.com/swaggo/http-swagger v1.3.3 // indirect
|
||||
github.com/tinylib/msgp v1.1.8 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
github.com/twmb/franz-go v1.20.6 // indirect
|
||||
github.com/twmb/franz-go/pkg/kadm v1.17.2 // indirect
|
||||
github.com/twmb/franz-go/pkg/kmsg v1.12.0 // indirect
|
||||
github.com/vmihailenco/bufpool v0.1.11 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
|
||||
github.com/vmihailenco/tagparser v0.1.2 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/xitongsys/parquet-go v1.6.2 // indirect
|
||||
github.com/xitongsys/parquet-go-source v0.0.0-20220315005136-aec0fe3e777c // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
||||
go.opentelemetry.io/otel v1.29.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.29.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go4.org/intern v0.0.0-20230525184215-6c62f75575cb // indirect
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/time v0.8.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a // indirect
|
||||
mellium.im/sasl v0.3.1 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/fiskerinc/cloud-services/pkg => ../../pkg
|
||||
github.com/fiskerinc/cloud-services/pkg/can-go => ../../pkg/can-go
|
||||
)
|
||||
912
services/ota_update_go/go.sum
Normal file
912
services/ota_update_go/go.sum
Normal file
@@ -0,0 +1,912 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
|
||||
github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
|
||||
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 h1:8kDqDngH+DmVBiCtIjCFTGa7MBnsIOkF9IccInFEbjk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0 h1:Ma67P/GGprNwsslzEH6+Kb8nybI8jpDTm4Wmzu2ReK8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0/go.mod h1:c+Lifp3EDEamAkPVzMooRNOK6CZjNSdEnf1A7jsI9u4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 h1:nVocQV40OQne5613EeLayJiRAJuKlBGy+m22qWG+WRg=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0/go.mod h1:7QJP7dr2wznCMeqIrhMgWGf7XpAQnVrJqDm9nvV3Cu4=
|
||||
github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck=
|
||||
github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk=
|
||||
github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
|
||||
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/ClickHouse/ch-go v0.58.2 h1:jSm2szHbT9MCAB1rJ3WuCJqmGLi5UTjlNu+f530UTS0=
|
||||
github.com/ClickHouse/ch-go v0.58.2/go.mod h1:Ap/0bEmiLa14gYjCiRkYGbXvbe8vwdrfTYWhsuQ99aw=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.6.0 h1:NmnPY2Cg4hCqS2ZGBep9EWHfQPAco2Vkpwb02VXtWew=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.6.0/go.mod h1:SvXuWqDsiHJE3VAn2+3+nz9W9exOSigyskcs4DAcxJQ=
|
||||
github.com/DataDog/appsec-internal-go v1.4.0 h1:KFI8ElxkJOgpw+cUm9TXK/jh5EZvRaWM07sXlxGg9Ck=
|
||||
github.com/DataDog/appsec-internal-go v1.4.0/go.mod h1:ONW8aV6R7Thgb4g0bB9ZQCm+oRgyz5eWiW7XoQ19wIc=
|
||||
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 h1:bUMSNsw1iofWiju9yc1f+kBd33E3hMJtq9GuU602Iy8=
|
||||
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0/go.mod h1:HzySONXnAgSmIQfL6gOv9hWprKJkx8CicuXuUbmgWfo=
|
||||
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 h1:5nE6N3JSs2IG3xzMthNFhXfOaXlrsdgqmJ73lndFf8c=
|
||||
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1/go.mod h1:Vc+snp0Bey4MrrJyiV2tVxxJb6BmLomPvN1RgAvjGaQ=
|
||||
github.com/DataDog/datadog-go/v5 v5.3.0 h1:2q2qjFOb3RwAZNU+ez27ZVDwErJv5/VpbBPprz7Z+s8=
|
||||
github.com/DataDog/datadog-go/v5 v5.3.0/go.mod h1:XRDJk1pTc00gm+ZDiBKsjh7oOOtJfYfglVCmFb8C2+Q=
|
||||
github.com/DataDog/go-libddwaf/v2 v2.2.3 h1:LpKE8AYhVrEhlmlw6FGD41udtDf7zW/aMdLNbCXpegQ=
|
||||
github.com/DataDog/go-libddwaf/v2 v2.2.3/go.mod h1:8nX0SYJMB62+fbwYmx5J7zuCGEjiC/RxAo3+AuYJuFE=
|
||||
github.com/DataDog/go-tuf v1.0.2-0.5.2 h1:EeZr937eKAWPxJ26IykAdWA4A0jQXJgkhUjqEI/w7+I=
|
||||
github.com/DataDog/go-tuf v1.0.2-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0=
|
||||
github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4=
|
||||
github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
|
||||
github.com/DataDog/sketches-go v1.4.2 h1:gppNudE9d19cQ98RYABOetxIhpTCl4m7CnbRZjvVA/o=
|
||||
github.com/DataDog/sketches-go v1.4.2/go.mod h1:xJIXldczJyyjnbDop7ZZcLxJdV3+7Kra7H1KMgpgkLk=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/ReneKroon/ttlcache/v2 v2.11.0 h1:OvlcYFYi941SBN3v9dsDcC2N8vRxyHcCmJb3Vl4QMoM=
|
||||
github.com/ReneKroon/ttlcache/v2 v2.11.0/go.mod h1:mBxvsNY+BT8qLLd6CuAJubbKo6r0jh3nb5et22bbfGY=
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/albenik/bcd v0.0.0-20170831201648-635201416bc7 h1:m3Ayfs5OcAlIMEdLIQKubBsVLGee4YMUr14+d1256WE=
|
||||
github.com/albenik/bcd v0.0.0-20170831201648-635201416bc7/go.mod h1:QIAMbrwsnQZ2ES3G26RubSrDB5SPyzsp9Hts5NJdTrI=
|
||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/apache/arrow/go/arrow v0.0.0-20200730104253-651201b0f516/go.mod h1:QNYViu/X0HXDHw7m3KXzWSVXIbfUvJqBFe6Gj8/pYA0=
|
||||
github.com/apache/arrow/go/arrow v0.0.0-20211013220434-5962184e7a30 h1:HGREIyk0QRPt70R69Gm1JFHDgoiyYpCyuGE8E9k/nf0=
|
||||
github.com/apache/arrow/go/arrow v0.0.0-20211013220434-5962184e7a30/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs=
|
||||
github.com/apache/thrift v0.0.0-20181112125854-24918abba929/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/apache/thrift v0.14.2/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/apache/thrift v0.16.0 h1:qEy6UW60iVOlUy+b9ZR0d5WzUWYGOo4HfopoyBaNmoY=
|
||||
github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
|
||||
github.com/aws/aws-sdk-go v1.30.19/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.44.327 h1:ZS8oO4+7MOBLhkdwIhgtVeDzCeWOlTfKJS7EgggbIEY=
|
||||
github.com/aws/aws-sdk-go v1.44.327/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/aws/aws-sdk-go-v2 v1.7.1/go.mod h1:L5LuPC1ZgDr2xQS7AmIec/Jlc7O/Y1u2KxJyNVab250=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.5.0/go.mod h1:RWlPOAW3E3tbtNAqTwvSW54Of/yP3oiZXMI0xfUdjyA=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.3.1/go.mod h1:r0n73xwsIVagq8RsxmZbGSRQFj9As3je72C2WzUIToc=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.3.0/go.mod h1:2LAuqPx1I6jNfaGDucWfA2zqQCYCOMCDHiCOciALyNw=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.3.2/go.mod h1:qaqQiHSrOUVOfKe6fhgQ6UzhxjwqVW8aHNegd6Ws4w4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.1.1/go.mod h1:Zy8smImhTdOETZqfyn01iNOe0CNggVbPjCajyaz6Gvg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.2.1/go.mod h1:v33JQ57i2nekYTA70Mb+O18KeH4KqhdqxTJZNK1zdRE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.2.1/go.mod h1:zceowr5Z1Nh2WVP8bf/3ikB41IZW59E4yIYbg+pC6mw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.5.1/go.mod h1:6EQZIwNNvHpq/2/QSJnp4+ECvqIy55w95Ofs0ze+nGQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.11.1/go.mod h1:XLAGFrEjbvMCLvAtWLLP32yTv8GpBquCApZEycDLunI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.3.1/go.mod h1:J3A3RGUvuCZjvSuZEcOpHDnzZP/sKbhDWV2T1EOzFIM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.6.0/go.mod h1:q7o0j7d7HrJk/vr9uUt3BVRASvcU7gYZB9PUgPiByXg=
|
||||
github.com/aws/smithy-go v1.6.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/colinmarc/hdfs/v2 v2.1.1/go.mod h1:M3x+k8UKKmxtFu++uAZ0OtDU8jR3jnaZIAc6yK4Ue0c=
|
||||
github.com/confluentinc/confluent-kafka-go/v2 v2.3.0 h1:icCHutJouWlQREayFwCc7lxDAhws08td+W3/gdqgZts=
|
||||
github.com/confluentinc/confluent-kafka-go/v2 v2.3.0/go.mod h1:/VTy8iEpe6mD9pkCH5BhijlUl8ulUXymKv1Qig5Rgb8=
|
||||
github.com/containerd/containerd v1.7.0 h1:G/ZQr3gMZs6ZT0qPUZ15znx5QSdQdASW11nXTLTM2Pg=
|
||||
github.com/containerd/containerd v1.7.0/go.mod h1:QfR7Efgb/6X2BDpTPJRvPTYDE9rsF0FsXX9J8sIs/sc=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
|
||||
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v23.0.4+incompatible h1:Kd3Bh9V/rO+XpTP/BLqM+gx8z7+Yb0AA2Ibj+nNo4ek=
|
||||
github.com/docker/docker v23.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||
github.com/ebitengine/purego v0.5.2 h1:r2MQEtkGzZ4LRtFZVAg5bjYKnUbxxloaeuGxH0t7qfs=
|
||||
github.com/ebitengine/purego v0.5.2/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
|
||||
github.com/elliotchance/orderedmap/v2 v2.2.0 h1:7/2iwO98kYT4XkOjA9mBEIwvi4KpGB4cyHeOFOnj4Vk=
|
||||
github.com/elliotchance/orderedmap/v2 v2.2.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
|
||||
github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
|
||||
github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI=
|
||||
github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY=
|
||||
github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
|
||||
github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks=
|
||||
github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
|
||||
github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY=
|
||||
github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw=
|
||||
github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-pg/pg/v10 v10.11.1 h1:vYwbFpqoMpTDphnzIPshPPepdy3VpzD8qo29OFKp4vo=
|
||||
github.com/go-pg/pg/v10 v10.11.1/go.mod h1:ExJWndhDNNftBdw1Ow83xqpSf4WMSJK8urmXD5VXS1I=
|
||||
github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU=
|
||||
github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM=
|
||||
github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
|
||||
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
|
||||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
|
||||
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v23.5.26+incompatible h1:M9dgRyhJemaM4Sw8+66GHBu8ioaQmyPLg1b8VwK5WJg=
|
||||
github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo=
|
||||
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
|
||||
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-uuid v0.0.0-20180228145832-27454136f036/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
|
||||
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/intel-go/fastjson v0.0.0-20170329170629-f846ae58a1ab h1:K7WJJ5AnrQV/6tEh0Qqs19KLzvsq5V15f9CifKii6aU=
|
||||
github.com/intel-go/fastjson v0.0.0-20170329170629-f846ae58a1ab/go.mod h1:xr9Svf97gkxlW+ZDxs47vReKp7m9EUzNhEGOLyBHR+8=
|
||||
github.com/jcmturner/gofork v0.0.0-20180107083740-2aebee971930/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
||||
github.com/jeremywohl/flatten v1.0.1 h1:LrsxmB3hfwJuE+ptGOijix1PIfOoKLJ3Uee/mzbgtrs=
|
||||
github.com/jeremywohl/flatten v1.0.1/go.mod h1:4AmD/VxjWcI5SRB0n6szE2A6s2fsNHDLO0nAlMHgfLQ=
|
||||
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
|
||||
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
||||
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
|
||||
github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
|
||||
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
|
||||
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
|
||||
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
|
||||
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
|
||||
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
|
||||
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
|
||||
github.com/lestrrat-go/jwx v1.2.25 h1:tAx93jN2SdPvFn08fHNAhqFJazn5mBBOB8Zli0g0otA=
|
||||
github.com/lestrrat-go/jwx v1.2.25/go.mod h1:zoNuZymNl5lgdcu6P7K6ie2QRll5HVfF4xwxBBK1NxY=
|
||||
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
||||
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
|
||||
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo=
|
||||
github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/ncw/swift v1.0.52/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
||||
github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y=
|
||||
github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
|
||||
github.com/opencontainers/runc v1.1.6 h1:XbhB8IfG/EsnhNvZtNdLB0GBw92GYEFvKlhaJk9jUgA=
|
||||
github.com/opencontainers/runc v1.1.6/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50=
|
||||
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||
github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0=
|
||||
github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac=
|
||||
github.com/paulmach/orb v0.8.0 h1:W5XAt5yNPNnhaMNEf0xNSkBMJ1LzOzdk2MRlB6EN0Vs=
|
||||
github.com/paulmach/orb v0.8.0/go.mod h1:FWRlTgl88VI1RBx/MkrwWDRhQ96ctqMCh8boXhmqB/A=
|
||||
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
|
||||
github.com/pborman/getopt v0.0.0-20180729010549-6fdd0a2c7117/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
||||
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
|
||||
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
|
||||
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
|
||||
github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
||||
github.com/pierrec/lz4/v4 v4.1.8/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
|
||||
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||
github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052 h1:Qp27Idfgi6ACvFQat5+VJvlYToylpM/hcyLBI3WaKPA=
|
||||
github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052/go.mod h1:uvX/8buq8uVeiZiFht+0lqSLBHF+uGV8BrTv8W/SIwk=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
|
||||
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.7.0 h1:OwvJ5jQf9LnIAS83waAjPbcMsODrTQUpJ02eNLUoxBg=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.7.0/go.mod h1:/2gYnlnHVQ6xeGtfIqFy7Do03K4cdCY0A/GlJLDKLHI=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f h1:1R9KdKjCNSd7F8iGTxIpoID9prlYH8nuNYKt0XvweHA=
|
||||
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f/go.mod h1:vQhwQ4meQEDfahT5kd61wLAF5AAeh5ZPLVI4JJ/tYo8=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY=
|
||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
|
||||
github.com/swaggo/http-swagger v1.3.3 h1:Hu5Z0L9ssyBLofaama21iYaF2VbWyA8jdohaaCGpHsc=
|
||||
github.com/swaggo/http-swagger v1.3.3/go.mod h1:sE+4PjD89IxMPm77FnkDz0sdO+p5lbXzrVWT6OTVVGo=
|
||||
github.com/swaggo/swag v1.8.8 h1:/GgJmrJ8/c0z4R4hoEPZ5UeEhVGdvsII4JbVDLbR7Xc=
|
||||
github.com/swaggo/swag v1.8.8/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
|
||||
github.com/testcontainers/testcontainers-go v0.14.0 h1:h0D5GaYG9mhOWr2qHdEKDXpkce/VlvaYOCzTRi6UBi8=
|
||||
github.com/testcontainers/testcontainers-go v0.14.0/go.mod h1:hSRGJ1G8Q5Bw2gXgPulJOLlEBaYJHeBSOkQM5JLG+JQ=
|
||||
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
|
||||
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
github.com/twmb/franz-go v1.20.6 h1:TpQTt4QcixJ1cHEmQGPOERvTzo99s8jAutmS7rbSD6w=
|
||||
github.com/twmb/franz-go v1.20.6/go.mod h1:u+FzH2sInp7b9HNVv2cZN8AxdXy6y/AQ1Bkptu4c0FM=
|
||||
github.com/twmb/franz-go/pkg/kadm v1.17.2 h1:g5f1sAxnTkYC6G96pV5u715HWhxd66hWaDZUAQ8xHY8=
|
||||
github.com/twmb/franz-go/pkg/kadm v1.17.2/go.mod h1:ST55zUB+sUS+0y+GcKY/Tf1XxgVilaFpB9I19UubLmU=
|
||||
github.com/twmb/franz-go/pkg/kmsg v1.12.0 h1:CbatD7ers1KzDNgJqPbKOq0Bz/WLBdsTH75wgzeVaPc=
|
||||
github.com/twmb/franz-go/pkg/kmsg v1.12.0/go.mod h1:+DPt4NC8RmI6hqb8G09+3giKObE6uD2Eya6CfqBpeJY=
|
||||
github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94=
|
||||
github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
|
||||
github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xitongsys/parquet-go v1.5.1/go.mod h1:xUxwM8ELydxh4edHGegYq1pA8NnMKDx0K/GyB0o2bww=
|
||||
github.com/xitongsys/parquet-go v1.6.2 h1:MhCaXii4eqceKPu9BwrjLqyK10oX9WF+xGhwvwbw7xM=
|
||||
github.com/xitongsys/parquet-go v1.6.2/go.mod h1:IulAQyalCm0rPiZVNnCgm/PCL64X2tdSVGMQ/UeKqWA=
|
||||
github.com/xitongsys/parquet-go-source v0.0.0-20190524061010-2b72cbee77d5/go.mod h1:xxCx7Wpym/3QCo6JhujJX51dzSXrwmb0oH6FQb39SEA=
|
||||
github.com/xitongsys/parquet-go-source v0.0.0-20200817004010-026bad9b25d0/go.mod h1:HYhIKsdns7xz80OgkbgJYrtQY7FjHWHKH6cvN7+czGE=
|
||||
github.com/xitongsys/parquet-go-source v0.0.0-20220315005136-aec0fe3e777c h1:UDtocVeACpnwauljUbeHD9UOjjcvF5kLUHruww7VT9A=
|
||||
github.com/xitongsys/parquet-go-source v0.0.0-20220315005136-aec0fe3e777c/go.mod h1:qLb2Itmdcp7KPa5KZKvhE9U1q5bYSOmgeOckF/H2rQA=
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
|
||||
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
|
||||
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
|
||||
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
|
||||
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
|
||||
go4.org/intern v0.0.0-20230525184215-6c62f75575cb h1:ae7kzL5Cfdmcecbh22ll7lYP3iuUdnfnhiPcSaDgH/8=
|
||||
go4.org/intern v0.0.0-20230525184215-6c62f75575cb/go.mod h1:Ycrt6raEcnF5FTsLiLKkhBTO6DPX3RCUCUVnks3gFJU=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 h1:lGdhQUN/cnWdSH3291CUuxSEqc+AsGTiDxPP3r2J0l4=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210112230658-8b4aab62c064/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
|
||||
gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0=
|
||||
gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E=
|
||||
gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
|
||||
gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20210630183607-d20f26d13c79/go.mod h1:yiaVoXHpRzHGyxV3o4DktVWY4mSUErTKaeEOq6C3t3U=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.67.3 h1:OgPcDAFKHnH8X3O4WcO4XUc8GRDeKsKReqbQtiCj7N8=
|
||||
google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/DataDog/dd-trace-go.v1 v1.60.1 h1:Sqkq62MxQW/RD+sgZsQuUdHWHyXI4JS5x0lxlxrv2Hk=
|
||||
gopkg.in/DataDog/dd-trace-go.v1 v1.60.1/go.mod h1:6aArYrAHjnuaofJ3lKuSRQbhrBx1LcSpiEYCIScJE5Y=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
|
||||
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
|
||||
gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4=
|
||||
gopkg.in/jcmturner/gokrb5.v7 v7.3.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
|
||||
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
honnef.co/go/gotraceui v0.2.0 h1:dmNsfQ9Vl3GwbiVD7Z8d/osC6WtGGrasyrC2suc4ZIQ=
|
||||
honnef.co/go/gotraceui v0.2.0/go.mod h1:qHo4/W75cA3bX0QQoSvDjbJa4R8mAyyFjbWAj63XElc=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a h1:1XCVEdxrvL6c0TGOhecLuB7U9zYNdxZEjvOqJreKZiM=
|
||||
inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a/go.mod h1:e83i32mAQOW1LAqEIweALsuK2Uw4mhQadA5r7b0Wobo=
|
||||
mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo=
|
||||
mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
46
services/ota_update_go/handlers/HandleGetCarsHMIKey.go
Normal file
46
services/ota_update_go/handlers/HandleGetCarsHMIKey.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
redis "github.com/fiskerinc/cloud-services/pkg/redisv2"
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
)
|
||||
|
||||
// HandleGetCarsHMIKey godoc
|
||||
// @Summary Returns HMI session ID from Redis
|
||||
// @Description If you don't know what this is, don't call it. It's that simple
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param vin query string true "VIN"
|
||||
// @Success 200 {object} string
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 404 {object} common.JSONError "Not Found"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /cars/hmi_key [get]
|
||||
func HandleGetCarsHMIKey(w http.ResponseWriter, r *http.Request) {
|
||||
queryParams := r.URL.Query()
|
||||
vin := queryParams.Get("vin")
|
||||
ok := validator.ValidateVINSimple(vin)
|
||||
if !ok {
|
||||
loggerdataresp.BadDataErrorResp(w, ErrInvalidVIN, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rd := services.GetRedisV2Client()
|
||||
res := rd.Client.Get(context.Background(), redis.HMISessionKey(vin))
|
||||
sessionOrSalt, err := res.Result()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("Failed To Get SessionOrSalt"))
|
||||
return
|
||||
}
|
||||
|
||||
w.Write([]byte(sessionOrSalt))
|
||||
}
|
||||
92
services/ota_update_go/handlers/apicalls_get.go
Normal file
92
services/ota_update_go/handlers/apicalls_get.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"otaupdate/services"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
orm "github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
// HandleAPICallsGet godoc
|
||||
// @Summary Search API calls
|
||||
// @Description Get API calls filtered by method, user, path and date.
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param search query string false "Text search"
|
||||
// @Param from query string false "Date before requests which client is looking for"
|
||||
// @Param to query string false "Date after requests which client is looking for"
|
||||
// @Param limit query int false "Max number of records"
|
||||
// @Param offset query int false "Records offset"
|
||||
// @Param order query string false "Sort on column with asc or desc"
|
||||
// @Success 200 {object} common.JSONDBQueryResult{data=[]common.APICall}
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /apicalls [get]
|
||||
func HandleAPICallsGet(w http.ResponseWriter, r *http.Request) {
|
||||
filter, err := parseAPICallsFilter(r)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
options, err := orm.ParsePageQuery(r)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
if options.Order == "" {
|
||||
options.Order = "created_at DESC"
|
||||
}
|
||||
|
||||
csDB := services.GetDB().GetAPICalls()
|
||||
cs, total, err := csDB.Search(filter, options)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{
|
||||
Data: cs,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
func parseAPICallsFilter(r *http.Request) (common.APICallsSearch, error) {
|
||||
qs := r.URL.Query()
|
||||
from, err := parseTimeFilter(qs, "from")
|
||||
if err != nil {
|
||||
return common.APICallsSearch{}, err
|
||||
}
|
||||
|
||||
to, err := parseTimeFilter(qs, "to")
|
||||
if err != nil {
|
||||
return common.APICallsSearch{}, err
|
||||
}
|
||||
|
||||
return common.APICallsSearch{
|
||||
Search: qs.Get("search"),
|
||||
From: from,
|
||||
To: to,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseTimeFilter(qs url.Values, pname string) (*time.Time, error) {
|
||||
stime := qs.Get(pname)
|
||||
if stime == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
t, err := time.Parse(time.RFC3339, stime)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return &t, nil
|
||||
}
|
||||
108
services/ota_update_go/handlers/apicalls_get_test.go
Normal file
108
services/ota_update_go/handlers/apicalls_get_test.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHandleAPICallsGet(t *testing.T) {
|
||||
db := services.GetDB()
|
||||
timeMock := time.Date(2022, 3, 11, 3, 16, 12, 0, time.UTC)
|
||||
callsList := []common.APICall{
|
||||
{
|
||||
ClientID: "jkm@fisker.com",
|
||||
AccessType: "jwt_token",
|
||||
Method: "GET",
|
||||
Endpoint: "/some/path",
|
||||
CreatedAt: &timeMock,
|
||||
},
|
||||
{
|
||||
ClientID: "3dec092e-d869-46e3-be85-258aed85b2fc",
|
||||
AccessType: "api_token",
|
||||
Method: "GET",
|
||||
Endpoint: "/some/path",
|
||||
CreatedAt: &timeMock,
|
||||
},
|
||||
}
|
||||
|
||||
tests := map[string]struct {
|
||||
urlQ string
|
||||
callsDB queries.APICallsInterface
|
||||
expStatus int
|
||||
expBody string
|
||||
}{
|
||||
"correct": {
|
||||
urlQ: "?from=" + timeMock.Format(time.RFC3339) + "&to=" + timeMock.Format(time.RFC3339) + "&search=text",
|
||||
callsDB: &mocks.MockAPICalls{
|
||||
SearchMock: func(filter common.APICallsSearch, paging *queries.PageQueryOptions) ([]common.APICall, int, error) {
|
||||
assert.Equal(t, common.APICallsSearch{
|
||||
Search: "text",
|
||||
From: &timeMock,
|
||||
To: &timeMock,
|
||||
}, filter)
|
||||
return callsList, 2, nil
|
||||
},
|
||||
},
|
||||
expStatus: http.StatusOK,
|
||||
expBody: `{"data":[{"client_id":"jkm@fisker.com","access_type":"jwt_token","method":"GET","endpoint":"/some/path","created_at":"2022-03-11T03:16:12Z"},{"client_id":"3dec092e-d869-46e3-be85-258aed85b2fc","access_type":"api_token","method":"GET","endpoint":"/some/path","created_at":"2022-03-11T03:16:12Z"}],"total":2}`,
|
||||
},
|
||||
"correct_no_params": {
|
||||
callsDB: &mocks.MockAPICalls{
|
||||
SearchMock: func(filter common.APICallsSearch, paging *queries.PageQueryOptions) ([]common.APICall, int, error) {
|
||||
return callsList, 2, nil
|
||||
},
|
||||
},
|
||||
expStatus: http.StatusOK,
|
||||
expBody: `{"data":[{"client_id":"jkm@fisker.com","access_type":"jwt_token","method":"GET","endpoint":"/some/path","created_at":"2022-03-11T03:16:12Z"},{"client_id":"3dec092e-d869-46e3-be85-258aed85b2fc","access_type":"api_token","method":"GET","endpoint":"/some/path","created_at":"2022-03-11T03:16:12Z"}],"total":2}`,
|
||||
},
|
||||
"bad_from": {
|
||||
urlQ: "?from=kk&to=" + timeMock.Format(time.RFC3339) + "&search=text",
|
||||
expStatus: http.StatusBadRequest,
|
||||
expBody: `{"message":"parsing time \"kk\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"kk\" as \"2006\"","error":"Bad Request"}`,
|
||||
},
|
||||
"bad_to": {
|
||||
urlQ: "?from=" + timeMock.Format(time.RFC3339) + "&to=kk&search=text",
|
||||
expStatus: http.StatusBadRequest,
|
||||
expBody: `{"message":"parsing time \"kk\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"kk\" as \"2006\"","error":"Bad Request"}`,
|
||||
},
|
||||
"bad_limit": {
|
||||
urlQ: "?limit=-2",
|
||||
expStatus: http.StatusBadRequest,
|
||||
expBody: `{"message":"Limit less than 0","error":"Bad Request"}`,
|
||||
},
|
||||
"bad_db": {
|
||||
callsDB: &mocks.MockAPICalls{
|
||||
SearchMock: func(filter common.APICallsSearch, paging *queries.PageQueryOptions) ([]common.APICall, int, error) {
|
||||
return nil, 0, someErr
|
||||
},
|
||||
},
|
||||
expStatus: http.StatusServiceUnavailable,
|
||||
expBody: `{"message":"some err","error":"Service Unavailable"}`,
|
||||
},
|
||||
}
|
||||
|
||||
for tname, tt := range tests {
|
||||
t.Run(tname, func(t *testing.T) {
|
||||
r := th.MakeTestRequest(http.MethodPost, "http://example.com/apicalls"+tt.urlQ, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
db.SetAPICalls(tt.callsDB)
|
||||
handlers.HandleAPICallsGet(w, r)
|
||||
|
||||
assert.Equal(t, tt.expStatus, w.Code)
|
||||
assert.Equal(t, tt.expBody, w.Body.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
37
services/ota_update_go/handlers/apitoken_add.go
Normal file
37
services/ota_update_go/handlers/apitoken_add.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/controllers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/go-pg/pg/v10/orm"
|
||||
)
|
||||
|
||||
// HandleAPITokenAdd godoc
|
||||
// @Summary Add API token
|
||||
// @Description Create API token. Requires API token permission
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param data body common.APIToken true "API token data"
|
||||
// @Success 200 {object} common.APIToken
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /apitoken [post]
|
||||
func HandleAPITokenAdd(w http.ResponseWriter, r *http.Request) {
|
||||
apiTokenAdd.Handle(w, r)
|
||||
}
|
||||
|
||||
var apiTokenAdd = controllers.NewCreate(&apiTokenCreateHelper{})
|
||||
|
||||
type apiTokenCreateHelper struct {
|
||||
APITokensHelper
|
||||
}
|
||||
|
||||
func (h *apiTokenCreateHelper) QueryInsert(model interface{}) (orm.Result, error) {
|
||||
return services.GetDB().GetAPITokens().Insert(*model.(*common.APIToken))
|
||||
}
|
||||
56
services/ota_update_go/handlers/apitoken_add_test.go
Normal file
56
services/ota_update_go/handlers/apitoken_add_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
"testing"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
mo "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestAPITokenAdd(t *testing.T) {
|
||||
mock := mo.MockAPITokens{}
|
||||
services.GetDB().SetAPITokens(&mock)
|
||||
validData := common.APIToken{
|
||||
Token: "TESTTOKEN",
|
||||
Roles: "TESTROLES1,TESTROLES2",
|
||||
Description: "FOR UNIT TESTS",
|
||||
}
|
||||
tests := []mo.DBHttpTest{
|
||||
{
|
||||
Name: "No data",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/apitoken", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"Token required. Roles required. Description required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Invalid data",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/apitoken", common.APIToken{
|
||||
Token: "TESTTOKEN",
|
||||
}),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"Roles required. Description required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Valid data",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/apitoken", validData),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"token":"TESTTOKEN","roles":"TESTROLES1,TESTROLES2","description":"FOR UNIT TESTS","expires_at":null}`,
|
||||
},
|
||||
{
|
||||
Name: "DB error",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/apitoken", validData),
|
||||
ExpectedStatus: http.StatusServiceUnavailable,
|
||||
ExpectedResponse: `{"message":"something went wrong","error":"Service Unavailable"}`,
|
||||
DBTestCase: mo.DBTestCase{
|
||||
MockError: fmt.Errorf("something went wrong"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mo.RunDBTests(t, tests, handlers.HandleAPITokenAdd, &mock)
|
||||
}
|
||||
53
services/ota_update_go/handlers/apitoken_delete.go
Normal file
53
services/ota_update_go/handlers/apitoken_delete.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/controllers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/go-pg/pg/v10/orm"
|
||||
"github.com/gorilla/schema"
|
||||
)
|
||||
|
||||
// APITokenDelete godoc
|
||||
// @Summary Delete API token
|
||||
// @Description Delete API token. Requires delete permissions
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param token query string true "API token"
|
||||
// @Success 200 {object} common.JSONMessage
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /apitoken [delete]
|
||||
func HandleAPITokenDelete(w http.ResponseWriter, r *http.Request) {
|
||||
apiTokenDelete.Handle(w, r)
|
||||
}
|
||||
|
||||
var apiTokenDelete = controllers.NewDelete(&apiTokenDeleteHelper{})
|
||||
|
||||
type apiTokenDeleteHelper struct {
|
||||
APITokensHelper
|
||||
}
|
||||
|
||||
func (h *apiTokenDeleteHelper) ParseDeleteQueryParams(r *http.Request) interface{} {
|
||||
req := common.APIToken{}
|
||||
decoder := schema.NewDecoder()
|
||||
|
||||
decoder.SetAliasTag("json")
|
||||
decoder.Decode(&req, r.URL.Query())
|
||||
|
||||
return &req
|
||||
}
|
||||
|
||||
func (h *apiTokenDeleteHelper) QueryDelete(model interface{}) (orm.Result, error) {
|
||||
result := model.(*common.APIToken)
|
||||
return services.GetDB().GetAPITokens().Delete(result.Token)
|
||||
}
|
||||
|
||||
type APITokenDeleteRequest struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
64
services/ota_update_go/handlers/apitoken_delete_test.go
Normal file
64
services/ota_update_go/handlers/apitoken_delete_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestAPITokenDelete(t *testing.T) {
|
||||
results := mocks.MockORMResults{
|
||||
ReturnedRows: 1,
|
||||
AffectedRows: 1,
|
||||
}
|
||||
mock := mocks.MockAPITokens{
|
||||
DBMockHelper: mocks.DBMockHelper{
|
||||
ORMResponse: &results,
|
||||
},
|
||||
}
|
||||
services.GetDB().SetAPITokens(&mock)
|
||||
|
||||
tests := []mocks.DBHttpTest{
|
||||
{
|
||||
Name: "No id",
|
||||
Request: th.MakeTestRequest(http.MethodDelete, "http://example.com/apitoken", common.APIToken{}),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"primary key required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Invalid data",
|
||||
Request: th.MakeTestRequest(http.MethodDelete, "http://example.com/apitoken?roles=TESTTOKEN", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"primary key required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Good id",
|
||||
Request: th.MakeTestRequest(http.MethodDelete, "http://example.com/apitoken?token=TESTTOKEN", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"message":"Deleted"}`,
|
||||
DBTestCase: mocks.DBTestCase{
|
||||
ExpectedFilter: &common.APIToken{
|
||||
Token: "TESTTOKEN",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "DB error",
|
||||
Request: th.MakeTestRequest(http.MethodDelete, "http://example.com/apitoken?token=TESTTOKEN", nil),
|
||||
ExpectedStatus: http.StatusServiceUnavailable,
|
||||
ExpectedResponse: `{"message":"something went wrong","error":"Service Unavailable"}`,
|
||||
DBTestCase: mocks.DBTestCase{
|
||||
MockError: fmt.Errorf("something went wrong"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mocks.RunDBTests(t, tests, handlers.HandleAPITokenDelete, &mock)
|
||||
}
|
||||
38
services/ota_update_go/handlers/apitoken_update.go
Normal file
38
services/ota_update_go/handlers/apitoken_update.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/controllers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
|
||||
"github.com/go-pg/pg/v10/orm"
|
||||
)
|
||||
|
||||
// APITokenUpdate godoc
|
||||
// @Summary Update API token
|
||||
// @Description Update API token. Requires API token permission
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param data body common.APIToken true "API token data"
|
||||
// @Success 200 {object} common.SubscriptionConfiguration
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /apitoken [put]
|
||||
func HandleAPITokenUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
apiTokenUpdate.Handle(w, r)
|
||||
}
|
||||
|
||||
var apiTokenUpdate = controllers.NewUpdate(&apiTokenUpdateHelper{})
|
||||
|
||||
type apiTokenUpdateHelper struct {
|
||||
APITokensHelper
|
||||
}
|
||||
|
||||
func (h *apiTokenUpdateHelper) QueryUpdate(model interface{}) (orm.Result, error) {
|
||||
return services.GetDB().GetAPITokens().Update(model.(*common.APIToken))
|
||||
}
|
||||
59
services/ota_update_go/handlers/apitoken_update_test.go
Normal file
59
services/ota_update_go/handlers/apitoken_update_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestAPITokenUpdate(t *testing.T) {
|
||||
mock := mocks.MockAPITokens{}
|
||||
services.GetDB().SetAPITokens(&mock)
|
||||
validData := common.APIToken{
|
||||
Token: "TESTTOKEN",
|
||||
Roles: "TESTROLES1,TESTROLES2",
|
||||
Description: "FOR UNIT TESTS",
|
||||
}
|
||||
|
||||
tests := []mocks.DBHttpTest{
|
||||
{
|
||||
Name: "No data",
|
||||
Request: th.MakeTestRequest(http.MethodPut, "http://example.com/apitoken", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"Token required. Roles required. Description required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Missing PK",
|
||||
Request: th.MakeTestRequest(http.MethodPut, "http://example.com/apitoken", common.APIToken{
|
||||
Roles: "TESTROLES1,TESTROLES2",
|
||||
Description: "FOR UNIT TESTS",
|
||||
}),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"Token required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Good data",
|
||||
Request: th.MakeTestRequest(http.MethodPut, "http://example.com/apitoken", validData),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"token":"TESTTOKEN","roles":"TESTROLES1,TESTROLES2","description":"FOR UNIT TESTS","expires_at":null}`,
|
||||
},
|
||||
{
|
||||
Name: "Error",
|
||||
Request: th.MakeTestRequest(http.MethodPut, "http://example.com/apitoken", validData),
|
||||
ExpectedStatus: http.StatusServiceUnavailable,
|
||||
ExpectedResponse: `{"message":"something went wrong","error":"Service Unavailable"}`,
|
||||
DBTestCase: mocks.DBTestCase{
|
||||
MockError: fmt.Errorf("something went wrong"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mocks.RunDBTests(t, tests, handlers.HandleAPITokenUpdate, &mock)
|
||||
}
|
||||
81
services/ota_update_go/handlers/apitokens_get.go
Normal file
81
services/ota_update_go/handlers/apitokens_get.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/controllers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
"github.com/gorilla/schema"
|
||||
)
|
||||
|
||||
// HandleAPITokensGetList godoc
|
||||
// @Summary List API tokens
|
||||
// @Description List API tokens. Requires API token permission
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param token query string false "API token"
|
||||
// @Param role query string false "Role"
|
||||
// @Param description query string false "Description"
|
||||
// @Param limit query int false "Max number of records"
|
||||
// @Param offset query int false "Records offset"
|
||||
// @Success 200 {object} common.JSONDBQueryResult{data=[]common.APIToken}
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /apitokens [get]
|
||||
func HandleAPITokensGetList(w http.ResponseWriter, r *http.Request) {
|
||||
apiTokensGetList.Handle(w, r)
|
||||
}
|
||||
|
||||
var apiTokensGetList = controllers.NewGetList(&apiTokensGetListHelper{})
|
||||
|
||||
type apiTokensGetListHelper struct {
|
||||
APITokensHelper
|
||||
}
|
||||
|
||||
func (h *apiTokensGetListHelper) ParseGetListQueryParams(r *http.Request) interface{} {
|
||||
schema := schema.NewDecoder()
|
||||
filter := common.APIToken{}
|
||||
|
||||
schema.SetAliasTag("json")
|
||||
schema.Decode(&filter, r.URL.Query())
|
||||
|
||||
return &filter
|
||||
}
|
||||
|
||||
func (h *apiTokensGetListHelper) QueryCount(filter interface{}) (int, error) {
|
||||
return services.GetDB().GetAPITokens().Count(filter.(*common.APIToken))
|
||||
}
|
||||
|
||||
func (h *apiTokensGetListHelper) QuerySelect(filter interface{}, options *queries.PageQueryOptions) (interface{}, error) {
|
||||
return services.GetDB().GetAPITokens().Select(filter.(*common.APIToken), options)
|
||||
}
|
||||
|
||||
type APITokensHelper struct {
|
||||
controllers.HelperBase
|
||||
}
|
||||
|
||||
func (h *APITokensHelper) NewModel() interface{} {
|
||||
return &common.APIToken{}
|
||||
}
|
||||
|
||||
func (h *APITokensHelper) HasPK(filter interface{}) bool {
|
||||
result := filter.(*common.APIToken)
|
||||
return result.Token != ""
|
||||
}
|
||||
|
||||
func (h *APITokensHelper) ValidatePK(model interface{}) error {
|
||||
result := model.(*common.APIToken)
|
||||
|
||||
err := validator.ValidateField(result.Token, "required")
|
||||
if err != nil {
|
||||
return controllers.ErrorPKRequired
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
125
services/ota_update_go/handlers/apitokens_get_test.go
Normal file
125
services/ota_update_go/handlers/apitokens_get_test.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
"testing"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
orm "github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
mo "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestAPITokensGetList(t *testing.T) {
|
||||
mock := mo.MockAPITokens{}
|
||||
services.GetDB().SetAPITokens(&mock)
|
||||
listData := []common.APIToken{
|
||||
{
|
||||
Token: "TESTTOKEN",
|
||||
Roles: "TESTROLES1,TESTROLES2",
|
||||
Description: "FOR UNIT TESTS",
|
||||
},
|
||||
}
|
||||
expectedResp := `{"data":[{"token":"TESTTOKEN","roles":"TESTROLES1,TESTROLES2","description":"FOR UNIT TESTS","expires_at":null}],"total":1}`
|
||||
expectedRespNoTotal := `{"data":[{"token":"TESTTOKEN","roles":"TESTROLES1,TESTROLES2","description":"FOR UNIT TESTS","expires_at":null}]}`
|
||||
defaultOrder := "created_at DESC"
|
||||
|
||||
tests := []mo.DBHttpTest{
|
||||
{
|
||||
Name: "No parameters",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/apitokens", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: expectedResp,
|
||||
DBTestCase: mo.DBTestCase{
|
||||
ExpectedFilter: &common.APIToken{},
|
||||
ExpectedPage: &orm.PageQueryOptions{
|
||||
Order: defaultOrder,
|
||||
Limit: orm.PageQueryOptionsLimitMaximum,
|
||||
Offset: 0,
|
||||
},
|
||||
MockListResponse: listData,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Token parameter",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/apitokens?token=TESTTOKEN", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: expectedRespNoTotal,
|
||||
DBTestCase: mo.DBTestCase{
|
||||
ExpectedFilter: &common.APIToken{
|
||||
Token: "TESTTOKEN",
|
||||
},
|
||||
ExpectedPage: &orm.PageQueryOptions{
|
||||
Order: defaultOrder,
|
||||
Limit: orm.PageQueryOptionsLimitMaximum,
|
||||
Offset: 0,
|
||||
},
|
||||
MockListResponse: listData,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ECU parameter",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/apitokens?roles=TEST", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: expectedResp,
|
||||
DBTestCase: mo.DBTestCase{
|
||||
ExpectedFilter: &common.APIToken{
|
||||
Roles: "TEST",
|
||||
},
|
||||
ExpectedPage: &orm.PageQueryOptions{
|
||||
Order: defaultOrder,
|
||||
Limit: orm.PageQueryOptionsLimitMaximum,
|
||||
Offset: 0,
|
||||
},
|
||||
MockListResponse: listData,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Paging parameters",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/apitokens?offset=10&limit=5", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: expectedRespNoTotal,
|
||||
DBTestCase: mo.DBTestCase{
|
||||
ExpectedFilter: &common.APIToken{},
|
||||
ExpectedPage: &orm.PageQueryOptions{
|
||||
Order: defaultOrder,
|
||||
Limit: 5,
|
||||
Offset: 10,
|
||||
},
|
||||
MockListResponse: listData,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Wrong limit, -100",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/apitokens?limit=-100", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"Limit less than 0","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Wrong limit, 1000",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/apitokens?limit=1000", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"Limit greater than 100","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Error",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/apitokens", nil),
|
||||
ExpectedStatus: http.StatusServiceUnavailable,
|
||||
ExpectedResponse: `{"message":"something went wrong","error":"Service Unavailable"}`,
|
||||
DBTestCase: mo.DBTestCase{
|
||||
ExpectedFilter: &common.APIToken{},
|
||||
ExpectedPage: &orm.PageQueryOptions{
|
||||
Order: defaultOrder,
|
||||
Limit: 100,
|
||||
Offset: 0,
|
||||
},
|
||||
MockError: fmt.Errorf("something went wrong"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mo.RunDBTests(t, tests, handlers.HandleAPITokensGetList, &mock)
|
||||
}
|
||||
44
services/ota_update_go/handlers/can_signal_list_get.go
Normal file
44
services/ota_update_go/handlers/can_signal_list_get.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
|
||||
ch "github.com/ClickHouse/clickhouse-go/v2"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
// HandleCanSignalListGet godoc
|
||||
// @Summary Lists of Can Signals used in Feature Table
|
||||
// @Description Returns a list of can signals Requires API token permission.
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Success 200 {object} common.JSONDBQueryResult{data=[]common.CANSignalNameList} "list of cars"
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /can_signals_list [get]
|
||||
func HandleCanSignalListGet(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var canlist []common.CANSignalNameList
|
||||
conn, err := services.GetClickhouseConn()
|
||||
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
logger.Error().Err(err).Msg("cannot get clickhouse client")
|
||||
return
|
||||
}
|
||||
chCtx := ch.Context(context.Background())
|
||||
err = conn.Select(chCtx, &canlist, "select Signal_Name from ml_var_list_table where Is_Feature=True")
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{Data: canlist})
|
||||
}
|
||||
48
services/ota_update_go/handlers/can_signal_list_get_test.go
Normal file
48
services/ota_update_go/handlers/can_signal_list_get_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
"testing"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/clickhouse"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHandleCanSignalListGetList(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
conn clickhouse.ConnInterface
|
||||
expStatus int
|
||||
expBody string
|
||||
}{
|
||||
"correct": {
|
||||
conn: &clickhouse.MockConn{ExpectedResult: []common.CANSignalNameList{{"A"}, {"B"}},},
|
||||
expStatus: http.StatusOK,
|
||||
expBody: `{"data":[{"signal_name":"A"},{"signal_name":"B"}]}`,
|
||||
},
|
||||
"failed_query": {
|
||||
conn: &clickhouse.MockConn{ExpectedResult: someErr},
|
||||
expStatus: http.StatusServiceUnavailable,
|
||||
expBody: `{"message":"json: cannot unmarshal object into Go value of type []common.CANSignalNameList","error":"Service Unavailable"}`,
|
||||
},
|
||||
}
|
||||
|
||||
for tname, tt := range tests {
|
||||
t.Run(tname, func(t *testing.T) {
|
||||
services.SetClickhouseConn(tt.conn)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
ctx := context.Background()
|
||||
r := httptest.NewRequest(http.MethodGet, "http://example.com/can_signals_list", nil).
|
||||
WithContext(ctx)
|
||||
|
||||
handlers.HandleCanSignalListGet(w, r)
|
||||
assert.Equal(t, tt.expStatus, w.Code)
|
||||
assert.Equal(t, tt.expBody, w.Body.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
200
services/ota_update_go/handlers/can_signal_vin_get.go
Normal file
200
services/ota_update_go/handlers/can_signal_vin_get.go
Normal file
@@ -0,0 +1,200 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"otaupdate/services"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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/validator"
|
||||
|
||||
ch "github.com/ClickHouse/clickhouse-go/v2"
|
||||
"github.com/gorilla/schema"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
const (
|
||||
limit = 100000
|
||||
)
|
||||
|
||||
// HandleCanSignalVINGet godoc
|
||||
// @Summary Export CAN signals for a specific VIN
|
||||
// @Description Exports CAN signals for a specific VIN based on specified time range and CAN signals. Requires API token permission.
|
||||
// @Accept json
|
||||
// @Produce octet-stream
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param select_all query boolean false "Select All CAN Signals"
|
||||
// @Param can_signals query []string false "CAN Signals"
|
||||
// @Param timestamp_start query float64 true "Start time must be included"
|
||||
// @Param timestamp_end query float64 true "End time must be included"
|
||||
// @Param vin query string true "VIN must be included in the query"
|
||||
// @Success 200 {file} CSV file with the specified CAN signals data
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /can_signals_export [get]
|
||||
func HandleCanSignalVINGet(w http.ResponseWriter, r *http.Request) {
|
||||
filter, err := parseCANSignalFilter(r)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
filter.Limit = limit
|
||||
|
||||
conn, err := services.GetClickhouseConn()
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
logger.Error().Err(err).Msg("cannot get clickhouse client")
|
||||
return
|
||||
}
|
||||
|
||||
if filter.SelectAll {
|
||||
allCanSignals, err := getListOfAllCanSignals(conn)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
filter.CanSignals = []string{allCanSignals}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/csv")
|
||||
res := 0
|
||||
|
||||
for res == filter.Limit || res == 0 {
|
||||
res, err = getCanSignalVin(conn, filter, w)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
if res == 0 {
|
||||
return
|
||||
}
|
||||
filter.Offset += res
|
||||
}
|
||||
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func parseCANSignalFilter(r *http.Request) (common.CANSignalQuery, error) {
|
||||
sch := schema.NewDecoder()
|
||||
filter := common.CANSignalQuery{}
|
||||
sch.SetAliasTag("json")
|
||||
|
||||
//parse, err := r.URL.Parse(r.URL.String())
|
||||
err := sch.Decode(&filter, r.URL.Query())
|
||||
if err != nil {
|
||||
return common.CANSignalQuery{}, errors.WithStack(err)
|
||||
}
|
||||
|
||||
err = validator.GetValidator().Struct(filter)
|
||||
if err != nil {
|
||||
return common.CANSignalQuery{}, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if len(filter.CanSignals) == 0 && !filter.SelectAll {
|
||||
return common.CANSignalQuery{}, errors.New("either Select All of a list of CAN Signals required")
|
||||
}
|
||||
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
func getCanSignalVin(conn clickhouse.ConnInterface, filter common.CANSignalQuery, w http.ResponseWriter) (int, error) {
|
||||
chCtx := ch.Context(context.Background())
|
||||
|
||||
query := fmt.Sprintf(`SELECT * from (SELECT VIN, Timestamp, %s FROM feature_table WHERE VIN = '%s' AND Timestamp BETWEEN %f AND %f LIMIT %d, %d) ORDER BY Timestamp DESC`,
|
||||
strings.Join(filter.CanSignals, ", "),
|
||||
filter.VIN,
|
||||
filter.TimestampStart,
|
||||
filter.TimestampEnd,
|
||||
filter.Offset,
|
||||
filter.Limit)
|
||||
rows, err := conn.Query(chCtx, query)
|
||||
if err != nil {
|
||||
return 0, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if filter.Offset == 0 {
|
||||
headerLine := "VIN,Timestamp"
|
||||
for _, signal := range filter.CanSignals {
|
||||
headerLine += "," + signal
|
||||
}
|
||||
headerLine += "\n"
|
||||
_, err = w.Write([]byte(headerLine))
|
||||
if err != nil {
|
||||
return 0, errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
columnTypes := rows.ColumnTypes()
|
||||
row := make([]interface{}, len(columnTypes))
|
||||
for i, cType := range columnTypes {
|
||||
kind := cType.ScanType().Kind()
|
||||
scanName := cType.ScanType().Name()
|
||||
|
||||
if kind == reflect.String || strings.Contains(scanName, "string") {
|
||||
row[i] = new(string)
|
||||
} else if kind == reflect.Float64 || kind == reflect.Float32 || strings.Contains(scanName, "float") || strings.Contains(scanName, "decimal") {
|
||||
row[i] = new(float64)
|
||||
} else if kind == reflect.Int64 || kind == reflect.Int || strings.Contains(scanName, "int") {
|
||||
row[i] = new(int64)
|
||||
} else if strings.Contains(scanName, "Time") {
|
||||
row[i] = new(time.Time)
|
||||
} else {
|
||||
row[i] = new(string)
|
||||
}
|
||||
}
|
||||
|
||||
var canSignals []string
|
||||
if len(filter.CanSignals) == 1 {
|
||||
canSignals = strings.Split(filter.CanSignals[0], ",")
|
||||
}
|
||||
rowCounter := 0
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
rowCounter++
|
||||
err := rows.Scan(row...)
|
||||
if err != nil {
|
||||
return 0, errors.WithStack(err)
|
||||
}
|
||||
|
||||
vin := *row[0].(*string)
|
||||
timestamp := *row[1].(*time.Time)
|
||||
dataline := vin + "," + timestamp.Format("2006-01-02 15:04:05.000000")
|
||||
|
||||
for i := 2; i < len(canSignals)+2; i++ {
|
||||
val := "0"
|
||||
if v, ok := row[i].(*float64); ok {
|
||||
val = strconv.FormatFloat(*v, 'f', -1, 64)
|
||||
}
|
||||
dataline += "," + val
|
||||
}
|
||||
|
||||
w.Write([]byte(dataline + "\n"))
|
||||
}
|
||||
w.(http.Flusher).Flush()
|
||||
return rowCounter, nil
|
||||
}
|
||||
|
||||
func getListOfAllCanSignals(conn clickhouse.ConnInterface) (string, error) {
|
||||
var allCanSignals []string
|
||||
|
||||
var canlist []common.CANSignalNameList
|
||||
chCtx := ch.Context(context.Background())
|
||||
err := conn.Select(chCtx, &canlist, "select Signal_Name from ml_var_list_table where Is_Feature=True")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, signalName := range canlist {
|
||||
allCanSignals = append(allCanSignals, signalName.Signal_Name)
|
||||
}
|
||||
|
||||
return strings.Join(allCanSignals, ","), nil
|
||||
}
|
||||
73
services/ota_update_go/handlers/can_signal_vin_get_test.go
Normal file
73
services/ota_update_go/handlers/can_signal_vin_get_test.go
Normal file
File diff suppressed because one or more lines are too long
83
services/ota_update_go/handlers/car_configuration_update.go
Normal file
83
services/ota_update_go/handlers/car_configuration_update.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/services"
|
||||
"strconv"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common/actionlogger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/httphandlers"
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
"github.com/fiskerinc/cloud-services/pkg/manifestsender"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var errInvalidVIN = errors.New("invalid VIN entered")
|
||||
|
||||
// CarConfigurationUpdate godoc
|
||||
// @Summary Send VOD and CDS to car
|
||||
// @Description Get all sap codes for a car, transform them to VOD and CDS, then send it to the car
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param vin path string true "VIN to send configuration update"
|
||||
// @Param forced query bool false "Force configuration update"
|
||||
// @Success 200 {object} common.JSONMessage
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /car_config/{vin} [post]
|
||||
func CarConfigurationUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
params := httprouter.ParamsFromContext(r.Context())
|
||||
vin := params.ByName("vin")
|
||||
|
||||
queryParams := r.URL.Query()
|
||||
forced, _ := strconv.ParseBool(queryParams.Get("forced"))
|
||||
|
||||
rds := services.RedisClientPool().GetFromPool()
|
||||
defer rds.Close()
|
||||
cs := services.GetVehicleConfig()
|
||||
db := services.GetDB()
|
||||
sms := services.GetSMSClient()
|
||||
|
||||
alDB := services.GetDB().GetActionLog()
|
||||
actionLog := actionlogger.ActionLog{
|
||||
VIN: vin,
|
||||
Action: actionlogger.CarConfigurationUpdate,
|
||||
UserIdentifier: httphandlers.GetClientID(r),
|
||||
CallLocation: "github.com/fiskerinc/cloud-services/services/ota_update_go/handlers/car_configuration_update.go",
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := alDB.Insert(actionLog)
|
||||
if err != nil {
|
||||
logger.Err(err).Msg("failed to insert action log inside CarConfigurationUpdate")
|
||||
}
|
||||
}()
|
||||
|
||||
username := httphandlers.GetClientID(r)
|
||||
|
||||
manifestSender := manifestsender.NewTBOXManifestSender(rds, cs, db, sms, nil)
|
||||
input := manifestsender.ProcessConfigUpdateStruct{
|
||||
VIN: vin,
|
||||
Username: username,
|
||||
SendToCar: true,
|
||||
DontCreateDatabaseEntry: false,
|
||||
Forced: forced,
|
||||
}
|
||||
|
||||
// Process and send as this is a new manifest that needs a car update
|
||||
_, err := manifestSender.ProcessConfigUpdate(input, services.GetDB().GetCarConfigData())
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONMessage{
|
||||
Message: "Sent",
|
||||
})
|
||||
}
|
||||
77
services/ota_update_go/handlers/car_software_information.go
Normal file
77
services/ota_update_go/handlers/car_software_information.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
)
|
||||
|
||||
// HandleCarSoftwareInformation godoc
|
||||
// @Summary Get overall software version from a car and its ecu version information
|
||||
// @Description Get overall software version from a car and its ecu version information
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param vin query string true "VIN"
|
||||
// @Success 200 {object} CarSoftwareInformationResponse
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /car/software_information [get]
|
||||
func HandleCarSoftwareInformation(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
vin := qs.Get("vin")
|
||||
information, err := carSoftwareInformation(vin)
|
||||
if loggerdataresp.BadDataError(err) {
|
||||
return
|
||||
}
|
||||
utils.RespJSON(w, http.StatusOK, information)
|
||||
}
|
||||
|
||||
func carSoftwareInformation(vin string) (info CarSoftwareInformationResponse, err error) {
|
||||
info.VIN = vin
|
||||
var ecus []common.CarECU
|
||||
ecus, err = services.GetDB().GetCars().GetCarECUs(common.CarECUFilter{VIN: vin, Unique: true}, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
info.ECUVersionInformation = convertCommonCarECUToECUVersionInformationArray(ecus)
|
||||
var carVersion common.CarPKCOSVersion
|
||||
carVersion, err = services.GetDB().GetCars().GetSoftwareVersion(vin)
|
||||
info.OSVersion = carVersion.OSVersion
|
||||
info.SUMsVersion = carVersion.SumsVersion
|
||||
return
|
||||
}
|
||||
|
||||
type CarSoftwareInformationResponse struct {
|
||||
VIN string `json:"vin"`
|
||||
SUMsVersion string `json:"sum_version"`
|
||||
OSVersion string `json:"os_version"`
|
||||
ECUVersionInformation []ECUVersionInformation `json:"ecu_version_information"`
|
||||
}
|
||||
|
||||
type ECUVersionInformation struct {
|
||||
ECU string `json:"ecu"`
|
||||
SoftwareVersion string `json:"software_version"`
|
||||
HardwareVersion string `json:"hardware_version"`
|
||||
}
|
||||
|
||||
func convertCommonCarECUToECUVersionInformationArray(input []common.CarECU) (output []ECUVersionInformation) {
|
||||
output = make([]ECUVersionInformation, len(input))
|
||||
for index, ecuInfo := range input {
|
||||
output[index] = convertCommonCarECUToECUVersionInformation(ecuInfo)
|
||||
}
|
||||
// Probably want to sort the ECU's alphabetically
|
||||
return output
|
||||
}
|
||||
|
||||
func convertCommonCarECUToECUVersionInformation(input common.CarECU) (output ECUVersionInformation) {
|
||||
output.ECU = input.ECU
|
||||
output.SoftwareVersion = input.Version
|
||||
output.HardwareVersion = input.HWVersion
|
||||
return output
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
)
|
||||
|
||||
// HandleCarSoftwareInformationV2 godoc
|
||||
// @Summary Get overall software version from a car and its ecu version information
|
||||
// @Description Get overall software version from a car and its ecu version information
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param vin query string true "VIN"
|
||||
// @Success 200 {object} CarSoftwareInformationResponseV2
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /car/software_information/v2 [get]
|
||||
func HandleCarSoftwareInformationV2(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
vin := qs.Get("vin")
|
||||
information, err := carSoftwareInformationV2(vin)
|
||||
if loggerdataresp.BadDataError(err) {
|
||||
return
|
||||
}
|
||||
utils.RespJSON(w, http.StatusOK, information)
|
||||
}
|
||||
|
||||
func carSoftwareInformationV2(vin string) (info CarSoftwareInformationResponseV2, err error) {
|
||||
info.VIN = vin
|
||||
var ecus []common.CarECU
|
||||
ecus, err = services.GetDB().GetCars().GetCarECUs(common.CarECUFilter{VIN: vin, Unique: true, ECUs: OVLoopECUList}, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
info.ECUVersionInformation = convertCommonCarECUToECUVersionInformationArrayV2(ecus)
|
||||
var carVersion common.CarPKCOSVersion
|
||||
carVersion, err = services.GetDB().GetCars().GetSoftwareVersion(vin)
|
||||
info.OSVersion = carVersion.OSVersion
|
||||
info.SUMsVersion = carVersion.SumsVersion
|
||||
return
|
||||
}
|
||||
|
||||
type CarSoftwareInformationResponseV2 struct {
|
||||
VIN string `json:"vin"`
|
||||
SUMsVersion string `json:"sum_version"`
|
||||
OSVersion string `json:"os_version"`
|
||||
ECUVersionInformation []ECUVersionInformationV2 `json:"ecu_version_information"`
|
||||
}
|
||||
|
||||
type ECUVersionInformationV2 struct {
|
||||
ECU string `json:"ecu"`
|
||||
SupplierSWVersion string `json:"supplier_sw_version,omitempty" validate:"max=1024"`
|
||||
BootLoaderVersion string `json:"boot_loader_version,omitempty" validate:"max=1024"`
|
||||
}
|
||||
|
||||
func convertCommonCarECUToECUVersionInformationArrayV2(input []common.CarECU) (output []ECUVersionInformationV2) {
|
||||
output = make([]ECUVersionInformationV2, len(input))
|
||||
for index, ecuInfo := range input {
|
||||
output[index] = convertCommonCarECUToECUVersionInformationV2(ecuInfo)
|
||||
}
|
||||
// Probably want to sort the ECU's alphabetically
|
||||
return output
|
||||
}
|
||||
|
||||
func convertCommonCarECUToECUVersionInformationV2(input common.CarECU) (output ECUVersionInformationV2) {
|
||||
output.ECU = input.ECU
|
||||
output.SupplierSWVersion = input.SupplierSWVersion
|
||||
output.BootLoaderVersion = input.BootLoaderVersion
|
||||
return output
|
||||
}
|
||||
|
||||
var OVLoopECUList = []string{"ACU","ADAS","AMP","BCM","BMS","CIM","CMRR_FL","CMRR_FR","CMRR_RL","CMRR_RR","DSMC","ECC","EPS1","EPS2","ESP","FCM","GW","iBooster","ICC","MCU","MCU_F","MCU_R","MRR","OBC","OHC","PKC","PLGM","PSM","PVIU","PWC","PWC_R","TRM","VCU","VSP"}
|
||||
153
services/ota_update_go/handlers/cars_allowed_access.go
Normal file
153
services/ota_update_go/handlers/cars_allowed_access.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
"github.com/fiskerinc/cloud-services/pkg/tmobile"
|
||||
)
|
||||
|
||||
// FIX BUG: current system allows for vins to be tmobile activated,
|
||||
// but because we don't have them, we don't get them in response list
|
||||
|
||||
// HandleCarsAllowedAccess godoc
|
||||
// @Summary Fetch the list of VINs that can connect to fisker cloud
|
||||
// @Description Note: when a VIN is added or removed, changes will take time to propagate to the network filters
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Success 200 {object} HandleCarsAllowedAccessResponse
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /cars/allowed_access [get]
|
||||
func HandleCarsAllowedAccess(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
res := HandleCarsAllowedAccessResponse{}
|
||||
res.AllowedVINs, err = fetchVINList()
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
res.AllowAll = len(res.AllowedVINs) == 0
|
||||
err = json.NewEncoder(w).Encode(res)
|
||||
loggerdataresp.BadDataErrorResp(w, err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
type HandleCarsAllowedAccessResponse struct {
|
||||
AllowAll bool `json:"allow_all"`
|
||||
AllowedVINs []string `json:"allowed_vins"`
|
||||
}
|
||||
|
||||
func fetchVINList() (vins []string, err error) {
|
||||
vins, err = services.GetDB().GetCars().GetWhiteListCars()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// HandleCarsAllowedAccess godoc
|
||||
// @Summary Check if a single VIN has cloud and t-mobile access
|
||||
// @Description
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param VINList body HandleAccessCheckInput true "List of VINs to check"
|
||||
// @Success 200 {object} HandleAccessCheckResponse
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /cars/allowed_access [post]
|
||||
func HandleCarAllowedAccess(w http.ResponseWriter, r *http.Request) {
|
||||
vinput := HandleAccessCheckInput{}
|
||||
err := json.NewDecoder(r.Body).Decode(&vinput)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
results, err := CarAllowedAccess(vinput.VINs)
|
||||
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
res := HandleAccessCheckResponse{
|
||||
VINStatuses: results,
|
||||
}
|
||||
|
||||
err = json.NewEncoder(w).Encode(res)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func CarAllowedAccess(vins []string) (results []AllowedStruct, err error) {
|
||||
var allowedVINs []string
|
||||
allowedVINs, err = fetchVINList()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
slices.Sort(allowedVINs)
|
||||
|
||||
carDB := services.GetDB().GetCars()
|
||||
tmobileClient, err := tmobile.NewTMobileMiniClient()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
results = make([]AllowedStruct, 0, len(vins))
|
||||
for _, v := range vins {
|
||||
temp := AllowedStruct{}
|
||||
temp.VIN = v
|
||||
temp.CloudAccess = slices.Contains(allowedVINs, v)
|
||||
// Slow but I dont give a damn
|
||||
var car *common.Car
|
||||
car, err = carDB.SelectByVIN(v)
|
||||
if err != nil {
|
||||
logger.Err(err).Msg("failed to select car by vin")
|
||||
} else if car.ICCID != "" && car.ICCID != "N/A" {
|
||||
car.ICCID = strings.TrimSuffix(car.ICCID, "F")
|
||||
var details *tmobile.DeviceDetailsResponse
|
||||
input := tmobile.DeviceDetailsRequest{
|
||||
ICCID: car.ICCID,
|
||||
}
|
||||
details, err = tmobileClient.DeviceDetails(context.Background(), &input)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
temp.TMobileStatus = string(details.Status)
|
||||
} else {
|
||||
temp.TMobileStatus = "INVALID ICCID"
|
||||
}
|
||||
results = append(results, temp)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type HandleAccessCheckInput struct {
|
||||
VINs []string `json:"vins"`
|
||||
}
|
||||
|
||||
type HandleAccessCheckResponse struct {
|
||||
VINStatuses []AllowedStruct `json:"vin_statuses"`
|
||||
}
|
||||
|
||||
type AllowedStruct struct {
|
||||
VIN string `json:"vin"`
|
||||
CloudAccess bool `json:"cloud_access"`
|
||||
TMobileStatus string `json:"tmobile_access"`
|
||||
}
|
||||
71
services/ota_update_go/handlers/cars_by_manifest.go
Normal file
71
services/ota_update_go/handlers/cars_by_manifest.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
// HandleGetCarsByManifest godoc
|
||||
// @Summary Get cars by manifest
|
||||
// @Description Returns list of cars selected by manifest id
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param limit query int false "Max number of records"
|
||||
// @Param offset query int false "Records offset"
|
||||
// @Param order query string false "Sort on column with asc or desc"
|
||||
// @Param manifest_id path int true "Manifest ID"
|
||||
// @Success 200 {object} common.JSONDBQueryResult{data=[]common.Car} "list of cars"
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /manifests/{manifest_id}/vehicles [get]
|
||||
func HandleGetCarsByManifest(w http.ResponseWriter, r *http.Request) {
|
||||
params := httprouter.ParamsFromContext(r.Context())
|
||||
idS := params.ByName("manifest_id")
|
||||
id, err := strconv.Atoi(idS)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
options, err := queries.ParsePageQuery(r)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
manDB := services.GetDB().GetUpdateManifests()
|
||||
man := common.UpdateManifest{ID: int64(id)}
|
||||
err = manDB.Load(&man)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable, loggerdataresp.PostgresNoRowsErrorCheck) {
|
||||
return
|
||||
}
|
||||
|
||||
carsDB := services.GetDB().GetCars()
|
||||
cars, err := carsDB.CarsByManifest(man, options)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
total, err := carsDB.CountCarsByManifest(man)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
if cars == nil {
|
||||
cars = []common.Car{}
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{
|
||||
Data: cars,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
116
services/ota_update_go/handlers/cars_by_manifest_get_test.go
Normal file
116
services/ota_update_go/handlers/cars_by_manifest_get_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
|
||||
m "github.com/fiskerinc/cloud-services/pkg/common"
|
||||
orm "github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
mo "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
func TestHandleGetCarsByManifest(t *testing.T) {
|
||||
mock := mo.MockCars{}
|
||||
mockMan := mo.MockUpdateManifests{
|
||||
LoadResponse: &m.UpdateManifest{},
|
||||
}
|
||||
services.GetDB().SetCars(&mock)
|
||||
services.GetDB().SetUpdateManifests(&mockMan)
|
||||
expectedCar := `{"vin":"1G1FP87S3GN100062","country":"US","year":2022,"model":"Ocean","trim":"Base","powertrain":"MD23","restraint":"None","body_type":"truck"}`
|
||||
expectedResp := fmt.Sprintf(`{"data":[%s],"total":1}`, expectedCar)
|
||||
expectedEmptyResp := `{"data":[]}`
|
||||
listData := []m.Car{
|
||||
{
|
||||
VIN: "1G1FP87S3GN100062",
|
||||
Model: "Ocean",
|
||||
Year: 2022,
|
||||
Trim: "Base",
|
||||
Country: "US",
|
||||
Powertrain: "MD23",
|
||||
Restraint: "None",
|
||||
BodyType: "truck",
|
||||
},
|
||||
}
|
||||
p := httprouter.Params{
|
||||
{"manifest_id", "8"},
|
||||
}
|
||||
ctx := context.WithValue(context.Background(), httprouter.ParamsKey, p)
|
||||
invalidP := httprouter.Params{
|
||||
{"manifest_id", "o"},
|
||||
}
|
||||
invalidCtx := context.WithValue(context.Background(), httprouter.ParamsKey, invalidP)
|
||||
tests := []mo.DBHttpTest{
|
||||
{
|
||||
Name: "Success",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/manifests/8/vehicles", nil).
|
||||
WithContext(ctx),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: expectedResp,
|
||||
DBTestCase: mo.DBTestCase{
|
||||
ExpectedPage: &orm.PageQueryOptions{
|
||||
Limit: orm.PageQueryOptionsLimitMaximum,
|
||||
Offset: 0,
|
||||
},
|
||||
MockListResponse: listData,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Empty",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/manifests/8/vehicles", nil).
|
||||
WithContext(ctx),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: expectedEmptyResp,
|
||||
DBTestCase: mo.DBTestCase{
|
||||
ExpectedPage: &orm.PageQueryOptions{
|
||||
Limit: orm.PageQueryOptionsLimitMaximum,
|
||||
Offset: 0,
|
||||
},
|
||||
MockListResponse: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Invalid param",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/manifests/8/vehicles", nil).
|
||||
WithContext(invalidCtx),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"strconv.Atoi: parsing \"o\": invalid syntax","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Error",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/manifests/8/vehicles", nil).
|
||||
WithContext(ctx),
|
||||
ExpectedStatus: http.StatusServiceUnavailable,
|
||||
ExpectedResponse: `{"message":"something went wrong","error":"Service Unavailable"}`,
|
||||
DBTestCase: mo.DBTestCase{
|
||||
ExpectedPage: &orm.PageQueryOptions{
|
||||
Limit: orm.PageQueryOptionsLimitMaximum,
|
||||
Offset: 0,
|
||||
},
|
||||
MockError: fmt.Errorf("something went wrong"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Wrong limit, -100",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/manifests/8/vehicles?limit=-100", nil).
|
||||
WithContext(ctx),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"Limit less than 0","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Wrong limit, 1000",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/manifests/8/vehicles?limit=1000", nil).
|
||||
WithContext(ctx),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"Limit greater than 100","error":"Bad Request"}`,
|
||||
},
|
||||
}
|
||||
|
||||
mo.RunDBTests(t, tests, handlers.HandleGetCarsByManifest, &mock)
|
||||
}
|
||||
104
services/ota_update_go/handlers/cars_change_access.go
Normal file
104
services/ota_update_go/handlers/cars_change_access.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
"github.com/fiskerinc/cloud-services/pkg/tmobile"
|
||||
"github.com/fiskerinc/cloud-services/pkg/vindecoder"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// HandleCarChangeAccess godoc
|
||||
// @Summary change access for a vin to connect to fisker cloud
|
||||
// @Description Note: when a VIN is added or removed, changes will take time to propagate to the network filters
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param data body CarChangeAccessInput true "car change access data"
|
||||
// @Success 200 {object} HandleCarsAllowedAccessResponse
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /cars/change_access [post]
|
||||
func HandleCarChangeAccess(w http.ResponseWriter, r *http.Request) {
|
||||
ccai := CarChangeAccessInput{}
|
||||
err := json.NewDecoder(r.Body).Decode(&ccai)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
failedVINs := []string{}
|
||||
for _, vin := range ccai.VINs {
|
||||
ok := vindecoder.ValidateVINSimple(vin)
|
||||
if !ok {
|
||||
failedVINs = append(failedVINs, vin)
|
||||
}
|
||||
}
|
||||
if len(failedVINs) > 0 {
|
||||
errors.New(fmt.Sprintf("VINS Invalid, no changes made: %+v", failedVINs))
|
||||
loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = CarChangeAccess(ccai)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func CarChangeAccess(ccai CarChangeAccessInput) (err error) {
|
||||
// Add/Remove from the database
|
||||
if ccai.AllowCloudAccess != nil {
|
||||
if *ccai.AllowCloudAccess {
|
||||
err = services.GetDB().GetCars().WhitelistCars(ccai.VINs, ccai.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = services.GetDB().GetCars().BlacklistCars(ccai.VINs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ccai.AllowTMobileAccess != nil {
|
||||
var miniClient *tmobile.TMobileMiniClient
|
||||
miniClient, err = tmobile.NewTMobileMiniClient()
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "Failed to Create TMOible Client")
|
||||
}
|
||||
|
||||
var iccids []string
|
||||
iccids, err = services.GetDB().GetCars().GetICCIDs(ccai.VINs)
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "Failed to get ICCIDs")
|
||||
}
|
||||
tmobileInput := tmobile.ChangeDeviceActivation{
|
||||
ICCIDs: iccids,
|
||||
Enabled: *ccai.AllowTMobileAccess,
|
||||
}
|
||||
|
||||
// Add/Remove from TMobile
|
||||
// TODO when T-Mobile gets back about access
|
||||
err = miniClient.ChangeDeviceStatus(context.Background(), tmobileInput)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type CarChangeAccessInput struct {
|
||||
VINs []string `json:"vins"` // List of VINs that will be changed
|
||||
AllowCloudAccess *bool `json:"allow_cloud_access,omitempty"` // Cars can connect through gateway
|
||||
AllowTMobileAccess *bool `json:"allow_tmobile_access,omitempty"` // Cars get tmobile access
|
||||
Source string `json:"source,omitempty"`
|
||||
}
|
||||
102
services/ota_update_go/handlers/carupdate_add.go
Normal file
102
services/ota_update_go/handlers/carupdate_add.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common/actionlogger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common/carupdatestatus"
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
uhelpers "github.com/fiskerinc/cloud-services/pkg/usecase_helpers"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/httphandlers"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
// HandleCarUpdatesAdd godoc
|
||||
// @Summary Add car updates
|
||||
// @Description Create car updates assigning manifest package to cars, and send notifications
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param data body usecase_helpers.JSONCarUpdatesRequest true "Update manifest or package id and, car ids"
|
||||
// @Success 200 {object} common.JSONDBQueryResult{data=[]common.CarUpdate} "Created car updates result"
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /carupdate [post]
|
||||
func HandleCarUpdatesAdd(w http.ResponseWriter, r *http.Request) {
|
||||
var req uhelpers.JSONCarUpdatesRequest
|
||||
var ups []common.CarUpdate
|
||||
|
||||
err := httphandlers.ParseRequest(r, &req)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
username := httphandlers.GetClientID(r)
|
||||
|
||||
manifest, err := getManifest(req.UpdateManifestID)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusNotFound, loggerdataresp.PostgresNoRowsErrorCheck) {
|
||||
return
|
||||
}
|
||||
|
||||
d := services.GetDB().GetCarUpdates()
|
||||
k := services.GetKafkaProducer()
|
||||
|
||||
alDB := services.GetDB().GetActionLog()
|
||||
go func() {
|
||||
actionLog := actionlogger.ActionLog{
|
||||
VIN: "",
|
||||
Action: actionlogger.CarUpdate,
|
||||
UserIdentifier: httphandlers.GetClientID(r),
|
||||
CallLocation: "github.com/fiskerinc/cloud-services/services/ota_update_go/handlers/car_update_add.go",
|
||||
Description: fmt.Sprintf("UpdateManifest: %d", req.UpdateManifestID),
|
||||
}
|
||||
for _, vin := range req.VINs {
|
||||
actionLog.VIN = vin
|
||||
err = alDB.Insert(actionLog)
|
||||
if err != nil {
|
||||
logger.Err(err).Msg("failed to insert action log inside HandleCarUpdatesAdd")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
notifier := uhelpers.NewUpdateNotifier(d, k)
|
||||
|
||||
ups, err = notifier.Send(req.VINs, manifest, username)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, vin := range req.VINs {
|
||||
// Notify car user of in progress update through FOA API
|
||||
fs := services.GetFoaService()
|
||||
foaResp, err := fs.OtaUpdateStatus(vin, &common.CarUpdate{UpdateManifestID: req.UpdateManifestID}, &common.CarUpdateProgress{Status: carupdatestatus.Pending})
|
||||
if err != nil || (foaResp != nil && foaResp.StatusCode != http.StatusOK) {
|
||||
bodyBytes, _ := io.ReadAll(foaResp.Body)
|
||||
bodyString := string(bodyBytes)
|
||||
logger.Err(err).Msgf("notify FOA for update manifest %d pending state %s for %s failed with http status %d and message %s", req.UpdateManifestID, carupdatestatus.Pending, vin, foaResp.StatusCode, bodyString)
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{
|
||||
Data: ups,
|
||||
})
|
||||
}
|
||||
|
||||
func getManifest(manifestID int64) (common.UpdateManifest, error) {
|
||||
um := services.GetDB().GetUpdateManifests()
|
||||
manifest := common.UpdateManifest{ID: manifestID}
|
||||
|
||||
err := um.Load(&manifest)
|
||||
|
||||
return manifest, err
|
||||
}
|
||||
317
services/ota_update_go/handlers/carupdate_add_test.go
Normal file
317
services/ota_update_go/handlers/carupdate_add_test.go
Normal file
@@ -0,0 +1,317 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
dbm "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||
"github.com/fiskerinc/cloud-services/pkg/grpc/kafka_grpc"
|
||||
htc "github.com/fiskerinc/cloud-services/pkg/httpclient/tester"
|
||||
"github.com/fiskerinc/cloud-services/pkg/httphandlers"
|
||||
"github.com/fiskerinc/cloud-services/pkg/kafka"
|
||||
km "github.com/fiskerinc/cloud-services/pkg/kafka/mock"
|
||||
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||
rm "github.com/fiskerinc/cloud-services/pkg/redis/tester"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
"github.com/fiskerinc/cloud-services/pkg/testrunner"
|
||||
uhelpers "github.com/fiskerinc/cloud-services/pkg/usecase_helpers"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils/whereami"
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
"github.com/go-pg/pg/v10"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func TestCarUpdateAdd(t *testing.T) {
|
||||
whereami.SetService(whereami.OTA)
|
||||
redis.MockRedisConnection()
|
||||
mockDB := dbm.MockCarUpdates{}
|
||||
mockKafka := km.KafkaMock{}
|
||||
mockRedis := rm.MockRedis{}
|
||||
vin := "1G1FP87S3GN100062"
|
||||
mockFoa := FoaServiceMock{}
|
||||
services.SetFoaService(&mockFoa)
|
||||
services.GetDB().SetCarUpdates(&mockDB)
|
||||
services.SetKafkaProducer(&mockKafka)
|
||||
services.SetRedisClientPool(rm.NewMockClientPool(&mockRedis))
|
||||
otaUpdateKey := common.Service.Key(vin)
|
||||
attendentTopic := kafka.AttendantServiceGRPCKafka
|
||||
updateMsg := &kafka_grpc.GRPC_AttendantPayload_UpdateManifest{
|
||||
UpdateManifest: &kafka_grpc.UpdateManifest{
|
||||
CarUpdateId: 1,
|
||||
},
|
||||
}
|
||||
kafkaMSG := kafka_grpc.GRPC_AttendantPayload{
|
||||
Handler: "send_manifest",
|
||||
Data: updateMsg,
|
||||
}
|
||||
binaryPayload, _ := proto.Marshal(&kafkaMSG)
|
||||
validMessage := fmt.Sprintf(`"%s"`, base64.StdEncoding.EncodeToString(binaryPayload))
|
||||
standardManifest := common.UpdateManifest{
|
||||
ID: 100,
|
||||
Name: "TEST",
|
||||
Version: "10000",
|
||||
Type: "standard",
|
||||
Country: "US",
|
||||
PowerTrain: "MD23",
|
||||
Restraint: "None",
|
||||
Model: "Ocean",
|
||||
Trim: "Sport",
|
||||
Year: 2022,
|
||||
BodyType: "truck",
|
||||
ECUs: []*common.UpdateManifestECU{
|
||||
{
|
||||
ECU: "ICC",
|
||||
Version: "SWVERSION",
|
||||
HWVersions: []string{"HWVERSION"},
|
||||
Mode: "ICC",
|
||||
SelfDownload: true,
|
||||
Files: []*common.UpdateManifestFile{
|
||||
{
|
||||
FileID: "AAAAAAA",
|
||||
URL: "http://download.com/file1.bin",
|
||||
FileSize: 1000,
|
||||
Checksum: "AAAAAAA",
|
||||
WriteRegionID: 100,
|
||||
WriteRegion: common.MemoryRegion{
|
||||
Offset: 101,
|
||||
Length: 102,
|
||||
},
|
||||
EraseRegionID: 200,
|
||||
EraseRegion: &common.MemoryRegion{
|
||||
Offset: 201,
|
||||
Length: 202,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ECU: "ECU",
|
||||
Version: "SWVERSION",
|
||||
HWVersions: []string{"HWVERSION"},
|
||||
Mode: "D",
|
||||
Files: []*common.UpdateManifestFile{
|
||||
{
|
||||
FileID: "BBBBBBB",
|
||||
URL: "http://download.com/file2.bin",
|
||||
FileSize: 2000,
|
||||
Checksum: "BBBBBBB",
|
||||
WriteRegionID: 300,
|
||||
WriteRegion: common.MemoryRegion{
|
||||
Offset: 301,
|
||||
Length: 302,
|
||||
},
|
||||
EraseRegionID: 400,
|
||||
EraseRegion: &common.MemoryRegion{
|
||||
Offset: 401,
|
||||
Length: 402,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
forcedManifest := standardManifest
|
||||
forcedManifest.Type = "forced"
|
||||
|
||||
tests := []testrunner.TestCase{
|
||||
{
|
||||
Name: "Bad car ids",
|
||||
HttpTestCase: &htc.HttpTestCase{
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", uhelpers.JSONCarUpdatesRequest{
|
||||
UpdateManifestID: 1,
|
||||
}),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"VINs required","error":"Bad Request"}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "No data",
|
||||
HttpTestCase: &htc.HttpTestCase{
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"UpdateManifestID required. VINs required","error":"Bad Request"}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Bad package ids",
|
||||
HttpTestCase: &htc.HttpTestCase{
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", uhelpers.JSONCarUpdatesRequest{
|
||||
VINs: []string{"NONEXISTENT", "NONEXISTENT2"},
|
||||
}),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"UpdateManifestID required. VINs[0] 'NONEXISTENT' invalid. VINs[1] 'NONEXISTENT2' invalid","error":"Bad Request"}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Good data standard manifest id",
|
||||
HttpTestCase: &htc.HttpTestCase{
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", uhelpers.JSONCarUpdatesRequest{
|
||||
UpdateManifestID: 1,
|
||||
VINs: []string{"1G1FP87S3GN100062"},
|
||||
}),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"data":[{"id":1,"vin":"1G1FP87S3GN100062","manifest_id":1,"status":"pending","username":"testusername","UpdateSource":"OTA"}]}`,
|
||||
},
|
||||
DBTestCase: &dbm.DBTestCase{
|
||||
SetupMockResponse: func() {
|
||||
services.GetDB().SetUpdateManifests(&dbm.MockUpdateManifests{
|
||||
LoadResponse: &standardManifest,
|
||||
})
|
||||
},
|
||||
},
|
||||
RedisTestCase: &rm.RedisTestCase{},
|
||||
KafkaTestCase: &km.KafkaTestCase{
|
||||
ExpectedProduceMessages: map[string]map[string]interface{}{
|
||||
attendentTopic: {
|
||||
otaUpdateKey: validMessage,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Case where manifest id does not exist",
|
||||
HttpTestCase: &htc.HttpTestCase{
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", uhelpers.JSONCarUpdatesRequest{
|
||||
UpdateManifestID: 1,
|
||||
VINs: []string{"1G1FP87S3GN100062"},
|
||||
}),
|
||||
ExpectedStatus: http.StatusNotFound,
|
||||
ExpectedResponse: `{"message":"pg: no rows in result set","error":"Not Found"}`,
|
||||
Setup: func() {
|
||||
services.GetDB().SetUpdateManifests(&dbm.MockUpdateManifests{
|
||||
LoadResponse: nil,
|
||||
DBMockHelper: dbm.DBMockHelper{
|
||||
Error: pg.ErrNoRows,
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Good data forced manifest id",
|
||||
HttpTestCase: &htc.HttpTestCase{
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", uhelpers.JSONCarUpdatesRequest{
|
||||
UpdateManifestID: 1,
|
||||
VINs: []string{"1G1FP87S3GN100062"},
|
||||
}),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"data":[{"id":1,"vin":"1G1FP87S3GN100062","manifest_id":1,"status":"pending","username":"testusername","UpdateSource":"OTA"}]}`,
|
||||
},
|
||||
DBTestCase: &dbm.DBTestCase{
|
||||
SetupMockResponse: func() {
|
||||
services.GetDB().SetUpdateManifests(&dbm.MockUpdateManifests{
|
||||
LoadResponse: &forcedManifest,
|
||||
})
|
||||
},
|
||||
},
|
||||
RedisTestCase: &rm.RedisTestCase{},
|
||||
KafkaTestCase: &km.KafkaTestCase{
|
||||
ExpectedProduceMessages: map[string]map[string]interface{}{
|
||||
attendentTopic: {
|
||||
otaUpdateKey: validMessage,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Error",
|
||||
HttpTestCase: &htc.HttpTestCase{
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", uhelpers.JSONCarUpdatesRequest{
|
||||
UpdateManifestID: 1,
|
||||
VINs: []string{"1G1FP87S3GN100062"},
|
||||
}),
|
||||
ExpectedStatus: http.StatusServiceUnavailable,
|
||||
ExpectedResponse: `{"message":"something went wrong","error":"Service Unavailable"}`,
|
||||
},
|
||||
DBTestCase: &dbm.DBTestCase{
|
||||
MockError: fmt.Errorf("something went wrong"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
mockRedis.Reset()
|
||||
mockKafka.Reset()
|
||||
|
||||
if test.DBTestCase != nil {
|
||||
test.DBTestCase.SetupDB(&mockDB)
|
||||
}
|
||||
if test.RedisTestCase != nil {
|
||||
test.RedisTestCase.SetupRedis(&mockRedis)
|
||||
}
|
||||
if test.KafkaTestCase != nil {
|
||||
test.KafkaTestCase.Setup(&mockKafka)
|
||||
}
|
||||
|
||||
if test.HttpTestCase != nil {
|
||||
if test.HttpTestCase.Setup != nil {
|
||||
test.HttpTestCase.Setup()
|
||||
}
|
||||
|
||||
ctx := context.WithValue(test.HttpTestCase.Request.Context(), httphandlers.ClientIDContextKey, "testusername")
|
||||
test.HttpTestCase.Request = test.HttpTestCase.Request.WithContext(ctx)
|
||||
|
||||
w := test.HttpTestCase.Test(handlers.HandleCarUpdatesAdd)
|
||||
test.HttpTestCase.ValidateHttp(t, test.Name, w)
|
||||
}
|
||||
|
||||
if test.DBTestCase != nil {
|
||||
test.DBTestCase.Validate(t, test.Name, &mockDB)
|
||||
}
|
||||
if test.RedisTestCase != nil {
|
||||
test.RedisTestCase.Validate(t, test.Name, &mockRedis)
|
||||
}
|
||||
if test.KafkaTestCase != nil {
|
||||
test.KafkaTestCase.Validate(t, test.Name, &mockKafka)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONCarUpdatesRequestValidation(t *testing.T) {
|
||||
request := uhelpers.JSONCarUpdatesRequest{
|
||||
UpdateManifestID: 1,
|
||||
VINs: []string{},
|
||||
}
|
||||
|
||||
err := validator.ValidateStruct(request)
|
||||
if err == nil {
|
||||
t.Errorf(th.TestErrorTemplate, "Validate VINs count", nil, err)
|
||||
} else {
|
||||
_, msg := validator.GetValidationErrorMsg(err)
|
||||
expected := "VINs less than 1"
|
||||
if msg != expected {
|
||||
t.Errorf(th.TestErrorTemplate, "Validate VINs count", expected, msg)
|
||||
}
|
||||
}
|
||||
|
||||
request.VINs = []string{"1G1FP87S3GN100062"}
|
||||
err = validator.ValidateStruct(request)
|
||||
if err != nil {
|
||||
t.Errorf(th.TestErrorTemplate, "Validate Good VIN", nil, err)
|
||||
}
|
||||
|
||||
request.VINs = []string{"1G1FP87S3GN10006I"}
|
||||
err = validator.ValidateStruct(request)
|
||||
if err == nil {
|
||||
t.Errorf(th.TestErrorTemplate, "Validate Bad VIN", nil, err)
|
||||
} else {
|
||||
_, msg := validator.GetValidationErrorMsg(err)
|
||||
expected := "VINs[0] '1G1FP87S3GN10006I' invalid"
|
||||
if msg != expected {
|
||||
t.Errorf(th.TestErrorTemplate, "Validate Bad VIN", expected, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type FoaServiceMock struct{}
|
||||
|
||||
func (f *FoaServiceMock) OtaUpdateStatus(vin string, carUpdate *common.CarUpdate, status *common.CarUpdateProgress) (*http.Response, error) {
|
||||
return &http.Response{StatusCode: 200}, nil
|
||||
}
|
||||
142
services/ota_update_go/handlers/carupdate_cancel.go
Normal file
142
services/ota_update_go/handlers/carupdate_cancel.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"otaupdate/services"
|
||||
"strconv"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common/actionlogger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common/carupdatestatus"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common/handlers"
|
||||
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
"github.com/fiskerinc/cloud-services/pkg/httphandlers"
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
re "github.com/gomodule/redigo/redis"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
// HandleCarUpdateCancel godoc
|
||||
// @Summary Cancel car update
|
||||
// @Description Cancels car update and send notifications
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param id path string true "Car update id to cancel"
|
||||
// @Success 200 {object} common.JSONMessage "Request result"
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /carupdate/{id}/cancel [post]
|
||||
func HandleCarUpdateCancel(w http.ResponseWriter, r *http.Request) {
|
||||
params := httprouter.ParamsFromContext(r.Context())
|
||||
id, err := strconv.ParseInt(params.ByName("id"), 10, 64)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
req := common.CarUpdateRequest{
|
||||
CarUpdateID: id,
|
||||
}
|
||||
|
||||
err = validator.ValidateStruct(req)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
carupdates := services.GetDB().GetCarUpdates()
|
||||
cu, err := carupdates.SelectByID(req.CarUpdateID)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable, loggerdataresp.PostgresNoRowsErrorCheck) {
|
||||
return
|
||||
}
|
||||
|
||||
alDB := services.GetDB().GetActionLog()
|
||||
go func() {
|
||||
actionLog := actionlogger.ActionLog{
|
||||
VIN: cu.VIN,
|
||||
Action: actionlogger.CarUpdate,
|
||||
UserIdentifier: httphandlers.GetClientID(r),
|
||||
CallLocation: "github.com/fiskerinc/cloud-services/services/ota_update_go/handlers/carupdate_cancel.go",
|
||||
Description: fmt.Sprintf("car update id: %d", req.CarUpdateID),
|
||||
}
|
||||
|
||||
err = alDB.Insert(actionLog)
|
||||
if err != nil {
|
||||
logger.Err(err).Msg("failed to insert action log inside HandleCarUpdateCancel")
|
||||
}
|
||||
}()
|
||||
|
||||
// If this update was from aftersales, then we just mark as canceled
|
||||
if cu.UpdateSource == common.UPDATE_SOURCE_AFTERSALES {
|
||||
err = cancelUpdateAftersales(req, cu, carupdates)
|
||||
} else {
|
||||
err = cancelUpdateOTA(req, cu, carupdates)
|
||||
}
|
||||
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONMessage{
|
||||
Message: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
func cancelUpdateAftersales(req common.CarUpdateRequest, cu *common.CarUpdate, carupdates queries.CarUpdatesInterface) (err error) {
|
||||
current := common.CarUpdate{
|
||||
ID: req.CarUpdateID,
|
||||
Status: carupdatestatus.ManifestCanceled,
|
||||
}
|
||||
_, err = carupdates.UpdateStatus(¤t)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func cancelUpdateOTA(req common.CarUpdateRequest, cu *common.CarUpdate, carupdates queries.CarUpdatesInterface) (err error) {
|
||||
current := common.CarUpdate{
|
||||
ID: req.CarUpdateID,
|
||||
Status: carupdatestatus.ManifestCancelPending,
|
||||
}
|
||||
_, err = carupdates.UpdateStatus(¤t)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
client := services.RedisClientPool().GetFromPool()
|
||||
defer client.Close()
|
||||
|
||||
key := redis.CarUpdateStatusHashKey(req.CarUpdateID)
|
||||
msg, err := json.Marshal(common.Message{
|
||||
Handler: handlers.UpdateManifestCancel,
|
||||
Data: req,
|
||||
})
|
||||
|
||||
batch := redis.NewRedisBatchCommands()
|
||||
batch.Add(re.Args{}.Add("HSET").Add(key).AddFlat(common.CarUpdateProgress{
|
||||
CarUpdateID: req.CarUpdateID,
|
||||
Status: carupdatestatus.ManifestCancelPending,
|
||||
})...)
|
||||
batch.Add("EXPIRE", key, 3600)
|
||||
batch.Add("RPUSH", redis.QueueKey(common.TRex.Key(cu.VIN)), msg)
|
||||
batch.Add("EXPIRE", redis.QueueKey(common.TRex.Key(cu.VIN)), 3600)
|
||||
batch.Add("RPUSH", redis.QueueKey(common.HMI.Key(cu.VIN)), msg)
|
||||
batch.Add("EXPIRE", redis.QueueKey(common.HMI.Key(cu.VIN)), 3600)
|
||||
|
||||
_, err = client.ExecuteBatch(batch)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
138
services/ota_update_go/handlers/carupdate_cancel_test.go
Normal file
138
services/ota_update_go/handlers/carupdate_cancel_test.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
"testing"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
mo "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||
"github.com/fiskerinc/cloud-services/pkg/httpclient/tester"
|
||||
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||
r "github.com/fiskerinc/cloud-services/pkg/redis"
|
||||
rm "github.com/fiskerinc/cloud-services/pkg/redis/tester"
|
||||
"github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
"github.com/fiskerinc/cloud-services/pkg/testrunner"
|
||||
|
||||
"github.com/go-pg/pg/v10"
|
||||
)
|
||||
|
||||
func TestCarUpdateCancel(t *testing.T) {
|
||||
r.MockRedisConnection()
|
||||
mockRedis := rm.MockRedis{
|
||||
GetSetResults: "[]",
|
||||
}
|
||||
services.SetRedisClientPool(rm.NewMockClientPool(&mockRedis))
|
||||
mock := mo.MockCarUpdates{}
|
||||
services.GetDB().SetCarUpdates(&mock)
|
||||
vin := "1G1FP87S3GN100062"
|
||||
mockCarUpdate := common.CarUpdate{
|
||||
ID: 1000,
|
||||
VIN: vin,
|
||||
UpdateManifestID: 10,
|
||||
}
|
||||
trexKey := common.TRex.Key(vin)
|
||||
hmiKey := common.HMI.Key(vin)
|
||||
tests := []testrunner.TestCase{
|
||||
{
|
||||
Name: "invalid update id",
|
||||
HttpTestCase: &tester.HttpTestCase{
|
||||
Request: testhelper.MakeTestRequest(http.MethodDelete, "http://example.com/carupdate/1000/cancel", nil),
|
||||
ExpectedStatus: http.StatusNotFound,
|
||||
ExpectedResponse: `{"message":"pg: no rows in result set","error":"Not Found"}`,
|
||||
},
|
||||
DBTestCase: &mo.DBTestCase{
|
||||
MockError: pg.ErrNoRows,
|
||||
},
|
||||
RedisTestCase: &rm.RedisTestCase{},
|
||||
},
|
||||
{
|
||||
Name: "non-numeric update id",
|
||||
HttpTestCase: &tester.HttpTestCase{
|
||||
Request: testhelper.MakeTestRequest(http.MethodDelete, "http://example.com/carupdate/xxxx/cancel", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"strconv.ParseInt: parsing \"xxxx\": invalid syntax","error":"Bad Request"}`,
|
||||
},
|
||||
DBTestCase: &mo.DBTestCase{},
|
||||
RedisTestCase: &rm.RedisTestCase{},
|
||||
},
|
||||
{
|
||||
Name: "database error",
|
||||
HttpTestCase: &tester.HttpTestCase{
|
||||
Request: testhelper.MakeTestRequest(http.MethodDelete, "http://example.com/carupdate/1000/cancel", nil),
|
||||
ExpectedStatus: http.StatusServiceUnavailable,
|
||||
ExpectedResponse: `{"message":"some err","error":"Service Unavailable"}`,
|
||||
},
|
||||
DBTestCase: &mo.DBTestCase{
|
||||
MockError: someErr,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "redis error",
|
||||
HttpTestCase: &tester.HttpTestCase{
|
||||
Request: testhelper.MakeTestRequest(http.MethodDelete, "http://example.com/carupdate/1000/cancel", nil),
|
||||
ExpectedStatus: http.StatusServiceUnavailable,
|
||||
ExpectedResponse: `{"message":"some err","error":"Service Unavailable"}`,
|
||||
},
|
||||
DBTestCase: &mo.DBTestCase{
|
||||
MockLoadResponse: &mockCarUpdate,
|
||||
},
|
||||
RedisTestCase: &rm.RedisTestCase{
|
||||
MockRedisError: someErr,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "good request",
|
||||
HttpTestCase: &tester.HttpTestCase{
|
||||
Request: testhelper.MakeTestRequest(http.MethodDelete, "http://example.com/carupdate/1000/cancel", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"message":"OK"}`,
|
||||
},
|
||||
DBTestCase: &mo.DBTestCase{
|
||||
MockLoadResponse: &mockCarUpdate,
|
||||
},
|
||||
RedisTestCase: &rm.RedisTestCase{
|
||||
ExpectedMessages: map[string]string{
|
||||
trexKey: `{"handler":"update_manifest_cancel","data":{"car_update_id":1000}}`,
|
||||
hmiKey: `{"handler":"update_manifest_cancel","data":{"car_update_id":1000}}`,
|
||||
},
|
||||
ExpectedCaches: map[string]rm.ExpiringCacheResult{
|
||||
redis.CarUpdateStatusHashKey(int64(1000)): {
|
||||
Value: `{"current_size":0,"ecu":"","errorcode":0,"file_size":0,"file_total":0,"id":1000,"installed":0,"status":"manifest_cancel_pending","total_files":0,"total_size":0}`,
|
||||
Expires: 3600,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
schemaTesterTRex := testhelper.NewSchemaTestHelper(t, schemaToTRex)
|
||||
for _, test := range tests {
|
||||
mockRedis.Reset()
|
||||
|
||||
if test.RedisTestCase != nil {
|
||||
test.RedisTestCase.SetupRedis(&mockRedis)
|
||||
}
|
||||
|
||||
if test.DBTestCase != nil {
|
||||
test.DBTestCase.SetupDB(&mock)
|
||||
}
|
||||
|
||||
if test.HttpTestCase != nil {
|
||||
w := test.HttpTestCase.TestWithParamPath(handlers.HandleCarUpdateCancel, "/carupdate/:id/cancel")
|
||||
test.HttpTestCase.ValidateHttp(t, test.Name, w)
|
||||
}
|
||||
|
||||
if test.DBTestCase != nil {
|
||||
test.DBTestCase.Validate(t, test.Name, &mock)
|
||||
}
|
||||
if test.RedisTestCase != nil {
|
||||
test.RedisTestCase.Validate(t, test.Name, &mockRedis)
|
||||
|
||||
for _, mes := range test.RedisTestCase.ExpectedMessages {
|
||||
schemaTesterTRex.ValidateSchemaObject(test.Name, []byte(mes))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
services/ota_update_go/handlers/carupdate_delete.go
Normal file
41
services/ota_update_go/handlers/carupdate_delete.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils/urlhelper"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
// HandleCarUpdateDelete godoc
|
||||
// @Summary Delete car update
|
||||
// @Description Delete car update. Requires delete permissions
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param id query int true "Car update id"
|
||||
// @Success 200 {object} common.JSONMessage
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /carupdate [delete]
|
||||
func HandleCarUpdateDelete(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
carupdate := common.CarUpdate{
|
||||
ID: urlhelper.GetQueryInt64(qs, "id"),
|
||||
}
|
||||
|
||||
_, err := services.GetDB().GetCarUpdates().Delete(&carupdate)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable, loggerdataresp.PostgresNoRowsErrorCheck) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONMessage{
|
||||
Message: "Deleted",
|
||||
})
|
||||
}
|
||||
38
services/ota_update_go/handlers/carupdate_delete_test.go
Normal file
38
services/ota_update_go/handlers/carupdate_delete_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestCarUpdateDelete(t *testing.T) {
|
||||
services.GetDB().SetCarUpdates(&mocks.MockCarUpdates{})
|
||||
tests := []th.BasicHttpTest{
|
||||
{
|
||||
Name: "No id",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/carupdates", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"id required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Zero id",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/carupdates?id=0", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"id required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Good id",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/carupdates?id=1", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"message":"Deleted"}`,
|
||||
},
|
||||
}
|
||||
|
||||
th.RunBasicHttpTests(t, tests, handlers.HandleCarUpdateDelete)
|
||||
}
|
||||
135
services/ota_update_go/handlers/carupdate_deploy.go
Normal file
135
services/ota_update_go/handlers/carupdate_deploy.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"otaupdate/services"
|
||||
"strconv"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common/actionlogger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common/carupdatestatus"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common/handlers"
|
||||
"github.com/fiskerinc/cloud-services/pkg/httphandlers"
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
re "github.com/gomodule/redigo/redis"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
// HandleCarUpdateDeploy godoc
|
||||
// @Summary Deploy car update
|
||||
// @Description Deploys car update and send notifications
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param id path string true "Car update id to deploy"
|
||||
// @Success 200 {object} common.JSONMessage "Request result"
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /carupdate/{id}/deploy [post]
|
||||
func HandleCarUpdateDeploy(w http.ResponseWriter, r *http.Request) {
|
||||
params := httprouter.ParamsFromContext(r.Context())
|
||||
id, err := strconv.ParseInt(params.ByName("id"), 10, 64)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
req := common.CarUpdateRequest{
|
||||
CarUpdateID: id,
|
||||
}
|
||||
|
||||
err = validator.ValidateStruct(req)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
carupdates := services.GetDB().GetCarUpdates()
|
||||
cu, err := carupdates.SelectByID(req.CarUpdateID)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable, loggerdataresp.PostgresNoRowsErrorCheck) {
|
||||
return
|
||||
}
|
||||
|
||||
alDB := services.GetDB().GetActionLog()
|
||||
go func() {
|
||||
actionLog := actionlogger.ActionLog{
|
||||
VIN: cu.VIN,
|
||||
Action: actionlogger.CarUpdate,
|
||||
UserIdentifier: httphandlers.GetClientID(r),
|
||||
CallLocation: "github.com/fiskerinc/cloud-services/services/ota_update_go/handlers/carupdate_deploy.go",
|
||||
Description: fmt.Sprintf("car update id: %d", req.CarUpdateID),
|
||||
}
|
||||
|
||||
err = alDB.Insert(actionLog)
|
||||
if err != nil {
|
||||
logger.Err(err).Msg("failed to insert action log inside HandleCarUpdateDeploy")
|
||||
}
|
||||
}()
|
||||
|
||||
if !isRedeployAvailable(cu.Status) {
|
||||
utils.RespJSON(w, http.StatusUnprocessableEntity, common.JSONMessage{
|
||||
Message: fmt.Sprintf("Unable to redeploy, CarUpdate is currently %s", cu.Status),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
current := common.CarUpdate{
|
||||
ID: req.CarUpdateID,
|
||||
Status: carupdatestatus.Pending,
|
||||
}
|
||||
_, err = carupdates.UpdateStatus(¤t)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
client := services.RedisClientPool().GetFromPool()
|
||||
defer client.Close()
|
||||
|
||||
key := redis.CarUpdateStatusHashKey(req.CarUpdateID)
|
||||
msg, err := json.Marshal(common.Message{
|
||||
Handler: handlers.UpdateManifestInstall,
|
||||
Data: req,
|
||||
})
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
batch := redis.NewRedisBatchCommands()
|
||||
batch.Add(re.Args{}.Add("HSET").Add(key).AddFlat(common.CarUpdateProgress{
|
||||
CarUpdateID: req.CarUpdateID,
|
||||
Status: carupdatestatus.Pending,
|
||||
})...)
|
||||
batch.Add("EXPIRE", key, 3600)
|
||||
batch.Add("RPUSH", redis.QueueKey(common.TRex.Key(cu.VIN)), msg)
|
||||
batch.Add("EXPIRE", redis.QueueKey(common.TRex.Key(cu.VIN)), 3600)
|
||||
batch.Add("RPUSH", redis.QueueKey(common.HMI.Key(cu.VIN)), msg)
|
||||
batch.Add("EXPIRE", redis.QueueKey(common.HMI.Key(cu.VIN)), 3600)
|
||||
|
||||
_, err = client.ExecuteBatch(batch)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONMessage{
|
||||
Message: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
func isRedeployAvailable(status string) bool {
|
||||
switch status {
|
||||
case carupdatestatus.ManifestSucceeded, carupdatestatus.ManifestCanceled,
|
||||
carupdatestatus.ManifestError, carupdatestatus.ManifestCancelPending,
|
||||
carupdatestatus.RollbackSucceeded, carupdatestatus.ManifestRejected,
|
||||
carupdatestatus.RollbackFailed, carupdatestatus.CleanupSucceeded:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
156
services/ota_update_go/handlers/carupdate_deploy_test.go
Normal file
156
services/ota_update_go/handlers/carupdate_deploy_test.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
"testing"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common/carupdatestatus"
|
||||
mo "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||
"github.com/fiskerinc/cloud-services/pkg/httpclient/tester"
|
||||
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||
rm "github.com/fiskerinc/cloud-services/pkg/redis/tester"
|
||||
"github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
"github.com/fiskerinc/cloud-services/pkg/testrunner"
|
||||
|
||||
"github.com/go-pg/pg/v10"
|
||||
)
|
||||
|
||||
func TestCarUpdateDeploy(t *testing.T) {
|
||||
redis.MockRedisConnection()
|
||||
mockRedis := rm.MockRedis{
|
||||
GetSetResults: "[]",
|
||||
}
|
||||
services.SetRedisClientPool(rm.NewMockClientPool(&mockRedis))
|
||||
mock := mo.MockCarUpdates{}
|
||||
services.GetDB().SetCarUpdates(&mock)
|
||||
|
||||
vin := "1G1FP87S3GN100062"
|
||||
mockCarUpdate := common.CarUpdate{
|
||||
ID: 1000,
|
||||
VIN: vin,
|
||||
UpdateManifestID: 10,
|
||||
Status: carupdatestatus.ManifestSucceeded,
|
||||
}
|
||||
trexKey := common.TRex.Key(vin)
|
||||
hmiKey := common.HMI.Key(vin)
|
||||
tests := []testrunner.TestCase{
|
||||
{
|
||||
Name: "invalid update id",
|
||||
HttpTestCase: &tester.HttpTestCase{
|
||||
Request: testhelper.MakeTestRequest(http.MethodPost, "http://example.com/carupdate/1000/deploy", nil),
|
||||
ExpectedStatus: http.StatusNotFound,
|
||||
ExpectedResponse: `{"message":"pg: no rows in result set","error":"Not Found"}`,
|
||||
},
|
||||
DBTestCase: &mo.DBTestCase{
|
||||
MockError: pg.ErrNoRows,
|
||||
},
|
||||
RedisTestCase: &rm.RedisTestCase{},
|
||||
},
|
||||
{
|
||||
Name: "non-numeric update id",
|
||||
HttpTestCase: &tester.HttpTestCase{
|
||||
Request: testhelper.MakeTestRequest(http.MethodPost, "http://example.com/carupdate/xxxx/deploy", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"strconv.ParseInt: parsing \"xxxx\": invalid syntax","error":"Bad Request"}`,
|
||||
},
|
||||
DBTestCase: &mo.DBTestCase{},
|
||||
RedisTestCase: &rm.RedisTestCase{},
|
||||
},
|
||||
{
|
||||
Name: "database error",
|
||||
HttpTestCase: &tester.HttpTestCase{
|
||||
Request: testhelper.MakeTestRequest(http.MethodPost, "http://example.com/carupdate/1000/deploy", nil),
|
||||
ExpectedStatus: http.StatusServiceUnavailable,
|
||||
ExpectedResponse: `{"message":"some err","error":"Service Unavailable"}`,
|
||||
},
|
||||
DBTestCase: &mo.DBTestCase{
|
||||
MockError: someErr,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "redis error",
|
||||
HttpTestCase: &tester.HttpTestCase{
|
||||
Request: testhelper.MakeTestRequest(http.MethodPost, "http://example.com/carupdate/1000/deploy", nil),
|
||||
ExpectedStatus: http.StatusServiceUnavailable,
|
||||
ExpectedResponse: `{"message":"some err","error":"Service Unavailable"}`,
|
||||
},
|
||||
DBTestCase: &mo.DBTestCase{
|
||||
MockLoadResponse: &mockCarUpdate,
|
||||
},
|
||||
RedisTestCase: &rm.RedisTestCase{
|
||||
MockRedisError: someErr,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "invalid status",
|
||||
HttpTestCase: &tester.HttpTestCase{
|
||||
Request: testhelper.MakeTestRequest(http.MethodPost, "http://example.com/carupdate/1000/deploy", nil),
|
||||
ExpectedStatus: http.StatusUnprocessableEntity,
|
||||
ExpectedResponse: `{"message":"Unable to redeploy, CarUpdate is currently pending"}`,
|
||||
},
|
||||
DBTestCase: &mo.DBTestCase{
|
||||
MockLoadResponse: &common.CarUpdate{
|
||||
ID: 1000,
|
||||
VIN: vin,
|
||||
UpdateManifestID: 10,
|
||||
Status: carupdatestatus.Pending,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "good request",
|
||||
HttpTestCase: &tester.HttpTestCase{
|
||||
Request: testhelper.MakeTestRequest(http.MethodPost, "http://example.com/carupdate/1000/deploy", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"message":"OK"}`,
|
||||
},
|
||||
DBTestCase: &mo.DBTestCase{
|
||||
MockLoadResponse: &mockCarUpdate,
|
||||
},
|
||||
RedisTestCase: &rm.RedisTestCase{
|
||||
ExpectedMessages: map[string]string{
|
||||
trexKey: `{"handler":"update_manifest_install","data":{"car_update_id":1000}}`,
|
||||
hmiKey: `{"handler":"update_manifest_install","data":{"car_update_id":1000}}`,
|
||||
},
|
||||
ExpectedCaches: map[string]rm.ExpiringCacheResult{
|
||||
redis.CarUpdateStatusHashKey(int64(1000)): {
|
||||
Value: `{"current_size":0,"ecu":"","errorcode":0,"file_size":0,"file_total":0,"id":1000,"installed":0,"status":"pending","total_files":0,"total_size":0}`,
|
||||
Expires: 3600,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
schemaTesterTRex := testhelper.NewSchemaTestHelper(t, schemaToTRex)
|
||||
for _, test := range tests {
|
||||
mockRedis.Reset()
|
||||
|
||||
if test.RedisTestCase != nil {
|
||||
test.RedisTestCase.SetupRedis(&mockRedis)
|
||||
}
|
||||
|
||||
if test.DBTestCase != nil {
|
||||
test.DBTestCase.SetupDB(&mock)
|
||||
}
|
||||
|
||||
if test.HttpTestCase != nil {
|
||||
w := test.HttpTestCase.TestWithParamPath(handlers.HandleCarUpdateDeploy, "/carupdate/:id/deploy")
|
||||
test.HttpTestCase.ValidateHttp(t, test.Name, w)
|
||||
}
|
||||
|
||||
if test.DBTestCase != nil {
|
||||
test.DBTestCase.Validate(t, test.Name, &mock)
|
||||
}
|
||||
if test.RedisTestCase != nil {
|
||||
test.RedisTestCase.Validate(t, test.Name, &mockRedis)
|
||||
|
||||
for _, mes := range test.RedisTestCase.ExpectedMessages {
|
||||
schemaTesterTRex.ValidateSchemaObject(test.Name, []byte(mes))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
133
services/ota_update_go/handlers/carupdate_vehicle_cancel.go
Normal file
133
services/ota_update_go/handlers/carupdate_vehicle_cancel.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"otaupdate/services"
|
||||
"strconv"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common/actionlogger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common/carupdatestatus"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common/handlers"
|
||||
"github.com/fiskerinc/cloud-services/pkg/httphandlers"
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
re "github.com/gomodule/redigo/redis"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
// HandleCarUpdateVehicleCancel godoc
|
||||
// @Summary Cancel car update on vehicle
|
||||
// @Description Cancel a rogue car update on a vehicle that's not found on cloud.
|
||||
// A car update may not be found in cloud following physical vehicle maintenance.
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param id path string true "Car update id to cancel"
|
||||
// @Param vin query string true "VIN of vehicle to send to"
|
||||
// @Success 200 {object} common.JSONMessage "Request result"
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /carupdate/{id}/vehicle-cancel [post]
|
||||
func HandleCarUpdateVehicleCancel(w http.ResponseWriter, r *http.Request) {
|
||||
params := httprouter.ParamsFromContext(r.Context())
|
||||
id, err := strconv.ParseInt(params.ByName("id"), 10, 64)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
vin := r.URL.Query().Get("vin")
|
||||
ok := validator.ValidateVINSimple(vin)
|
||||
if !ok {
|
||||
loggerdataresp.BadDataErrorResp(w, ErrInvalidVIN, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
req := common.CarUpdateRequest{
|
||||
CarUpdateID: id,
|
||||
}
|
||||
|
||||
err = validator.ValidateStruct(req)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
alDB := services.GetDB().GetActionLog()
|
||||
go func() {
|
||||
actionLog := actionlogger.ActionLog{
|
||||
VIN: vin,
|
||||
Action: actionlogger.CarUpdate,
|
||||
UserIdentifier: httphandlers.GetClientID(r),
|
||||
CallLocation: "github.com/fiskerinc/cloud-services/services/ota_update_go/handlers/carupdate_vehicle_cancel.go",
|
||||
Description: fmt.Sprintf("car update id: %d", req.CarUpdateID),
|
||||
}
|
||||
|
||||
err = alDB.Insert(actionLog)
|
||||
if err != nil {
|
||||
logger.Err(err).Msg("failed to insert action log inside HandleCarUpdateVehicleCancel")
|
||||
}
|
||||
}()
|
||||
|
||||
cu := &common.CarUpdate{
|
||||
ID: id,
|
||||
VIN: vin,
|
||||
}
|
||||
|
||||
carupdates := services.GetDB().GetCarUpdates()
|
||||
_, err = carupdates.SelectByID(req.CarUpdateID)
|
||||
if err == nil {
|
||||
err = fmt.Errorf("car update was found in cloud, use /carupdate/%d/cancel", req.CarUpdateID)
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable, loggerdataresp.PostgresNoRowsErrorCheck) {
|
||||
return
|
||||
}
|
||||
|
||||
err = sendCancelUpdate(req, cu)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONMessage{
|
||||
Message: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
func sendCancelUpdate(req common.CarUpdateRequest, cu *common.CarUpdate) (err error) {
|
||||
client := services.RedisClientPool().GetFromPool()
|
||||
defer client.Close()
|
||||
|
||||
key := redis.CarUpdateStatusHashKey(req.CarUpdateID)
|
||||
msg, err := json.Marshal(common.Message{
|
||||
Handler: handlers.UpdateManifestCancel,
|
||||
Data: req,
|
||||
})
|
||||
|
||||
batch := redis.NewRedisBatchCommands()
|
||||
batch.Add(re.Args{}.Add("HSET").Add(key).AddFlat(common.CarUpdateProgress{
|
||||
CarUpdateID: req.CarUpdateID,
|
||||
Status: carupdatestatus.ManifestCancelPending,
|
||||
})...)
|
||||
batch.Add("EXPIRE", key, 3600)
|
||||
batch.Add("RPUSH", redis.QueueKey(common.TRex.Key(cu.VIN)), msg)
|
||||
batch.Add("EXPIRE", redis.QueueKey(common.TRex.Key(cu.VIN)), 3600)
|
||||
batch.Add("RPUSH", redis.QueueKey(common.HMI.Key(cu.VIN)), msg)
|
||||
batch.Add("EXPIRE", redis.QueueKey(common.HMI.Key(cu.VIN)), 3600)
|
||||
|
||||
_, err = client.ExecuteBatch(batch)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
151
services/ota_update_go/handlers/carupdate_vehicle_cancel_test.go
Normal file
151
services/ota_update_go/handlers/carupdate_vehicle_cancel_test.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
"testing"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
mo "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||
"github.com/fiskerinc/cloud-services/pkg/httpclient/tester"
|
||||
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||
r "github.com/fiskerinc/cloud-services/pkg/redis"
|
||||
rm "github.com/fiskerinc/cloud-services/pkg/redis/tester"
|
||||
"github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
"github.com/fiskerinc/cloud-services/pkg/testrunner"
|
||||
|
||||
"github.com/go-pg/pg/v10"
|
||||
)
|
||||
|
||||
func TestCarUpdateVehicleCancel(t *testing.T) {
|
||||
r.MockRedisConnection()
|
||||
mockRedis := rm.MockRedis{
|
||||
GetSetResults: "[]",
|
||||
}
|
||||
services.SetRedisClientPool(rm.NewMockClientPool(&mockRedis))
|
||||
mock := mo.MockCarUpdates{}
|
||||
services.GetDB().SetCarUpdates(&mock)
|
||||
vin := "1G1FP87S3GN100062"
|
||||
mockCarUpdate := common.CarUpdate{
|
||||
ID: 1000,
|
||||
VIN: vin,
|
||||
UpdateManifestID: 10,
|
||||
}
|
||||
trexKey := common.TRex.Key(vin)
|
||||
hmiKey := common.HMI.Key(vin)
|
||||
tests := []testrunner.TestCase{
|
||||
{
|
||||
Name: "car update known to cloud",
|
||||
HttpTestCase: &tester.HttpTestCase{
|
||||
Request: testhelper.MakeTestRequest(http.MethodPost, fmt.Sprintf("http://example.com/carupdate/1000/vehicle-cancel?vin=%s", vin), nil),
|
||||
ExpectedStatus: http.StatusServiceUnavailable,
|
||||
ExpectedResponse: `{"message":"car update was found in cloud, use /carupdate/1000/cancel","error":"Service Unavailable"}`,
|
||||
},
|
||||
DBTestCase: &mo.DBTestCase{
|
||||
MockLoadResponse: &mockCarUpdate,
|
||||
},
|
||||
RedisTestCase: &rm.RedisTestCase{},
|
||||
},
|
||||
{
|
||||
Name: "non-numeric update id",
|
||||
HttpTestCase: &tester.HttpTestCase{
|
||||
Request: testhelper.MakeTestRequest(http.MethodPost, fmt.Sprintf("http://example.com/carupdate/xxxx/vehicle-cancel?vin=%s", vin), nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"strconv.ParseInt: parsing \"xxxx\": invalid syntax","error":"Bad Request"}`,
|
||||
},
|
||||
DBTestCase: &mo.DBTestCase{},
|
||||
RedisTestCase: &rm.RedisTestCase{},
|
||||
},
|
||||
{
|
||||
Name: "database error",
|
||||
HttpTestCase: &tester.HttpTestCase{
|
||||
Request: testhelper.MakeTestRequest(http.MethodPost, fmt.Sprintf("http://example.com/carupdate/1000/vehicle-cancel?vin=%s", vin), nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"message":"OK"}`,
|
||||
},
|
||||
DBTestCase: &mo.DBTestCase{
|
||||
MockError: someErr,
|
||||
},
|
||||
RedisTestCase: &rm.RedisTestCase{
|
||||
ExpectedMessages: map[string]string{
|
||||
trexKey: `{"handler":"update_manifest_cancel","data":{"car_update_id":1000}}`,
|
||||
hmiKey: `{"handler":"update_manifest_cancel","data":{"car_update_id":1000}}`,
|
||||
},
|
||||
ExpectedCaches: map[string]rm.ExpiringCacheResult{
|
||||
redis.CarUpdateStatusHashKey(int64(1000)): {
|
||||
Value: `{"current_size":0,"ecu":"","errorcode":0,"file_size":0,"file_total":0,"id":1000,"installed":0,"status":"manifest_cancel_pending","total_files":0,"total_size":0}`,
|
||||
Expires: 3600,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "redis error",
|
||||
HttpTestCase: &tester.HttpTestCase{
|
||||
Request: testhelper.MakeTestRequest(http.MethodPost, fmt.Sprintf("http://example.com/carupdate/1000/vehicle-cancel?vin=%s", vin), nil),
|
||||
ExpectedStatus: http.StatusServiceUnavailable,
|
||||
ExpectedResponse: `{"message":"some err","error":"Service Unavailable"}`,
|
||||
},
|
||||
DBTestCase: &mo.DBTestCase{
|
||||
MockError: pg.ErrNoRows,
|
||||
},
|
||||
RedisTestCase: &rm.RedisTestCase{
|
||||
MockRedisError: someErr,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "car update unknown to cloud",
|
||||
HttpTestCase: &tester.HttpTestCase{
|
||||
Request: testhelper.MakeTestRequest(http.MethodPost, fmt.Sprintf("http://example.com/carupdate/1000/vehicle-cancel?vin=%s", vin), nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"message":"OK"}`,
|
||||
},
|
||||
DBTestCase: &mo.DBTestCase{
|
||||
MockError: pg.ErrNoRows,
|
||||
},
|
||||
RedisTestCase: &rm.RedisTestCase{
|
||||
ExpectedMessages: map[string]string{
|
||||
trexKey: `{"handler":"update_manifest_cancel","data":{"car_update_id":1000}}`,
|
||||
hmiKey: `{"handler":"update_manifest_cancel","data":{"car_update_id":1000}}`,
|
||||
},
|
||||
ExpectedCaches: map[string]rm.ExpiringCacheResult{
|
||||
redis.CarUpdateStatusHashKey(int64(1000)): {
|
||||
Value: `{"current_size":0,"ecu":"","errorcode":0,"file_size":0,"file_total":0,"id":1000,"installed":0,"status":"manifest_cancel_pending","total_files":0,"total_size":0}`,
|
||||
Expires: 3600,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
schemaTesterTRex := testhelper.NewSchemaTestHelper(t, schemaToTRex)
|
||||
for _, test := range tests {
|
||||
mockRedis.Reset()
|
||||
|
||||
if test.RedisTestCase != nil {
|
||||
test.RedisTestCase.SetupRedis(&mockRedis)
|
||||
}
|
||||
|
||||
if test.DBTestCase != nil {
|
||||
test.DBTestCase.SetupDB(&mock)
|
||||
}
|
||||
|
||||
if test.HttpTestCase != nil {
|
||||
w := test.HttpTestCase.TestWithParamPath(handlers.HandleCarUpdateVehicleCancel, "/carupdate/:id/vehicle-cancel")
|
||||
test.HttpTestCase.ValidateHttp(t, test.Name, w)
|
||||
}
|
||||
|
||||
if test.DBTestCase != nil {
|
||||
test.DBTestCase.Validate(t, test.Name, &mock)
|
||||
}
|
||||
if test.RedisTestCase != nil {
|
||||
test.RedisTestCase.Validate(t, test.Name, &mockRedis)
|
||||
|
||||
for _, mes := range test.RedisTestCase.ExpectedMessages {
|
||||
schemaTesterTRex.ValidateSchemaObject(test.Name, []byte(mes))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
80
services/ota_update_go/handlers/carupdates_get.go
Normal file
80
services/ota_update_go/handlers/carupdates_get.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
orm "github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils/urlhelper"
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
// HandleCarUpdatesGet godoc
|
||||
// @Summary Search car updates
|
||||
// @Description Get car updates filtered by id, car id, and update package id
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param id query int false "CarUpdate id"
|
||||
// @Param vin query string false "Car VIN"
|
||||
// @Param manifest_id query int false "Update manifest id"
|
||||
// @Param limit query int false "Max number of records"
|
||||
// @Param offset query int false "Records offset"
|
||||
// @Param order query string false "Sort on column with asc or desc"
|
||||
// @Success 200 {object} common.JSONDBQueryResult{data=[]common.CarUpdate}
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /carupdates [get]
|
||||
func HandleCarUpdatesGet(w http.ResponseWriter, r *http.Request) {
|
||||
var total int
|
||||
cu := services.GetDB().GetCarUpdates()
|
||||
filter, err := parseCarsUpdateFilter(r)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
options, err := orm.ParsePageQuery(r)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
if options.Order == "" {
|
||||
options.Order = "id DESC"
|
||||
}
|
||||
|
||||
ups, err := cu.Select(filter, options)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable, loggerdataresp.PostgresNoRowsErrorCheck) {
|
||||
return
|
||||
}
|
||||
|
||||
if options.Offset == 0 && filter.ID == 0 {
|
||||
total, err = cu.Count(filter)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{
|
||||
Data: ups,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
func parseCarsUpdateFilter(r *http.Request) (*common.CarUpdate, error) {
|
||||
qs := r.URL.Query()
|
||||
|
||||
filter := common.CarUpdate{
|
||||
ID: urlhelper.GetQueryInt64(qs, "id"),
|
||||
VIN: qs.Get("vin"),
|
||||
UpdateManifestID: urlhelper.GetQueryInt64(qs, "manifest_id"),
|
||||
}
|
||||
|
||||
err := validator.ValidateNonRequired(filter)
|
||||
|
||||
return &filter, err
|
||||
}
|
||||
143
services/ota_update_go/handlers/carupdates_get_test.go
Normal file
143
services/ota_update_go/handlers/carupdates_get_test.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
|
||||
m "github.com/fiskerinc/cloud-services/pkg/common"
|
||||
orm "github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
mo "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestCarUpdatesGet(t *testing.T) {
|
||||
mock := mo.MockCarUpdates{}
|
||||
services.GetDB().SetCarUpdates(&mock)
|
||||
expectedCarUpdate := `{"id":1,"vin":"1G1FP87S3GN100062","manifest_id":1,"updatemanifest":{"name":"TEST","version":"1.1","description":"description","release_notes":"http://releasenotes.com","rollback":false,"type":"forced","country":"US","powertrain":"MD23","restraint":"None","model":"Ocean","trim":"Sport","year":2022,"body_type":"truck"},"UpdateSource":"OTA"}`
|
||||
expectedResp := fmt.Sprintf(`{"data":[%s],"total":1}`, expectedCarUpdate)
|
||||
expectedRespNoTotal := fmt.Sprintf(`{"data":[%s]}`, expectedCarUpdate)
|
||||
defaultOrder := "id DESC"
|
||||
listData := []m.CarUpdate{
|
||||
{
|
||||
ID: 1,
|
||||
VIN: "1G1FP87S3GN100062",
|
||||
UpdateManifestID: 1,
|
||||
UpdateManifest: &m.UpdateManifest{
|
||||
Name: "TEST",
|
||||
Description: "description",
|
||||
Version: "1.1",
|
||||
ReleaseNotes: "http://releasenotes.com",
|
||||
RollbackEnabled: false,
|
||||
Type: "forced",
|
||||
Country: "US",
|
||||
PowerTrain: "MD23",
|
||||
Restraint: "None",
|
||||
Model: "Ocean",
|
||||
Trim: "Sport",
|
||||
Year: 2022,
|
||||
BodyType: "truck",
|
||||
},
|
||||
UpdateSource: "OTA",
|
||||
},
|
||||
}
|
||||
tests := []mo.DBHttpTest{
|
||||
{
|
||||
Name: "No parameters",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/carupdates", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: expectedResp,
|
||||
DBTestCase: mo.DBTestCase{
|
||||
ExpectedFilter: m.CarUpdate{},
|
||||
ExpectedPage: &orm.PageQueryOptions{
|
||||
Order: defaultOrder,
|
||||
Limit: orm.PageQueryOptionsLimitMaximum,
|
||||
Offset: 0,
|
||||
},
|
||||
MockListResponse: listData,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Id parameter",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/carupdates?id=1", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: expectedRespNoTotal,
|
||||
DBTestCase: mo.DBTestCase{
|
||||
ExpectedFilter: m.CarUpdate{
|
||||
ID: 1,
|
||||
},
|
||||
ExpectedPage: &orm.PageQueryOptions{
|
||||
Order: defaultOrder,
|
||||
Limit: orm.PageQueryOptionsLimitMaximum,
|
||||
Offset: 0,
|
||||
},
|
||||
MockListResponse: listData,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "VIN and UpdateManifestID parameters",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/carupdates?vin=1G1FP87S3GN100062&manifest_id=1", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: expectedResp,
|
||||
DBTestCase: mo.DBTestCase{
|
||||
ExpectedFilter: m.CarUpdate{
|
||||
VIN: "1G1FP87S3GN100062",
|
||||
UpdateManifestID: 1,
|
||||
},
|
||||
ExpectedPage: &orm.PageQueryOptions{
|
||||
Order: defaultOrder,
|
||||
Limit: orm.PageQueryOptionsLimitMaximum,
|
||||
Offset: 0,
|
||||
},
|
||||
MockListResponse: listData,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Paging parameters",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/carupdates?offset=10&limit=5", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: expectedRespNoTotal,
|
||||
DBTestCase: mo.DBTestCase{
|
||||
ExpectedFilter: m.CarUpdate{},
|
||||
ExpectedPage: &orm.PageQueryOptions{
|
||||
Order: defaultOrder,
|
||||
Limit: 5,
|
||||
Offset: 10,
|
||||
},
|
||||
MockListResponse: listData,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Error",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/carupdates", nil),
|
||||
ExpectedStatus: http.StatusServiceUnavailable,
|
||||
ExpectedResponse: `{"message":"something went wrong","error":"Service Unavailable"}`,
|
||||
DBTestCase: mo.DBTestCase{
|
||||
ExpectedFilter: m.CarUpdate{},
|
||||
ExpectedPage: &orm.PageQueryOptions{
|
||||
Order: defaultOrder,
|
||||
Limit: orm.PageQueryOptionsLimitMaximum,
|
||||
Offset: 0,
|
||||
},
|
||||
MockError: fmt.Errorf("something went wrong"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Wrong limit, -100",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/carupdates?limit=-100", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"Limit less than 0","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Wrong limit, 1000",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/carupdates?limit=1000", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"Limit greater than 100","error":"Bad Request"}`,
|
||||
},
|
||||
}
|
||||
|
||||
mo.RunDBTests(t, tests, handlers.HandleCarUpdatesGet, &mock)
|
||||
}
|
||||
76
services/ota_update_go/handlers/carupdates_log.go
Normal file
76
services/ota_update_go/handlers/carupdates_log.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
orm "github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
|
||||
"otaupdate/services"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
// HandleCarUpdatesLog godoc
|
||||
// @Summary Gets log of car update statuses
|
||||
// @Description Returns array of car update statuses
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param carupdateid query int true "car update id"
|
||||
// @Success 200 {object} CarUpdateStatuses "Car update statuses"
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /carupdateslog [get]
|
||||
func HandleCarUpdatesLog(w http.ResponseWriter, r *http.Request) {
|
||||
var total int
|
||||
carupdateID, err := validateCarUpdatesLog(r)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
options, err := orm.ParsePageQuery(r)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
if options.Order == "" {
|
||||
options.Order = "id DESC"
|
||||
}
|
||||
|
||||
cu := services.GetDB().GetCarUpdates()
|
||||
statuses, err := cu.GetUpdateStatuses(carupdateID, options)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable, loggerdataresp.PostgresNoRowsErrorCheck) {
|
||||
return
|
||||
}
|
||||
|
||||
if options.Offset == 0 {
|
||||
total, err = cu.CountUpdateStatuses(carupdateID)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{
|
||||
Data: statuses,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
func validateCarUpdatesLog(r *http.Request) (int64, error) {
|
||||
qs := r.URL.Query()
|
||||
qsID := qs.Get("carupdateid")
|
||||
|
||||
if qsID == "" {
|
||||
return 0, fmt.Errorf("car update id required")
|
||||
}
|
||||
|
||||
id, err := strconv.ParseInt(qsID, 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid id %s", qsID)
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
56
services/ota_update_go/handlers/carupdates_log_test.go
Normal file
56
services/ota_update_go/handlers/carupdates_log_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common/dbbasemodel"
|
||||
mo "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestHandleCarUpdatesLog(t *testing.T) {
|
||||
date := time.Date(2021, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||
mock := mo.MockCarUpdates{
|
||||
SelectCarUpdateStatusesResponse: []common.CarUpdateStatus{
|
||||
{
|
||||
ID: 1000,
|
||||
CarUpdateID: 100,
|
||||
Status: "pending",
|
||||
DBModelBase: dbbasemodel.DBModelBase{
|
||||
CreatedAt: &date,
|
||||
UpdatedAt: &date,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
services.GetDB().SetCarUpdates(&mock)
|
||||
//year int, month Month, day, hour, min, sec, nsec int, loc *Location
|
||||
tests := []th.BasicHttpTest{
|
||||
{
|
||||
Name: "Missing query",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/carupdateslog", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"car update id required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Bad car update ids",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdateslog?carupdateid=XXXXXXXXX", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"invalid id XXXXXXXXX","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Good request",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdateslog?carupdateid=100", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"data":[{"id":1000,"carupdate_id":100,"status":"pending","error_code":0,"created":"2021-11-10T23:00:00Z","updated":"2021-11-10T23:00:00Z"}],"total":1}`,
|
||||
},
|
||||
}
|
||||
|
||||
th.RunBasicHttpTests(t, tests, handlers.HandleCarUpdatesLog)
|
||||
}
|
||||
109
services/ota_update_go/handlers/carupdates_statuses.go
Normal file
109
services/ota_update_go/handlers/carupdates_statuses.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils/querystring"
|
||||
"github.com/go-pg/pg/v10"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
// HandleCarUpdatesStatuses godoc
|
||||
// @Summary Gets statuses for car update by car update ids
|
||||
// @Description Returns array of car update statuses
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param carupdateids query string true "Comma delimited list of car update ids"
|
||||
// @Success 200 {object} CarUpdateStatuses "Car update statuses"
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /carupdatesstatuses [get]
|
||||
func HandleCarUpdatesStatuses(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := validateCarUpdatesStatuses(r)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
keys := make([]string, len(data))
|
||||
// These should be of type common.CarUpdateProgress
|
||||
statuses := make([]interface{}, len(data))
|
||||
for i, carupdateID := range data {
|
||||
keys[i] = redis.CarUpdateStatusHashKey(carupdateID)
|
||||
statuses[i] = &common.CarUpdateProgress{}
|
||||
}
|
||||
|
||||
conn := services.RedisClientPool().GetFromPool()
|
||||
defer conn.Close()
|
||||
|
||||
err = conn.GetObjectsMulti(keys, statuses)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, CarUpdateStatuses{
|
||||
Statuses: statuses,
|
||||
})
|
||||
}
|
||||
|
||||
// I am not a fan of doing this, going from Redis and then checking the database
|
||||
func filterCarUpdatedStatuses(statuses []interface{}) (filtered []interface{}) {
|
||||
filtered = make([]interface{}, 0)
|
||||
for x := range statuses {
|
||||
status, ok := statuses[x].(common.CarUpdateProgress)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// LOAD does not search on CarUpdateID
|
||||
manifest := &common.CarUpdate{ID: status.CarUpdateID, UpdateManifest: &common.UpdateManifest{ManifestType: common.MagnaManifestUpdateType}}
|
||||
um := services.GetDB().GetCarUpdates()
|
||||
|
||||
err := um.Load(manifest)
|
||||
if err != nil {
|
||||
if errors.Is(err, pg.ErrNoRows) {
|
||||
continue
|
||||
}
|
||||
logger.Warn().Err(err).Send()
|
||||
}
|
||||
if manifest.ID > 0 {
|
||||
filtered = append(filtered, statuses[x])
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
func validateCarUpdatesStatuses(r *http.Request) ([]int64, error) {
|
||||
qs := r.URL.Query()
|
||||
qsIDs := qs.Get("carupdateids")
|
||||
|
||||
if qsIDs == "" {
|
||||
return nil, fmt.Errorf("car update ids required")
|
||||
}
|
||||
|
||||
if len(qsIDs) > 6000 {
|
||||
return nil, fmt.Errorf("carupdateids too long")
|
||||
}
|
||||
|
||||
carupdateIDs, err := querystring.SplitIntArray(qsIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return carupdateIDs, nil
|
||||
}
|
||||
|
||||
type CarUpdateStatuses struct {
|
||||
Statuses []interface{} `json:"statuses"`
|
||||
}
|
||||
40
services/ota_update_go/handlers/carupdates_statuses_test.go
Normal file
40
services/ota_update_go/handlers/carupdates_statuses_test.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||
"github.com/fiskerinc/cloud-services/pkg/redis/tester"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestHandleCarUpdatesStatuses(t *testing.T) {
|
||||
redis.MockRedisConnection()
|
||||
services.SetRedisClientPool(tester.NewMockClientPool())
|
||||
tests := []th.BasicHttpTest{
|
||||
{
|
||||
Name: "Missing query",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/carupdatesstatuses", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"car update ids required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Bad car update ids",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdatesstatuses?carupdateids=XXXXXXXXX", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"invalid id XXXXXXXXX","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Good request",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdatesstatuses?carupdateids=100,101,102", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"statuses":[{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":0,"ecu":"","msg":"","err":0},{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":0,"ecu":"","msg":"","err":0},{"file_current":0,"file_total":0,"package_current":0,"package_total":0,"installed":0,"total_files":0,"car_update_id":0,"ecu":"","msg":"","err":0}]}`,
|
||||
},
|
||||
}
|
||||
|
||||
th.RunBasicHttpTests(t, tests, handlers.HandleCarUpdatesStatuses)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/httphandlers"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
// HandleCustomerOtaEmails godoc
|
||||
// @Summary Sends customer emails by list of vins
|
||||
// @Description Sends OTA notification emails to all emails associated with vins in request body
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param data body common.CustomerOtaEmailsRequest true "Customer OTA Emails Request"
|
||||
// @Success 200 {object} map[string]bool "Customer Ota Emails"
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /customer_ota_emails [put]
|
||||
func HandleCustomerOtaEmails(w http.ResponseWriter, r *http.Request) {
|
||||
var request common.CustomerOtaEmailsRequest
|
||||
err := httphandlers.ParseRequest(r, &request)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
var fromEmail = "fastservice@ovloop.com"
|
||||
var toEmails = []string{}
|
||||
var subject = request.EmailSubject
|
||||
var body = request.EmailBody
|
||||
|
||||
driverEmails, err := services.GetDB().GetDriverEmails().SelectByVINs(request.VINs)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable, loggerdataresp.PostgresNoRowsErrorCheck) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, driverEmail := range driverEmails {
|
||||
toEmails = append(toEmails, driverEmail.Email)
|
||||
}
|
||||
|
||||
err = services.GetSMTP().Send(fromEmail, toEmails, subject, body)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
mo "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||
"github.com/fiskerinc/cloud-services/pkg/smtpclient"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestCustomerOtaEmails(t *testing.T) {
|
||||
mock := MockDriverEmails{
|
||||
SelectResponse: &[]common.DriverEmail{
|
||||
{
|
||||
Vin: "VCF1ZBU28PG002114",
|
||||
DriverId: "446b4d69-8768-4e6b-bcf8-0507ea74c952",
|
||||
Email: "ivan.delgadillo@indigotech.com",
|
||||
GivenName: "Ivan",
|
||||
FamilyName: "Delgadillo",
|
||||
},
|
||||
{
|
||||
Vin: "VCF1ZBU25PG003608",
|
||||
DriverId: "7d9c8fed-51b3-4df3-9603-5dfe0e8979f2",
|
||||
Email: "junsub.lee@indigotech.com",
|
||||
GivenName: "Junsub",
|
||||
FamilyName: "Lee",
|
||||
},
|
||||
{
|
||||
Vin: "VCF1EBU2XPG011442",
|
||||
DriverId: "9855e14c-22f6-438d-84ee-47a7be675773",
|
||||
Email: "csimpson@ovloop.com",
|
||||
GivenName: "Clea",
|
||||
FamilyName: "Simpson",
|
||||
},
|
||||
},
|
||||
}
|
||||
services.GetDB().SetDriverEmails(&mock)
|
||||
|
||||
mocksmtp := smtpclient.MockSMTP{}
|
||||
services.SetSMTP(&mocksmtp)
|
||||
|
||||
tests := []mo.DBHttpTest{
|
||||
{
|
||||
Name: "Good data",
|
||||
Request: th.MakeTestRequest(
|
||||
http.MethodGet,
|
||||
"/customer_ota_emails",
|
||||
common.CustomerOtaEmailsRequest{
|
||||
VINs: []string{"VCF1ZBU28PG002114", "VCF1ZBU25PG003608", "VCF1EBU2XPG011442"},
|
||||
EmailSubject: "Test Email Subject",
|
||||
EmailBody: `Dear Ocean Owner:
|
||||
Test Email Body
|
||||
Sincerely,
|
||||
Me`,
|
||||
}),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: ``,
|
||||
},
|
||||
}
|
||||
|
||||
mo.RunParamHttpTests(t, tests, handlers.HandleCustomerOtaEmails, "/customer_ota_emails", &mock)
|
||||
}
|
||||
|
||||
type MockDriverEmails struct {
|
||||
SelectResponse *[]common.DriverEmail
|
||||
Error error
|
||||
mo.DBMockHelper
|
||||
}
|
||||
|
||||
func (d *MockDriverEmails) SelectByVINs(vins []string) ([]common.DriverEmail, error) {
|
||||
return *d.SelectResponse, d.Error
|
||||
}
|
||||
59
services/ota_update_go/handlers/dbc_signals_get.go
Normal file
59
services/ota_update_go/handlers/dbc_signals_get.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"otaupdate/services"
|
||||
|
||||
"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/julienschmidt/httprouter"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
// HandleDBCSignalsGetList godoc
|
||||
// @Summary List API tokens
|
||||
// @Description List API tokens. Requires API token permission
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param limit query int false "Max number of records"
|
||||
// @Param offset query int false "Records offset"
|
||||
// @Param dbc path string true "DBC hash"
|
||||
// @Success 200 {object} common.JSONDBQueryResult{data=[]common.SignalDescWithECU}
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /can_signals/{dbc} [get]
|
||||
func HandleDBCSignalsGetList(w http.ResponseWriter, r *http.Request) {
|
||||
options, err := clickhouse.ParsePageQuery(r)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
params := httprouter.ParamsFromContext(r.Context())
|
||||
dbc := params.ByName("dbc")
|
||||
err = validator.GetValidator().Var(dbc, "required")
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
cl, err := services.GetClickhouseClient()
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
logger.Error().Err(err).Msg("cannot get clickhouse client")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
signals, count, err := cl.SelectDBCSignals(dbc, options)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{Data: signals, Total: count})
|
||||
}
|
||||
115
services/ota_update_go/handlers/dbc_signals_get_test.go
Normal file
115
services/ota_update_go/handlers/dbc_signals_get_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fiskerinc/cloud-services/pkg/clickhouse"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHandleDBCSignalsGetList(t *testing.T) {
|
||||
validQuery := "?limit=5&offset=2"
|
||||
tests := map[string]struct {
|
||||
q string
|
||||
conn clickhouse.ConnInterface
|
||||
expStatus int
|
||||
expBody string
|
||||
}{
|
||||
"correct": {
|
||||
q: validQuery,
|
||||
conn: &clickhouse.MockConn{
|
||||
ExpectedResult: []common.SignalDescWithECU{
|
||||
{
|
||||
ECUName: "ADAS",
|
||||
SignalDesc: common.SignalDesc{
|
||||
DBCHash: "hash",
|
||||
MessageID: 2,
|
||||
Name: "All_Signals_Sum_Check0",
|
||||
Start: 0,
|
||||
Length: 8,
|
||||
IsBigEndian: true,
|
||||
IsSigned: true,
|
||||
IsMultiplexer: true,
|
||||
IsMultiplexed: false,
|
||||
MultiplexerValue: 5,
|
||||
Offset: 0,
|
||||
Scale: 3,
|
||||
Min: 0,
|
||||
Max: 20,
|
||||
Unit: "sm",
|
||||
Description: "desc",
|
||||
ValueDescriptions: []string{"lt", "lg", "lk"},
|
||||
ReceiverNodes: []string{"GW", "OO"},
|
||||
DefaultValue: 0,
|
||||
},
|
||||
}, {
|
||||
ECUName: "ICC",
|
||||
SignalDesc: common.SignalDesc{
|
||||
DBCHash: "hash",
|
||||
MessageID: 20,
|
||||
Name: "All_Signals_Sum_Check1",
|
||||
Start: 8,
|
||||
Length: 12,
|
||||
IsBigEndian: false,
|
||||
IsSigned: false,
|
||||
IsMultiplexer: false,
|
||||
IsMultiplexed: true,
|
||||
MultiplexerValue: 7,
|
||||
Offset: 8,
|
||||
Scale: 31,
|
||||
Min: 5,
|
||||
Max: 12,
|
||||
Unit: "kg",
|
||||
Description: "desc 1",
|
||||
ValueDescriptions: nil,
|
||||
ReceiverNodes: nil,
|
||||
DefaultValue: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
QueryRowtMock: func(ctx context.Context, query string, args ...interface{}) driver.Row {
|
||||
return clickhouse.RowMock{RowResult: 5}
|
||||
}},
|
||||
expStatus: http.StatusOK,
|
||||
expBody: `{"data":[{"dbc_hash":"hash","message_id":2,"name":"All_Signals_Sum_Check0","start":0,"length":8,"big_endian":true,"signed":true,"multiplexer":true,"multiplexed":false,"multiplexer_value":5,"offset":0,"scale":3,"min":0,"max":20,"unit":"sm","description":"desc","value_descriptions":["lt","lg","lk"],"receiver_nodes":["GW","OO"],"default_value":0,"ECUName":"","ecu_name":"ADAS"},{"dbc_hash":"hash","message_id":20,"name":"All_Signals_Sum_Check1","start":8,"length":12,"big_endian":false,"signed":false,"multiplexer":false,"multiplexed":true,"multiplexer_value":7,"offset":8,"scale":31,"min":5,"max":12,"unit":"kg","description":"desc 1","value_descriptions":null,"receiver_nodes":null,"default_value":10,"ECUName":"","ecu_name":"ICC"}]}`,
|
||||
},
|
||||
"failed_query": {
|
||||
q: validQuery,
|
||||
conn: &clickhouse.MockConn{ExpectedResult: someErr},
|
||||
expStatus: http.StatusServiceUnavailable,
|
||||
expBody: `{"message":"json: cannot unmarshal object into Go value of type []common.SignalDescWithECU","error":"Service Unavailable"}`,
|
||||
},
|
||||
"wrong limit": {
|
||||
q: "?limit=-2",
|
||||
expStatus: http.StatusBadRequest,
|
||||
expBody: `{"message":"Limit less than 0","error":"Bad Request"}`,
|
||||
},
|
||||
}
|
||||
|
||||
for tname, tt := range tests {
|
||||
t.Run(tname, func(t *testing.T) {
|
||||
services.SetClickhouseConn(tt.conn)
|
||||
w := httptest.NewRecorder()
|
||||
p := httprouter.Params{
|
||||
{
|
||||
Key: "dbc",
|
||||
Value: "hash",
|
||||
},
|
||||
}
|
||||
ctx := context.WithValue(context.Background(), httprouter.ParamsKey, p)
|
||||
r := httptest.NewRequest(http.MethodGet, "http://example.com/can_signals/dbc"+tt.q, nil).
|
||||
WithContext(ctx)
|
||||
|
||||
handlers.HandleDBCSignalsGetList(w, r)
|
||||
assert.Equal(t, tt.expStatus, w.Code)
|
||||
assert.Equal(t, tt.expBody, w.Body.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/services"
|
||||
"strconv"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/cache"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
// "github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
)
|
||||
|
||||
// HandleDigitalTwinSignal godoc
|
||||
// @Summary List signals with updated timestamp
|
||||
// @Description Returns list of state with last updated timestamp.
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param limit query int false "Limit"
|
||||
// @Param offset query int false "Offset"
|
||||
// @Success 200 {object} []interface{}
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /ditto/carstate [get]
|
||||
func HandleDigitalTwinSignal(w http.ResponseWriter, r *http.Request) {
|
||||
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
||||
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
|
||||
|
||||
if limit < 1 || limit > 1000 {
|
||||
limit = 1000
|
||||
}
|
||||
|
||||
if offset < 1 {
|
||||
offset = 0
|
||||
}
|
||||
|
||||
// vin := r.URL.Query().Get("vin")
|
||||
// if vin != "" {
|
||||
// vins := strings.Split(vin, ",")
|
||||
// for _, v := range vins {
|
||||
// ok, err := validator.ValidateVINSimple(v)
|
||||
// if !ok || err != nil {
|
||||
// loggerdataresp.BadDataErrorResp(w, ErrInvalidVIN, http.StatusBadRequest)
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
conn := services.RedisClientPool().GetFromPool()
|
||||
defer conn.Close()
|
||||
|
||||
data := cache.NewDigitalTwinTimestampState(conn).GetDigitalTwinSignals(offset, limit)
|
||||
utils.RespJSON(w, http.StatusOK, data)
|
||||
}
|
||||
21
services/ota_update_go/handlers/docs.go
Normal file
21
services/ota_update_go/handlers/docs.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"otaupdate/docs"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/httphandlers"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
|
||||
)
|
||||
|
||||
func InitSwaggerDoc() {
|
||||
schemes := envtool.GetEnv("SWAGGER_SCHEMES", "https")
|
||||
|
||||
docs.SwaggerInfo.Title = "Fisker Inc OTA API"
|
||||
docs.SwaggerInfo.Description = "Fisker Inc OTA portals APIs"
|
||||
docs.SwaggerInfo.Version = "1.0"
|
||||
docs.SwaggerInfo.Host = ""
|
||||
docs.SwaggerInfo.BasePath = httphandlers.ServiceBaseURL
|
||||
docs.SwaggerInfo.Schemes = strings.Split(schemes, ",")
|
||||
}
|
||||
100
services/ota_update_go/handlers/docs_test.go
Normal file
100
services/ota_update_go/handlers/docs_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"otaupdate/handlers"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/httphandlers"
|
||||
"github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
)
|
||||
|
||||
const headerContentType = "Content-Type"
|
||||
|
||||
var swaggerHandler http.HandlerFunc
|
||||
|
||||
func init() {
|
||||
handlers.InitSwaggerDoc()
|
||||
|
||||
swaggerHandler = httphandlers.GetSwaggerHandler()
|
||||
}
|
||||
|
||||
func TestHandleSwaggerRedirect(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "http://example.com/api", nil)
|
||||
req.RequestURI = req.URL.Path
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
swaggerHandler(recorder, req)
|
||||
|
||||
validateSwaggerRedirect(t, recorder)
|
||||
}
|
||||
|
||||
func TestHandleSwagger(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "http://example.com/api/", nil)
|
||||
req.RequestURI = req.URL.Path
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
swaggerHandler(recorder, req)
|
||||
|
||||
validateSwaggerRedirect(t, recorder)
|
||||
}
|
||||
|
||||
func TestHandleSwaggerJSON(t *testing.T) {
|
||||
contentType := "application/json; charset=utf-8"
|
||||
req, _ := http.NewRequest("GET", "http://example.com/api/doc.json", nil)
|
||||
req.RequestURI = req.URL.Path
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
swaggerHandler(recorder, req)
|
||||
headers := recorder.Result().Header
|
||||
|
||||
if headers.Get(headerContentType) != contentType {
|
||||
t.Errorf(testhelper.TestErrorTemplate, headerContentType, contentType, headers.Get(headerContentType))
|
||||
}
|
||||
|
||||
var data map[string]interface{}
|
||||
|
||||
err := json.Unmarshal(recorder.Body.Bytes(), &data)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleSwaggerHTML(t *testing.T) {
|
||||
contentType := "text/html; charset=utf-8"
|
||||
htmlTitle := "<title>Swagger UI</title>"
|
||||
req, _ := http.NewRequest("GET", "http://example.com/api/index.html", nil)
|
||||
req.RequestURI = req.URL.Path
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
swaggerHandler(recorder, req)
|
||||
headers := recorder.Result().Header
|
||||
|
||||
if headers.Get(headerContentType) != contentType {
|
||||
t.Errorf(testhelper.TestErrorTemplate, headerContentType, contentType, headers.Get(headerContentType))
|
||||
}
|
||||
|
||||
if !strings.Contains(recorder.Body.String(), htmlTitle) {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "HTML", htmlTitle, recorder.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func validateSwaggerRedirect(t *testing.T, recorder *httptest.ResponseRecorder) {
|
||||
if recorder.Code != http.StatusMovedPermanently {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "Status code", http.StatusMovedPermanently, recorder.Code)
|
||||
}
|
||||
|
||||
u, err := url.Parse(recorder.Header().Get("location"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if u.Path != "/api/index.html" {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "Path", "/api/index.html", u.Path)
|
||||
}
|
||||
}
|
||||
194
services/ota_update_go/handlers/dtc_ecu_get.go
Normal file
194
services/ota_update_go/handlers/dtc_ecu_get.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
orm "github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/pkg/errors"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
// HandleECUDTCGet godoc
|
||||
// @Summary Get ECU DTCs for a specific vehicle
|
||||
// @Description Get ECU diagnostic trouble codes (DTCs) for a specific vehicle within a given time range
|
||||
// @Tags ECU
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param vin path string true "VIN"
|
||||
// @Param ecu query string false "ECU"
|
||||
// @Param trouble_code query string false "Trouble Code"
|
||||
// @Param start_time query string false "Start time (RFC3339 format)"
|
||||
// @Param end_time query string false "End time (RFC3339 format)"
|
||||
// @Param limit query int false "Max number of records"
|
||||
// @Param offset query int false "Records offset"
|
||||
// @Param order query string false "Sort on column with asc or desc"
|
||||
// @Param decode query bool false "Return decoded dtc information"
|
||||
// @Success 200 {object} common.JSONDBQueryResult{data=[]common.DTC_ECU} "List of DTC ECU data"
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 404 {object} common.JSONError "Not found"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /dtcs/{vin} [get]
|
||||
func HandleECUDTCGet(w http.ResponseWriter, r *http.Request) {
|
||||
params := httprouter.ParamsFromContext(r.Context())
|
||||
vin := params.ByName("vin")
|
||||
|
||||
queryParams := r.URL.Query()
|
||||
ecu := queryParams.Get("ecu")
|
||||
troubleCode := queryParams.Get("trouble_code")
|
||||
startStr := queryParams.Get("start_time")
|
||||
endStr := queryParams.Get("end_time")
|
||||
|
||||
decode, _ := strconv.ParseBool(queryParams.Get("decode"))
|
||||
filter := bson.M{
|
||||
"vin": vin}
|
||||
if ecu != "" {
|
||||
filter["ecu"] = ecu
|
||||
}
|
||||
if troubleCode != "" {
|
||||
troubleCodeInt, err := strconv.ParseInt(troubleCode, 10, 64)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid trouble_code format, use int64", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
filter["dtc"] = troubleCodeInt
|
||||
}
|
||||
|
||||
err := validator.GetValidator().Var(vin, "vin|vinsuffix")
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
var start time.Time
|
||||
if startStr != "" {
|
||||
start, err = time.Parse(time.RFC3339, startStr)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid start_time format, use RFC3339 format", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
var end time.Time
|
||||
|
||||
if endStr != "" {
|
||||
end, err = time.Parse(time.RFC3339, endStr)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid end_time format, use RFC3339 format", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
if !start.IsZero() && !end.IsZero() {
|
||||
filter["created_at"] = bson.M{
|
||||
"$gte": start,
|
||||
"$lte": end,
|
||||
}
|
||||
} else if !start.IsZero() {
|
||||
filter["created_at"] = bson.M{
|
||||
"$gte": start,
|
||||
}
|
||||
} else if !end.IsZero() {
|
||||
filter["created_at"] = bson.M{
|
||||
"$lte": end,
|
||||
}
|
||||
}
|
||||
|
||||
mongoOpts := options.Find()
|
||||
|
||||
query_params, err := orm.ParsePageQuery(r)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
mongoOpts.SetLimit(int64(query_params.Limit))
|
||||
|
||||
if query_params.Order != "" {
|
||||
mongoOpts.SetSort(bson.D{
|
||||
{"created_at", -1}}) // Descending order for 'created_at'.
|
||||
}
|
||||
|
||||
if query_params.Offset != 0 {
|
||||
mongoOpts.SetSkip(int64(query_params.Offset))
|
||||
}
|
||||
|
||||
mongo, err := services.GetMongoClient()
|
||||
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var total int64
|
||||
if query_params.Offset == 0 {
|
||||
total, err = mongo.Collection("dtcs").CountDocuments(ctx, filter)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable, loggerdataresp.PostgresNoRowsErrorCheck) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
cursor, err := mongo.Collection("dtcs").Find(ctx, filter, mongoOpts)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
var dtcs []common.DTC_ECU
|
||||
|
||||
for cursor.Next(ctx) {
|
||||
var result common.DTC_ECU
|
||||
err := cursor.Decode(&result)
|
||||
if err != nil {
|
||||
logger.Warn().Msg(err.Error())
|
||||
continue
|
||||
}
|
||||
if decode {
|
||||
fetchDTCDataFromMongo(&result)
|
||||
}
|
||||
dtcs = append(dtcs, result)
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{
|
||||
Data: dtcs,
|
||||
Total: int(total),
|
||||
})
|
||||
}
|
||||
|
||||
func fetchDTCDataFromMongo(dtc *common.DTC_ECU) (err error) {
|
||||
client, err := mongo.GetPDXMongoClient()
|
||||
if err != nil {
|
||||
err = errors.WithStack(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle the dtc string, need to drop the first byte as its the status code
|
||||
troubleCodeHex := fmt.Sprintf("%X", dtc.TroubleCode)
|
||||
troubleCodeHex = strings.ToUpper(troubleCodeHex)
|
||||
info, err := client.GetDTCDefinitionByHexString(troubleCodeHex, dtc.ECU)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if info == nil {
|
||||
logger.Warn().Msgf("Failed to find dtc code from ecu: %s troubleCodeHex: %s", dtc.ECU, troubleCodeHex)
|
||||
}
|
||||
|
||||
dtc.Information = info
|
||||
dtc.StatusByteDecode = dtc.DTCStatusByteMeaning()
|
||||
return
|
||||
}
|
||||
83
services/ota_update_go/handlers/dtc_ecu_get_test.go
Normal file
83
services/ota_update_go/handlers/dtc_ecu_get_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHandlers_HandleECUDTCGet(t *testing.T) {
|
||||
/*
|
||||
db := services.GetDB()
|
||||
|
||||
dtcs := []common.DTC_ECU{
|
||||
{
|
||||
ID: 196,
|
||||
VIN: "1GNGC26RXXJ407648",
|
||||
ECU: "AMP",
|
||||
DTC: []byte{9, 81, 118, 19},
|
||||
Epoch_usec: 1624485598,
|
||||
},
|
||||
{
|
||||
ID: 197,
|
||||
VIN: "1GNGC26RXXJ407648",
|
||||
ECU: "AMP",
|
||||
TroubleCode: 12,
|
||||
DTC: []byte{9, 81, 118, 19},
|
||||
Epoch_usec: 1624485598,
|
||||
},
|
||||
}
|
||||
|
||||
tests := map[string]struct {
|
||||
vin string
|
||||
ecu string
|
||||
start string
|
||||
end string
|
||||
dtcs q.ECUInterface
|
||||
expStatus int
|
||||
expBody string
|
||||
}{
|
||||
"success": {
|
||||
vin: "1GNGC26RXXJ407648",
|
||||
ecu: "AMP",
|
||||
start: "",
|
||||
end: "",
|
||||
dtcs: &mocks.MockEcuDtc{
|
||||
SelectDTCECUResponse: dtcs,
|
||||
},
|
||||
expStatus: http.StatusOK,
|
||||
expBody: `{"data":[{"id":196,"vin":"1GNGC26RXXJ407648","ecu_name":"AMP","dtc":"CVF2Ew==","trouble_code":0,"status_byte":0,"epoch_usec":1624485598},{"id":197,"vin":"1GNGC26RXXJ407648","ecu_name":"AMP","dtc":"CVF2Ew==","trouble_code":12,"status_byte":0,"epoch_usec":1624485598}]}`,
|
||||
},
|
||||
"invalid_vin": {
|
||||
vin: "INVALID_VIN",
|
||||
ecu: "AMP",
|
||||
start: "",
|
||||
end: "",
|
||||
dtcs: &mocks.MockEcuDtc{},
|
||||
expStatus: http.StatusBadRequest,
|
||||
expBody: `{"message":"vin|vinsuffix vin|vinsuffix ","error":"Bad Request"}`,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
db.SetDTCECU(tt.dtcs)
|
||||
|
||||
p := httprouter.Params{
|
||||
{"vin", tt.vin},
|
||||
{"ecu", tt.ecu},
|
||||
{"trouble_code", "12"},
|
||||
{"start_time", tt.start},
|
||||
{"end_time", tt.end},
|
||||
}
|
||||
|
||||
ctx := context.WithValue(context.Background(), httprouter.ParamsKey, p)
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/dtcs/"+tt.vin, nil).
|
||||
WithContext(ctx)
|
||||
handlers.HandleECUDTCGet(w, request)
|
||||
assert.Equal(t, tt.expStatus, w.Code)
|
||||
assert.Equal(t, tt.expBody, w.Body.String())
|
||||
})
|
||||
}
|
||||
*/
|
||||
}
|
||||
155
services/ota_update_go/handlers/ecu_stats_get.go
Normal file
155
services/ota_update_go/handlers/ecu_stats_get.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"otaupdate/services"
|
||||
|
||||
"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"
|
||||
|
||||
ch "github.com/ClickHouse/clickhouse-go/v2"
|
||||
"github.com/gorilla/schema"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
// HandleECUStatsGetList godoc
|
||||
// @Summary List API tokens
|
||||
// @Description List API tokens. Requires API token permission
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param ecus query []string true "ECU names"
|
||||
// @Param dbcs query []string true "DBC hashes"
|
||||
// @Param vins query []string true "Array of VINs"
|
||||
// @Param hours query int true "Past hours that must be included into the request"
|
||||
// @Param min_zero_pct query float32 true "Minimum zero values percent"
|
||||
// @Param min_out_of_range_pct query int true "Minimum out of range percent"
|
||||
// @Success 200 {object} common.JSONDBQueryResult{data=[]common.ECUStat}
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /ecu_stats [get]
|
||||
func HandleECUStatsGetList(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := services.GetClickhouseConn()
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
logger.Error().Err(err).Msg("cannot get clickhouse client")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
filter, err := parseStatsFilter(r)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
stats, err := getEcusStats(conn, filter)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{Data: stats})
|
||||
}
|
||||
|
||||
func parseStatsFilter(r *http.Request) (common.StatsFilter, error) {
|
||||
sch := schema.NewDecoder()
|
||||
filter := common.StatsFilter{}
|
||||
|
||||
sch.SetAliasTag("json")
|
||||
err := sch.Decode(&filter, r.URL.Query())
|
||||
if err != nil {
|
||||
return common.StatsFilter{}, errors.WithStack(err)
|
||||
}
|
||||
|
||||
err = validator.GetValidator().Struct(filter)
|
||||
if err != nil {
|
||||
return common.StatsFilter{}, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
func getEcusStats(conn clickhouse.ConnInterface, filter common.StatsFilter) ([]common.ECUStat, error) {
|
||||
var result []common.ECUStat
|
||||
|
||||
chCtx := ch.Context(context.Background(), ch.WithParameters(ch.Parameters{
|
||||
"minOutOfRangePct": fmt.Sprint(filter.MinOutOfRangePct),
|
||||
"minZeroPct": fmt.Sprint(filter.MinZeroPct),
|
||||
"hours": fmt.Sprint(filter.Hours),
|
||||
"vins": "['" + strings.Join(filter.VINs, "','") + "']",
|
||||
"dbcs": "['" + strings.Join(filter.DBCs, "','") + "']",
|
||||
"ecus": "['" + strings.Join(filter.ECUs, "','") + "']",
|
||||
}))
|
||||
|
||||
if err := conn.Select(chCtx, &result, `select ecu_name,
|
||||
sum(case when value_out_range_pct> {minOutOfRangePct:Float32} then 1 else 0 end) as signals_w_incorrect_values,
|
||||
sum(case when zero_pct> {minZeroPct:Float32} then 1 else 0 end ) as signals_all_zero,
|
||||
count(*) as number_of_ecu_signals,
|
||||
sum(tot_cnt) as total_signal_records,
|
||||
(signals_w_incorrect_values/number_of_ecu_signals) incorrect_val_signal_pct,
|
||||
signals_all_zero/number_of_ecu_signals as zero_signals_pct
|
||||
from ( select
|
||||
case when aa.Name<>'' then aa.Name
|
||||
when bb.signal_name<>'' then bb.signal_name
|
||||
else null end as signal_name,
|
||||
case when aa.ID<>'0' then aa.ID
|
||||
when bb.message_id<>'0' then bb.message_id
|
||||
else null end as can_id,
|
||||
case when aa.ecu_name<>'' then aa.ecu_name
|
||||
when dbcm.ecu_name<>'' then dbcm.ecu_name
|
||||
else null end as ecu_name,
|
||||
zero_count,value_out_range_cnt, tot_cnt,
|
||||
case when tot_cnt <> 0 then value_out_range_pct else 0 end as value_out_range_pct,
|
||||
case when tot_cnt <> 0 then zero_pct else 0 end as zero_pct
|
||||
|
||||
from
|
||||
|
||||
(/* check if signals are within dbc value range and if 0 on joined CAN signals and dbc*/
|
||||
select Name, ID, signal_name, ecu_name,cycle_time_ns,sender_node,
|
||||
sum(case when Value=0 then 1 else 0 end) as zero_count,
|
||||
sum(case when a.Value<b.min or a.Value>b.max then 1 else 0 end) as value_out_range_cnt,
|
||||
count(*) as tot_cnt,
|
||||
value_out_range_cnt/tot_cnt as value_out_range_pct,
|
||||
zero_count/tot_cnt as zero_pct
|
||||
from vehicle_signal as a
|
||||
inner join
|
||||
|
||||
(/* select dbc_messages and dbc_signals */
|
||||
select b1.*, b2.message_id, b2.ecu_name, b2.cycle_time_ns, b2.sender_node
|
||||
from dbc_signals as b1
|
||||
inner join dbc_messages as b2
|
||||
on b1.dbc_hash=b2.dbc_hash
|
||||
and b1.message_id=b2.message_id
|
||||
where b1.dbc_hash in {dbcs:Array(String)}
|
||||
|
||||
) as b
|
||||
on a.Name=b.signal_name
|
||||
|
||||
where
|
||||
a.Timestamp> (select max(Timestamp) from vehicle_signal where VIN in {vins:Array(String)}) - toIntervalHour({hours:UInt64})
|
||||
and
|
||||
a.VIN in {vins:Array(String)}
|
||||
group by 1,2,3,4,5,6
|
||||
) as aa
|
||||
full outer join dbc_signals as bb
|
||||
on aa.Name=bb.signal_name
|
||||
and aa.ID=bb.message_id
|
||||
inner join dbc_messages as dbcm
|
||||
on bb.message_id=dbcm.message_id
|
||||
where bb.dbc_hash in {dbcs:Array(String)} and dbcm.dbc_hash in {dbcs:Array(String)}
|
||||
and ecu_name in {ecus:Array(String)})
|
||||
group by ecu_name
|
||||
order by zero_signals_pct desc, incorrect_val_signal_pct desc`); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
71
services/ota_update_go/handlers/ecu_stats_get_test.go
Normal file
71
services/ota_update_go/handlers/ecu_stats_get_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"github.com/fiskerinc/cloud-services/pkg/clickhouse"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHandleECUStatsGetList(t *testing.T) {
|
||||
validQuery := "?min_out_of_range_pct=10&min_zero_pct=0.009&hours=160&vins=TREXTEST7TUR9NXGC&vins=TREXTEST5T61T1BR2&dbcs=73583d63735b404f5209a71107c3d2174b0ab1ba40bd826b8cb69668598b0395&ecus=ADAS&ecus=ICC&ecus=TREX"
|
||||
tests := map[string]struct {
|
||||
q string
|
||||
conn clickhouse.ConnInterface
|
||||
expStatus int
|
||||
expBody string
|
||||
}{
|
||||
"correct": {
|
||||
q: validQuery,
|
||||
conn: &clickhouse.MockConn{ExpectedResult: []common.ECUStat{
|
||||
{
|
||||
ECUName: "ADAS",
|
||||
IncorrectValues: 1,
|
||||
AllZero: 1,
|
||||
ECUSignalsTotal: 231,
|
||||
SignalsTotal: 2221,
|
||||
IncorrectPercent: 0.04,
|
||||
ZeroPercent: 0.003,
|
||||
},
|
||||
{
|
||||
ECUName: "TREX",
|
||||
IncorrectValues: 0,
|
||||
AllZero: 0,
|
||||
ECUSignalsTotal: 77,
|
||||
SignalsTotal: 9789,
|
||||
IncorrectPercent: 0.55,
|
||||
ZeroPercent: 0.36,
|
||||
},
|
||||
}},
|
||||
expStatus: http.StatusOK,
|
||||
expBody: `{"data":[{"ecu_name":"ADAS","signals_w_incorrect_values":1,"signals_all_zero":1,"number_of_ecu_signals":231,"total_signal_records":2221,"incorrect_val_signal_pct":0.04,"zero_signals_pct":0.003},{"ecu_name":"TREX","signals_w_incorrect_values":0,"signals_all_zero":0,"number_of_ecu_signals":77,"total_signal_records":9789,"incorrect_val_signal_pct":0.55,"zero_signals_pct":0.36}]}`,
|
||||
},
|
||||
"failed_filter": {
|
||||
q: "",
|
||||
expStatus: http.StatusBadRequest,
|
||||
expBody: `{"message":"MinOutOfRangePct required. MinZeroPct required. Hours required. VINs required. DBCs required. ECUs required","error":"Bad Request"}`,
|
||||
},
|
||||
"failed_query": {
|
||||
q: validQuery,
|
||||
conn: &clickhouse.MockConn{ExpectedResult: someErr},
|
||||
expStatus: http.StatusServiceUnavailable,
|
||||
expBody: `{"message":"json: cannot unmarshal object into Go value of type []common.ECUStat","error":"Service Unavailable"}`,
|
||||
},
|
||||
}
|
||||
|
||||
for tname, tt := range tests {
|
||||
t.Run(tname, func(t *testing.T) {
|
||||
services.SetClickhouseConn(tt.conn)
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodGet, "http://example.com/ecu_stats"+tt.q, nil)
|
||||
|
||||
handlers.HandleECUStatsGetList(w, r)
|
||||
assert.Equal(t, tt.expStatus, w.Code)
|
||||
assert.Equal(t, tt.expBody, w.Body.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
165
services/ota_update_go/handlers/ecu_stats_vin_get.go
Normal file
165
services/ota_update_go/handlers/ecu_stats_vin_get.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
|
||||
"otaupdate/services"
|
||||
|
||||
"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"
|
||||
|
||||
ch "github.com/ClickHouse/clickhouse-go/v2"
|
||||
"github.com/gorilla/schema"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
)
|
||||
|
||||
// HandleVINECUStatsGetList godoc
|
||||
// @Summary List API tokens
|
||||
// @Description List API tokens. Requires API token permission
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param ecus query []string true "ECU names"
|
||||
// @Param hours query int true "Past hours that must be included into the request"
|
||||
// @Param min_zero_pct query float32 true "Minimum zero values percent"
|
||||
// @Param min_out_of_range_pct query int true "Minimum out of range percent"
|
||||
// @Param dbc path string true "DBC hash"
|
||||
// @Param vin path string true "VIN"
|
||||
// @Success 200 {object} common.JSONDBQueryResult{data=[]common.ECUStat}
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /ecu_stats/{vin}/{dbc} [get]
|
||||
func HandleVINECUStatsGetList(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := services.GetClickhouseConn()
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
logger.Error().Err(err).Msg("cannot get clickhouse client")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
filter, err := parseVINStatsFilter(r)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
stats, err := getEcusVINStats(conn, filter)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{Data: stats})
|
||||
}
|
||||
|
||||
func parseVINStatsFilter(r *http.Request) (common.VINStatsFilter, error) {
|
||||
sch := schema.NewDecoder()
|
||||
filter := common.VINStatsFilter{}
|
||||
|
||||
sch.SetAliasTag("json")
|
||||
err := sch.Decode(&filter, r.URL.Query())
|
||||
if err != nil {
|
||||
return common.VINStatsFilter{}, errors.WithStack(err)
|
||||
}
|
||||
|
||||
params := httprouter.ParamsFromContext(r.Context())
|
||||
filter.VIN = params.ByName("vin")
|
||||
filter.DBC = params.ByName("dbc")
|
||||
err = validator.GetValidator().Struct(filter)
|
||||
if err != nil {
|
||||
return common.VINStatsFilter{}, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
func getEcusVINStats(conn clickhouse.ConnInterface, filter common.VINStatsFilter) ([]common.ECUStat, error) {
|
||||
var result []common.ECUStat
|
||||
|
||||
chCtx := ch.Context(context.Background(), ch.WithParameters(ch.Parameters{
|
||||
"minOutOfRangePct": fmt.Sprint(filter.MinOutOfRangePct),
|
||||
"minZeroPct": fmt.Sprint(filter.MinZeroPct),
|
||||
"hours": fmt.Sprint(filter.Hours),
|
||||
"vin": filter.VIN,
|
||||
"dbc": filter.DBC,
|
||||
"ecus": "['" + strings.Join(filter.ECUs, "','") + "']",
|
||||
}))
|
||||
|
||||
if err := conn.Select(chCtx, &result, `
|
||||
select ecu_name,
|
||||
sum(case when value_out_range_pct>{minOutOfRangePct:Float32} then 1 else 0 end) as signals_w_incorrect_values,
|
||||
sum(case when zero_pct>{minZeroPct:Float32} then 1 else 0 end ) as signals_all_zero,
|
||||
count(*) as number_of_ecu_signals,
|
||||
sum(tot_cnt) as total_signal_records,
|
||||
(signals_w_incorrect_values/number_of_ecu_signals) incorrect_val_signal_pct,
|
||||
signals_all_zero/number_of_ecu_signals as zero_signals_pct
|
||||
from
|
||||
|
||||
(/* add missing signals and ecus from dbc as full outer join and generate per a CAN signal stats */
|
||||
select
|
||||
case when aa.Name<>'' then aa.Name
|
||||
when bb.signal_name<>'' then bb.signal_name
|
||||
else null end as signal_name,
|
||||
case when aa.ID<>'0' then aa.ID
|
||||
when bb.message_id<>'0' then bb.message_id
|
||||
else null end as can_id,
|
||||
case when aa.ecu_name<>'' then aa.ecu_name
|
||||
when dbcm.ecu_name<>'' then dbcm.ecu_name
|
||||
else null end as ecu_name,
|
||||
zero_count,value_out_range_cnt, tot_cnt,
|
||||
case when tot_cnt <> 0 then value_out_range_pct else 0 end as value_out_range_pct,
|
||||
case when tot_cnt <> 0 then zero_pct else 0 end as zero_pct
|
||||
from
|
||||
|
||||
(/* check if signals are within dbc value range and if 0 on joined CAN signals and dbc*/
|
||||
select Name, ID, signal_name, ecu_name,cycle_time_ns,sender_node,
|
||||
sum(case when Value=0 then 1 else 0 end) as zero_count,
|
||||
sum(case when a.Value<b.min or a.Value>b.max then 1 else 0 end) as value_out_range_cnt,
|
||||
count(*) as tot_cnt,
|
||||
value_out_range_cnt/tot_cnt as value_out_range_pct,
|
||||
zero_count/tot_cnt as zero_pct
|
||||
from vehicle_signal as a
|
||||
inner join
|
||||
|
||||
(/* select dbc_messages and dbc_signals */
|
||||
select b1.*, b2.message_id, b2.ecu_name, b2.cycle_time_ns, b2.sender_node
|
||||
from dbc_signals as b1
|
||||
inner join dbc_messages as b2
|
||||
on b1.dbc_hash=b2.dbc_hash
|
||||
and b1.message_id=b2.message_id
|
||||
where b1.dbc_hash = {dbc:String}
|
||||
|
||||
) as b
|
||||
on a.Name=b.signal_name
|
||||
|
||||
where
|
||||
a.Timestamp> (select max(Timestamp) from vehicle_signal where VIN = {vin:String}) - toIntervalHour({hours:UInt64})
|
||||
and
|
||||
a.VIN = {vin:String}
|
||||
group by 1,2,3,4,5,6
|
||||
) as aa
|
||||
full outer join dbc_signals as bb
|
||||
on aa.Name=bb.signal_name
|
||||
and aa.ID=bb.message_id
|
||||
inner join dbc_messages as dbcm
|
||||
on bb.message_id=dbcm.message_id
|
||||
where bb.dbc_hash = {dbc:String} and dbcm.dbc_hash = {dbc:String}
|
||||
and ecu_name in {ecus:Array(String)}
|
||||
)
|
||||
group by ecu_name
|
||||
order by zero_signals_pct desc, incorrect_val_signal_pct desc
|
||||
`); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
82
services/ota_update_go/handlers/ecu_stats_vin_get_test.go
Normal file
82
services/ota_update_go/handlers/ecu_stats_vin_get_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fiskerinc/cloud-services/pkg/clickhouse"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHandleVINECUStatsGetList(t *testing.T) {
|
||||
validQuery := "?min_out_of_range_pct=10&min_zero_pct=0.009&hours=160&ecus=ADAS&ecus=ICC&ecus=TREX"
|
||||
validVin := "TREXTEST7TUR9NXGC"
|
||||
validDBC := "73583d63735b404f5209a71107c3d2174b0ab1ba40bd826b8cb69668598b0395"
|
||||
tests := map[string]struct {
|
||||
q string
|
||||
conn clickhouse.ConnInterface
|
||||
expStatus int
|
||||
expBody string
|
||||
}{
|
||||
"correct": {
|
||||
q: validQuery,
|
||||
conn: &clickhouse.MockConn{ExpectedResult: []common.ECUStat{
|
||||
{
|
||||
ECUName: "ADAS",
|
||||
IncorrectValues: 1,
|
||||
AllZero: 1,
|
||||
ECUSignalsTotal: 231,
|
||||
SignalsTotal: 2221,
|
||||
IncorrectPercent: 0.04,
|
||||
ZeroPercent: 0.003,
|
||||
},
|
||||
{
|
||||
ECUName: "TREX",
|
||||
IncorrectValues: 0,
|
||||
AllZero: 0,
|
||||
ECUSignalsTotal: 77,
|
||||
SignalsTotal: 9789,
|
||||
IncorrectPercent: 0.55,
|
||||
ZeroPercent: 0.36,
|
||||
},
|
||||
}},
|
||||
expStatus: http.StatusOK,
|
||||
expBody: `{"data":[{"ecu_name":"ADAS","signals_w_incorrect_values":1,"signals_all_zero":1,"number_of_ecu_signals":231,"total_signal_records":2221,"incorrect_val_signal_pct":0.04,"zero_signals_pct":0.003},{"ecu_name":"TREX","signals_w_incorrect_values":0,"signals_all_zero":0,"number_of_ecu_signals":77,"total_signal_records":9789,"incorrect_val_signal_pct":0.55,"zero_signals_pct":0.36}]}`,
|
||||
},
|
||||
"failed_filter": {
|
||||
q: "",
|
||||
expStatus: http.StatusBadRequest,
|
||||
expBody: `{"message":"MinOutOfRangePct required. MinZeroPct required. Hours required. ECUs required","error":"Bad Request"}`,
|
||||
},
|
||||
"failed_query": {
|
||||
q: validQuery,
|
||||
conn: &clickhouse.MockConn{ExpectedResult: someErr},
|
||||
expStatus: http.StatusServiceUnavailable,
|
||||
expBody: `{"message":"json: cannot unmarshal object into Go value of type []common.ECUStat","error":"Service Unavailable"}`,
|
||||
},
|
||||
}
|
||||
|
||||
for tname, tt := range tests {
|
||||
t.Run(tname, func(t *testing.T) {
|
||||
services.SetClickhouseConn(tt.conn)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
p := httprouter.Params{
|
||||
{Key: "vin", Value: validVin},
|
||||
{Key: "dbc", Value: validDBC},
|
||||
}
|
||||
ctx := context.WithValue(context.Background(), httprouter.ParamsKey, p)
|
||||
r := httptest.NewRequest(http.MethodGet, "http://example.com/ecu_stats/vin/dbc"+tt.q, nil).
|
||||
WithContext(ctx)
|
||||
|
||||
handlers.HandleVINECUStatsGetList(w, r)
|
||||
assert.Equal(t, tt.expStatus, w.Code)
|
||||
assert.Equal(t, tt.expBody, w.Body.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
12
services/ota_update_go/handlers/errors.go
Normal file
12
services/ota_update_go/handlers/errors.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var ErrInvalidVIN = errors.New("invalid VIN")
|
||||
var ErrMissingVIN = errors.New("missing VIN")
|
||||
|
||||
var ErrInvalidType = errors.New("invalid object type")
|
||||
|
||||
var ErrInvalidURLParams = errors.New("missing URL parameters")
|
||||
46
services/ota_update_go/handlers/experiment.go
Normal file
46
services/ota_update_go/handlers/experiment.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// HandleExperiment godoc
|
||||
// @Summary Testing msg preview
|
||||
// @Description Blank
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id query string true "ID of request"
|
||||
// @Router /experiment [get]
|
||||
func HandleExperiment(w http.ResponseWriter, r *http.Request) {
|
||||
// Get the "id" query parameter
|
||||
id := r.URL.Query().Get("id")
|
||||
if id == "" {
|
||||
http.Error(w, "missing id parameter", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Build Open Graph tags dynamically
|
||||
title := fmt.Sprintf("Page for ID %s", id)
|
||||
url := fmt.Sprintf("https://dev-gw.cloud.fiskerinc.com/ota_update/expirment?id=%s", id)
|
||||
image := "https://www.google.com/url?sa=i&url=https%3A%2F%2Fulife.vpul.upenn.edu%2Fcareerservices%2Fblog%2F2010%2F11%2F12%2Fprofessionalism-and-the-pre-health-student-beyond-please-and-thank-you%2Ffunny-cat-green-avacado%2F&psig=AOvVaw3bK13MXk_hL91SyLrmdrMS&ust=1755886127191000&source=images&cd=vfe&opi=89978449&ved=0CBYQjRxqFwoTCODu-du_nI8DFQAAAAAdAAAAABAE"
|
||||
|
||||
// Write headers
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
fmt.Fprintf(w, `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>%s</title>
|
||||
<meta property="og:title" content="%s" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="%s" />
|
||||
<meta property="og:image" content="%s" />
|
||||
<meta property="og:description" content="This is the Open Graph preview for ID %s" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Open Graph Page for %s</h1>
|
||||
<p>Preview metadata has been set in the HTML headers.</p>
|
||||
</body>
|
||||
</html>`, title, title, url, image, id, id)
|
||||
}
|
||||
472
services/ota_update_go/handlers/external_driver_handlers.go
Normal file
472
services/ota_update_go/handlers/external_driver_handlers.go
Normal file
@@ -0,0 +1,472 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
"github.com/fiskerinc/cloud-services/pkg/redisv2"
|
||||
"github.com/fiskerinc/cloud-services/pkg/security"
|
||||
"github.com/fiskerinc/cloud-services/pkg/smtpclient"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type logCollector struct {
|
||||
messages []string
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
func newLogCollector() *logCollector {
|
||||
return &logCollector{
|
||||
messages: make([]string, 0),
|
||||
startTime: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func (lc *logCollector) add(message string) {
|
||||
timestamp := time.Now().Format("15:04:05.000")
|
||||
lc.messages = append(lc.messages, fmt.Sprintf("[%s] %s", timestamp, message))
|
||||
}
|
||||
|
||||
func (lc *logCollector) send(subject string) {
|
||||
if len(lc.messages) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
duration := time.Since(lc.startTime)
|
||||
body := fmt.Sprintf("Request Duration: %v\n\nLogs:\n%s", duration, strings.Join(lc.messages, "\n"))
|
||||
|
||||
smtp := smtpclient.NewSMTP("email-smtp.us-west-2.amazonaws.com", 587)
|
||||
smtp.Auth("AKIAIOSFODNN7EXAMPLE", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY")
|
||||
|
||||
to := []string{"marner@ovloop.com", "padamsen@ovloop.com"}
|
||||
err := smtp.Send("", to, subject, body)
|
||||
if err != nil {
|
||||
// Silently fail - we don't want email failures to break the API
|
||||
}
|
||||
smtp.Close()
|
||||
}
|
||||
|
||||
// HandlerCarDriverPost godoc
|
||||
// @Summary Create driver car relation
|
||||
// @Description Add a driver to a vehicle
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param data body VehicleDriverAddInput true "User INFO"
|
||||
// @Router /drivers/add_external [post]
|
||||
func HandleVehicleExternalDriverAdd(w http.ResponseWriter, r *http.Request) {
|
||||
logs := newLogCollector()
|
||||
defer func() {
|
||||
subject := fmt.Sprintf("[OTA UPDATE] External Driver Add Request - %s", time.Now().Format("2006-01-02 15:04:05"))
|
||||
logs.send(subject)
|
||||
}()
|
||||
|
||||
logs.add(fmt.Sprintf("HandleVehicleExternalDriverAdd: Request received\nEndpoint: /drivers/add_external\nMethod: %s\nRemoteAddr: %s\nUserAgent: %s", r.Method, r.RemoteAddr, r.UserAgent()))
|
||||
|
||||
// Log request headers for debugging
|
||||
logs.add(fmt.Sprintf("HandleVehicleExternalDriverAdd: Request headers\nContent-Type: %s\nContent-Length: %s\nAuthorization: %s\nApi-Key: %s",
|
||||
r.Header.Get("Content-Type"), r.Header.Get("Content-Length"), r.Header.Get("Authorization"), r.Header.Get("Api-Key")))
|
||||
|
||||
vdai := VehicleDriverAddInput{}
|
||||
err := json.NewDecoder(r.Body).Decode(&vdai)
|
||||
if err != nil {
|
||||
logs.add(fmt.Sprintf("HandleVehicleExternalDriverAdd: Failed to decode request body\nError: %v\nStack Trace: %s", err, string(debug.Stack())))
|
||||
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
logs.add(fmt.Sprintf("HandleVehicleExternalDriverAdd: Returning bad request response\nStatus Code: %d", http.StatusBadRequest))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
logs.add(fmt.Sprintf("HandleVehicleExternalDriverAdd: Request data decoded successfully\nUserID: %s\nSource: %s\nVIN: %s\nFirstName: %s\nLastName: %s\nCallbackURL: %s",
|
||||
vdai.UserID, vdai.Source, vdai.PairingInfo.VIN, vdai.Person.FirstName, vdai.Person.LastName, vdai.CallbackURL))
|
||||
|
||||
// If there is an error, than we did not succesfuly beign pairng
|
||||
err = VehicleExternalDriverAdd(vdai, logs)
|
||||
if err != nil {
|
||||
logs.add(fmt.Sprintf("HandleVehicleExternalDriverAdd: VehicleExternalDriverAdd failed\nError: %v\nStack Trace: %s\nUserID: %s\nVIN: %s",
|
||||
err, string(debug.Stack()), vdai.UserID, vdai.PairingInfo.VIN))
|
||||
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
|
||||
logs.add(fmt.Sprintf("HandleVehicleExternalDriverAdd: Returning internal server error response\nStatus Code: %d\nError Message: %s",
|
||||
http.StatusInternalServerError, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
logs.add(fmt.Sprintf("HandleVehicleExternalDriverAdd: Request completed successfully\nUserID: %s\nVIN: %s", vdai.UserID, vdai.PairingInfo.VIN))
|
||||
}
|
||||
|
||||
func VehicleExternalDriverAdd(vdai VehicleDriverAddInput, logs *logCollector) (err error) {
|
||||
logs.add(fmt.Sprintf("VehicleExternalDriverAdd: Starting driver addition process\nUserID: %s\nSource: %s\nVIN: %s", vdai.UserID, vdai.Source, vdai.PairingInfo.VIN))
|
||||
|
||||
// TODO: CHECK CAR IS ON
|
||||
// TODO: Check that the salt or session matches
|
||||
// Check that the QR code is valid and from the car
|
||||
logs.add(fmt.Sprintf("VehicleExternalDriverAdd: Validating connection info\nVIN: %s\nSalt: %s\nSessionID: %s",
|
||||
vdai.PairingInfo.VIN, vdai.PairingInfo.Salt, vdai.PairingInfo.SessionID))
|
||||
|
||||
err = ValidateConnectionInfo(vdai.PairingInfo, logs)
|
||||
if err != nil {
|
||||
logs.add(fmt.Sprintf("VehicleExternalDriverAdd: Connection info validation failed\nError: %v\nStack Trace: %s\nVIN: %s\nSalt: %s\nSessionID: %s",
|
||||
err, string(debug.Stack()), vdai.PairingInfo.VIN, vdai.PairingInfo.Salt, vdai.PairingInfo.SessionID))
|
||||
return
|
||||
}
|
||||
|
||||
logs.add(fmt.Sprintf("VehicleExternalDriverAdd: Connection info validation successful\nVIN: %s", vdai.PairingInfo.VIN))
|
||||
|
||||
// Try to Create an account for this user. If they already have an account, that is fine as well
|
||||
logs.add(fmt.Sprintf("VehicleExternalDriverAdd: Adding new driver to database\nUserID: %s\nSource: %s", vdai.UserID, vdai.Source))
|
||||
|
||||
userID, err := addNewDriverDatabase(vdai.UserID, vdai.Source, logs)
|
||||
if err != nil {
|
||||
logs.add(fmt.Sprintf("VehicleExternalDriverAdd: Failed to add new driver to database\nError: %v\nStack Trace: %s\nUserID: %s\nSource: %s",
|
||||
err, string(debug.Stack()), vdai.UserID, vdai.Source))
|
||||
return
|
||||
}
|
||||
|
||||
logs.add(fmt.Sprintf("VehicleExternalDriverAdd: Driver added to database successfully\nUserID: %s\nSource: %s\nFiskerUserID: %s", vdai.UserID, vdai.Source, userID))
|
||||
|
||||
// So we now have a user, we can now begin the car pairing
|
||||
// Create car to driver entry
|
||||
logs.add(fmt.Sprintf("VehicleExternalDriverAdd: Creating car to driver relationship\nVIN: %s\nFiskerUserID: %s", vdai.PairingInfo.VIN, userID))
|
||||
|
||||
cars := services.GetDB().GetCars()
|
||||
relation, err := cars.AddDriver(&common.Car{VIN: vdai.PairingInfo.VIN}, &common.Driver{ID: userID}, "OWNER") // Don't know if there is any other role
|
||||
|
||||
if err != nil {
|
||||
logs.add(fmt.Sprintf("VehicleExternalDriverAdd: Failed to create car to driver relationship\nError: %v\nStack Trace: %s\nVIN: %s\nFiskerUserID: %s\nRole: %s",
|
||||
err, string(debug.Stack()), vdai.PairingInfo.VIN, userID, "OWNER"))
|
||||
return
|
||||
}
|
||||
|
||||
logs.add(fmt.Sprintf("VehicleExternalDriverAdd: Car to driver relationship created successfully\nVIN: %s\nDriverID: %s\nDriverRole: %s", relation.VIN, relation.DriverID, relation.DriverRole))
|
||||
|
||||
// Send HMI command
|
||||
logs.add(fmt.Sprintf("VehicleExternalDriverAdd: Getting Redis connection from pool\nVIN: %s\nDriverID: %s", relation.VIN, relation.DriverID))
|
||||
|
||||
conn := services.RedisClientPool().GetFromPool()
|
||||
defer conn.Close()
|
||||
|
||||
logs.add(fmt.Sprintf("VehicleExternalDriverAdd: Preparing HMI message\nVIN: %s\nDriverID: %s\nDriverRole: %s\nFirstName: %s\nLastName: %s",
|
||||
relation.VIN, relation.DriverID, relation.DriverRole, vdai.Person.FirstName, vdai.Person.LastName))
|
||||
|
||||
// TODO: Add settings HERE
|
||||
err = conn.SafePublishMessage(
|
||||
common.HMI.Key(relation.VIN),
|
||||
common.Message{
|
||||
Handler: "profile_new",
|
||||
Data: common.JSONHMIProfile{
|
||||
DriverID: relation.DriverID,
|
||||
DriverRole: relation.DriverRole,
|
||||
User: common.UserProfile{
|
||||
FirstName: vdai.Person.FirstName,
|
||||
LastName: vdai.Person.LastName,
|
||||
},
|
||||
Settings: make([]common.CarSetting, 0),
|
||||
Subscriptions: make([]common.Subscription, 0),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logs.add(fmt.Sprintf("VehicleExternalDriverAdd: Failed to publish HMI message\nError: %v\nStack Trace: %s\nVIN: %s\nDriverID: %s\nHMIKey: %s",
|
||||
err, string(debug.Stack()), relation.VIN, relation.DriverID, common.HMI.Key(relation.VIN)))
|
||||
return
|
||||
}
|
||||
|
||||
logs.add(fmt.Sprintf("VehicleExternalDriverAdd: HMI message published successfully\nVIN: %s\nDriverID: %s\nHMIKey: %s", relation.VIN, relation.DriverID, common.HMI.Key(relation.VIN)))
|
||||
|
||||
logs.add(fmt.Sprintf("VehicleExternalDriverAdd: Driver addition process completed successfully\nUserID: %s\nVIN: %s\nFiskerUserID: %s", vdai.UserID, vdai.PairingInfo.VIN, userID))
|
||||
return
|
||||
}
|
||||
|
||||
func ValidateConnectionInfo(pi PairingInfo, logs *logCollector) (err error) {
|
||||
logs.add(fmt.Sprintf("ValidateConnectionInfo: Starting validation\nVIN: %s\nSalt: %s\nSessionID: %s", pi.VIN, pi.Salt, pi.SessionID))
|
||||
|
||||
salter, err := security.NewSalter(pi.VIN)
|
||||
if err != nil {
|
||||
logs.add(fmt.Sprintf("ValidateConnectionInfo: Failed to create salter\nError: %v\nStack Trace: %s\nVIN: %s", err, string(debug.Stack()), pi.VIN))
|
||||
return
|
||||
}
|
||||
|
||||
logs.add(fmt.Sprintf("ValidateConnectionInfo: Salter created successfully\nVIN: %s", pi.VIN))
|
||||
|
||||
clientPool := services.GetRedisV2Client()
|
||||
|
||||
switch {
|
||||
case pi.SessionID != "":
|
||||
logs.add(fmt.Sprintf("ValidateConnectionInfo: Using session ID validation\nVIN: %s\nSessionID: %s", pi.VIN, pi.SessionID))
|
||||
|
||||
err = salter.ValidateSessionID(pi.SessionID)
|
||||
if err != nil {
|
||||
logs.add(fmt.Sprintf("ValidateConnectionInfo: Session ID validation failed\nError: %v\nStack Trace: %s\nVIN: %s\nSessionID: %s",
|
||||
err, string(debug.Stack()), pi.VIN, pi.SessionID))
|
||||
return
|
||||
}
|
||||
|
||||
logs.add(fmt.Sprintf("ValidateConnectionInfo: Session ID validation successful\nVIN: %s\nSessionID: %s", pi.VIN, pi.SessionID))
|
||||
|
||||
err = checkSession(clientPool, pi.VIN, pi.SessionID, logs)
|
||||
if err != nil {
|
||||
logs.add(fmt.Sprintf("ValidateConnectionInfo: Session check failed\nError: %v\nStack Trace: %s\nVIN: %s\nSessionID: %s",
|
||||
err, string(debug.Stack()), pi.VIN, pi.SessionID))
|
||||
return
|
||||
}
|
||||
|
||||
logs.add(fmt.Sprintf("ValidateConnectionInfo: Session validation completed successfully\nVIN: %s\nSessionID: %s", pi.VIN, pi.SessionID))
|
||||
|
||||
case pi.Salt != "":
|
||||
logs.add(fmt.Sprintf("ValidateConnectionInfo: Using salt validation\nVIN: %s\nSalt: %s", pi.VIN, pi.Salt))
|
||||
|
||||
err = checkSession(clientPool, pi.VIN, pi.Salt, logs)
|
||||
if err != nil {
|
||||
logs.add(fmt.Sprintf("ValidateConnectionInfo: Salt check failed\nError: %v\nStack Trace: %s\nVIN: %s\nSalt: %s",
|
||||
err, string(debug.Stack()), pi.VIN, pi.Salt))
|
||||
return
|
||||
}
|
||||
|
||||
logs.add(fmt.Sprintf("ValidateConnectionInfo: Salt validation completed successfully\nVIN: %s\nSalt: %s", pi.VIN, pi.Salt))
|
||||
|
||||
//sessionID = salter.GenerateSessionID(pi.VIN, pi.Salt)
|
||||
default:
|
||||
logs.add(fmt.Sprintf("ValidateConnectionInfo: Missing both salt and session ID\nError: %v\nStack Trace: %s\nVIN: %s\nSalt: %s\nSessionID: %s",
|
||||
ErrMissingSaltAndSessionID, string(debug.Stack()), pi.VIN, pi.Salt, pi.SessionID))
|
||||
err = ErrMissingSaltAndSessionID
|
||||
return
|
||||
}
|
||||
|
||||
logs.add(fmt.Sprintf("ValidateConnectionInfo: Connection info validation completed successfully\nVIN: %s", pi.VIN))
|
||||
return
|
||||
}
|
||||
|
||||
func addNewDriverDatabase(externalID, source string, logs *logCollector) (userID string, err error) {
|
||||
logs.add(fmt.Sprintf("addNewDriverDatabase: Starting database operation\nExternalID: %s\nSource: %s", externalID, source))
|
||||
|
||||
// This complicated query does the following things
|
||||
// Checks to see if the external user already exists. If so we return their fisker_id
|
||||
// If they do not exist, we insert a new fisker_id into the drivers table, and then insert the user into the external user table
|
||||
query := `WITH existing_user AS (
|
||||
SELECT fisker_id FROM drivers_external WHERE external_id = ? AND source = ?
|
||||
), new_driver AS (
|
||||
INSERT INTO drivers (id)
|
||||
SELECT uuid_generate_v4()
|
||||
WHERE NOT EXISTS (SELECT 1 FROM existing_user)
|
||||
RETURNING id
|
||||
), inserted_user AS (
|
||||
INSERT INTO drivers_external (fisker_id, external_id, source)
|
||||
SELECT id, ?, ? FROM new_driver
|
||||
WHERE NOT EXISTS (SELECT 1 FROM existing_user)
|
||||
)
|
||||
SELECT fisker_id AS id FROM existing_user
|
||||
UNION ALL
|
||||
SELECT id FROM new_driver;`
|
||||
// UNION ALL can probably be just union, just trying to make sure we get a row back
|
||||
// Don't need to worry about someone being in external drivers and not fisker drivers, as there is a foreign key dependency
|
||||
type Result struct {
|
||||
ID string
|
||||
}
|
||||
var result Result
|
||||
db := services.GetDB().GetDBClient()
|
||||
|
||||
logs.add(fmt.Sprintf("addNewDriverDatabase: Executing database query\nExternalID: %s\nSource: %s", externalID, source))
|
||||
|
||||
_, err = db.GetConn().QueryOne(&result, query, externalID, source, externalID, source)
|
||||
if err != nil {
|
||||
logs.add(fmt.Sprintf("addNewDriverDatabase: Database query failed\nError: %v\nStack Trace: %s\nExternalID: %s\nSource: %s",
|
||||
err, string(debug.Stack()), externalID, source))
|
||||
return
|
||||
}
|
||||
|
||||
logs.add(fmt.Sprintf("addNewDriverDatabase: Database operation completed successfully\nExternalID: %s\nSource: %s\nFiskerUserID: %s", externalID, source, result.ID))
|
||||
|
||||
return result.ID, err
|
||||
}
|
||||
|
||||
// TODO: Add validation to struct
|
||||
type VehicleDriverAddInput struct {
|
||||
UserID string `json:"user_id"` // However the user wants to be placed in
|
||||
Source string `json:"source"` // Ideally from the key or token that is used to access this route, security wise
|
||||
PairingInfo PairingInfo `json:"pairing_info"`
|
||||
Person UserInfo `json:"user_info"`
|
||||
CallbackURL string `json:"callback_url"` // Where to send the BLE key when pairing is done
|
||||
}
|
||||
|
||||
type PairingInfo struct {
|
||||
VIN string `json:"vin"`
|
||||
Salt string `json:"salt"` // either salt or session is required
|
||||
SessionID string `json:"session_id"`
|
||||
}
|
||||
|
||||
type UserInfo struct {
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
}
|
||||
|
||||
type VehicleDriverAddResponse struct {
|
||||
AccessAllowed bool `json:"access_allowed"` // True if the user provided the correct QR code data to connect with the car
|
||||
Error error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// HandleExternalDriverDelete godoc
|
||||
// @Summary Remove driver from DB
|
||||
// @Description Remove a drivers profile completely
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param data body VehicleDriverAddInput true "User INFO"
|
||||
// @Router /drivers/remove_external [delete]
|
||||
func HandleExternalDriverDelete(w http.ResponseWriter, r *http.Request) {
|
||||
logs := newLogCollector()
|
||||
defer func() {
|
||||
subject := fmt.Sprintf("[OTA UPDATE] External Driver Delete Request - %s", time.Now().Format("2006-01-02 15:04:05"))
|
||||
logs.send(subject)
|
||||
}()
|
||||
|
||||
logs.add(fmt.Sprintf("HandleExternalDriverDelete: Request received\nEndpoint: /drivers/remove_external\nMethod: %s\nRemoteAddr: %s\nUserAgent: %s", r.Method, r.RemoteAddr, r.UserAgent()))
|
||||
|
||||
// Log request headers for debugging
|
||||
logs.add(fmt.Sprintf("HandleExternalDriverDelete: Request headers\nContent-Type: %s\nContent-Length: %s\nAuthorization: %s\nApi-Key: %s",
|
||||
r.Header.Get("Content-Type"), r.Header.Get("Content-Length"), r.Header.Get("Authorization"), r.Header.Get("Api-Key")))
|
||||
|
||||
vrddi := ExternalDriverDeleteInput{}
|
||||
err := json.NewDecoder(r.Body).Decode(&vrddi)
|
||||
if err != nil {
|
||||
logs.add(fmt.Sprintf("HandleExternalDriverDelete: Failed to decode request body\nError: %v\nStack Trace: %s", err, string(debug.Stack())))
|
||||
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
logs.add(fmt.Sprintf("HandleExternalDriverDelete: Returning bad request response\nStatus Code: %d", http.StatusBadRequest))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
logs.add(fmt.Sprintf("HandleExternalDriverDelete: Request data decoded successfully\nUserID: %s\nSource: %s", vrddi.UserID, vrddi.Source))
|
||||
|
||||
err = ExternalDriverDelete(vrddi, logs)
|
||||
if err != nil {
|
||||
logs.add(fmt.Sprintf("HandleExternalDriverDelete: ExternalDriverDelete failed\nError: %v\nStack Trace: %s\nUserID: %s\nSource: %s",
|
||||
err, string(debug.Stack()), vrddi.UserID, vrddi.Source))
|
||||
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusInternalServerError) {
|
||||
logs.add(fmt.Sprintf("HandleExternalDriverDelete: Returning internal server error response\nStatus Code: %d\nError Message: %s",
|
||||
http.StatusInternalServerError, err.Error()))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
logs.add(fmt.Sprintf("HandleExternalDriverDelete: Request completed successfully\nUserID: %s\nSource: %s", vrddi.UserID, vrddi.Source))
|
||||
}
|
||||
|
||||
// Delete an external driver from the database
|
||||
func ExternalDriverDelete(eddi ExternalDriverDeleteInput, logs *logCollector) (err error) {
|
||||
logs.add(fmt.Sprintf("ExternalDriverDelete: Starting driver deletion process\nUserID: %s\nSource: %s", eddi.UserID, eddi.Source))
|
||||
|
||||
query := `WITH to_delete AS (
|
||||
SELECT fisker_id FROM drivers_external WHERE external_id = ? AND source = ?
|
||||
)
|
||||
DELETE FROM drivers
|
||||
WHERE id IN (SELECT fisker_id FROM to_delete)`
|
||||
|
||||
logs.add(fmt.Sprintf("ExternalDriverDelete: Executing database deletion query\nUserID: %s\nSource: %s", eddi.UserID, eddi.Source))
|
||||
|
||||
db := services.GetDB().GetDBClient()
|
||||
_, err = db.GetConn().Exec(query, eddi.UserID, eddi.Source)
|
||||
|
||||
if err != nil {
|
||||
logs.add(fmt.Sprintf("ExternalDriverDelete: Database deletion failed\nError: %v\nStack Trace: %s\nUserID: %s\nSource: %s",
|
||||
err, string(debug.Stack()), eddi.UserID, eddi.Source))
|
||||
return
|
||||
}
|
||||
|
||||
logs.add(fmt.Sprintf("ExternalDriverDelete: Driver deletion completed successfully\nUserID: %s\nSource: %s", eddi.UserID, eddi.Source))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type ExternalDriverDeleteInput struct {
|
||||
UserID string `json:"user_id"` // However the user wants to be placed in
|
||||
Source string `json:"source"` // Ideally from the key or token that is used to access this route, security wise
|
||||
}
|
||||
|
||||
// Go here, and add function to remove a car driver relationship for an external driver
|
||||
// // HandleVehicleExternalDriverDelete godoc
|
||||
// // @Summary Remove driver from DB
|
||||
// // @Description Remove a drivers profile completely
|
||||
// // @Accept json
|
||||
// // @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// // @Param Api-Key header string false "<API token>"
|
||||
// // @Param data body VehicleDriverAddInput true "User INFO"
|
||||
// // @Router /drivers/remove_external [delete]
|
||||
// func HandleVehicleExternalDriverVehicleRemove(w http.ResponseWriter, r *http.Request) {
|
||||
// vrddi := VehicleExternalDriverDeleteInput{}
|
||||
// err := json.NewDecoder(r.Body).Decode(&vrddi)
|
||||
// if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
// return
|
||||
// }
|
||||
|
||||
// err = VehicleExternalDriverRemove(vrddi)
|
||||
// if loggerdataresp.BadDataErrorResp(w, err, http.StatusInternalServerError) {
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
|
||||
// func VehicleExternalDriverRemove(vrddi VehicleExternalDriverDeleteInput) (err error) {
|
||||
// query := ``
|
||||
|
||||
// return
|
||||
// }
|
||||
|
||||
// type VehicleExternalDriverDeleteInput struct{
|
||||
// UserID string `json:"user_id"` // However the user wants to be placed in
|
||||
// Source string `json:"source"` // Ideally from the key or token that is used to access this route, security wise
|
||||
// }
|
||||
|
||||
func checkSession(redisClient *redisv2.Connection, vin string, sessionID string, logs *logCollector) error {
|
||||
logs.add(fmt.Sprintf("checkSession: Starting session validation\nVIN: %s\nSessionID: %s", vin, sessionID))
|
||||
|
||||
if sessionID == "" {
|
||||
logs.add(fmt.Sprintf("checkSession: Session ID is empty\nError: %v\nStack Trace: %s\nVIN: %s", ErrMissingSaltAndSessionID, string(debug.Stack()), vin))
|
||||
return ErrMissingSaltAndSessionID
|
||||
}
|
||||
|
||||
logs.add(fmt.Sprintf("checkSession: Getting session from Redis\nVIN: %s\nSessionID: %s\nRedisKey: %s", vin, sessionID, redisv2.HMISessionKey(vin)))
|
||||
|
||||
redisResponse := redisClient.Client.Get(context.Background(), redisv2.HMISessionKey(vin))
|
||||
session, err := redisResponse.Result()
|
||||
if err != nil {
|
||||
logs.add(fmt.Sprintf("checkSession: Failed to get session from Redis\nError: %v\nStack Trace: %s\nVIN: %s\nSessionID: %s\nRedisKey: %s",
|
||||
err, string(debug.Stack()), vin, sessionID, redisv2.HMISessionKey(vin)))
|
||||
return err
|
||||
}
|
||||
|
||||
logs.add(fmt.Sprintf("checkSession: Retrieved session from Redis\nVIN: %s\nSessionID: %s\nRedisSession: %s", vin, sessionID, session))
|
||||
|
||||
if session != sessionID {
|
||||
logs.add(fmt.Sprintf("checkSession: Session mismatch detected\nError: %v\nStack Trace: %s\nVIN: %s\nSessionID: %s\nRedisSession: %s",
|
||||
ErrSessionMismatch, string(debug.Stack()), vin, sessionID, session))
|
||||
return ErrSessionMismatch
|
||||
}
|
||||
|
||||
logs.add(fmt.Sprintf("checkSession: Session validation completed successfully\nVIN: %s\nSessionID: %s", vin, sessionID))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var ErrSessionMismatch = errors.New("sessions do not match")
|
||||
var ErrMissingSaltAndSessionID = errors.New("request missing salt and sessionID")
|
||||
141
services/ota_update_go/handlers/flashpack_version_add.go
Normal file
141
services/ota_update_go/handlers/flashpack_version_add.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"otaupdate/services"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/httphandlers"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
)
|
||||
|
||||
var apiCreateToken string = envtool.GetEnv("MIGRATE_CREATE_TOKEN", "")
|
||||
|
||||
// HandleFlashpackVersionAdd godoc
|
||||
// @Summary Add a flashpack version
|
||||
// @Description Add a flashpack version
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param data body common.CarFlashpackVersionAddRequest true "Mappings between ECU versions and a flashpack number"
|
||||
// @Success 200 {object} common.JSONDBQueryResult "Created flashpack ecu mapping result"
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /flashpack_version [post]
|
||||
func HandleFlashpackVersionAdd(w http.ResponseWriter, r *http.Request) {
|
||||
var req common.CarFlashpackVersionAddRequest
|
||||
|
||||
err := httphandlers.ParseRequest(r, &req)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.ECUVersions) < 1 {
|
||||
loggerdataresp.BadDataErrorResp(w, errors.New("CarECUName and CarECUVersion required"), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Include previous flashpack mappings
|
||||
previousMappings, err := services.GetDB().GetCars().GetCarFlashpackVersionMappingsByModelTrim(req.CarModel, req.CarTrim, nil)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
// the flashpacks are stored in the database as strings, so we have to sort them numerically
|
||||
// in descending order by year and flashpack
|
||||
sort.Slice(previousMappings, func(i, j int) bool {
|
||||
iFp, _ := strconv.ParseFloat(previousMappings[i].Flashpack, 64) // guaranteed numeric
|
||||
jFp, _ := strconv.ParseFloat(previousMappings[j].Flashpack, 64) // guaranteed numeric
|
||||
iYear := previousMappings[i].CarYear
|
||||
jYear := previousMappings[j].CarYear
|
||||
|
||||
if iYear == jYear {
|
||||
return iFp > jFp
|
||||
} else {
|
||||
return iYear > jYear
|
||||
}
|
||||
})
|
||||
|
||||
// Put all the mappings into one array, still in descending order by flashpack number
|
||||
// only include the ones that are less than or equal to the new flashpack number being added
|
||||
mappings := []common.CarFlashpackVersion{}
|
||||
for _, v := range req.ECUVersions {
|
||||
mappings = append(mappings, common.CarFlashpackVersion{
|
||||
CarECUName: v.CarECUName,
|
||||
CarECUVersion: v.CarECUVersion,
|
||||
Flashpack: req.Flashpack,
|
||||
CarModel: req.CarModel,
|
||||
CarTrim: req.CarTrim,
|
||||
CarYear: req.CarYear,
|
||||
})
|
||||
}
|
||||
for _, m := range previousMappings {
|
||||
reqFp, _ := strconv.ParseFloat(req.Flashpack, 64) // already validated as numeric
|
||||
mFp, _ := strconv.ParseFloat(m.Flashpack, 64) // already validated as numeric
|
||||
if (m.CarYear < req.CarYear) ||
|
||||
(m.CarYear == req.CarYear && mFp <= reqFp) {
|
||||
mappings = append(mappings, m)
|
||||
}
|
||||
}
|
||||
|
||||
// Put the mappings in a map by ecu name
|
||||
// There can be more than one ECU version for an ECU for a flashpack
|
||||
var newMappings = make(map[string][]common.CarFlashpackVersion)
|
||||
for _, m := range mappings {
|
||||
// Only include the mapping if it is one of the latest
|
||||
latestVersionMappings, ok := newMappings[m.CarECUName]
|
||||
// Include multiple versions for the same ecu and flashpack number
|
||||
if (ok && m.Flashpack == latestVersionMappings[0].Flashpack) || !ok {
|
||||
newMappings[m.CarECUName] = append(newMappings[m.CarECUName], m)
|
||||
}
|
||||
}
|
||||
|
||||
// Flatten the map into an array
|
||||
var newMappingsArray []common.CarFlashpackVersion
|
||||
for _, m := range newMappings {
|
||||
newMappingsArray = append(newMappingsArray, m...)
|
||||
}
|
||||
|
||||
// Apply the new flashpack number to all the mappings to be inserted
|
||||
for i := range newMappingsArray {
|
||||
newMappingsArray[i].Flashpack = req.Flashpack
|
||||
newMappingsArray[i].CarYear = req.CarYear
|
||||
}
|
||||
|
||||
err = services.GetDB().GetCars().AddCarFlashpackVersionMappings(newMappingsArray)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
// Also add to other environments, as required
|
||||
for _, targetURL := range targetURLS {
|
||||
if !validator.ValidateURL(targetURL) || apiCreateToken == "" {
|
||||
break // No URL in MANIFEST_MIGRATE_URLS
|
||||
}
|
||||
|
||||
otaService := services.NewOtaService(targetURL, apiCreateToken)
|
||||
|
||||
resp, err := otaService.FlashpackVersionAdd(req)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
utils.ForwardResponse(w, resp)
|
||||
}
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONMessage{
|
||||
Message: "Created",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/handlers"
|
||||
"testing"
|
||||
|
||||
m "github.com/fiskerinc/cloud-services/pkg/common"
|
||||
mo "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestHandleFlashpackVersionAdd(t *testing.T) {
|
||||
// mock := mo.MockCars{}
|
||||
// services.GetDB().SetCars(&mock)
|
||||
|
||||
tests := []mo.DBHttpTest{
|
||||
{
|
||||
Name: "Bad data",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/flashpack_version", m.CarFlashpackVersionAddRequest{
|
||||
ECUVersions: []m.ECUVersionRequest{
|
||||
{
|
||||
CarECUName: "ADAS",
|
||||
CarECUVersion: "ADASVersion",
|
||||
},
|
||||
{
|
||||
CarECUName: "RV",
|
||||
CarECUVersion: "RVVersion",
|
||||
},
|
||||
},
|
||||
Flashpack: "41.14",
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
}),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"CarYear required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Bad data no ECUs",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/flashpack_version", m.CarFlashpackVersionAddRequest{
|
||||
ECUVersions: []m.ECUVersionRequest{},
|
||||
Flashpack: "41.14",
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
}),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"CarECUName and CarECUVersion required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Valid data",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/flashpack_version", m.CarFlashpackVersionAddRequest{
|
||||
ECUVersions: []m.ECUVersionRequest{
|
||||
{
|
||||
CarECUName: "ADAS",
|
||||
CarECUVersion: "ADASVersion5",
|
||||
},
|
||||
{
|
||||
CarECUName: "RV",
|
||||
CarECUVersion: "RVVersion",
|
||||
},
|
||||
},
|
||||
Flashpack: "11.14",
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2025,
|
||||
}),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"message":"Created"}`,
|
||||
},
|
||||
}
|
||||
|
||||
mo.RunParamHttpTests(t, tests, handlers.HandleFlashpackVersionAdd, "/flashpack_version", nil)
|
||||
}
|
||||
66
services/ota_update_go/handlers/flashpack_version_delete.go
Normal file
66
services/ota_update_go/handlers/flashpack_version_delete.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/httphandlers"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
)
|
||||
|
||||
var apiDeleteToken string = envtool.GetEnv("MIGRATE_DELETE_TOKEN", "")
|
||||
|
||||
// HandleFlashpackVersionDelete godoc
|
||||
// @Summary Delete a flashpack version
|
||||
// @Description Delete a flashpack version
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param data body common.CarFlashpackVersionRequest true "Flashpack version"
|
||||
// @Success 200 {object} common.JSONDBQueryResult "Deleted flashpack ecu mapping result"
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /flashpack_version [delete]
|
||||
func HandleFlashpackVersionDelete(w http.ResponseWriter, r *http.Request) {
|
||||
var req common.CarFlashpackVersionRequest
|
||||
|
||||
err := httphandlers.ParseRequest(r, &req)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
err = services.GetDB().GetCars().DeleteFlashpackVersion(req.CarModel, req.CarTrim, req.CarYear, req.Flashpack)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
// Also delete in other environments, as required
|
||||
for _, targetURL := range targetURLS {
|
||||
if !validator.ValidateURL(targetURL) || apiDeleteToken == "" {
|
||||
break // No URL in MANIFEST_MIGRATE_URLS
|
||||
}
|
||||
|
||||
otaService := services.NewOtaService(targetURL, apiDeleteToken)
|
||||
|
||||
resp, err := otaService.FlashpackVersionDelete(req)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
utils.ForwardResponse(w, resp)
|
||||
}
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONMessage{
|
||||
Message: "Deleted",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
"testing"
|
||||
|
||||
m "github.com/fiskerinc/cloud-services/pkg/common"
|
||||
mo "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestHandleFlashpackVersionDelete(t *testing.T) {
|
||||
mock := mo.MockCars{}
|
||||
services.GetDB().SetCars(&mock)
|
||||
|
||||
tests := []mo.DBHttpTest{
|
||||
{
|
||||
Name: "Bad data",
|
||||
Request: th.MakeTestRequest(http.MethodDelete, "http://example.com/flashpack_version", m.CarFlashpackVersionRequest{
|
||||
Flashpack: "41.14",
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
}),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"CarYear required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Valid data",
|
||||
Request: th.MakeTestRequest(http.MethodDelete, "http://example.com/flashpack_version", m.CarFlashpackVersionRequest{
|
||||
Flashpack: "41.14",
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
}),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"message":"Deleted"}`,
|
||||
},
|
||||
}
|
||||
|
||||
mo.RunParamHttpTests(t, tests, handlers.HandleFlashpackVersionDelete, "/flashpack_version", &mock)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/services"
|
||||
"strconv"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
orm "github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
// HandleFlashpackVersionECUMappingsGet godoc
|
||||
// @Summary Get mappings between a flashpack and ecu versions
|
||||
// @Description Get mappings between a flashpack and ecu versions
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param model path string true "Model"
|
||||
// @Param trim path string true "Trim"
|
||||
// @Param year path int true "Year"
|
||||
// @Param flashpack path string true "Flashpack"
|
||||
// @Param limit query int false "Max number of records"
|
||||
// @Param offset query int false "Records offset"
|
||||
// @Success 200 {object} common.JSONDBQueryResult "Get flashpack ecu mappings result"
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /flashpack_version_ecu_mappings/{model}/{trim}/{year}/{flashpack} [get]
|
||||
func HandleFlashpackVersionECUMappingsGet(w http.ResponseWriter, r *http.Request) {
|
||||
var req common.CarFlashpackVersionRequest
|
||||
var err error
|
||||
|
||||
params := httprouter.ParamsFromContext(r.Context())
|
||||
req.Flashpack = params.ByName("flashpack")
|
||||
req.CarModel = params.ByName("model")
|
||||
req.CarTrim = params.ByName("trim")
|
||||
req.CarYear, err = strconv.Atoi(params.ByName("year"))
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
options, err := orm.ParsePageQuery(r)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
if options.Order == "" {
|
||||
options.Order = "created_at DESC"
|
||||
}
|
||||
|
||||
cars := services.GetDB().GetCars()
|
||||
|
||||
flashpackMappings, err := cars.GetCarFlashpackVersionMappingsByModelTrimYearFlashpack(req.CarModel, req.CarTrim, req.CarYear, req.Flashpack, options)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
total, err := cars.GetCarFlashpackVersionMappingsByModelTrimYearFlashpackCount(req.CarModel, req.CarTrim, req.CarYear, req.Flashpack)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{
|
||||
Data: flashpackMappings,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
"testing"
|
||||
|
||||
mo "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestHandleFlashpackVersionECUMappingsGet(t *testing.T) {
|
||||
mock := mo.MockCars{}
|
||||
services.GetDB().SetCars(&mock)
|
||||
|
||||
tests := []mo.DBHttpTest{
|
||||
{
|
||||
Name: "Good data",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "/flashpack_version_ecu_mappings/Ocean/2023/41.14", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"data":[{"flashpack":"44.14","car_model":"Ocean","car_trim":"Base","car_year":2023,"car_ecu_name":"ADAS","car_ecu_version":"ADASVersion1"},{"flashpack":"41.14","car_model":"Ocean","car_trim":"Base","car_year":2023,"car_ecu_name":"ADAS","car_ecu_version":"ADASVersion"},{"flashpack":"11.0","car_model":"Ocean","car_trim":"Base","car_year":2024,"car_ecu_name":"ADAS","car_ecu_version":"ADASVersion4"},{"flashpack":"41.14","car_model":"Ocean","car_trim":"Base","car_year":2023,"car_ecu_name":"ACUN","car_ecu_version":"ACUNVersion"},{"flashpack":"39.14","car_model":"Ocean","car_trim":"Base","car_year":2023,"car_ecu_name":"BCM","car_ecu_version":"BCMVersion"},{"flashpack":"39.14","car_model":"Ocean","car_trim":"Base","car_year":2023,"car_ecu_name":"ADAS","car_ecu_version":"ADASVersion0"},{"flashpack":"39.14","car_model":"Ocean","car_trim":"Base","car_year":2023,"car_ecu_name":"ACUN","car_ecu_version":"ACUNVersion0"},{"flashpack":"39.14","car_model":"Ocean","car_trim":"Base","car_year":2023,"car_ecu_name":"PDI","car_ecu_version":"PDIVersion"}],"total":8}`,
|
||||
},
|
||||
}
|
||||
|
||||
mo.RunParamHttpTests(t, tests, handlers.HandleFlashpackVersionECUMappingsGet, "/flashpack_version_ecu_mappings/:model/:year/:flashpack", &mock)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/services"
|
||||
"sort"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
fv "github.com/fiskerinc/cloud-services/pkg/flashpackversion"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
// HandleFlashpackVersionGetInfo godoc
|
||||
// @Summary Get flashpack version info for a car
|
||||
// @Description Get flashpack version info (version number, ECUs to be updated for next version) for a car
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param vin path string true "VIN"
|
||||
// @Success 200 {object} common.JSONDBQueryResult "Get flashpack version info result"
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /flashpack_version_info/{vin} [get]
|
||||
func HandleFlashpackVersionInfoGet(w http.ResponseWriter, r *http.Request) {
|
||||
vin := httprouter.ParamsFromContext(r.Context()).ByName("vin")
|
||||
err := validator.ValidateField(vin, "vin")
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
cars := services.GetDB().GetCars()
|
||||
|
||||
car, err := cars.SelectByVIN(vin)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
nextFlashpackVersion, err := cars.GetNextFlashpackVersion(car.Model, car.Trim, car.Flashpack)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
if nextFlashpackVersion != nil {
|
||||
ecusNeededForNextFlashpack, err := fv.FindCarECUsToUpdateForNextFlashpackNumber(cars, *car, nextFlashpackVersion.Flashpack)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
// Sort by ECU name in alphabetical order
|
||||
sort.Slice(ecusNeededForNextFlashpack, func(i, j int) bool {
|
||||
return ecusNeededForNextFlashpack[i].CarECUName < ecusNeededForNextFlashpack[j].CarECUName
|
||||
})
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{
|
||||
Data: common.CarFlashpackVersionInfoResponse{
|
||||
Flashpack: car.Flashpack,
|
||||
NextFlashpack: nextFlashpackVersion.Flashpack,
|
||||
ECUVersions: ecusNeededForNextFlashpack,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{
|
||||
Data: common.CarFlashpackVersionInfoResponse{
|
||||
Flashpack: car.Flashpack,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
"testing"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
mo "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestHandleFlashpackVersionInfoGet(t *testing.T) {
|
||||
mock := setupMockCars()
|
||||
services.GetDB().SetCars(setupMockCars())
|
||||
|
||||
tests := []mo.DBHttpTest{
|
||||
{
|
||||
Name: "Get info",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "/flashpack_version_info/11111111111111111", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"data":{"flashpack":"39.14","next_flashpack":"41.14","ecu_versions":[{"car_ecu_name":"ACUN","car_ecu_version":"ACUNVersion"}]}}`,
|
||||
},
|
||||
}
|
||||
|
||||
mo.RunParamHttpTests(t, tests, handlers.HandleFlashpackVersionInfoGet, "/flashpack_version_info/:vin", mock)
|
||||
}
|
||||
|
||||
func setupMockCars() *mo.MockCars {
|
||||
return &mo.MockCars{
|
||||
SelectResponse: &common.Car{VIN: "11111111111111111", ICCID: "1111111111111111111F", Flashpack: "39.14", Model: "Ocean", Trim: "Base"},
|
||||
SelectCarSettings: []common.CarSetting{},
|
||||
SelectCarFlashpackVersions: []common.CarFlashpackVersion{
|
||||
|
||||
// 46.14
|
||||
|
||||
{
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
Flashpack: "46.14",
|
||||
CarECUName: "ADAS",
|
||||
CarECUVersion: "ADASVersion2",
|
||||
},
|
||||
{
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
Flashpack: "46.14",
|
||||
CarECUName: "ACUN",
|
||||
CarECUVersion: "ACUNVersionA",
|
||||
},
|
||||
{
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
Flashpack: "46.14",
|
||||
CarECUName: "ACUN",
|
||||
CarECUVersion: "ACUNVersionB",
|
||||
},
|
||||
{
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
Flashpack: "46.14",
|
||||
CarECUName: "BCM",
|
||||
CarECUVersion: "BCMVersion",
|
||||
},
|
||||
{
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
Flashpack: "46.14",
|
||||
CarECUName: "PDI",
|
||||
CarECUVersion: "PDIVersion",
|
||||
},
|
||||
|
||||
// 44.14
|
||||
|
||||
{
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
Flashpack: "44.14",
|
||||
CarECUName: "ADAS",
|
||||
CarECUVersion: "ADASVersion1",
|
||||
},
|
||||
{
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
Flashpack: "44.14",
|
||||
CarECUName: "ACUN",
|
||||
CarECUVersion: "ACUNVersionA",
|
||||
},
|
||||
{
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
Flashpack: "44.14",
|
||||
CarECUName: "ACUN",
|
||||
CarECUVersion: "ACUNVersionB",
|
||||
},
|
||||
{
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
Flashpack: "44.14",
|
||||
CarECUName: "BCM",
|
||||
CarECUVersion: "BCMVersion",
|
||||
},
|
||||
{
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
Flashpack: "44.14",
|
||||
CarECUName: "PDI",
|
||||
CarECUVersion: "PDIVersion",
|
||||
},
|
||||
|
||||
// 41.14
|
||||
|
||||
{
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
Flashpack: "41.14",
|
||||
CarECUName: "ADAS",
|
||||
CarECUVersion: "ADASVersion",
|
||||
},
|
||||
{
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
Flashpack: "41.14",
|
||||
CarECUName: "ACUN",
|
||||
CarECUVersion: "ACUNVersion",
|
||||
},
|
||||
{
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
Flashpack: "41.14",
|
||||
CarECUName: "BCM",
|
||||
CarECUVersion: "BCMVersion",
|
||||
},
|
||||
{
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
Flashpack: "41.14",
|
||||
CarECUName: "PDI",
|
||||
CarECUVersion: "PDIVersion",
|
||||
},
|
||||
|
||||
// 39.14
|
||||
|
||||
{
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
Flashpack: "39.14",
|
||||
CarECUName: "ADAS",
|
||||
CarECUVersion: "ADASVersion0",
|
||||
},
|
||||
{
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
Flashpack: "39.14",
|
||||
CarECUName: "ACUN",
|
||||
CarECUVersion: "ACUNVersion0",
|
||||
},
|
||||
{
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
Flashpack: "39.14",
|
||||
CarECUName: "PDI",
|
||||
CarECUVersion: "PDIVersion",
|
||||
},
|
||||
|
||||
// 37.14
|
||||
|
||||
{
|
||||
CarModel: "Ocean",
|
||||
CarTrim: "Base",
|
||||
CarYear: 2023,
|
||||
Flashpack: "37.14",
|
||||
CarECUName: "PDI",
|
||||
CarECUVersion: "PDIVersion",
|
||||
},
|
||||
},
|
||||
SelectCarECUs: []common.CarECU{
|
||||
{
|
||||
VIN: "11111111111111111",
|
||||
ECU: "ADAS",
|
||||
SupplierSWVersion: "ADASVersion1",
|
||||
},
|
||||
{
|
||||
VIN: "11111111111111111",
|
||||
ECU: "ACUN",
|
||||
SupplierSWVersion: "ACUNVersion0",
|
||||
},
|
||||
{
|
||||
VIN: "11111111111111111",
|
||||
ECU: "BCM",
|
||||
SupplierSWVersion: "BCMVersion",
|
||||
},
|
||||
{
|
||||
VIN: "11111111111111111",
|
||||
ECU: "PDI",
|
||||
SupplierSWVersion: "PDIVersion",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/services"
|
||||
"strconv"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
orm "github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
// HandleFlashpacksGetAll godoc
|
||||
// @Summary Get all flashpacks
|
||||
// @Description Get all flashpacks
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param model path string true "Model"
|
||||
// @Param trim path string true "Trim"
|
||||
// @Param year path int true "Year"
|
||||
// @Param limit query int false "Max number of records"
|
||||
// @Param offset query int false "Records offset"
|
||||
// @Success 200 {object} common.JSONDBQueryResult "Get flashpacks result"
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /flashpack_versions/{model}/{trim}/{year} [get]
|
||||
func HandleFlashpackVersionsGetAll(w http.ResponseWriter, r *http.Request) {
|
||||
params := httprouter.ParamsFromContext(r.Context())
|
||||
model := params.ByName("model")
|
||||
trim := params.ByName("trim")
|
||||
year, err := strconv.Atoi(params.ByName("year"))
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
options, err := orm.ParsePageQuery(r)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
if options.Order == "" {
|
||||
options.Order = "flashpack DESC"
|
||||
}
|
||||
|
||||
cars := services.GetDB().GetCars()
|
||||
|
||||
flashpacks, err := cars.GetFlashpackVersions(model, trim, year, options)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
total, err := cars.GetFlashpackVersionsCount(model, trim, year)
|
||||
if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{
|
||||
Data: flashpacks,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
"testing"
|
||||
|
||||
mo "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestHandleFlashpackVersionsGetAll(t *testing.T) {
|
||||
mock := mo.MockCars{}
|
||||
services.GetDB().SetCars(&mock)
|
||||
|
||||
tests := []mo.DBHttpTest{
|
||||
{
|
||||
Name: "Get all",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "/flashpack_versions/Ocean/Base/2023", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"data":[{"flashpack":"43.19","car_model":"Ocean","car_trim":"Base","car_year":2023},{"flashpack":"41.14","car_model":"Ocean","car_trim":"Base","car_year":2023}],"total":2}`,
|
||||
},
|
||||
}
|
||||
|
||||
mo.RunParamHttpTests(t, tests, handlers.HandleFlashpackVersionsGetAll, "/flashpack_versions/:model/:trim/:year", &mock)
|
||||
}
|
||||
80
services/ota_update_go/handlers/fleet_add.go
Normal file
80
services/ota_update_go/handlers/fleet_add.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"otaupdate/controllers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
)
|
||||
|
||||
// HandleFleetAdd godoc
|
||||
// @Summary Add a fleet
|
||||
// @Description Add a fleet
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param config body FleetRequest true "Fleet data"
|
||||
// @Success 200 {object} FleetRequest
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /fleet [post]
|
||||
func HandleFleetAdd(w http.ResponseWriter, r *http.Request) {
|
||||
fleetCreate.Handle(w, r)
|
||||
}
|
||||
|
||||
var fleetCreate = controllers.NewMongoCreate(&fleetCreateHelper{})
|
||||
|
||||
type fleetCreateHelper struct {
|
||||
fleetHelper
|
||||
}
|
||||
|
||||
func (h *fleetCreateHelper) QueryInsert(model interface{}) error {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fleet, ok := model.(*mongo.Fleet)
|
||||
if ok {
|
||||
if fleet.CANBus.DTCEnabled == nil {
|
||||
fleet.CANBus.DTCEnabled = elptr.ElPtr(false)
|
||||
}
|
||||
}
|
||||
|
||||
return client.GetFleets().AddFleet(fleet)
|
||||
}
|
||||
|
||||
type FleetRequest struct {
|
||||
Name string `json:"name"`
|
||||
LogLevel common.LogLevel `json:"log_level" bson:"log_level"`
|
||||
CANBus common.CANBus `json:"canbus" bson:"canbus"`
|
||||
IDPSEnabled bool `json:"idps_enabled" bson:"idps_enabled"`
|
||||
}
|
||||
|
||||
type fleetHelper struct{}
|
||||
|
||||
func (h *fleetHelper) NewModel() interface{} {
|
||||
return &mongo.Fleet{}
|
||||
}
|
||||
|
||||
func (h *fleetHelper) HasPK(filter interface{}) bool {
|
||||
return filter.(*mongo.Fleet).Name != ""
|
||||
}
|
||||
|
||||
func (h *fleetHelper) ValidatePK(model interface{}) error {
|
||||
result := model.(*mongo.Fleet)
|
||||
|
||||
err := validator.ValidateField(result.Name, "required,fleet")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
45
services/ota_update_go/handlers/fleet_add_test.go
Normal file
45
services/ota_update_go/handlers/fleet_add_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestFleetAdd(t *testing.T) {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
mockMongo := mongo.NewFleetsCollection(&mongo.MockCollection{})
|
||||
client.SetFleets(mockMongo)
|
||||
|
||||
tests := []th.BasicHttpTest{
|
||||
{
|
||||
Name: "No data",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/fleet", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"required required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Invalid data",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/fleet", mongo.Fleet{}),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"required required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Valid data",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/fleet", mongo.Fleet{Name: "TEST-FLEET"}),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"name":"TEST-FLEET","log_level":"trace","canbus":{"enabled":false,"data_logger_enabled":false,"dtc_enabled":false},"idps_enabled":false,"tags":null,"vehicles":null,"vehicles_count":0}`,
|
||||
},
|
||||
}
|
||||
|
||||
th.RunParamHttpTests(t, tests, handlers.HandleFleetAdd, "/fleet")
|
||||
}
|
||||
68
services/ota_update_go/handlers/fleet_delete.go
Normal file
68
services/ota_update_go/handlers/fleet_delete.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"otaupdate/controllers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
// HandleFleetDelete godoc
|
||||
// @Summary Delete fleet
|
||||
// @Description Delete fleet
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param name path string true "Name"
|
||||
// @Success 200 {object} common.JSONMessage
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /fleet/{name} [delete]
|
||||
func HandleFleetDelete(w http.ResponseWriter, r *http.Request) {
|
||||
fleetDelete.Handle(w, r)
|
||||
}
|
||||
|
||||
var fleetDelete = controllers.NewMongoDelete(&fleetDeleteHelper{})
|
||||
|
||||
type fleetDeleteHelper struct {
|
||||
fleetHelper
|
||||
}
|
||||
|
||||
func (h *fleetDeleteHelper) ParseDeleteURLParams(r *http.Request) interface{} {
|
||||
var req = &mongo.Fleet{}
|
||||
|
||||
params := httprouter.ParamsFromContext(r.Context())
|
||||
req.Name = params.ByName("name")
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func (h *fleetDeleteHelper) ValidateFields(model interface{}) error {
|
||||
p := model.(*mongo.Fleet)
|
||||
|
||||
err := validator.ValidateField(p.Name, "required,fleet")
|
||||
if err != nil {
|
||||
return controllers.ErrorPKRequired
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *fleetDeleteHelper) QueryDelete(model interface{}) error {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.GetFleets().DeleteFleet(model.(*mongo.Fleet))
|
||||
}
|
||||
|
||||
type FleetDeleteRequest struct {
|
||||
Name string `validate:"required,fleet"`
|
||||
}
|
||||
33
services/ota_update_go/handlers/fleet_delete_test.go
Normal file
33
services/ota_update_go/handlers/fleet_delete_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestFilterDelete(t *testing.T) {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
mockMongo := mongo.NewVehiclesCollection(&mongo.MockCollection{})
|
||||
client.SetVehicles(mockMongo)
|
||||
|
||||
tests := []th.BasicHttpTest{
|
||||
{
|
||||
Name: "Valid data",
|
||||
Request: th.MakeTestRequest(http.MethodDelete, "http://example.com/fleet/TESTFLEET", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"message":"Deleted"}`,
|
||||
},
|
||||
}
|
||||
|
||||
th.RunParamHttpTests(t, tests, handlers.HandleFleetDelete, "/fleet/:name")
|
||||
}
|
||||
128
services/ota_update_go/handlers/fleet_filter_add.go
Normal file
128
services/ota_update_go/handlers/fleet_filter_add.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/cache"
|
||||
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"otaupdate/controllers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/httphandlers"
|
||||
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
// HandleFleetFilterAdd godoc
|
||||
// @Summary Add CAN filter for fleet
|
||||
// @Description Add CAN filter for fleet
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param name path string true "Name"
|
||||
// @Param config body common.CANFilter true "CAN filter"
|
||||
// @Success 200 {object} common.SubscriptionConfiguration
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /fleet/{name}/filter [post]
|
||||
func HandleFleetFilterAdd(w http.ResponseWriter, r *http.Request) {
|
||||
fleetFilterAdd.Handle(w, r)
|
||||
}
|
||||
|
||||
var fleetFilterAdd = controllers.NewMongoUpdate(&fleetFilterAddHelper{})
|
||||
|
||||
type fleetFilterAddHelper struct{}
|
||||
|
||||
func (h *fleetFilterAddHelper) ParseUpdateURLParams(r *http.Request) interface{} {
|
||||
req := &mongo.Fleet{}
|
||||
|
||||
params := httprouter.ParamsFromContext(r.Context())
|
||||
req.Name = params.ByName("name")
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func (h *fleetFilterAddHelper) ValidateFields(model interface{}) error {
|
||||
result, ok := model.(*mongo.Fleet)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := validator.ValidateField(result.Name, "required,fleet")
|
||||
if err != nil {
|
||||
return controllers.ErrorPKRequired
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *fleetFilterAddHelper) NewModel() interface{} {
|
||||
return &common.CANFilter{}
|
||||
}
|
||||
|
||||
func (h *fleetFilterAddHelper) ParseRequestBody(r *http.Request, model interface{}) error {
|
||||
if err := httphandlers.ParseRequest(r, model); err != nil {
|
||||
return errors.WithMessage(err, "failed to parse request body")
|
||||
}
|
||||
|
||||
p := model.(*common.CANFilter)
|
||||
|
||||
if p.EdgeMask == nil && p.Interval == nil {
|
||||
return &validator.FieldError{
|
||||
ErrorMsg: "At least one of edge_mask or interval is required",
|
||||
}
|
||||
}
|
||||
|
||||
if p.EdgeMask != nil && p.Interval != nil {
|
||||
if (*p.EdgeMask).String() == "" && *p.Interval == 0 ||
|
||||
(*p.EdgeMask).String() != "" && *p.Interval != 0 {
|
||||
return &validator.FieldError{
|
||||
ErrorMsg: "Only one of edge_mask or interval can be specified",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *fleetFilterAddHelper) QueryUpdate(filter interface{}, model interface{}) error {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = client.GetFleets().AddFilterToFleet(filter.(*mongo.Fleet).Name, model.(*common.CANFilter)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ResetFleetVehiclesConfigCache(filter.(*mongo.Fleet).Name)
|
||||
}
|
||||
|
||||
func ResetFleetVehiclesConfigCache(fleetName string) error {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vehicles, err := client.GetFleets().GetVehiclesForFleet(fleetName, "", &queries.PageQueryOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := services.RedisClientPool().GetFromPool()
|
||||
defer r.Close()
|
||||
|
||||
if err = cache.RemoveCacheConfigForVehicles(r, vehicles); err != nil {
|
||||
logger.Warn().Msgf("failed to remove cache config for vehicles: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
68
services/ota_update_go/handlers/fleet_filter_add_test.go
Normal file
68
services/ota_update_go/handlers/fleet_filter_add_test.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
|
||||
)
|
||||
|
||||
func TestFleetFilterAdd(t *testing.T) {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
mockMongo := mongo.NewFleetsCollection(&mongo.MockCollection{})
|
||||
client.SetFleets(mockMongo)
|
||||
|
||||
tests := []th.BasicHttpTest{
|
||||
{
|
||||
Name: "Invalid fleet",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/fleet/$TEST/filter", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"primary key required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Invalid vin parameter",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/fleet/US-TEST/filter", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"CANID required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Invalid data",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/fleet/US-TEST/filter", common.CANFilter{}),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"CANID required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Invalid data with can id",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/fleet/US-TEST/filter", common.CANFilter{CANID: "123"}),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"At least one of edge_mask or interval is required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Invalid data with all fields",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/fleet/US-TEST/filter",
|
||||
common.CANFilter{CANID: "123", EdgeMask: elptr.ElPtr(common.BinaryHex("123")), Interval: elptr.ElPtr(1)}),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"Only one of edge_mask or interval can be specified","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Valid data",
|
||||
Request: th.MakeTestRequest(
|
||||
http.MethodPost, "http://example.com/fleet/US-TEST/filter",
|
||||
common.CANFilter{CANID: "123", Interval: elptr.ElPtr(100)}),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"can_id":"123","interval":100}`,
|
||||
},
|
||||
}
|
||||
|
||||
th.RunParamHttpTests(t, tests, handlers.HandleFleetFilterAdd, "/fleet/:name/filter")
|
||||
}
|
||||
74
services/ota_update_go/handlers/fleet_filter_delete.go
Normal file
74
services/ota_update_go/handlers/fleet_filter_delete.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"otaupdate/controllers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
// HandleFleetFilterDelete godoc
|
||||
// @Summary Delete filter from fleet
|
||||
// @Description Delete filter from fleet
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param name path string true "Name"
|
||||
// @Param id path string true "CAN ID"
|
||||
// @Success 200 {object} common.JSONMessage
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /fleet/{name}/filter/{id} [delete]
|
||||
func HandleFleetFilterDelete(w http.ResponseWriter, r *http.Request) {
|
||||
fleetFilterDelete.Handle(w, r)
|
||||
}
|
||||
|
||||
var fleetFilterDelete = controllers.NewMongoDelete(&fleetFilterDeleteHelper{})
|
||||
|
||||
type fleetFilterDeleteHelper struct{}
|
||||
|
||||
func (h *fleetFilterDeleteHelper) ParseDeleteURLParams(r *http.Request) interface{} {
|
||||
req := &FleetFilterDeleteParams{}
|
||||
|
||||
params := httprouter.ParamsFromContext(r.Context())
|
||||
req.Name = params.ByName("name")
|
||||
req.CANID = params.ByName("id")
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func (h *fleetFilterDeleteHelper) ValidateFields(model interface{}) error {
|
||||
result := model.(*FleetFilterDeleteParams)
|
||||
|
||||
err := validator.ValidateStruct(result)
|
||||
if err != nil {
|
||||
return controllers.ErrorPKRequired
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *fleetFilterDeleteHelper) QueryDelete(filter interface{}) error {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f := filter.(*FleetFilterDeleteParams)
|
||||
|
||||
if err = client.GetFleets().DeleteFilterFromFleet(f.Name, f.CANID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ResetFleetVehiclesConfigCache(f.Name)
|
||||
}
|
||||
|
||||
type FleetFilterDeleteParams struct {
|
||||
Name string `validate:"fleet"`
|
||||
CANID string `validate:"can_id"`
|
||||
}
|
||||
33
services/ota_update_go/handlers/fleet_filter_delete_test.go
Normal file
33
services/ota_update_go/handlers/fleet_filter_delete_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestFleetFilterDelete(t *testing.T) {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
mockMongo := mongo.NewFleetsCollection(&mongo.MockCollection{})
|
||||
client.SetFleets(mockMongo)
|
||||
|
||||
tests := []th.BasicHttpTest{
|
||||
{
|
||||
Name: "Valid data",
|
||||
Request: th.MakeTestRequest(http.MethodDelete, "http://example.com/fleet/US-TEST/filter/123", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"message":"Deleted"}`,
|
||||
},
|
||||
}
|
||||
|
||||
th.RunParamHttpTests(t, tests, handlers.HandleFleetFilterDelete, "/fleet/:name/filter/:id")
|
||||
}
|
||||
80
services/ota_update_go/handlers/fleet_filter_get_list.go
Normal file
80
services/ota_update_go/handlers/fleet_filter_get_list.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"otaupdate/controllers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
// HandleFleetFilterGetList godoc
|
||||
// @Summary Get filters for fleet
|
||||
// @Description Get filters for fleet
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param name path string true "Name"
|
||||
// @Param limit query int false "Max number of records"
|
||||
// @Param offset query int false "Records offset"
|
||||
// @Success 200 {object} common.JSONDBQueryResult
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /fleet/{name}/filters [get]
|
||||
func HandleFleetFilterGetList(w http.ResponseWriter, r *http.Request) {
|
||||
fleetFilterGetList.Handle(w, r)
|
||||
}
|
||||
|
||||
var fleetFilterGetList = controllers.NewMongoGetList(&fleetFilterGetListHelper{})
|
||||
|
||||
type fleetFilterGetListHelper struct{}
|
||||
|
||||
func (h *fleetFilterGetListHelper) NewModel() interface{} {
|
||||
return &mongo.Fleet{}
|
||||
}
|
||||
|
||||
func (h *fleetFilterGetListHelper) ParseGetListURLParams(r *http.Request, model interface{}) {
|
||||
filter := model.(*mongo.Fleet)
|
||||
|
||||
params := httprouter.ParamsFromContext(r.Context())
|
||||
filter.Name = params.ByName("name")
|
||||
}
|
||||
|
||||
func (h *fleetFilterGetListHelper) ValidateStruct(model interface{}) error {
|
||||
result := model.(*mongo.Fleet)
|
||||
|
||||
err := validator.ValidateField(result.Name, "required,fleet")
|
||||
if err != nil {
|
||||
return controllers.ErrorPKRequired
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *fleetFilterGetListHelper) ParseGetListQueryParams(r *http.Request, model interface{}) {
|
||||
// does not utilize URL queries so leave this function empty
|
||||
}
|
||||
|
||||
func (h *fleetFilterGetListHelper) QueryCount(filter interface{}) (int64, error) {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return client.GetFleets().GetFiltersForFleetCount(filter.(*mongo.Fleet).Name)
|
||||
}
|
||||
|
||||
func (h *fleetFilterGetListHelper) QuerySelect(filter interface{}, options *queries.PageQueryOptions) (interface{}, error) {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client.GetFleets().GetFiltersForFleet(filter.(*mongo.Fleet).Name, options)
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
|
||||
)
|
||||
|
||||
func TestFleetFilterGetList(t *testing.T) {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
mockMongo := mongo.NewFleetsCollection(
|
||||
&mongo.MockCollection{
|
||||
AggregateObject: []mongo.Fleet{
|
||||
{
|
||||
Name: "US-TEST",
|
||||
CANBus: common.CANBus{
|
||||
Filters: []common.CANFilter{
|
||||
{
|
||||
CANID: "123",
|
||||
Interval: elptr.ElPtr(100),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
client.SetFleets(mockMongo)
|
||||
|
||||
tests := []th.BasicHttpTest{
|
||||
{
|
||||
Name: "Invalid name parameter",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/fleet/$TEST/filters", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"primary key required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Valid data",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/fleet/US-TEST/filters", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"data":[{"can_id":"123","interval":100}]}`,
|
||||
},
|
||||
{
|
||||
Name: "Valid data limit 50",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/fleet/US-TEST/filters?limit=50", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"data":[{"can_id":"123","interval":100}]}`,
|
||||
},
|
||||
{
|
||||
Name: "Wrong limit, -100",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/fleet/US-TEST/filters?limit=-100", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"Limit less than 0","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Wrong limit, 1000",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/fleet/US-TEST/filters?limit=1000", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"Limit greater than 100","error":"Bad Request"}`,
|
||||
},
|
||||
}
|
||||
|
||||
th.RunParamHttpTests(t, tests, handlers.HandleFleetFilterGetList, "/fleet/:name/filters")
|
||||
}
|
||||
111
services/ota_update_go/handlers/fleet_filter_update.go
Normal file
111
services/ota_update_go/handlers/fleet_filter_update.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/httphandlers"
|
||||
|
||||
"otaupdate/controllers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// HandleFleetFilterUpdate godoc
|
||||
// @Summary Update a fleet filter
|
||||
// @Description Update a fleet filter
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param name path string true "Name"
|
||||
// @Param id path string true "CAN ID"
|
||||
// @Param config body common.CANFilter true "Fleet filter data"
|
||||
// @Success 200 {object} common.SubscriptionConfiguration
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /fleet/{name}/filter/{id} [put]
|
||||
func HandleFleetFilterUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
fleetFilterUpdate.Handle(w, r)
|
||||
}
|
||||
|
||||
var fleetFilterUpdate = controllers.NewMongoUpdate(&fleetFilterUpdateHelper{})
|
||||
|
||||
type fleetFilterUpdateHelper struct{}
|
||||
|
||||
func (h *fleetFilterUpdateHelper) ParseUpdateURLParams(r *http.Request) interface{} {
|
||||
req := &FleetFilterUpdateParams{}
|
||||
|
||||
params := httprouter.ParamsFromContext(r.Context())
|
||||
req.Name = params.ByName("name")
|
||||
req.CANID = params.ByName("id")
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func (h *fleetFilterUpdateHelper) ValidateFields(model interface{}) error {
|
||||
result, ok := model.(*FleetFilterUpdateParams)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := validator.ValidateStruct(result)
|
||||
if err != nil {
|
||||
return controllers.ErrorPKRequired
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *fleetFilterUpdateHelper) NewModel() interface{} {
|
||||
return &common.CANFilter{}
|
||||
}
|
||||
|
||||
func (h *fleetFilterUpdateHelper) ParseRequestBody(r *http.Request, model interface{}) error {
|
||||
if err := httphandlers.ParseRequest(r, model); err != nil {
|
||||
return errors.WithMessage(err, "failed to parse request body")
|
||||
}
|
||||
|
||||
p := model.(*common.CANFilter)
|
||||
|
||||
if p.EdgeMask == nil && p.Interval == nil {
|
||||
return &validator.FieldError{
|
||||
ErrorMsg: "At least one of edge_mask or interval is required",
|
||||
}
|
||||
}
|
||||
|
||||
if p.EdgeMask != nil && p.Interval != nil {
|
||||
if (*p.EdgeMask).String() == "" && *p.Interval == 0 ||
|
||||
(*p.EdgeMask).String() != "" && *p.Interval != 0 {
|
||||
return &validator.FieldError{
|
||||
ErrorMsg: "Only one of edge_mask or interval can be specified",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *fleetFilterUpdateHelper) QueryUpdate(filter interface{}, model interface{}) error {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f := filter.(*FleetFilterUpdateParams)
|
||||
if err = client.GetFleets().UpdateFilterForFleet(f.Name, f.CANID, model.(*common.CANFilter)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ResetFleetVehiclesConfigCache(f.Name)
|
||||
}
|
||||
|
||||
type FleetFilterUpdateParams struct {
|
||||
Name string `validate:"fleet"`
|
||||
CANID string `validate:"can_id"`
|
||||
}
|
||||
56
services/ota_update_go/handlers/fleet_filter_update_test.go
Normal file
56
services/ota_update_go/handlers/fleet_filter_update_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
|
||||
)
|
||||
|
||||
func TestFleetFilterUpdate(t *testing.T) {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
mockMongo := mongo.NewFleetsCollection(&mongo.MockCollection{})
|
||||
client.SetFleets(mockMongo)
|
||||
|
||||
tests := []th.BasicHttpTest{
|
||||
{
|
||||
Name: "Invalid data",
|
||||
Request: th.MakeTestRequest(http.MethodPut, "http://example.com/fleet/US-TEST/filter/123",
|
||||
common.CANFilter{Interval: elptr.ElPtr(0)}),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"CANID required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Valid data",
|
||||
Request: th.MakeTestRequest(http.MethodPut, "http://example.com/fleet/US-TEST/filter/123",
|
||||
common.CANFilter{CANID: "123", Interval: elptr.ElPtr(100)}),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"can_id":"123","interval":100}`,
|
||||
},
|
||||
{
|
||||
Name: "Invalid data with can id",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/fleet/US-TEST/filter/123", common.CANFilter{CANID: "123"}),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"At least one of edge_mask or interval is required","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Invalid data with all fields",
|
||||
Request: th.MakeTestRequest(http.MethodPost, "http://example.com/fleet/US-TEST/filter/123",
|
||||
common.CANFilter{CANID: "123", EdgeMask: elptr.ElPtr(common.BinaryHex("123")), Interval: elptr.ElPtr(1)}),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"Only one of edge_mask or interval can be specified","error":"Bad Request"}`,
|
||||
},
|
||||
}
|
||||
|
||||
th.RunParamHttpTests(t, tests, handlers.HandleFleetFilterUpdate, "/fleet/:name/filter/:id")
|
||||
}
|
||||
63
services/ota_update_go/handlers/fleet_get.go
Normal file
63
services/ota_update_go/handlers/fleet_get.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"otaupdate/controllers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
|
||||
)
|
||||
|
||||
// HandleFleetGet godoc
|
||||
// @Summary Get fleet
|
||||
// @Description Get fleet
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param name path string true "Name"
|
||||
// @Success 200 {object} mongo.Fleet
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /fleet/{name} [get]
|
||||
func HandleFleetGet(w http.ResponseWriter, r *http.Request) {
|
||||
fleetGet.Handle(w, r)
|
||||
}
|
||||
|
||||
var fleetGet = controllers.NewMongoGetModel(&fleetGetModelHelper{})
|
||||
|
||||
type fleetGetModelHelper struct {
|
||||
fleetHelper
|
||||
}
|
||||
|
||||
func (h *fleetGetModelHelper) ParseGetURLParams(r *http.Request) interface{} {
|
||||
req := &mongo.Fleet{}
|
||||
|
||||
params := httprouter.ParamsFromContext(r.Context())
|
||||
req.Name = params.ByName("name")
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func (h *fleetGetModelHelper) Query(filter interface{}) (interface{}, error) {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fleet, ok := filter.(*mongo.Fleet)
|
||||
if ok {
|
||||
if fleet.CANBus.DTCEnabled == nil {
|
||||
fleet.CANBus.DTCEnabled = elptr.ElPtr(false)
|
||||
}
|
||||
}
|
||||
return client.GetFleets().FindFleet(filter.(*mongo.Fleet))
|
||||
}
|
||||
|
||||
type FleetFilterParams struct {
|
||||
Name string `json:"name" validate:"required,fleet"`
|
||||
}
|
||||
73
services/ota_update_go/handlers/fleet_get_list.go
Normal file
73
services/ota_update_go/handlers/fleet_get_list.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"otaupdate/controllers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||
)
|
||||
|
||||
// HandleFleetGetList godoc
|
||||
// @Summary Get list of fleets
|
||||
// @Description Get list of fleets
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param tags query string false "Tags associated with fleet"
|
||||
// @Param limit query int false "Max number of records"
|
||||
// @Param offset query int false "Records offset"
|
||||
// @Success 200 {object} common.JSONDBQueryResult{data=[]mongo.Fleet}
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /fleets [get]
|
||||
func HandleFleetGetList(w http.ResponseWriter, r *http.Request) {
|
||||
fleetGetList.Handle(w, r)
|
||||
}
|
||||
|
||||
var fleetGetList = controllers.NewMongoGetList(&fleetGetListHelper{})
|
||||
var fleetListSort = map[string]string{
|
||||
"canbus_": "canbus.",
|
||||
}
|
||||
|
||||
type fleetGetListHelper struct {
|
||||
fleetHelper
|
||||
}
|
||||
|
||||
func (h *fleetGetListHelper) ParseGetListURLParams(r *http.Request, model interface{}) {
|
||||
// does not utilize URL params so leave this function empty
|
||||
}
|
||||
|
||||
func (h *fleetGetListHelper) ParseGetListQueryParams(r *http.Request, model interface{}) {
|
||||
filter := model.(*mongo.Fleet)
|
||||
|
||||
filter.SetSearchQuery(r.URL.Query().Get("search"))
|
||||
}
|
||||
|
||||
func (h *fleetGetListHelper) ValidateStruct(model interface{}) error { return nil }
|
||||
|
||||
func (h *fleetGetListHelper) QuerySelect(filter interface{}, options *queries.PageQueryOptions) (interface{}, error) {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if options != nil {
|
||||
options.Order = mongo.AdaptOrder(options.Order, fleetListSort)
|
||||
}
|
||||
|
||||
return client.GetFleets().SelectFleets(filter.(*mongo.Fleet), options)
|
||||
}
|
||||
|
||||
func (h *fleetGetListHelper) QueryCount(filter interface{}) (int64, error) {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return client.GetFleets().GetFleetCount(filter.(*mongo.Fleet))
|
||||
}
|
||||
59
services/ota_update_go/handlers/fleet_get_list_test.go
Normal file
59
services/ota_update_go/handlers/fleet_get_list_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestFleetGetList(t *testing.T) {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
mockMongo := mongo.NewFleetsCollection(
|
||||
&mongo.MockCollection{
|
||||
FindObject: []mongo.Fleet{
|
||||
{
|
||||
Name: "TESTFLEET",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
client.SetFleets(mockMongo)
|
||||
|
||||
tests := []th.BasicHttpTest{
|
||||
{
|
||||
Name: "Valid data",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/fleets", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"data":[{"name":"TESTFLEET","log_level":"trace","canbus":{"enabled":false,"data_logger_enabled":false,"dtc_enabled":false},"idps_enabled":false,"tags":null,"vehicles":null,"vehicles_count":0}]}`,
|
||||
},
|
||||
{
|
||||
Name: "Valid data with max limit",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/fleets?limit=100", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"data":[{"name":"TESTFLEET","log_level":"trace","canbus":{"enabled":false,"data_logger_enabled":false,"dtc_enabled":false},"idps_enabled":false,"tags":null,"vehicles":null,"vehicles_count":0}]}`,
|
||||
},
|
||||
{
|
||||
Name: "Wrong limit, -100",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/fleets?limit=-100", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"Limit less than 0","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Wrong limit, 1000",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/fleets?limit=1000", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"Limit greater than 100","error":"Bad Request"}`,
|
||||
},
|
||||
}
|
||||
|
||||
th.RunParamHttpTests(t, tests, handlers.HandleFleetGetList, "/fleets")
|
||||
}
|
||||
50
services/ota_update_go/handlers/fleet_get_test.go
Normal file
50
services/ota_update_go/handlers/fleet_get_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"otaupdate/handlers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||
th "github.com/fiskerinc/cloud-services/pkg/testhelper"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
|
||||
)
|
||||
|
||||
func TestFleetGet(t *testing.T) {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
mockMongo := mongo.NewFleetsCollection(
|
||||
&mongo.MockCollection{
|
||||
AggregateObject: []mongo.Fleet{
|
||||
{
|
||||
Name: "TESTFLEET",
|
||||
CANBus: common.CANBus{DTCEnabled: elptr.ElPtr(true)},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
client.SetFleets(mockMongo)
|
||||
|
||||
tests := []th.BasicHttpTest{
|
||||
{
|
||||
Name: "Valid data",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/fleet/INVALIDFLEET$", nil),
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
ExpectedResponse: `{"message":"fleet fleet ","error":"Bad Request"}`,
|
||||
},
|
||||
{
|
||||
Name: "Valid data",
|
||||
Request: th.MakeTestRequest(http.MethodGet, "http://example.com/fleet/TESTFLEET", nil),
|
||||
ExpectedStatus: http.StatusOK,
|
||||
ExpectedResponse: `{"name":"","log_level":"trace","canbus":{"enabled":false,"data_logger_enabled":false,"dtc_enabled":null},"idps_enabled":false,"tags":null,"vehicles":null,"vehicles_count":0}`,
|
||||
},
|
||||
}
|
||||
|
||||
th.RunParamHttpTests(t, tests, handlers.HandleFleetGet, "/fleet/:name")
|
||||
}
|
||||
149
services/ota_update_go/handlers/fleet_update.go
Normal file
149
services/ota_update_go/handlers/fleet_update.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"otaupdate/controllers"
|
||||
"otaupdate/services"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/cache"
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
"github.com/fiskerinc/cloud-services/pkg/httphandlers"
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||
e "github.com/fiskerinc/cloud-services/pkg/mongo/error"
|
||||
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
|
||||
"github.com/fiskerinc/cloud-services/pkg/validator"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
// HandleFleetUpdate godoc
|
||||
// @Summary Update fleet
|
||||
// @Description Update fleet
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string false "Bearer <ID token>"
|
||||
// @Param Api-Key header string false "<API token>"
|
||||
// @Param name path string true "Name"
|
||||
// @Param config body mongo.Fleet true "Fleet data"
|
||||
// @Success 200 {object} mongo.Fleet
|
||||
// @Failure 400 {object} common.JSONError "Bad request"
|
||||
// @Failure 401 {object} common.JSONError "Unauthorized"
|
||||
// @Failure 503 {object} common.JSONError "Service unavailable"
|
||||
// @Router /fleet/{name} [put]
|
||||
func HandleFleetUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
fleetUpdate.Handle(w, r)
|
||||
}
|
||||
|
||||
var fleetUpdate = controllers.NewMongoUpdate(&fleetUpdateHelper{})
|
||||
|
||||
type fleetUpdateHelper struct {
|
||||
fleetHelper
|
||||
}
|
||||
|
||||
func (h *fleetUpdateHelper) ParseUpdateURLParams(r *http.Request) interface{} {
|
||||
req := &mongo.Fleet{}
|
||||
|
||||
params := httprouter.ParamsFromContext(r.Context())
|
||||
req.Name = params.ByName("name")
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func (h *fleetUpdateHelper) ValidateFields(model interface{}) error {
|
||||
p := model.(*mongo.Fleet)
|
||||
|
||||
err := validator.ValidateField(p.Name, "required,fleet")
|
||||
if err != nil {
|
||||
return controllers.ErrorPKRequired
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *fleetUpdateHelper) ParseRequestBody(r *http.Request, model interface{}) error {
|
||||
err := httphandlers.ParseRequest(r, model)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fleet, ok := model.(*mongo.Fleet)
|
||||
if ok {
|
||||
if fleet.CANBus.DTCEnabled == nil {
|
||||
fleet.CANBus.DTCEnabled = elptr.ElPtr(false)
|
||||
model = fleet
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *fleetUpdateHelper) QueryUpdate(filter interface{}, model interface{}) error {
|
||||
client, err := services.GetMongoClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
flt := model.(*mongo.Fleet)
|
||||
if flt.CANBus.DTCEnabled == nil {
|
||||
flt.CANBus.DTCEnabled = elptr.ElPtr(true)
|
||||
}
|
||||
|
||||
err = client.GetFleets().UpdateFleet(filter.(*mongo.Fleet), flt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fleetVINs, err := client.GetFleets().GetVehiclesForFleet(flt.Name, "", &queries.PageQueryOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
batch := redis.NewRedisBatchCommands()
|
||||
|
||||
for _, fleetVIN := range fleetVINs {
|
||||
v := &mongo.Vehicle{VIN: fleetVIN, LogLevel: flt.LogLevel, CANBus: flt.CANBus, DebugMask: flt.DebugMask, IDPSEnabled: flt.IDPSEnabled}
|
||||
|
||||
err = client.GetVehicles().UpdateVehicle(v)
|
||||
if err != nil && errors.Is(err, e.ErrInvalidNumberOfDocs) {
|
||||
logger.At(logger.Warn(), fleetVIN, "mongodb").Err(err).Send()
|
||||
continue
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
batch.Add("DEL", redis.CarConfigKey(fleetVIN))
|
||||
|
||||
if flt.CANBus.DTCEnabled != nil {
|
||||
data := common.TRexConfigResponse{
|
||||
LogLevel: flt.LogLevel,
|
||||
CANBus: flt.CANBus,
|
||||
}
|
||||
if cache.ENABLE_DEBUG_MASK {
|
||||
data.DebugMask = flt.DebugMask
|
||||
}
|
||||
|
||||
data.IDPSEnabled = flt.IDPSEnabled
|
||||
|
||||
err = batch.AddPublish(common.TRex.Key(fleetVIN), common.Message{
|
||||
Handler: "config",
|
||||
Data: data,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conn := services.RedisClientPool().GetFromPool()
|
||||
defer conn.Close()
|
||||
_, err = conn.ExecuteBatch(batch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user