1158 lines
32 KiB
Go
1158 lines
32 KiB
Go
package queries
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
|
"github.com/fiskerinc/cloud-services/pkg/validator"
|
|
"github.com/fiskerinc/cloud-services/pkg/vindecoder"
|
|
|
|
"github.com/go-pg/pg/v10"
|
|
"github.com/go-pg/pg/v10/orm"
|
|
"github.com/pkg/errors"
|
|
errors0 "errors"
|
|
)
|
|
|
|
type CarsInterface interface {
|
|
Count(filter *common.Car) (int, error)
|
|
Delete(car *common.Car) (orm.Result, error)
|
|
Insert(car *common.Car) (orm.Result, error)
|
|
Select(filter *common.Car, paging *PageQueryOptions) ([]common.Car, error)
|
|
SelectByVIN(vin string) (*common.Car, error)
|
|
SelectOrInsert(car *common.Car) (bool, error)
|
|
Update(car *common.Car) (orm.Result, error)
|
|
UpdateICCID(car *common.Car) (orm.Result, error)
|
|
UpdateSoldStatus(car *common.Car) (orm.Result, error)
|
|
Load(car *common.Car) error
|
|
AddDriver(car *common.Car, driver *common.Driver, role string) (*common.CarToDriver, error)
|
|
RemoveDriver(vin string, driverID string) (orm.Result, error)
|
|
SelectCarToDriver(filter *common.CarToDriver) ([]common.CarToDriver, error)
|
|
GetDrivers(vin string) ([]common.CarToDriver, error)
|
|
GetTRexSetting(vin string) (common.TRexSetting, error)
|
|
GetModels() ([]string, error)
|
|
GetYears() ([]int, error)
|
|
SetSetting(setting *common.CarSetting) (orm.Result, error)
|
|
GetSettings(driver *common.CarToDriver) ([]common.CarSetting, error)
|
|
GetVehicleSpecificSettings(driver *common.CarToDriver) ([]common.CarSetting, error)
|
|
DeleteSetting(setting *common.CarSetting) (orm.Result, error)
|
|
Search(*common.CarSearch, *PageQueryOptions) ([]common.Car, error)
|
|
SearchCount(*common.CarSearch) (int, error)
|
|
UpdateCarECU(*common.CarECU) error
|
|
UpdateCarECUs([]common.CarECU) error
|
|
InsertCarECUs([]common.CarECU) error
|
|
GetCarECUs(filter common.CarECUFilter, paging *PageQueryOptions) ([]common.CarECU, error)
|
|
GetCarECUsCount(filter common.CarECUFilter) (int, error)
|
|
UpdateCarFlashpackVersion(vin string, flashpack string) (orm.Result, error)
|
|
GetFlashpackVersions(carModel string, carTrim string, carYear int, options *PageQueryOptions) ([]common.CarFlashpackVersionResponse, error)
|
|
GetFlashpackVersionsCount(carModel string, carTrim string, carYear int) (int, error)
|
|
GetNextFlashpackVersion(carModel string, carTrim string, flashpack string) (*common.CarFlashpackVersionResponse, error)
|
|
DeleteFlashpackVersion(carModel string, carTrim string, carYear int, flashpack string) error
|
|
GetCarFlashpackVersionMappingsByModelTrim(carModel string, carTrim string, options *PageQueryOptions) ([]common.CarFlashpackVersion, error)
|
|
GetCarFlashpackVersionMappingsByModelTrimYearFlashpack(carModel string, carTrim string, carYear int, flashpack string, options *PageQueryOptions) ([]common.CarFlashpackVersion, error)
|
|
GetCarFlashpackVersionMappingsByModelTrimYearFlashpackCount(carModel string, carTrim string, carYear int, flashpack string) (int, error)
|
|
GetCarECUNamesByModelTrim(carModel string, carTrim string) ([]string, error)
|
|
AddCarFlashpackVersionMappings(carFlashpackVersions []common.CarFlashpackVersion) error
|
|
GetCarsForDriver(driverID string) ([]common.CarToDriver, error)
|
|
CarsByManifest(manifest common.UpdateManifest, paging *PageQueryOptions) ([]common.Car, error)
|
|
CountCarsByManifest(manifest common.UpdateManifest) (int, error)
|
|
UpdateBLEKey(vin string, driverid string, blekey string) (externalUser common.DriverExternal, err error)
|
|
ECUUpdatedAt(ecu common.CarECU) (orm.Result, error)
|
|
// Weakly associated American Lease data
|
|
GetSoftwareAndPKCVersions(vins []string) (results []common.CarPKCOSVersion, err error)
|
|
GetSoftwareVersion(vin string)(result common.CarPKCOSVersion, err error) // Function is incomplete for its final true meaning
|
|
|
|
GetWhiteListCars()(vins []string, err error)
|
|
WhitelistCars(vins []string, source string)(err error)
|
|
BlacklistCars(vins []string)(err error)
|
|
GetICCIDs(vins []string)(iccids []string, err error)
|
|
}
|
|
|
|
// Cars query methods
|
|
type Cars struct {
|
|
QueryBase
|
|
}
|
|
|
|
func (c *Cars) ECUUpdatedAt(ecu common.CarECU) (orm.Result, error) {
|
|
now := time.Now()
|
|
ecu.UpdatedAt = &now
|
|
return c.GetDBConn().Model(&ecu).Column("updated_at").Where("vin = ?vin AND ecu = ?ecu").Update()
|
|
}
|
|
|
|
func (c *Cars) SearchCount(filter *common.CarSearch) (int, error) {
|
|
query := c.GetDBConn().Model((*common.Car)(nil))
|
|
|
|
c.searchFilter(query, filter)
|
|
|
|
count, err := query.Count()
|
|
|
|
return count, errors.WithStack(err)
|
|
}
|
|
|
|
func (c *Cars) Search(filter *common.CarSearch, paging *PageQueryOptions) ([]common.Car, error) {
|
|
cars := []common.Car{}
|
|
query := c.GetDBConn().Model(&cars)
|
|
|
|
c.searchFilter(query, filter)
|
|
c.pageQuery(query, paging)
|
|
|
|
err := query.Select()
|
|
|
|
return cars, errors.WithStack(err)
|
|
}
|
|
|
|
func (c *Cars) CarsByManifest(manifest common.UpdateManifest, paging *PageQueryOptions) ([]common.Car, error) {
|
|
var cars []common.Car
|
|
query := c.GetDBConn().Model(&cars)
|
|
query = c.manifestFilter(query, manifest)
|
|
err := c.pageQuery(query, paging).Select()
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
return cars, nil
|
|
}
|
|
|
|
func (c *Cars) CountCarsByManifest(manifest common.UpdateManifest) (int, error) {
|
|
var cars []common.Car
|
|
query := c.GetDBConn().Model(&cars)
|
|
query = c.manifestFilter(query, manifest)
|
|
total, err := query.Count()
|
|
if err != nil {
|
|
return 0, errors.WithStack(err)
|
|
}
|
|
|
|
return total, nil
|
|
}
|
|
|
|
func (c *Cars) manifestFilter(query *orm.Query, manifest common.UpdateManifest) *orm.Query {
|
|
if manifest.PowerTrain != "" {
|
|
query.Where("powertrain = ?", manifest.PowerTrain)
|
|
}
|
|
if manifest.Country != "" {
|
|
query.Where("country = ?", manifest.Country)
|
|
}
|
|
if manifest.Restraint != "" {
|
|
query.Where("restraint = ?", manifest.Restraint)
|
|
}
|
|
if manifest.BodyType != "" {
|
|
query.Where("body_type = ?", manifest.BodyType)
|
|
}
|
|
if manifest.Year != 0 {
|
|
query.Where("year = ?", manifest.Year)
|
|
}
|
|
if manifest.Model != "" {
|
|
query.Where("model = ?", manifest.Model)
|
|
}
|
|
if manifest.Trim != "" {
|
|
query.Where("trim = ?", manifest.Trim)
|
|
}
|
|
|
|
return query
|
|
}
|
|
|
|
func (c *Cars) searchFilter(query *orm.Query, filter *common.CarSearch) {
|
|
if filter.Search != "" {
|
|
query.Where("document @@ plainto_tsquery(?) OR vin ILIKE ?", filter.Search, fmt.Sprintf("%%%s%%", filter.Search))
|
|
}
|
|
|
|
if filter.VINs != "" {
|
|
c.queryVINsWhereIn(query, filter.VINs)
|
|
}
|
|
|
|
AddOnlineFilter(query, filter)
|
|
|
|
c.selectFilter(query, &filter.Car)
|
|
|
|
if filter.NoEU {
|
|
query.Where("region != ?", common.EU)
|
|
}
|
|
}
|
|
|
|
// AddOnlineFilter filters only the online cars, despite the VINsOnline|HMIsOnline being empty or not.
|
|
// Probably it needs to be polished so that HMIsOnline and VINsOnline would work separately.
|
|
func AddOnlineFilter(query *orm.Query, filter *common.CarSearch) {
|
|
if filter.Online == nil {
|
|
return
|
|
}
|
|
|
|
var isOnline, nilOnline bool
|
|
if filter.Online.Online != nil {
|
|
isOnline = *filter.Online.Online
|
|
} else {
|
|
nilOnline = true
|
|
}
|
|
|
|
if filter.Online.HMI != nil {
|
|
isOnline = *filter.Online.HMI || isOnline
|
|
nilOnline = false
|
|
}
|
|
|
|
if nilOnline {
|
|
return
|
|
}
|
|
|
|
onlineOperator := "IN"
|
|
if !isOnline {
|
|
onlineOperator = "NOT IN"
|
|
}
|
|
|
|
if len(filter.Online.VINsOnline) == 0 {
|
|
filter.Online.VINsOnline = []string{""}
|
|
}
|
|
|
|
query.Where("vin "+onlineOperator+" (?)", pg.In(filter.Online.VINsOnline))
|
|
}
|
|
|
|
func (c *Cars) selectFilter(query *orm.Query, filter *common.Car) {
|
|
if filter.VIN != "" && len(filter.VIN) == 17 {
|
|
query.Where("vin = ?", filter.VIN)
|
|
}
|
|
|
|
if filter.VIN != "" && len(filter.VIN) == 7 {
|
|
query.Where("vin LIKE ?", "%"+filter.VIN)
|
|
}
|
|
|
|
if filter.Model != "" {
|
|
query.Where("model = ?", filter.Model)
|
|
}
|
|
|
|
if filter.Year > 0 {
|
|
query.Where("year = ?", filter.Year)
|
|
}
|
|
}
|
|
|
|
func (c *Cars) queryVINsWhereIn(query *orm.Query, vinCSV string) {
|
|
vins := strings.Split(vinCSV, ",")
|
|
|
|
for i, vin := range vins {
|
|
valid := vindecoder.ValidateVINSimple(vin)
|
|
if valid {
|
|
valid = vindecoder.VerifyVinCheckDigit(vin)
|
|
}
|
|
|
|
if i == 0 {
|
|
if valid {
|
|
query.Where("vin = ?", vin)
|
|
} else {
|
|
query.Where("vin LIKE ?", "%"+vin)
|
|
}
|
|
} else {
|
|
if valid {
|
|
query.WhereOr("vin = ?", vin)
|
|
} else {
|
|
query.WhereOr("vin LIKE ?", "%"+vin)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// SelectByVIN returns a car by VIN
|
|
func (c *Cars) SelectByVIN(vin string) (*common.Car, error) {
|
|
car := common.Car{}
|
|
|
|
query := c.GetDBConn().
|
|
Model(&car).
|
|
Column("car.*", "sv.os_version").
|
|
Join("LEFT JOIN sums_versions AS sv ON car.sums_version = sv.version").
|
|
Where("car.vin = ?", vin)
|
|
|
|
err := query.Select()
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
return &car, nil
|
|
}
|
|
|
|
// Select returns list of cars
|
|
func (c *Cars) Select(filter *common.Car, paging *PageQueryOptions) ([]common.Car, error) {
|
|
cars := []common.Car{}
|
|
query := c.GetDBConn().Model(&cars)
|
|
|
|
c.selectFilter(query, filter)
|
|
c.pageQuery(query, paging)
|
|
|
|
err := query.Select()
|
|
|
|
return cars, errors.WithStack(err)
|
|
}
|
|
|
|
func (c *Cars) SelectOrInsert(car *common.Car) (bool, error) {
|
|
car.VIN = strings.ToUpper(car.VIN)
|
|
q := c.GetDBConn().Model(car)
|
|
|
|
if car.VIN != "" {
|
|
q.Where("vin = ?vin")
|
|
} else {
|
|
return false, errors.New("no VIN")
|
|
}
|
|
|
|
inserted, err := q.SelectOrInsert()
|
|
|
|
return inserted, errors.WithStack(err)
|
|
}
|
|
|
|
func (c *Cars) Delete(car *common.Car) (orm.Result, error) {
|
|
var err error
|
|
|
|
if car.VIN == "" {
|
|
return nil, errors.WithStack(&validator.FieldError{
|
|
ErrorMsg: "VIN required",
|
|
})
|
|
}
|
|
|
|
tx, err := c.GetDBConn().Begin()
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
tx.Rollback()
|
|
}
|
|
tx.Close()
|
|
}()
|
|
|
|
_, err = tx.Exec("DELETE FROM subscriptions WHERE car_to_driver_id IN (SELECT id FROM car_to_drivers WHERE vin = ?)", car.VIN)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
_, err = tx.Model(&common.CarSetting{}).Where("vin = ?", car.VIN).Delete()
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
_, err = tx.Model(&common.CarToDriver{VIN: car.VIN}).Where("vin = ?vin").Delete()
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
_, err = tx.Model(&common.CarECU{VIN: car.VIN}).Where("vin = ?vin").Delete()
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
_, err = tx.Exec("DELETE FROM car_update_statuses WHERE car_update_id IN (SELECT id FROM car_updates WHERE vin = ?)", car.VIN)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
_, err = tx.Model(&common.CarUpdate{VIN: car.VIN}).Where("vin = ?vin").Delete()
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
_, err = tx.Model(&common.CarVersionLogs{VIN: car.VIN}).Where("vin = ?vin").Delete()
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
result, err := tx.Model(car).WherePK().Delete()
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
err = tx.Commit()
|
|
|
|
return result, err
|
|
}
|
|
|
|
func (c *Cars) Update(car *common.Car) (orm.Result, error) {
|
|
if car.VIN == "" {
|
|
return nil, errors.WithStack(&validator.FieldError{
|
|
ErrorMsg: "VIN required",
|
|
})
|
|
}
|
|
|
|
return c.resultWithStack(c.GetDBConn().Model(car).Column("iccid", "model", "year", "trim", "country", "powertrain", "restraint", "body_type", "tags", "sums_version").WherePK().Update())
|
|
}
|
|
|
|
// UpdateICCID should be merged with the Update method
|
|
func (c *Cars) UpdateICCID(car *common.Car) (orm.Result, error) {
|
|
if car.VIN == "" {
|
|
return nil, errors.WithStack(&validator.FieldError{
|
|
ErrorMsg: "VIN required",
|
|
})
|
|
}
|
|
|
|
// Catch no-op where the iccid is updated unnecessarily
|
|
result, err := c.GetDBConn().Model(car).
|
|
Column("iccid").
|
|
Where("vin = ? AND iccid != ?", car.VIN, car.ICCID).Update()
|
|
|
|
return result, errors.WithStack(err)
|
|
}
|
|
|
|
func (c *Cars) UpdateSoldStatus(car *common.Car) (orm.Result, error) {
|
|
if car.VIN == "" {
|
|
return nil, errors.WithStack(&validator.FieldError{
|
|
ErrorMsg: "VIN required",
|
|
})
|
|
}
|
|
|
|
result, err := c.GetDBConn().Model(car).
|
|
Column("sold_status").
|
|
Where("vin = ?", car.VIN).Update()
|
|
|
|
return result, errors.WithStack(err)
|
|
}
|
|
|
|
func (c *Cars) Insert(car *common.Car) (orm.Result, error) {
|
|
car.VIN = strings.ToUpper(car.VIN)
|
|
return c.insert(car)
|
|
}
|
|
|
|
func (c *Cars) Load(car *common.Car) error {
|
|
query := c.GetDBConn().Model(car)
|
|
|
|
if car.VIN != "" {
|
|
query.WherePK()
|
|
} else {
|
|
return errors.New("no VIN")
|
|
}
|
|
|
|
err := query.Select()
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
drivers, err := c.GetDrivers(car.VIN)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
car.Drivers = drivers
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Cars) Count(filter *common.Car) (int, error) {
|
|
query := c.GetDBConn().Model((*common.Car)(nil))
|
|
c.selectFilter(query, filter)
|
|
|
|
return c.countWithStack(query.Count())
|
|
}
|
|
|
|
func (c *Cars) AddDriver(car *common.Car, driver *common.Driver, role string) (*common.CarToDriver, error) {
|
|
if car.VIN == "" {
|
|
return nil, errors.New("car needs insert")
|
|
}
|
|
if driver.ID == "" {
|
|
return nil, errors.New("driver needs insert")
|
|
}
|
|
rel := common.CarToDriver{
|
|
VIN: car.VIN,
|
|
DriverID: driver.ID,
|
|
DriverRole: role,
|
|
}
|
|
result, err := c.GetDBConn().Model(&rel).Column("driver_role").Where("vin = ?vin AND driver_id = ?driver_id").Update()
|
|
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
if result.RowsAffected() == 1 {
|
|
return &rel, nil
|
|
}
|
|
|
|
_, err = c.GetDBConn().Model(&rel).Insert()
|
|
|
|
return &rel, errors.WithStack(err)
|
|
}
|
|
|
|
// RemoveDriver removes driver to the car
|
|
func (c *Cars) RemoveDriver(vin string, driverID string) (orm.Result, error) {
|
|
if vin == "" {
|
|
return nil, errors.New("vin required")
|
|
}
|
|
|
|
if driverID == "" {
|
|
return nil, errors.New("driver_id required")
|
|
}
|
|
|
|
rel := common.CarToDriver{
|
|
VIN: vin,
|
|
DriverID: driverID,
|
|
}
|
|
|
|
// select the CarToDriver
|
|
err := c.GetDBConn().Model(&rel).ColumnExpr("id").Where("vin = ?vin AND driver_id = ?driver_id").Select()
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
total := &ORMResults{}
|
|
tx, err := c.GetDBConn().Begin()
|
|
if err != nil {
|
|
return total, errors.WithStack(err)
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
tx.Rollback()
|
|
}
|
|
tx.Close()
|
|
}()
|
|
|
|
// delete any related subscriptions before deleting the CarToDriver
|
|
result, err := tx.Model(&common.Subscription{}).Where("car_to_driver_id = ?", rel.ID).Delete()
|
|
if err != nil {
|
|
return total, errors.WithStack(err)
|
|
}
|
|
total.AddResult(result)
|
|
|
|
// now delete the CarToDriver
|
|
result, err = tx.Model(&rel).Where("vin = ?vin AND driver_id = ?driver_id").Delete()
|
|
if err != nil {
|
|
return total, errors.WithStack(err)
|
|
}
|
|
total.AddResult(result)
|
|
|
|
err = tx.Commit()
|
|
if err != nil {
|
|
return total, errors.WithStack(err)
|
|
}
|
|
|
|
return total, nil
|
|
}
|
|
|
|
// SelectCarToDriver queries CarToDrivers according to the filters
|
|
func (c *Cars) SelectCarToDriver(filter *common.CarToDriver) ([]common.CarToDriver, error) {
|
|
drivers := []common.CarToDriver{}
|
|
query := c.GetDBConn().Model(&drivers)
|
|
|
|
if filter.VIN != "" {
|
|
query.Where("vin = ?", filter.VIN)
|
|
}
|
|
|
|
if filter.DriverID != "" {
|
|
query.Where("driver_id = ?", filter.DriverID)
|
|
}
|
|
|
|
err := query.Relation("Subscriptions").Select()
|
|
if err != nil {
|
|
return drivers, errors.WithStack(err)
|
|
}
|
|
|
|
err = c.addSettings(drivers)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return drivers, nil
|
|
}
|
|
|
|
// GetDrivers gets drivers for the car
|
|
func (c *Cars) GetDrivers(vin string) ([]common.CarToDriver, error) {
|
|
if vin == "" {
|
|
return nil, errors.New("car required")
|
|
}
|
|
|
|
cardrivers := []common.CarToDriver{}
|
|
err := c.GetDBConn().Model(&cardrivers).Where("vin = ?", vin).Select()
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
err = c.addSettings(cardrivers)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return cardrivers, err
|
|
}
|
|
|
|
func (c *Cars) addSettings(car2dr []common.CarToDriver) error {
|
|
if len(car2dr) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var settings []common.CarSetting
|
|
qSettings := c.GetDBConn().Model(&settings)
|
|
|
|
indexMap := make(map[string]map[string]int, len(car2dr))
|
|
for k, d := range car2dr {
|
|
qSettings.WhereOr("vin = ? AND driver_id = ?", d.VIN, d.DriverID)
|
|
e, ok := indexMap[d.VIN]
|
|
if !ok {
|
|
indexMap[d.VIN] = map[string]int{d.DriverID: k}
|
|
continue
|
|
}
|
|
|
|
e[d.DriverID] = k
|
|
}
|
|
|
|
err := qSettings.Select()
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
for _, sett := range settings {
|
|
idxs := indexMap[sett.VIN]
|
|
if sett.DriverID == "" {
|
|
for _, idx := range idxs {
|
|
car2dr[idx].Settings = append(car2dr[idx].Settings, sett)
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
idx, ok := idxs[sett.DriverID]
|
|
if !ok {
|
|
return errors.WithStack(errors.New("unexpected index; consider code changes"))
|
|
}
|
|
|
|
car2dr[idx].Settings = append(car2dr[idx].Settings, sett)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Cars) GetTRexSetting(vin string) (common.TRexSetting, error) {
|
|
tRexSetting := common.TRexSetting{}
|
|
if vin == "" {
|
|
return tRexSetting, errors.New("car required")
|
|
}
|
|
|
|
err := c.GetDBConn().Model(&tRexSetting).Where("vin = ?", vin).Select()
|
|
|
|
return tRexSetting, errors.WithStack(err)
|
|
}
|
|
|
|
func (c *Cars) GetModels() ([]string, error) {
|
|
models := []string{}
|
|
_, err := c.GetDBConn().Query(&models, "SELECT model from view_car_models ORDER BY model")
|
|
return models, errors.WithStack(err)
|
|
}
|
|
|
|
func (c *Cars) GetYears() ([]int, error) {
|
|
years := []int{}
|
|
_, err := c.GetDBConn().Query(&years, "SELECT year from view_car_years ORDER BY year")
|
|
return years, errors.WithStack(err)
|
|
}
|
|
|
|
func (c *Cars) SetSetting(setting *common.CarSetting) (orm.Result, error) {
|
|
// TODO: Refactor to make only one call
|
|
// _, err := db.Model(book).
|
|
// OnConflict("(id) DO UPDATE").
|
|
// Set("title = EXCLUDED.title").
|
|
// Insert()
|
|
|
|
existing := common.CarSetting{
|
|
VIN: setting.VIN,
|
|
DriverID: setting.DriverID,
|
|
Name: setting.Name,
|
|
}
|
|
err := c.GetDBConn().Model(&existing).Where(c.getSettingsWhere(setting)).Select()
|
|
if err != nil {
|
|
if errors.Is(err, pg.ErrNoRows) {
|
|
return c.insert(setting)
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
return c.updateSetting(setting)
|
|
}
|
|
|
|
func (c *Cars) getSettingsWhere(setting *common.CarSetting) string {
|
|
where := "vin = ?vin and driver_id = ?driver_id and name = ?name"
|
|
if setting.DriverID == "" {
|
|
where = "vin = ?vin and (driver_id IS NULL) and name = ?name"
|
|
}
|
|
return where
|
|
}
|
|
|
|
func (c *Cars) updateSetting(setting *common.CarSetting) (orm.Result, error) {
|
|
return c.resultWithStack(c.GetDBConn().Model(setting).Column("value").
|
|
Where(c.getSettingsWhere(setting)).Update())
|
|
}
|
|
|
|
func (c *Cars) GetSettings(driver *common.CarToDriver) ([]common.CarSetting, error) {
|
|
settings := []common.CarSetting{}
|
|
err := c.GetDBConn().Model(&settings).Where("vin = ? AND (driver_id = ? OR driver_id IS NULL)", driver.VIN, driver.DriverID).Select()
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
return settings, nil
|
|
}
|
|
|
|
func (c *Cars) GetVehicleSpecificSettings(driver *common.CarToDriver) ([]common.CarSetting, error) {
|
|
settings := []common.CarSetting{}
|
|
query := c.GetDBConn().
|
|
Model(&settings).
|
|
Where("vin = ?", driver.VIN)
|
|
err := query.Select()
|
|
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
return settings, nil
|
|
}
|
|
|
|
func (c *Cars) DeleteSetting(setting *common.CarSetting) (orm.Result, error) {
|
|
return c.resultWithStack(c.GetDBConn().Model(&setting).Where("vin = ?vin AND driver_id = ?driver_id AND name = ?name").Delete())
|
|
}
|
|
|
|
func (c *Cars) UpdateCarECU(ecu *common.CarECU) error {
|
|
result, err := c.GetDBConn().Model(ecu).Where("vin = ?vin AND ecu = ?ecu").Column("version", "fingerprint", "code_data_string", "serial_number", "hw_version", "boot_loader_version", "vendor", "supplier_sw_version").Update()
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
if result.RowsAffected() == 1 {
|
|
return nil
|
|
}
|
|
|
|
_, err = c.GetDBConn().Model(ecu).Insert()
|
|
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
func (c *Cars) UpdateCarECUs(ecus []common.CarECU) error {
|
|
query := c.GetDBConn().
|
|
Model(&ecus).
|
|
OnConflict("(vin, ecu) DO UPDATE").
|
|
Set("version = EXCLUDED.version").
|
|
Set("serial_number = COALESCE(EXCLUDED.serial_number, car_ecu.serial_number)").
|
|
Set("hw_version = COALESCE(EXCLUDED.hw_version, car_ecu.hw_version)").
|
|
Set("boot_loader_version = COALESCE(EXCLUDED.boot_loader_version, car_ecu.boot_loader_version)").
|
|
Set("fingerprint = COALESCE(EXCLUDED.fingerprint, car_ecu.fingerprint)").
|
|
Set("code_data_string = COALESCE(EXCLUDED.code_data_string, car_ecu.code_data_string)").
|
|
Set("vendor = COALESCE(EXCLUDED.vendor, car_ecu.vendor)").
|
|
Set("supplier_sw_version = COALESCE(EXCLUDED.supplier_sw_version, car_ecu.supplier_sw_version)")
|
|
|
|
_, err := query.Insert()
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Cars) InsertCarECUs(ecus []common.CarECU) error {
|
|
var err error
|
|
var tx *pg.Tx
|
|
conn := c.client.GetConn()
|
|
tx, err = conn.Begin()
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
txerr := tx.Rollback()
|
|
if txerr != nil {
|
|
logger.Error().Err(errors.WithStack(txerr)).Send()
|
|
}
|
|
}
|
|
}()
|
|
for _, ecu := range ecus {
|
|
onConflict := "(coalesce(vin, 'null'), coalesce(ecu, 'null'), coalesce(version, 'null'), coalesce(serial_number, 'null'), coalesce(hw_version, 'null'), coalesce(boot_loader_version, 'null'), coalesce(fingerprint, 'null'), coalesce(code_data_string, 'null'), coalesce(vendor, 'null'), coalesce(supplier_sw_version, 'null'), coalesce(assy_number, 'null')) DO UPDATE SET updated_at = EXCLUDED.updated_at"
|
|
_, err = tx.Model(&ecu).OnConflict(onConflict).Insert()
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
}
|
|
|
|
err = errors.WithStack(tx.Commit())
|
|
return err
|
|
}
|
|
|
|
// car_ecus table is also a log of the ecu versions of a car. Use the .Unique on filter to
|
|
func (c *Cars) GetCarECUs(filter common.CarECUFilter, paging *PageQueryOptions) ([]common.CarECU, error) {
|
|
ecus := []common.CarECU{}
|
|
query := c.GetDBConn().Model(&ecus)
|
|
|
|
c.selectECUsFilter(query, filter)
|
|
c.pageQuery(query, paging)
|
|
err := query.Select()
|
|
|
|
return ecus, errors.WithStack(err)
|
|
}
|
|
|
|
func (c *Cars) selectECUsFilter(query *orm.Query, filter common.CarECUFilter) {
|
|
if filter.Unique {
|
|
query.DistinctOn("vin, ecu")
|
|
query.Order("vin", "ecu", "updated_at DESC")
|
|
}
|
|
|
|
if filter.VIN != "" {
|
|
query.Where("vin = ?", filter.VIN)
|
|
}
|
|
|
|
if len(filter.ECUs) > 0 {
|
|
query.Where("ecu in (?)", pg.In(filter.ECUs))
|
|
}
|
|
|
|
if filter.Search != "" {
|
|
query.Where("document @@ plainto_tsquery(?)", filter.Search)
|
|
}
|
|
|
|
if filter.FlashPackNumberExist {
|
|
query.Where("flashpack_number IS NOT null")
|
|
}
|
|
|
|
if filter.HWVersionRequired {
|
|
query.Where("hw_version IS NOT null")
|
|
}
|
|
}
|
|
|
|
func (c *Cars) GetCarECUsCount(filter common.CarECUFilter) (int, error) {
|
|
query := c.GetDBConn().Model((*common.CarECU)(nil))
|
|
c.selectECUsFilter(query, filter)
|
|
return c.countWithStack(query.Count())
|
|
}
|
|
|
|
func (c *Cars) UpdateCarFlashpackVersion(vin string, flashpack string) (orm.Result, error) {
|
|
if vin == "" {
|
|
return nil, errors.WithStack(&validator.FieldError{
|
|
ErrorMsg: "VIN required",
|
|
})
|
|
}
|
|
|
|
car := &common.Car{VIN: vin, Flashpack: flashpack}
|
|
|
|
return c.resultWithStack(c.GetDBConn().Model(car).Column("flashpack").WherePK().Update())
|
|
}
|
|
|
|
func (c *Cars) GetFlashpackVersions(carModel string, carTrim string, carYear int, options *PageQueryOptions) ([]common.CarFlashpackVersionResponse, error) {
|
|
var flashpacks = []common.CarFlashpackVersionResponse{}
|
|
|
|
model := []common.CarFlashpackVersion{}
|
|
q := c.GetDBConn().Model(&model).
|
|
DistinctOn("flashpack").
|
|
Where("car_model = ?", carModel).
|
|
Where("car_trim = ?", carTrim).
|
|
Where("car_year = ?", carYear)
|
|
|
|
c.pageQuery(q, options)
|
|
|
|
err := q.Select()
|
|
if err == nil {
|
|
for _, m := range model {
|
|
flashpacks = append(flashpacks, common.CarFlashpackVersionResponse{
|
|
Flashpack: m.Flashpack,
|
|
CarTrim: m.CarTrim,
|
|
CarModel: m.CarModel,
|
|
CarYear: m.CarYear,
|
|
})
|
|
}
|
|
|
|
return flashpacks, nil
|
|
} else if !errors.Is(err, pg.ErrNoRows) {
|
|
return flashpacks, errors.WithStack(err)
|
|
}
|
|
|
|
return flashpacks, nil
|
|
}
|
|
|
|
func (c *Cars) GetFlashpackVersionsCount(carModel string, carTrim string, carYear int) (int, error) {
|
|
query := c.GetDBConn().Model(&common.CarFlashpackVersion{}).
|
|
DistinctOn("flashpack").
|
|
Where("car_model = ?", carModel).
|
|
Where("car_trim = ?", carTrim).
|
|
Where("car_year = ?", carYear)
|
|
|
|
return query.Count()
|
|
}
|
|
|
|
func (c *Cars) GetNextFlashpackVersion(carModel string, carTrim string, flashpack string) (*common.CarFlashpackVersionResponse, error) {
|
|
var flashpacks = []common.CarFlashpackVersionResponse{}
|
|
|
|
model := []common.CarFlashpackVersion{}
|
|
q := c.GetDBConn().Model(&model).
|
|
DistinctOn("flashpack").
|
|
Where("car_model = ?", carModel).
|
|
Where("car_trim = ?", carTrim)
|
|
|
|
err := q.Select()
|
|
if err == nil {
|
|
// sort the flashpack numbers in descending order
|
|
for _, c := range model {
|
|
flashpacks = append(flashpacks, common.CarFlashpackVersionResponse{
|
|
Flashpack: c.Flashpack,
|
|
CarModel: c.CarModel,
|
|
CarYear: c.CarYear,
|
|
})
|
|
}
|
|
sort.Slice(flashpacks, func(i, j int) bool {
|
|
iFp, _ := strconv.ParseFloat(flashpacks[i].Flashpack, 64) // guaranteed numeric
|
|
jFp, _ := strconv.ParseFloat(flashpacks[j].Flashpack, 64) // guaranteed numeric
|
|
iYear := flashpacks[i].CarYear
|
|
jYear := flashpacks[j].CarYear
|
|
|
|
if iYear == jYear {
|
|
return iFp > jFp
|
|
} else {
|
|
return iYear > jYear
|
|
}
|
|
})
|
|
|
|
// get the next flashpack in order
|
|
for i, f := range flashpacks {
|
|
if f.Flashpack == flashpack && i > 0 {
|
|
return &flashpacks[i-1], nil
|
|
}
|
|
}
|
|
} else if !errors.Is(err, pg.ErrNoRows) {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (c *Cars) DeleteFlashpackVersion(carModel string, carTrim string, carYear int, flashpack string) error {
|
|
var err error
|
|
var tx *pg.Tx
|
|
conn := c.client.GetConn()
|
|
tx, err = conn.Begin()
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
txerr := tx.Rollback()
|
|
if txerr != nil {
|
|
logger.Error().Err(errors.WithStack(txerr)).Send()
|
|
}
|
|
}
|
|
}()
|
|
_, err = tx.Model(&common.CarFlashpackVersion{}).
|
|
Where("flashpack = ? AND car_model = ? AND car_trim = ? AND car_year = ?", flashpack, carModel, carTrim, carYear).
|
|
Delete()
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
err = errors.WithStack(tx.Commit())
|
|
return err
|
|
}
|
|
|
|
func (c *Cars) GetCarFlashpackVersionMappingsByModelTrim(carModel string, carTrim string, options *PageQueryOptions) ([]common.CarFlashpackVersion, error) {
|
|
model := []common.CarFlashpackVersion{}
|
|
q := c.GetDBConn().Model(&model).
|
|
Where("car_model = ?", carModel).
|
|
Where("car_trim = ?", carTrim)
|
|
|
|
c.pageQuery(q, options)
|
|
|
|
err := q.Select()
|
|
if err == nil {
|
|
return model, nil
|
|
} else if !errors.Is(err, pg.ErrNoRows) {
|
|
return model, errors.WithStack(err)
|
|
}
|
|
|
|
return model, nil
|
|
}
|
|
|
|
func (c *Cars) GetCarFlashpackVersionMappingsByModelTrimYearFlashpack(carModel string, carTrim string, carYear int, flashpack string, options *PageQueryOptions) ([]common.CarFlashpackVersion, error) {
|
|
model := []common.CarFlashpackVersion{}
|
|
q := c.GetDBConn().Model(&model).
|
|
Where("car_model = ?", carModel).
|
|
Where("car_trim = ?", carTrim).
|
|
Where("car_year = ?", carYear).
|
|
Where("flashpack = ?", flashpack)
|
|
|
|
c.pageQuery(q, options)
|
|
|
|
err := q.Select()
|
|
if err == nil {
|
|
return model, nil
|
|
} else if !errors.Is(err, pg.ErrNoRows) {
|
|
return model, errors.WithStack(err)
|
|
}
|
|
|
|
return model, nil
|
|
}
|
|
|
|
func (c *Cars) GetCarFlashpackVersionMappingsByModelTrimYearFlashpackCount(carModel string, carTrim string, carYear int, flashpack string) (int, error) {
|
|
model := []common.CarFlashpackVersion{}
|
|
q := c.GetDBConn().Model(&model).
|
|
Where("car_model = ?", carModel).
|
|
Where("car_trim = ?", carTrim).
|
|
Where("car_year = ?", carYear).
|
|
Where("flashpack = ?", flashpack)
|
|
|
|
return q.Count()
|
|
}
|
|
|
|
func (c *Cars) GetCarECUNamesByModelTrim(carModel string, carTrim string) ([]string, error) {
|
|
model := []common.CarECU{}
|
|
q := c.GetDBConn().Model(&model).
|
|
DistinctOn("car_ecu.ecu").
|
|
Join("INNER JOIN cars").
|
|
JoinOn("car_ecu.vin = cars.vin").
|
|
Where("cars.model = ?", carModel).
|
|
Where("cars.trim = ?", carTrim).
|
|
Where("car_ecu.ecu ~ '^[a-zA-Z_]+$'").
|
|
Order("car_ecu.ecu ASC")
|
|
|
|
err := q.Select()
|
|
if err == nil {
|
|
var ecuNames []string
|
|
|
|
for _, c := range model {
|
|
ecuNames = append(ecuNames, c.ECU)
|
|
}
|
|
|
|
return ecuNames, nil
|
|
} else if errors.Is(err, pg.ErrNoRows) {
|
|
return nil, nil
|
|
}
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
func (c *Cars) AddCarFlashpackVersionMappings(carFlashpackVersions []common.CarFlashpackVersion) error {
|
|
var err error
|
|
var tx *pg.Tx
|
|
conn := c.client.GetConn()
|
|
tx, err = conn.Begin()
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
txerr := tx.Rollback()
|
|
if txerr != nil {
|
|
logger.Error().Err(errors.WithStack(txerr)).Send()
|
|
}
|
|
}
|
|
}()
|
|
for _, v := range carFlashpackVersions {
|
|
_, err = tx.Model(&v).
|
|
Where("flashpack = ? AND car_model = ? AND car_year = ? AND car_ecu_name = ? AND car_ecu_version = ?", v.Flashpack, v.CarModel, v.CarYear, v.CarECUName, v.CarECUVersion).
|
|
SelectOrInsert()
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
}
|
|
|
|
err = errors.WithStack(tx.Commit())
|
|
return err
|
|
}
|
|
|
|
func (c *Cars) GetCarsForDriver(driverID string) ([]common.CarToDriver, error) {
|
|
items := []common.CarToDriver{}
|
|
query := c.GetDBConn().
|
|
Model(&items).
|
|
Where("driver_id = ?", driverID)
|
|
err := query.Select()
|
|
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
return items, nil
|
|
}
|
|
|
|
func (c *Cars) UpdateBLEKey(vin string, driverid string, blekey string) (externalUser common.DriverExternal, err error) {
|
|
item := common.CarToDriver{
|
|
VIN: vin,
|
|
DriverID: driverid,
|
|
BLEKey: blekey,
|
|
}
|
|
|
|
result, err := c.GetDBConn().Model(&item).Column("ble_key").Where("vin = ?vin AND driver_id = ?driver_id").Update()
|
|
if err != nil {
|
|
return externalUser, errors.WithStack(err)
|
|
}
|
|
|
|
if result.RowsAffected() == 0 {
|
|
return externalUser, errors.Errorf("cannot update ble_key. %s and %s does not exist", vin, driverid)
|
|
}
|
|
|
|
// This was kind of dumb
|
|
query := `SELECT external_id, source FROM drivers_external WHERE fisker_id = ?`
|
|
_, err = c.GetDBConn().QueryOne(&externalUser, query, driverid)
|
|
if err == pg.ErrNoRows {
|
|
err = nil
|
|
}
|
|
|
|
return externalUser, nil
|
|
}
|
|
|
|
// Should add ECU's to this as well, in the future
|
|
func (c *Cars) GetSoftwareVersion(vin string)(result common.CarPKCOSVersion, err error){
|
|
query := `
|
|
SELECT cars.sums_version,
|
|
sums_versions.os_version
|
|
FROM public.cars
|
|
JOIN sums_versions ON cars.sums_version = sums_versions.version
|
|
WHERE cars.vin = ?`
|
|
_, err = c.GetDBConn().Query(&result, query, vin)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *Cars) GetSoftwareAndPKCVersions(vins []string) (results []common.CarPKCOSVersion, err error) {
|
|
query := `
|
|
SELECT
|
|
cars.vin,
|
|
car_pkcs.version AS pkc_version,
|
|
cars.sums_version,
|
|
sums_versions.os_version
|
|
FROM public.cars
|
|
JOIN sums_versions ON cars.sums_version = sums_versions.version
|
|
JOIN (
|
|
SELECT DISTINCT ON (vin, ecu) vin, version
|
|
FROM public.car_ecus
|
|
WHERE ecu = 'PKC' AND vin IN (?)
|
|
ORDER BY vin, ecu, created_at DESC
|
|
) AS car_pkcs ON cars.vin = car_pkcs.vin
|
|
WHERE cars.vin IN (?);`
|
|
_, err = c.GetDBConn().Query(&results, query, pg.In(vins), pg.In(vins))
|
|
if err != nil {
|
|
return
|
|
}
|
|
// for _, entry := range results{
|
|
// res[entry.Vin] = common.CarStateAL{
|
|
// OSVersion: entry.OSVersion,
|
|
// SumsVersion: entry.SumsVersion,
|
|
// PKCVersion: entry.PKCVersion,
|
|
// }
|
|
// }
|
|
|
|
return
|
|
}
|
|
|
|
func (c *Cars) GetWhiteListCars()(vins []string, err error){
|
|
query := `SELECT vin FROM cars_whitelist`
|
|
_, err = c.GetDBConn().Query(&vins, query)
|
|
if errors.Is(err, pg.ErrNoRows) {
|
|
err = nil
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *Cars) WhitelistCars(vins []string, source string)(err error){
|
|
// This is a really dumb way to do this, but not sure how the package supports multiple inserts like this
|
|
query := `INSERT into cars_whitelist (vin, source) VALUES (?, ?) ON CONFLICT DO NOTHING`
|
|
for _, vin := range vins {
|
|
_, tempErr := c.GetDBConn().Exec(query, vin, source)
|
|
if tempErr != nil {
|
|
tempErr = errors.New(fmt.Sprintf("%s - VIN: %s", tempErr.Error(), vin))
|
|
err = errors0.Join(err, tempErr)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *Cars) BlacklistCars(vins []string)(err error){
|
|
query := `DELETE FROM cars_whitelist WHERE vin IN (?)`
|
|
_, err = c.GetDBConn().Exec(query, pg.In(vins))
|
|
return
|
|
}
|
|
|
|
// May want to change this so it returns the VIN along with the ICCID
|
|
func (c *Cars) GetICCIDs(vins []string)(iccids []string, err error){
|
|
query := `SELECT iccid FROM cars WHERE vin IN (?)`
|
|
_, err = c.GetDBConn().Query(&iccids, query, pg.In(vins))
|
|
if err != nil {
|
|
return
|
|
}
|
|
return
|
|
} |