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 }