Files
cloud-services/pkg/db/queries/cars.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
}