427 lines
14 KiB
Go
427 lines
14 KiB
Go
package queries
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/fiskerinc/cloud-services/pkg/common"
|
|
"github.com/fiskerinc/cloud-services/pkg/common/carupdatestatus"
|
|
"github.com/fiskerinc/cloud-services/pkg/logger"
|
|
"github.com/fiskerinc/cloud-services/pkg/validator"
|
|
|
|
"github.com/go-pg/pg/v10"
|
|
"github.com/go-pg/pg/v10/orm"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var FINAL_UPDATE_STATUS = carupdatestatus.FINAL_UPDATE_STATUS
|
|
|
|
type CarUpdatesInterface interface {
|
|
Count(filter *common.CarUpdate) (int, error)
|
|
Delete(update *common.CarUpdate) (orm.Result, error)
|
|
CountUpdateStatuses(carupdateid int64) (int, error)
|
|
GetUpdateStatuses(carupdateid int64, paging *PageQueryOptions) ([]common.CarUpdateStatus, error)
|
|
TruncateRequirementsAwaitForUpdate(carupdateid int64) (orm.Result, error)
|
|
Insert(update *common.CarUpdate) (orm.Result, error)
|
|
InsertAndCreateStatus(update *common.CarUpdate) (orm.Result, error) // Insert a car update and create the appropriate update_status entry
|
|
Load(update *common.CarUpdate) error
|
|
LogStatus(update *common.CarUpdate) (orm.Result, error)
|
|
SelectByID(id int64) (*common.CarUpdate, error)
|
|
SelectByManifestID(int64) ([]common.CarUpdate, error)
|
|
SelectByVIN(vin string) ([]common.CarUpdate, error)
|
|
SelectMostRecentByVINs(vins []string) ([]common.CarUpdate, error)
|
|
SelectOrInsert(update *common.CarUpdate) (bool, error)
|
|
Select(filter *common.CarUpdate, paging *PageQueryOptions) ([]common.CarUpdate, error)
|
|
UpdateStatus(update *common.CarUpdate) (orm.Result, error)
|
|
UpdateStatusIfNotRepeat(update *common.CarUpdate) (orm.Result, error)
|
|
GetManifest(carupdateid int64) (*common.UpdateManifest, error)
|
|
HasPendingUpdates(manifestID int64, vin string) (bool, error)
|
|
HasPendingUpdatesFromAftersalesUser(manifestID int64, vin string) (updateID int64, pendingUpdateAftersales bool, err error) // Cancel any pending aftersales update
|
|
InsertMissingFlashpack(vin string, flashpackVersion string) error // Flashpack Version = OS Version Number
|
|
}
|
|
|
|
// CarUpdate query methods
|
|
type CarUpdates struct {
|
|
QueryBase
|
|
}
|
|
|
|
func (c *CarUpdates) selectFilter(query *orm.Query, filter *common.CarUpdate) {
|
|
if filter.ID > 0 {
|
|
query.Where("car_update.id = ?", filter.ID)
|
|
}
|
|
|
|
if filter.VIN != "" {
|
|
query.Where("vin = ?", filter.VIN)
|
|
}
|
|
|
|
if filter.UpdateManifestID > 0 {
|
|
query.Where("update_manifest_id = ?", filter.UpdateManifestID)
|
|
}
|
|
|
|
if filter.Status != "" {
|
|
query.Where("status = ?", filter.Status)
|
|
}
|
|
|
|
if filter.UpdateSource != "" {
|
|
query.Where("update_source = ?", filter.UpdateSource)
|
|
}
|
|
}
|
|
|
|
// StatusHistoryFilter get CarUpdates with certain status' filtered out.
|
|
// When a status is filtered out, CarUpdates with that status will still
|
|
// be returned, but will contain their last status not ignored.
|
|
func StatusHistoryFilter(query *orm.Query, ignoreStatuses []string) {
|
|
query.ColumnExpr("car_update.id")
|
|
query.ColumnExpr("car_update.vin")
|
|
query.ColumnExpr("car_update.created_at")
|
|
query.ColumnExpr("car_update.updated_at")
|
|
query.ColumnExpr("car_update.update_manifest_id")
|
|
query.ColumnExpr("car_update.error_code")
|
|
query.ColumnExpr("car_update.info")
|
|
query.ColumnExpr("car_update.username")
|
|
query.ColumnExpr("car_update_display_status.display_status AS status")
|
|
query.Join(`LEFT JOIN car_update_display_status ON car_update.id = car_update_display_status.car_update_id`)
|
|
}
|
|
|
|
// CarUpdate select by car update id
|
|
func (c *CarUpdates) SelectByID(id int64) (*common.CarUpdate, error) {
|
|
update := common.CarUpdate{ID: id}
|
|
|
|
err := c.GetDBConn().Model(&update).WherePK().
|
|
Relation("UpdateManifest").
|
|
Select()
|
|
|
|
return &update, errors.WithStack(err)
|
|
}
|
|
|
|
// SelectByVIN returns list of car updates by VIN
|
|
func (c *CarUpdates) SelectByVIN(vin string) ([]common.CarUpdate, error) {
|
|
updates := []common.CarUpdate{}
|
|
|
|
err := c.GetDBConn().Model(&updates).Where("vin = ?", vin).
|
|
Relation("UpdateManifest").
|
|
Select()
|
|
|
|
return updates, errors.WithStack(err)
|
|
}
|
|
|
|
// SelectMostRecentByVINs returns a list of most recent car update for VINs
|
|
func (c *CarUpdates) SelectMostRecentByVINs(vins []string) ([]common.CarUpdate, error) {
|
|
updates := []common.CarUpdate{}
|
|
|
|
err := c.GetDBConn().Model(&updates).
|
|
Where("vin IN (?)", pg.In(vins)).
|
|
WhereGroup(func(q *pg.Query) (*pg.Query, error) {
|
|
return q.Where("(car_update.created_at = (SELECT MAX(t2.created_at) FROM public.car_updates t2 WHERE t2.vin = car_update.vin))"), nil
|
|
}).
|
|
Relation("UpdateManifest").
|
|
Select()
|
|
|
|
return updates, errors.WithStack(err)
|
|
}
|
|
|
|
// SelectByManifestID returns list of car updates by package update id
|
|
func (c *CarUpdates) SelectByManifestID(manifest_id int64) ([]common.CarUpdate, error) {
|
|
updates := []common.CarUpdate{}
|
|
|
|
err := c.GetDBConn().Model(&updates).Where("update_manifest.id = ?", manifest_id).
|
|
Relation("UpdateManifest").
|
|
Select()
|
|
|
|
return updates, errors.WithStack(err)
|
|
}
|
|
|
|
func (c *CarUpdates) SelectOrInsert(update *common.CarUpdate) (bool, error) {
|
|
return c.insertSelectWithStack(c.GetDBConn().Model(update).Where("vin = ?vin AND update_manifest_id = ?update_manifest_id").SelectOrInsert())
|
|
}
|
|
|
|
// Select returns list of cars
|
|
func (c *CarUpdates) Select(filter *common.CarUpdate, paging *PageQueryOptions) ([]common.CarUpdate, error) {
|
|
ups := []common.CarUpdate{}
|
|
query := c.GetDBConn().Model(&ups)
|
|
c.selectFilter(query, filter)
|
|
StatusHistoryFilter(query, []string{"cleanup_succeeded"})
|
|
c.pageQuery(query, paging)
|
|
|
|
if filter.UpdateManifest != nil && filter.UpdateManifest.ManifestType > 0 {
|
|
query.
|
|
Relation("UpdateManifest", func(q *orm.Query) (*orm.Query, error) {
|
|
return q.Where("manifest_type = ?", filter.UpdateManifest.ManifestType), nil
|
|
})
|
|
} else {
|
|
query.Relation("UpdateManifest")
|
|
}
|
|
|
|
err := query.Select()
|
|
|
|
return ups, errors.WithStack(err)
|
|
}
|
|
|
|
func (c *CarUpdates) Delete(update *common.CarUpdate) (orm.Result, error) {
|
|
err := validator.ValidateIDField(update.ID)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
conn := c.GetDBConn()
|
|
tx, err := conn.Begin()
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
tx.Rollback()
|
|
}
|
|
tx.Close()
|
|
}()
|
|
|
|
_, err = tx.Model(&common.CarUpdateStatus{CarUpdateID: update.ID}).Where("car_update_id = ?car_update_id").Delete()
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
result, err := tx.Model(update).WherePK().Delete()
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
err = tx.Commit()
|
|
|
|
return result, errors.WithStack(err)
|
|
}
|
|
|
|
func (c *CarUpdates) UpdateStatus(update *common.CarUpdate) (orm.Result, error) {
|
|
result, err := c.UpdateCarUpdate(update)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
|
|
_, err = c.LogStatus(update)
|
|
|
|
return result, err
|
|
}
|
|
|
|
func (c *CarUpdates) UpdateStatusIfNotRepeat(update *common.CarUpdate) (orm.Result, error) {
|
|
orm, err := c.LogStatusIfNotARepeat(update)
|
|
if err != nil {
|
|
return orm, err
|
|
}
|
|
result, err := c.UpdateCarUpdate(update)
|
|
if err != nil {
|
|
err = errors.WithStack(err)
|
|
}
|
|
return result, err
|
|
}
|
|
|
|
func (c *CarUpdates) UpdateCarUpdate(update *common.CarUpdate) (orm.Result, error) {
|
|
err := validator.ValidateIDField(update.ID)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
return c.resultWithStack(c.GetDBConn().Model(update).Column("status", "error_code").WherePK().Update())
|
|
}
|
|
|
|
func (c *CarUpdates) LogStatus(update *common.CarUpdate) (orm.Result, error) {
|
|
// Should this also be updated to have the catch of double statuses
|
|
status := common.CarUpdateStatus{
|
|
CarUpdateID: update.ID,
|
|
Status: update.Status,
|
|
ErrorCode: update.ErrorCode,
|
|
Info: update.Info,
|
|
}
|
|
|
|
result, err := c.resultWithStack(c.GetDBConn().Model(&status).Insert())
|
|
if err != nil {
|
|
err = errors.WithStack(fmt.Errorf("LogStatus::%s. with status %d %s %d %s", err.Error(), status.CarUpdateID, status.Status, status.ErrorCode, status.Info))
|
|
logger.Err(err)
|
|
|
|
}
|
|
return result, err
|
|
}
|
|
|
|
// If the last status that was inserted is the same as the one we are trying to insert, we do not do it
|
|
var RepeatedStatus = errors.New("RepeatedStatus")
|
|
|
|
func (c *CarUpdates) LogStatusIfNotARepeat(update *common.CarUpdate) (orm orm.Result, err error) {
|
|
logger.Debug().Msgf("attempt to add new status %s for update %d", update.Status, update.ID) // CEC-5650 debugging
|
|
status := common.CarUpdateStatus{
|
|
CarUpdateID: update.ID,
|
|
Status: update.Status,
|
|
ErrorCode: update.ErrorCode,
|
|
Info: update.Info,
|
|
}
|
|
|
|
// This query insert's our status if the most recent status on the car_update_id does not match the new status
|
|
// If it does match the new status, then we do not insert
|
|
// 1: Update ID, 2: Status, 3: error_code, 4: The info, 5: UpdateID, 6: The status
|
|
query := `INSERT INTO public.car_update_statuses(
|
|
car_update_id, status, error_code, info)
|
|
SELECT ?, ?, ?, ? WHERE NOT EXISTS(
|
|
SELECT 1 FROM (SELECT id, car_update_id, status, error_code, created_at, updated_at, info
|
|
FROM public.car_update_statuses WHERE car_update_id = ? ORDER BY created_at DESC LIMIT 1) AS temp WHERE status = ?)`
|
|
orm, err = c.GetDBConn().Exec(query, status.CarUpdateID, status.Status, status.ErrorCode, status.Info, status.CarUpdateID, status.Status)
|
|
if err != nil {
|
|
err = errors.WithStack(fmt.Errorf("%s. with status %d %s %d %s", err.Error(), status.CarUpdateID, status.Status, status.ErrorCode, status.Info))
|
|
logger.Err(err)
|
|
return orm, err
|
|
}
|
|
if orm.RowsAffected() == 0 {
|
|
return orm, RepeatedStatus
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *CarUpdates) Insert(update *common.CarUpdate) (orm.Result, error) {
|
|
return c.insert(update)
|
|
}
|
|
|
|
func (c *CarUpdates) InsertAndCreateStatus(update *common.CarUpdate) (res orm.Result, err error) {
|
|
res, err = c.insert(update)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return c.LogStatus(update)
|
|
}
|
|
|
|
func (c *CarUpdates) Load(update *common.CarUpdate) error {
|
|
query := c.GetDBConn().Model(update)
|
|
|
|
if update.ID > 0 {
|
|
query.WherePK()
|
|
} else if update.VIN == "" && update.UpdateManifestID > 0 {
|
|
query.Where("vin = ?vin AND update_manifest_id = ?update_manifest_id")
|
|
} else {
|
|
return errors.New("no id, vin, update_manifest_id")
|
|
}
|
|
|
|
if update.UpdateManifest != nil && update.UpdateManifest.ManifestType > 0 {
|
|
query.
|
|
Relation("UpdateManifest", func(q *orm.Query) (*orm.Query, error) {
|
|
return q.Where("manifest_type = ?", update.UpdateManifest.ManifestType), nil
|
|
})
|
|
} else {
|
|
query.Relation("UpdateManifest")
|
|
}
|
|
|
|
return errors.WithStack(query.
|
|
Relation("UpdateManifest.ECUs").
|
|
Relation("UpdateManifest.ECUs.Files").
|
|
Relation("UpdateManifest.ECUs.Files.WriteRegion").
|
|
Relation("UpdateManifest.ECUs.Files.EraseRegion").
|
|
Select())
|
|
}
|
|
|
|
func (c *CarUpdates) Count(filter *common.CarUpdate) (int, error) {
|
|
query := c.GetDBConn().Model((*common.CarUpdate)(nil))
|
|
|
|
c.selectFilter(query, filter)
|
|
|
|
return c.countWithStack(query.Count())
|
|
}
|
|
|
|
func (c *CarUpdates) CountUpdateStatuses(carupdateid int64) (int, error) {
|
|
query := c.GetDBConn().Model((*common.CarUpdateStatus)(nil))
|
|
|
|
return c.countWithStack(query.Where("car_update_id = ?", carupdateid).Count())
|
|
}
|
|
|
|
func (c *CarUpdates) GetUpdateStatuses(carupdateid int64, paging *PageQueryOptions) ([]common.CarUpdateStatus, error) {
|
|
result := []common.CarUpdateStatus{}
|
|
query := c.GetDBConn().Model(&result)
|
|
|
|
c.pageQuery(query, paging)
|
|
|
|
err := query.Where("car_update_id = ?", carupdateid).Select()
|
|
if err == nil && len(result) == 0 {
|
|
return result, errors.WithStack(pg.ErrNoRows)
|
|
}
|
|
|
|
return result, errors.WithStack(err)
|
|
}
|
|
|
|
func (c *CarUpdates) TruncateRequirementsAwaitForUpdate(carupdateid int64) (orm.Result, error) {
|
|
queryString := `with row_nums as (select *, row_number() over (partition by car_update_id order by updated_at) as row
|
|
from car_update_statuses where status = 'requirements_await'
|
|
and car_update_id = ?)
|
|
delete from car_update_statuses where id in (
|
|
select id from row_nums where (row != (select max(row) from row_nums)) and (row != 1)
|
|
)`
|
|
|
|
err := validator.ValidateIDField(carupdateid)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
conn := c.GetDBConn()
|
|
tx, err := conn.Begin()
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
tx.Rollback()
|
|
}
|
|
tx.Close()
|
|
}()
|
|
|
|
result, err := conn.Query(&common.CarUpdateStatus{}, queryString, carupdateid)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
err = tx.Commit()
|
|
|
|
return result, errors.WithStack(err)
|
|
}
|
|
|
|
func (c *CarUpdates) GetManifest(carupdateid int64) (*common.UpdateManifest, error) {
|
|
manifest := common.UpdateManifest{}
|
|
|
|
err := c.GetDBConn().Model(&manifest).Join("JOIN car_updates").JoinOn("car_updates.update_manifest_id = update_manifest.id").JoinOn("car_updates.id = ?", carupdateid).Relation("ECUs").Relation("ECUs.Files").Select()
|
|
|
|
return &manifest, errors.WithStack(err)
|
|
}
|
|
|
|
func (c *CarUpdates) HasPendingUpdates(manifestID int64, vin string) (bool, error) {
|
|
count, err := c.GetDBConn().Model(&common.CarUpdate{}).Where("update_manifest_id = ? AND vin = ? AND status NOT IN (?)", manifestID, vin, pg.In(FINAL_UPDATE_STATUS)).Count()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return count > 0, nil
|
|
}
|
|
|
|
// An updateID of 0 means no pending update
|
|
func (c *CarUpdates) HasPendingUpdatesFromAftersalesUser(manifestID int64, vin string) (updateID int64, pendingUpdateAftersales bool, err error) {
|
|
var carUpdates []common.CarUpdate
|
|
err = c.GetDBConn().Model(&carUpdates).Where("update_manifest_id = ? AND vin = ? AND status NOT IN (?)", manifestID, vin, pg.In(FINAL_UPDATE_STATUS)).Select()
|
|
if err != nil {
|
|
return
|
|
}
|
|
if len(carUpdates) == 0 {
|
|
return
|
|
}
|
|
|
|
if len(carUpdates) > 1 {
|
|
logger.Error().Msgf("Have more than one pending update for manifestID %d on vin %s. This should not happen and our logic needs to change", manifestID, vin)
|
|
}
|
|
// This should only ever be one
|
|
for _, u := range carUpdates {
|
|
updateID = u.ID
|
|
if u.UpdateSource == common.UPDATE_SOURCE_AFTERSALES {
|
|
pendingUpdateAftersales = true
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *CarUpdates) InsertMissingFlashpack(vin string, flashpackVersion string) (err error) {
|
|
mf := common.MissingFlashpack{
|
|
VIN: vin,
|
|
FlashPackVersion: flashpackVersion,
|
|
}
|
|
_, err = c.insert(&mf)
|
|
return
|
|
}
|