package mongo import ( "strings" "github.com/fiskerinc/cloud-services/pkg/common" "github.com/fiskerinc/cloud-services/pkg/db/queries" "go.mongodb.org/mongo-driver/bson/primitive" "github.com/fiskerinc/cloud-services/pkg/utils/elptr" "github.com/pkg/errors" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" ) // NewFleetsCollection is the constructor for Fleets and utilizes the same mongo connection func NewFleetsCollection(c CollectionInterface) FleetsCollectionInterface { return &FleetsCollection{c} } type FleetsCollectionInterface interface { AddFleet(fleet *Fleet) error FindFleet(filter *Fleet) (*Fleet, error) UpdateFleet(filter *Fleet, fleet *Fleet) error DeleteFleet(fleet *Fleet) error SelectFleets(filter *Fleet, options *queries.PageQueryOptions) ([]Fleet, error) GetFleetCount(filter *Fleet) (int64, error) GetVehiclesForFleet(name, search string, options *queries.PageQueryOptions) ([]string, error) GetVehiclesForFleetCount(name, search string) (int64, error) AddVehiclesToFleet(name string, vins []string) error DeleteVehiclesFromFleet(name string, vins []string) error GetFiltersForFleet(name string, options *queries.PageQueryOptions) ([]common.CANFilter, error) GetFiltersForFleetCount(name string) (int64, error) AddFilterToFleet(name string, filter *common.CANFilter) error UpdateFilterForFleet(name string, id string, filter *common.CANFilter) error DeleteFilterFromFleet(name string, id string) error GetCANBusForVehicle(vin string) (*Fleet, error) CollectionInterface } // FleetsCollection is the client for the collection type FleetsCollection struct { CollectionInterface } // Fleet is an embedded object within Vehicle type Fleet struct { Name string `json:"name" bson:"name"` LogLevel common.LogLevel `json:"log_level" bson:"log_level,omitempty"` CANBus common.CANBus `json:"canbus" bson:"canbus,omitempty"` IDPSEnabled bool `json:"idps_enabled" bson:"idps_enabled"` DebugMask string `json:"debug_mask,omitempty" bson:"debug_mask,omitempty"` Tags []string `json:"tags" bson:"tags,omitempty"` Vehicles []string `json:"vehicles" bson:"vehicles,omitempty"` VehiclesCount int `json:"vehicles_count" bson:"vehicles_count,omitempty"` search string `json:"-" bson:"-"` } func (f *Fleet) SetSearchQuery(search string) { f.search = search } func (f *Fleet) SearchQuery() string { return f.search } func (f *Fleet) AsExpr() bson.M { if f == nil || len(strings.Trim(f.search, " ")) == 0 { return bson.M{} } return bson.M{"$text": bson.M{"$search": f.search}} } func (c *FleetsCollection) AddFleet(fleet *Fleet) error { _, err := c.InsertOne(fleet) if err != nil { return err } return nil } func (c *FleetsCollection) FindFleet(filter *Fleet) (*Fleet, error) { m := bson.M{"name": filter.Name} projection := bson.D{{"vehicles", 0}} f := &Fleet{} err := c.FindOne(m, f, projection) if err != nil { if errors.Is(err, mongo.ErrNoDocuments) { return nil, nil } return nil, err } return f, nil } func (c *FleetsCollection) UpdateFleet(filter *Fleet, fleet *Fleet) error { if fleet.CANBus.DTCEnabled == nil { fleet.CANBus.DTCEnabled = elptr.ElPtr(false) } m := bson.M{"name": filter.Name} u := bson.M{"$set": bson.M{ "name": fleet.Name, "canbus.enabled": fleet.CANBus.Enabled, "canbus.data_logger": fleet.CANBus.DataLogger, "canbus.dtc_enabled": fleet.CANBus.DTCEnabled, "canbus.mem_buff_size": fleet.CANBus.MemBuffSize, "canbus.disk_buff_size": fleet.CANBus.DiskBuffSize, "idps_enabled": fleet.IDPSEnabled, "log_level": fleet.LogLevel, "debug_mask": fleet.DebugMask, }} _, err := c.UpdateOne(m, u) if err != nil { return err } return nil } func (c *FleetsCollection) DeleteFleet(fleet *Fleet) error { m := bson.M{"name": fleet.Name} err := c.DeleteOne(m) if err != nil { return err } return nil } func (c *FleetsCollection) SelectFleets(filter *Fleet, options *queries.PageQueryOptions) ([]Fleet, error) { m, f := filter.AsExpr(), make([]Fleet, 0, 0) if err := c.Find(m, &f, options); err != nil { return nil, err } for x := range f { if f[x].CANBus.DTCEnabled == nil { f[x].CANBus.DTCEnabled = elptr.ElPtr(false) } } return f, nil } func (c *FleetsCollection) GetFleetCount(filter *Fleet) (int64, error) { m := filter.AsExpr() if len(filter.Tags) > 0 { m["tags"] = bson.M{"$all": filter.Tags} } return c.Count(m) } func (c *FleetsCollection) GetVehiclesForFleet(name, search string, options *queries.PageQueryOptions) ([]string, error) { preProc := mongo.Pipeline{ {{"$match", bson.D{{"name", name}}}}, {{"$project", bson.D{{ "vin", bson.D{{ "$cond", bson.A{bson.D{{"$isArray", "$vehicles"}}, "$vehicles", []string{}}, }}, }}}}, {{"$unwind", "$vin"}}, } if search != "" { preProc = append(preProc, bson.D{ {"$match", bson.M{"vin": primitive.Regex{ Pattern: search, Options: "i", }}}}) } preProc = append(preProc, bson.D{{"$sort", bson.D{{"vin", 1}}}}) postProc := mongo.Pipeline{ {{"$group", bson.D{{"_id", "$_id"}, {"vehicles", bson.D{{"$push", "$vin"}}}}}}, } 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 []Fleet err := c.Aggregate(pipe, &fleets) if err != nil { return nil, err } if len(fleets) != 1 { return []string{}, nil } return fleets[0].Vehicles, nil } type count struct { Total int64 `bson:"total"` } func (c *FleetsCollection) GetVehiclesForFleetCount(name, search string) (int64, error) { pipe := mongo.Pipeline{ {{"$match", bson.D{{"name", name}}}}, } if search == "" { pipe = append(pipe, bson.D{{"$project", bson.D{{ "total", bson.D{{ "$size", bson.D{{ "$cond", bson.A{ bson.D{{"$isArray", "$vehicles"}}, "$vehicles", []string{}}, }}, }}}}, }}) } else { pipe = append(pipe, bson.D{{"$project", bson.D{{"vin", bson.D{{"$cond", bson.A{ bson.D{{"$isArray", "$vehicles"}}, "$vehicles", []string{}}}}}}}}, bson.D{{"$unwind", "$vin"}}, bson.D{{"$match", bson.M{"vin": primitive.Regex{ Pattern: search, Options: "i", }}}}, bson.D{{"$count", "total"}}, ) } 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 } // Creates a Mongodb pipeline to accomplish several steps // 1. Get document by name // 2. Merge vins as sets // 3. Calculate an accurate count // 4. Store result in db func createPipelineForFleetVehicles(name string, vins []string, mergeStrategy string) mongo.Pipeline { return mongo.Pipeline{ {{"$match", bson.D{{"name", name}}}}, {{"$addFields", bson.D{ {"vehicles", bson.D{ {mergeStrategy, bson.A{ bson.D{{"$ifNull", bson.A{"$vehicles", bson.A{}}}}, vins, }}, }}, }}}, {{"$set", bson.D{ {"vehicles_count", bson.D{ {"$size", bson.D{ {"$ifNull", bson.A{"$vehicles", bson.A{}}}, }}, }}, }}}, {{"$merge", bson.D{{"into", "fleets"}}}}, } } func (c *FleetsCollection) AddVehiclesToFleet(name string, vins []string) error { if len(vins) == 0 { return errors.Errorf("No VINs to add to fleet") } pipe := createPipelineForFleetVehicles(name, vins, "$setUnion") var result []interface{} err := c.Aggregate(pipe, &result) return err } func (c *FleetsCollection) DeleteVehiclesFromFleet(name string, vins []string) error { if len(vins) == 0 { return errors.Errorf("No VINs to remove from fleet") } pipe := createPipelineForFleetVehicles(name, vins, "$setDifference") var result []interface{} err := c.Aggregate(pipe, &result) return err } func (c *FleetsCollection) GetFiltersForFleet(name string, options *queries.PageQueryOptions) ([]common.CANFilter, error) { preProc := mongo.Pipeline{ {{"$match", bson.D{{"name", name}}}}, {{"$project", bson.D{{ "canbus.filters", bson.D{{ "$cond", bson.A{bson.D{{"$isArray", "$canbus.filters"}}, "$canbus.filters", []common.CANFilter{}}, }}, }}}}, {{"$unwind", "$canbus.filters"}}, {{"$sort", bson.D{{"canbus.filters.can_id", 1}}}}, } postProc := mongo.Pipeline{ {{"$group", bson.D{{"_id", "$_id"}, {"filters", bson.D{{"$push", "$canbus.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 fleets []Fleet err := c.Aggregate(pipe, &fleets) if err != nil { return nil, err } if len(fleets) != 1 { return []common.CANFilter{}, nil } return fleets[0].CANBus.Filters, nil } func (c *FleetsCollection) GetFiltersForFleetCount(name string) (int64, error) { pipe := mongo.Pipeline{ {{"$match", bson.D{{"name", name}}}}, {{"$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 *FleetsCollection) AddFilterToFleet(name string, filter *common.CANFilter) error { m := bson.M{"name": name} u := bson.M{"$addToSet": bson.M{"canbus.filters": filter}} _, err := c.UpdateOne(m, u) if err != nil { return err } return nil } func (c *FleetsCollection) UpdateFilterForFleet(name string, id string, filter *common.CANFilter) error { m := bson.M{ "name": name, "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 *FleetsCollection) DeleteFilterFromFleet(name string, id string) error { m := bson.M{"name": name} 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 *FleetsCollection) GetCANBusForVehicle(vin string) (*Fleet, error) { pipe := mongo.Pipeline{ {{"$match", bson.D{{"vehicles", vin}}}}, {{"$unwind", bson.D{{"path", "$canbus.filters"}, {"preserveNullAndEmptyArrays", true}}}}, {{"$group", bson.D{ {"_id", nil}, {"filters", bson.D{{"$push", "$canbus.filters"}}}, {"enabled", bson.D{{"$max", "$canbus.enabled"}}}, {"data_logger", bson.D{{"$max", "$canbus.data_logger"}}}, {"mem_buff_size", bson.D{{"$min", "$canbus.mem_buff_size"}}}, {"disk_buff_size", bson.D{{"$min", "$canbus.disk_buff_size"}}}, }}}, {{"$project", bson.D{ {"canbus.filters", "$filters"}, {"canbus.enabled", "$enabled"}, {"canbus.data_logger", "$data_logger"}, {"canbus.mem_buff_size", "$mem_buff_size"}, {"canbus.disk_buff_size", "$disk_buff_size"}, {"canbus.dtc_enabled", "$dtc_enabled"}, }}}, } var fleets []Fleet err := c.Aggregate(pipe, &fleets) if err != nil { return nil, err } if len(fleets) != 1 { return nil, nil } theFleet := &fleets[0] if theFleet.CANBus.DTCEnabled == nil { theFleet.CANBus.DTCEnabled = elptr.ElPtr(false) } return theFleet, nil }