package queries import ( "fmt" "sort" "strconv" "strings" "time" "fiskerinc.com/modules/common" "fiskerinc.com/modules/logger" "fiskerinc.com/modules/validator" "fiskerinc.com/modules/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 }