Initial cloud-services repo - gateway service + pkg modules
This commit is contained in:
426
pkg/db/queries/carupdates.go
Normal file
426
pkg/db/queries/carupdates.go
Normal file
@@ -0,0 +1,426 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"fiskerinc.com/modules/common"
|
||||
"fiskerinc.com/modules/common/carupdatestatus"
|
||||
"fiskerinc.com/modules/logger"
|
||||
"fiskerinc.com/modules/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
|
||||
}
|
||||
Reference in New Issue
Block a user