Files
cloud-services/pkg/mongo/vehicles.go

422 lines
11 KiB
Go

package mongo
import (
"strings"
"github.com/fiskerinc/cloud-services/pkg/common"
"github.com/fiskerinc/cloud-services/pkg/db/queries"
"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
)
// Vehicle serves as the schema for Vehicles collection
type Vehicle struct {
VIN string `json:"vin" bson:"vin"`
LogLevel common.LogLevel `json:"log_level" bson:"log_level"`
DLTEnabled bool `json:"dlt_enabled" bson:"dlt_enabled"`
DLTLevel int `json:"dlt_level,omitempty" bson:"dlt_level,omitempty"`
CANBus common.CANBus `json:"canbus" bson:"canbus"`
IDPSEnabled bool `json:"idps_enabled" bson:"idps_enabled"`
DebugMask string `json:"debug_mask,omitempty" bson:"debug_mask,omitempty"`
Fleets []Fleet `json:"fleets" bson:"fleets,omitempty"`
search string `json:"-" bson:"-"`
}
func (f *Vehicle) SetSearchQuery(search string) {
f.search = search
}
func (f *Vehicle) SearchQuery() string {
return f.search
}
type VehicleCANBusWithFleet struct {
CANBus struct {
Filters []common.CANFilterWithFleet `json:"filters,omitempty" bson:"filters,omitempty"`
} `json:"canbus" bson:"canbus"`
}
// NewVehiclesCollection is the construct for Vehicles and utilizes the same mongo connection
func NewVehiclesCollection(c CollectionInterface) VehiclesCollectionInterface {
return &VehiclesCollection{c}
}
type VehiclesCollectionInterface interface {
AddVehicle(vehicle *Vehicle) error
FindVehicle(filter *Vehicle) (*Vehicle, error)
UpdateVehicle(vehicle *Vehicle) error
DeleteVehicle(filter *Vehicle) error
AddFilterToVehicle(vin string, filter *common.CANFilter) error
GetFiltersForVehicle(vin string, options *queries.PageQueryOptions) ([]common.CANFilterWithFleet, error)
UpdateFilterForVehicle(vin string, id string, filter *common.CANFilter) error
DeleteFilterForVehicle(vin string, id string) error
GetFilterCountForVehicle(vin string) (int64, error)
UpdateFiltersForVehicle(vin string, filters []common.CANFilter) error
GetFleetsForVehicle(
vin, fleetName string,
options *queries.PageQueryOptions,
) ([]string, error)
GetFleetCountForVehicle(
vin, fleetName string,
) (int64, error)
CollectionInterface
}
// VehiclesCollection is the client for the collection
type VehiclesCollection struct {
CollectionInterface
}
func (c *VehiclesCollection) AddVehicle(vehicle *Vehicle) error {
vehicle.VIN = strings.ToUpper(vehicle.VIN)
_, err := c.InsertOne(vehicle)
if err != nil {
return err
}
return nil
}
func (c *VehiclesCollection) FindVehicle(filter *Vehicle) (*Vehicle, error) {
m := bson.M{"vin": filter.VIN}
v := &Vehicle{}
err := c.FindOne(m, v, nil)
if err != nil {
return nil, err
}
if v.CANBus.DTCEnabled == nil {
v.CANBus.DTCEnabled = elptr.ElPtr(false)
}
return v, nil
}
func (c *VehiclesCollection) UpdateVehicle(vehicle *Vehicle) error {
m := bson.M{"vin": vehicle.VIN}
u := bson.M{"$set": bson.M{
"canbus.enabled": vehicle.CANBus.Enabled,
"canbus.data_logger": vehicle.CANBus.DataLogger,
"canbus.dtc_enabled": vehicle.CANBus.DTCEnabled,
"canbus.mem_buff_size": vehicle.CANBus.MemBuffSize,
"canbus.disk_buff_size": vehicle.CANBus.DiskBuffSize,
"log_level": vehicle.LogLevel,
"debug_mask": vehicle.DebugMask,
"dlt_enabled": vehicle.DLTEnabled,
"dlt_level": vehicle.DLTLevel,
"idps_enabled": vehicle.IDPSEnabled,
}}
_, err := c.UpdateOne(m, u)
if err != nil {
return err
}
return nil
}
func (c *VehiclesCollection) DeleteVehicle(vehicle *Vehicle) error {
m := bson.M{"vin": vehicle.VIN}
err := c.DeleteOne(m)
if err != nil {
return err
}
return nil
}
func (c *VehiclesCollection) AddFilterToVehicle(vin string, filter *common.CANFilter) error {
m := bson.M{"vin": vin}
u := bson.M{"$addToSet": bson.M{"canbus.filters": filter}}
_, err := c.UpdateOne(m, u)
if err != nil {
return err
}
return nil
}
func (c *VehiclesCollection) filtersForVehiclePipe(
vin string,
) mongo.Pipeline {
// This pipeline works in a following way:
// 1. matches the vin from vehicles.
// 2. checks whether the canbus.filters is array, if not creates an empty one.
// 3. does lookup in fleets collection
// 3.1. checks whether fleets.vehicles is array and
// 3.2. matches a current vin with the vins in fleets.vehicles
// 3.3. checks if the canbus.filters of the matching fleet is array, if not creates an empty one
// 3.4. names it as matches
// 4. creates transformed matches as cfilters
// 4.1. goes through every canbus filter of the matches
// 4.2. concatenates them through creating a new object with a "fleet" field representing fleet name
// 4.3. eventually named as cfilters
// 5. merges cfilters and canbus.filters selected as "filters" eventually
// 6. unwinds filters
// 7. sorts the unwinded through filters.can_id
return mongo.Pipeline{
{{"$match", bson.D{{"vin", vin}}}},
{{"$project", bson.D{
{"canbus.filters", bson.D{
{"$cond",
bson.A{bson.D{{"$isArray", "$canbus.filters"}}, "$canbus.filters", bson.A{}},
},
}},
{"vin", 1}}}},
{{"$lookup",
bson.D{
{"from", "fleets"},
{"let", bson.D{{"vin", "$vin"}}},
{"pipeline", bson.A{bson.D{
{"$match", bson.D{
{"$expr", bson.D{
{"$and", bson.A{
bson.D{{"$isArray", "$vehicles"}},
bson.D{{"$in", bson.A{"$$vin", "$vehicles"}}},
}}}}}}}, bson.D{
{"$project", bson.D{
{"canbus.filters", bson.D{
{"$cond", bson.A{
bson.D{{"$isArray", "$canbus.filters"}},
"$canbus.filters",
bson.A{},
}}}},
{"name", 1},
}}}}},
{"as", "matches"}}}},
{{"$addFields", bson.D{
{"cfilters", bson.D{
{"$reduce", bson.D{
{"input", "$matches"},
{"initialValue", bson.A{}},
{"in", bson.D{
{"$concatArrays", bson.A{
"$$value", bson.D{
{"$map", bson.D{
{"input", "$$this.canbus.filters"},
{"as", "cfilter"},
{"in", bson.D{
{"fleet", "$$this.name"},
{"can_id", "$$cfilter.can_id"},
{"interval", "$$cfilter.interval"},
{"edge_mask", "$$cfilter.edge_mask"}}},
}}}}}}}}}}}}}},
{{"$project", bson.D{
{"filters", bson.D{
{"$concatArrays", bson.A{
"$canbus.filters",
"$cfilters",
}}}}}}},
{{"$unwind", "$filters"}},
{{"$sort", bson.D{{"filters.can_id", 1}}}},
}
}
func (c *VehiclesCollection) GetFiltersForVehicle(
vin string,
options *queries.PageQueryOptions,
) ([]common.CANFilterWithFleet, error) {
preProc := c.filtersForVehiclePipe(vin)
postProc := mongo.Pipeline{{
{"$group", bson.D{
{"_id", "$_id"},
{"filters", bson.D{{"$push", "$filters"}}}}}},
{{"$project", bson.D{{"canbus.filters", "$filters"}}}}}
pipe := mongo.Pipeline{}
pipe = append(pipe, preProc...)
if options.Offset > 0 {
pipe = append(pipe, bson.D{{"$skip", options.Offset}})
}
if options.Limit > 0 {
pipe = append(pipe, bson.D{{"$limit", options.Limit}})
}
pipe = append(pipe, postProc...)
var vehicles []VehicleCANBusWithFleet
err := c.Aggregate(pipe, &vehicles)
if err != nil {
return nil, err
}
if len(vehicles) != 1 {
return []common.CANFilterWithFleet{}, nil
}
return vehicles[0].CANBus.Filters, nil
}
func (c *VehiclesCollection) UpdateFilterForVehicle(vin string, id string, filter *common.CANFilter) error {
m := bson.M{
"vin": vin,
"canbus.filters.can_id": id,
}
u := bson.M{"$set": bson.M{
"canbus.filters.$.interval": filter.Interval,
"canbus.filters.$.edge_mask": filter.EdgeMask}}
_, err := c.UpdateOne(m, u)
if err != nil {
return err
}
return nil
}
func (c *VehiclesCollection) DeleteFilterForVehicle(vin string, id string) error {
m := bson.M{"vin": vin}
u := bson.M{"$pull": bson.M{"canbus.filters": bson.M{"can_id": id}}}
_, err := c.UpdateOne(m, u)
if err != nil {
return err
}
return nil
}
func (c *VehiclesCollection) GetFilterCountForVehicle(vin string) (int64, error) {
pipe := append(c.filtersForVehiclePipe(vin), mongo.Pipeline{
{{"$group", bson.D{
{"_id", "$_id"},
{"filters", bson.D{{"$push", "$filters"}}}}}},
{{"$project", bson.D{{"canbus.filters", "$filters"}}}},
{{"$project", bson.D{{
"total", bson.D{{
"$size", bson.D{{
"$cond", bson.A{bson.D{{"$isArray", "$canbus.filters"}}, "$canbus.filters", []common.CANBus{}},
}},
}}}},
}},
}...)
var counter []count
err := c.Aggregate(pipe, &counter)
if err != nil {
return 0, err
}
if len(counter) != 1 {
return 0, nil
}
return counter[0].Total, nil
}
func (c *VehiclesCollection) UpdateFiltersForVehicle(vin string, filters []common.CANFilter) error {
m := bson.M{"vin": vin}
u := bson.M{"$set": bson.M{"canbus.filters": filters}}
_, err := c.UpdateOne(m, u)
if err != nil {
return err
}
return nil
}
func (c *VehiclesCollection) getFleetsForVehiclePipe(vin, fleetName string) mongo.Pipeline {
ofFleet := bson.E{}
if fleetName != "" {
ofFleet = bson.E{"name", fleetName}
}
return mongo.Pipeline{
{{"$match", bson.D{{"vin", vin}}}},
{{"$project", bson.D{{"vin", 1}}}},
{{"$lookup", bson.D{
{"from", "fleets"},
{"let", bson.D{{"vin", "$vin"}}},
{"pipeline", bson.A{bson.D{
{"$match", bson.D{
{"$expr", bson.D{
{"$and", bson.A{
bson.D{{"$isArray", "$vehicles"}},
bson.D{{"$in", bson.A{"$$vin", "$vehicles"}}},
}}}}, ofFleet,
}}}, bson.D{
{"$project", bson.D{{"name", 1}}}}}},
{"as", "fleets"}}}},
{{"$project", bson.D{
{"fleets", bson.D{
{"$map", bson.D{
{"input", "$fleets"},
{"as", "fleet"},
{"in", "$$fleet.name"}}}}}}}},
{{"$unwind", "$fleets"}}}
}
func (c *VehiclesCollection) GetFleetCountForVehicle(
vin, fleetName string,
) (int64, error) {
pipe := append(c.getFleetsForVehiclePipe(vin, fleetName), mongo.Pipeline{
{{"$group", bson.D{
{"_id", "$_id"},
{"fleets", bson.D{{"$push", "$fleets"}}}}}},
{{"$project", bson.D{{
"total", bson.D{{
"$size", bson.D{{
"$cond", bson.A{bson.D{{"$isArray", "$fleets"}}, "$fleets", []common.CANBus{}},
}},
}}}},
}},
}...)
var counter []count
err := c.Aggregate(pipe, &counter)
if err != nil {
return 0, err
}
if len(counter) != 1 {
return 0, nil
}
return counter[0].Total, nil
}
func (c *VehiclesCollection) GetFleetsForVehicle(
vin, fleetName string,
options *queries.PageQueryOptions,
) ([]string, error) {
preProc := c.getFleetsForVehiclePipe(vin, fleetName)
postProc := mongo.Pipeline{{
{"$group", bson.D{
{"_id", "$_id"},
{"fleets", bson.D{{"$push", "$fleets"}}}}}}}
pipe := mongo.Pipeline{}
pipe = append(pipe, preProc...)
if options.Offset > 0 {
pipe = append(pipe, bson.D{{"$skip", options.Offset}})
}
if options.Limit > 0 {
pipe = append(pipe, bson.D{{"$limit", options.Limit}})
}
pipe = append(pipe, postProc...)
var fleets []struct {
Fleets []string `bson:"fleets"`
}
err := c.Aggregate(pipe, &fleets)
if err != nil {
return nil, err
}
if len(fleets) != 1 {
return []string{}, nil
}
return fleets[0].Fleets, nil
}