Initial cloud-services repo - gateway service + pkg modules
This commit is contained in:
130
pkg/mongo/client.go
Normal file
130
pkg/mongo/client.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"fiskerinc.com/modules/logger"
|
||||
"fiskerinc.com/modules/utils/envtool"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
|
||||
mongotrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
var (
|
||||
conn_str = envtool.GetEnv("MONGO_CONN_STR", "mongodb://REPLACE_ME:REPLACE_ME@localhost:27017/?authSource=admin&w=majority")
|
||||
StandardDB = envtool.GetEnv("MONGO_DB_NAME", "db")
|
||||
ODXDB = envtool.GetEnv("MONGO_ODX_DB_NAME", "odx_db")
|
||||
timeout = time.Duration(envtool.GetEnvInt("MONGO_CLIENT_TIMEOUT", 60)) * time.Second
|
||||
)
|
||||
|
||||
// Supply the database to connect to, either mongo.StandardDB or mongo.ODXDB
|
||||
func NewClient(dbToConnectTo string) (Client, error) {
|
||||
var conn Client
|
||||
client, database, err := NewMongoConnection(dbToConnectTo)
|
||||
if err != nil {
|
||||
return conn, err
|
||||
}
|
||||
conn = &Conn{
|
||||
conn: client,
|
||||
database: database,
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Sets up a mongo connection, if connection string is empty, defaults to the env connection string
|
||||
func NewMongoConnection(databaseName string) (client *mongo.Client, db *mongo.Database, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
logger.Info().Msgf("Connection string %s", conn_str)
|
||||
|
||||
serverAPIOptions := options.ServerAPI(options.ServerAPIVersion1)
|
||||
opts := options.Client().ApplyURI(conn_str).SetServerAPIOptions(serverAPIOptions)
|
||||
opts.Monitor = mongotrace.NewMonitor()
|
||||
client, err = mongo.Connect(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, nil, errors.WithStack(err)
|
||||
}
|
||||
db = client.Database(databaseName)
|
||||
return
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
GetVehicles() VehiclesCollectionInterface
|
||||
SetVehicles(v VehiclesCollectionInterface)
|
||||
|
||||
GetFleets() FleetsCollectionInterface
|
||||
SetFleets(f FleetsCollectionInterface)
|
||||
|
||||
Collection(name string) *mongo.Collection
|
||||
Close() error
|
||||
|
||||
Ping(ctx context.Context) error
|
||||
}
|
||||
|
||||
type Conn struct {
|
||||
conn *mongo.Client
|
||||
database *mongo.Database
|
||||
|
||||
vehicles VehiclesCollectionInterface
|
||||
vehiclesOnce sync.Once
|
||||
|
||||
fleets FleetsCollectionInterface
|
||||
fleetsOnce sync.Once
|
||||
}
|
||||
|
||||
func (c *Conn) GetVehicles() VehiclesCollectionInterface {
|
||||
c.vehiclesOnce.Do(func() {
|
||||
if c.vehicles == nil {
|
||||
c.vehicles = NewVehiclesCollection(
|
||||
NewCollection(c.Collection("vehicles")),
|
||||
)
|
||||
}
|
||||
})
|
||||
return c.vehicles
|
||||
}
|
||||
|
||||
func (c *Conn) SetVehicles(v VehiclesCollectionInterface) {
|
||||
c.vehicles = v
|
||||
}
|
||||
|
||||
func (c *Conn) GetFleets() FleetsCollectionInterface {
|
||||
c.fleetsOnce.Do(func() {
|
||||
if c.fleets == nil {
|
||||
c.fleets = NewFleetsCollection(
|
||||
NewCollection(c.Collection("fleets")),
|
||||
)
|
||||
}
|
||||
})
|
||||
return c.fleets
|
||||
}
|
||||
|
||||
func (c *Conn) SetFleets(f FleetsCollectionInterface) {
|
||||
c.fleets = f
|
||||
}
|
||||
|
||||
func (c *Conn) Collection(name string) *mongo.Collection {
|
||||
return c.database.Collection(name)
|
||||
}
|
||||
|
||||
func (c *Conn) Close() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
err := c.conn.Disconnect(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) Ping(ctx context.Context) error {
|
||||
return c.conn.Ping(ctx, nil)
|
||||
}
|
||||
81
pkg/mongo/client_test.go
Normal file
81
pkg/mongo/client_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package mongo_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fiskerinc.com/modules/mongo"
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
)
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
_, err := mongo.NewClient(mongo.StandardDB)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestNewClient", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientGetVehicles(t *testing.T) {
|
||||
client, err := mongo.NewClient(mongo.StandardDB)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestClientGetVehicles", nil, err)
|
||||
}
|
||||
|
||||
client.GetVehicles()
|
||||
}
|
||||
|
||||
func TestClientSetVehicles(t *testing.T) {
|
||||
client, err := mongo.NewClient(mongo.StandardDB)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestClientSetVehicles", nil, err)
|
||||
}
|
||||
|
||||
client.SetVehicles(mongo.NewVehiclesCollection(
|
||||
mongo.NewCollection(client.Collection("vehicles")),
|
||||
))
|
||||
}
|
||||
|
||||
func TestClientGetFleets(t *testing.T) {
|
||||
client, err := mongo.NewClient(mongo.StandardDB)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestClientGetFleets", nil, err)
|
||||
}
|
||||
|
||||
client.GetFleets()
|
||||
}
|
||||
|
||||
func TestClientSetFleets(t *testing.T) {
|
||||
client, err := mongo.NewClient(mongo.StandardDB)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestClientSetFleets", nil, err)
|
||||
}
|
||||
|
||||
client.SetFleets(mongo.NewFleetsCollection(
|
||||
mongo.NewCollection(client.Collection("fleets")),
|
||||
))
|
||||
}
|
||||
|
||||
func TestClientCollection(t *testing.T) {
|
||||
client, err := mongo.NewClient(mongo.StandardDB)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestClientCollection", nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
c := client.Collection("test")
|
||||
if c == nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestClientCollection", "*mongo.Collection", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientClose(t *testing.T) {
|
||||
client, err := mongo.NewClient(mongo.StandardDB)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestClientClose", nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = client.Close()
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestClientClose", nil, err)
|
||||
}
|
||||
}
|
||||
177
pkg/mongo/collection.go
Normal file
177
pkg/mongo/collection.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"fiskerinc.com/modules/db/queries"
|
||||
e "fiskerinc.com/modules/mongo/error"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func NewCollection(collection *mongo.Collection) CollectionInterface {
|
||||
return &Collection{
|
||||
collection: collection,
|
||||
}
|
||||
}
|
||||
|
||||
type CollectionInterface interface {
|
||||
InsertOne(document interface{}) (interface{}, error)
|
||||
FindOne(filter interface{}, object interface{}, projection interface{}) error
|
||||
ReplaceOne(filter interface{}, update interface{}) error
|
||||
DeleteOne(document interface{}) error
|
||||
|
||||
UpdateOne(filter interface{}, update interface{}) (*mongo.UpdateResult, error)
|
||||
UpdateMany(filter interface{}, update interface{}) error
|
||||
|
||||
Find(filter interface{}, objects interface{}, pq *queries.PageQueryOptions) error
|
||||
Count(filter interface{}) (int64, error)
|
||||
|
||||
Aggregate(pipeline mongo.Pipeline, object interface{}) error
|
||||
}
|
||||
|
||||
type Collection struct {
|
||||
collection *mongo.Collection
|
||||
}
|
||||
|
||||
// InsertOne is an abstraction over Mongo's library
|
||||
func (c *Collection) InsertOne(document interface{}) (interface{}, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
r, err := c.collection.InsertOne(ctx, document)
|
||||
if err != nil {
|
||||
return r, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return r.InsertedID, nil
|
||||
}
|
||||
|
||||
// FindOne is an abstraction over Mongo's library
|
||||
func (c *Collection) FindOne(filter interface{}, object interface{}, projection interface{}) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
opts := options.FindOne().SetProjection(projection)
|
||||
|
||||
err := c.collection.FindOne(ctx, filter, opts).Decode(object)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReplaceOne is an abstraction over Mongo's library
|
||||
func (c *Collection) ReplaceOne(filter interface{}, update interface{}) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
r, err := c.collection.ReplaceOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
} else if r.MatchedCount == 0 {
|
||||
return e.ErrInvalidNumberOfDocs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteOne is an abstraction over Mongo's library
|
||||
func (c *Collection) DeleteOne(document interface{}) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
r, err := c.collection.DeleteOne(ctx, document)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
} else if r.DeletedCount != 1 {
|
||||
return e.ErrUnableToDelete
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collection) UpdateOne(filter interface{}, update interface{}) (*mongo.UpdateResult, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
r, err := c.collection.UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
} else if r.MatchedCount == 0 {
|
||||
return nil, e.ErrInvalidNumberOfDocs
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (c *Collection) UpdateMany(filter interface{}, update interface{}) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
r, err := c.collection.UpdateMany(ctx, filter, update)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
} else if r.MatchedCount == 0 {
|
||||
return e.ErrInvalidNumberOfDocs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collection) Find(filter interface{}, objects interface{}, pq *queries.PageQueryOptions) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
f, err := c.collection.Find(
|
||||
ctx,
|
||||
filter,
|
||||
options.Find().SetLimit(int64(pq.Limit)),
|
||||
options.Find().SetSkip(int64(pq.Offset)),
|
||||
options.Find().SetSort(getOrder(pq.Order)),
|
||||
options.Find().SetProjection(getFieldsToIgnore(pq.Ignore)),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
err = f.All(ctx, objects)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collection) Count(filter interface{}) (int64, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
t, err := c.collection.CountDocuments(ctx, filter)
|
||||
if err != nil {
|
||||
return 0, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// Aggregate when all else fails
|
||||
func (c *Collection) Aggregate(pipeline mongo.Pipeline, object interface{}) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
a, err := c.collection.Aggregate(ctx, pipeline)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
err = a.All(ctx, object)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
213
pkg/mongo/collection_test.go
Normal file
213
pkg/mongo/collection_test.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package mongo_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
e "fiskerinc.com/modules/mongo/error"
|
||||
|
||||
"fiskerinc.com/modules/common"
|
||||
"fiskerinc.com/modules/mongo"
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"fiskerinc.com/modules/utils/elptr"
|
||||
)
|
||||
|
||||
func TestNewCollection(t *testing.T) {
|
||||
client, err := mongo.NewClient(mongo.StandardDB)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestNewCollection", nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
mongo.NewCollection(client.Collection("vehicles"))
|
||||
}
|
||||
|
||||
func TestCollectionInsertOneIntegration(t *testing.T) {
|
||||
t.Skip()
|
||||
|
||||
client, err := mongo.NewClient(mongo.StandardDB)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionInsertOneIntegration", nil, err)
|
||||
return
|
||||
}
|
||||
collection := mongo.NewCollection(client.Collection("vehicles"))
|
||||
|
||||
v := mongo.Vehicle{
|
||||
VIN: "TESTVIN123",
|
||||
}
|
||||
_, err = collection.InsertOne(v)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionInsertOneIntegration", nil, err)
|
||||
}
|
||||
|
||||
f := mongo.Fleet{
|
||||
Name: "testfleet",
|
||||
}
|
||||
_, err = collection.InsertOne(f)
|
||||
if !errors.Is(err, e.ErrInvalidStruct) {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionInsertOneIntegration", e.ErrInvalidStruct, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectionFindOneIntegration(t *testing.T) {
|
||||
t.Skip()
|
||||
|
||||
client, err := mongo.NewClient(mongo.StandardDB)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionFindOneIntegration", nil, err)
|
||||
return
|
||||
}
|
||||
collection := mongo.NewCollection(client.Collection("vehicles"))
|
||||
|
||||
var vehicle *mongo.Vehicle
|
||||
vin := "TESTVIN123"
|
||||
err = collection.FindOne(vin, vehicle, nil)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionFindOneIntegration", nil, err)
|
||||
}
|
||||
|
||||
vin = "TESTVIN456"
|
||||
err = collection.FindOne(vin, vehicle, nil)
|
||||
if err == nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionFindOneIntegration", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectionReplaceOneIntegration(t *testing.T) {
|
||||
t.Skip()
|
||||
|
||||
client, err := mongo.NewClient(mongo.StandardDB)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionReplaceOneIntegration", nil, err)
|
||||
return
|
||||
}
|
||||
collection := mongo.NewCollection(client.Collection("vehicles"))
|
||||
|
||||
f := bson.M{"vin": "TESTVIN123"}
|
||||
u := bson.M{"$set": bson.M{"fleets": bson.A{}}}
|
||||
_, err = collection.UpdateOne(f, u)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionReplaceOneIntegration", nil, err)
|
||||
}
|
||||
|
||||
f = bson.M{"vin": "TESTVIN456"}
|
||||
u = bson.M{"$set": bson.M{"fleets": bson.A{}}}
|
||||
_, err = collection.UpdateOne(f, u)
|
||||
if err == nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionReplaceOneIntegration", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectionDeleteOneIntegration(t *testing.T) {
|
||||
t.Skip()
|
||||
|
||||
client, err := mongo.NewClient(mongo.StandardDB)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionDeleteOneIntegration", nil, err)
|
||||
return
|
||||
}
|
||||
collection := mongo.NewCollection(client.Collection("vehicles"))
|
||||
|
||||
f := bson.M{"vin": "TESTVIN123"}
|
||||
err = collection.DeleteOne(f)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionDeleteOneIntegration", nil, err)
|
||||
}
|
||||
|
||||
f = bson.M{"vin": "TESTVIN456"}
|
||||
err = collection.DeleteOne(f)
|
||||
if err == nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionDeleteOneIntegration", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectionUpdateOneIntegration(t *testing.T) {
|
||||
t.Skip()
|
||||
|
||||
client, err := mongo.NewClient(mongo.StandardDB)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionUpdateOneIntegration", nil, err)
|
||||
return
|
||||
}
|
||||
collection := mongo.NewCollection(client.Collection("vehicles"))
|
||||
|
||||
m := bson.M{"vin": "TESTVIN123"}
|
||||
u := bson.M{"$addToSet": bson.M{"canbus.filters": []common.CANFilter{{CANID: "123", Interval: elptr.ElPtr(100)}}}}
|
||||
_, err = collection.UpdateOne(m, u)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionUpdateOneIntegration", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectionUpdateManyIntegration(t *testing.T) {
|
||||
t.Skip()
|
||||
|
||||
client, err := mongo.NewClient(mongo.StandardDB)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionUpdateManyIntegration", nil, err)
|
||||
return
|
||||
}
|
||||
collection := mongo.NewCollection(client.Collection("vehicles"))
|
||||
|
||||
m := bson.M{}
|
||||
u := bson.M{"$addToSet": bson.M{"canbus.filters": []common.CANFilter{{CANID: "123", Interval: elptr.ElPtr(100)}}}}
|
||||
err = collection.UpdateMany(m, u)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionUpdateManyIntegration", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectionFindIntegration(t *testing.T) {
|
||||
t.Skip()
|
||||
|
||||
client, err := mongo.NewClient(mongo.StandardDB)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionFindIntegration", nil, err)
|
||||
return
|
||||
}
|
||||
collection := mongo.NewCollection(client.Collection("vehicles"))
|
||||
|
||||
m := bson.M{"vin": "TESTVIN123"}
|
||||
vehicles := []mongo.Vehicle{}
|
||||
err = collection.Find(m, &vehicles, nil)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionFindIntegration", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectionCountIntegration(t *testing.T) {
|
||||
t.Skip()
|
||||
|
||||
client, err := mongo.NewClient(mongo.StandardDB)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionCountIntegration", nil, err)
|
||||
return
|
||||
}
|
||||
collection := mongo.NewCollection(client.Collection("vehicles"))
|
||||
|
||||
m := bson.M{}
|
||||
_, err = collection.Count(m)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionCountIntegration", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectionAggregateIntegration(t *testing.T) {
|
||||
t.Skip()
|
||||
|
||||
client, err := mongo.NewClient(mongo.StandardDB)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionAggregateIntegration", nil, err)
|
||||
return
|
||||
}
|
||||
collection := mongo.NewCollection(client.Collection("vehicles"))
|
||||
|
||||
pipeline := []bson.D{}
|
||||
vehicles := []mongo.Vehicle{}
|
||||
err = collection.Aggregate(pipeline, &vehicles)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestCollectionAggregateIntegration", nil, err)
|
||||
}
|
||||
}
|
||||
154
pkg/mongo/dtc_codes.go
Normal file
154
pkg/mongo/dtc_codes.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"fiskerinc.com/modules/common"
|
||||
"fiskerinc.com/modules/logger"
|
||||
"github.com/pkg/errors"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
type DTCCollectionInterface interface {
|
||||
GetDTCDefinitionByHexString(troubleCodeHex string) (info common.DTCInformation, err error)
|
||||
GetLatestPDXVersions() (latestVersion PDXVersion, err error)
|
||||
}
|
||||
|
||||
var (
|
||||
pdxMongodbOnce sync.Once
|
||||
pdxMongodbInstance *DTCCollection
|
||||
)
|
||||
|
||||
type DTCCollection struct {
|
||||
CollectionInterface
|
||||
Client *mongo.Client
|
||||
DB *mongo.Database
|
||||
}
|
||||
|
||||
func GetPDXMongoClient() (*DTCCollection, error) {
|
||||
var err error
|
||||
pdxMongodbOnce.Do(func() {
|
||||
logger.Info().Msg("init MongoDB instance")
|
||||
pdxMongodbInstance = &DTCCollection{}
|
||||
var cl *mongo.Client
|
||||
var db *mongo.Database
|
||||
cl, db, err = NewMongoConnection(ODXDB)
|
||||
|
||||
pdxMongodbInstance.Client = cl
|
||||
pdxMongodbInstance.DB = db
|
||||
})
|
||||
return pdxMongodbInstance, err
|
||||
}
|
||||
|
||||
// Given a Hex String for 3 bytes, get the dtc information from mongo
|
||||
func (coll *DTCCollection) GetDTCDefinitionByHexString(troubleCodeHex string, ecuSource string) (info *common.DTCInformation, err error) {
|
||||
if replacement, exist := common.CarDTCLookupInverse[ecuSource]; exist {
|
||||
ecuSource = replacement
|
||||
}
|
||||
|
||||
collect := coll.DB.Collection("dtc_lookup")
|
||||
//db.dtc_lookup.aggregate({"$match": {"ecuName": "ECU", "DtcUniqueId": "V2.6.0"}}, {"$unwind": "$dtcData.DtcList"}, {"$match": {"dtcData.DtcList.TroubleCodeHex":"800156"}}, {"$project":{"_id": 0, "Obj": "$dtcData.DtcList"}})
|
||||
pdxVersion, err := coll.GetLatestPDXVersions()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
a := bson.D{}
|
||||
if ecuSource == "AMP" {
|
||||
a = bson.D{{"$match", bson.D{{"ecuName", bson.M{"$in": []string{"AMPSO", "AMPHA"}}}, {"DtcUniqueId", pdxVersion.PDXVersion}}}}
|
||||
} else {
|
||||
a = bson.D{{"$match", bson.D{{"ecuName", ecuSource}, {"DtcUniqueId", pdxVersion.PDXVersion}}}}
|
||||
}
|
||||
|
||||
b := bson.D{{"$unwind", "$dtcData.DtcList"}}
|
||||
c := bson.D{{"$match", bson.D{{"dtcData.DtcList.TroubleCodeHex", troubleCodeHex}}}}
|
||||
d := bson.D{{"$project", bson.D{{"_id", 0}, {"Obj", "$dtcData.DtcList"}}}}
|
||||
cursor, err := collect.Aggregate(context.Background(), mongo.Pipeline{a, b, c, d})
|
||||
if err != nil {
|
||||
err = errors.WithStack(err)
|
||||
logger.Err(err).Msg("Failed to call aggregate in GetDTCDefinitionByHexString")
|
||||
return
|
||||
}
|
||||
|
||||
var obj DTCOuterObj
|
||||
for cursor.Next(context.Background()) {
|
||||
err = cursor.Decode(&obj)
|
||||
if err != nil {
|
||||
err = errors.WithStack(err)
|
||||
logger.Err(err).Msg("Failed to decode mongo object into DTCInformation")
|
||||
return
|
||||
}
|
||||
info = &obj.DTCInformation
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// This value should be cached for a period of time
|
||||
func (coll *DTCCollection) GetLatestPDXVersions() (latestVersion PDXVersion, err error) {
|
||||
//db.vod_lookup.aggregate({"$project": {"VodUniqueId": 1, "createDate": 1, "updateDate": 1}})
|
||||
collect := coll.DB.Collection("vod_lookup")
|
||||
a := bson.D{{"$project", bson.D{{"VodUniqueId", 1}, {"createDate", 1}, {"updateDate", 1}, {"_id", 0}}}}
|
||||
cursor, err := collect.Aggregate(context.Background(), mongo.Pipeline{a})
|
||||
if err != nil {
|
||||
err = errors.WithStack(err)
|
||||
logger.Err(err).Msg("")
|
||||
return
|
||||
}
|
||||
|
||||
// Need to determine the latest version
|
||||
for cursor.Next(context.Background()) {
|
||||
var ag PDXVersion
|
||||
err = cursor.Decode(&ag)
|
||||
if err != nil {
|
||||
err = errors.WithStack(err)
|
||||
logger.Err(err).Msg("")
|
||||
return
|
||||
}
|
||||
if isVersionNewer(latestVersion.PDXVersion, ag.PDXVersion) {
|
||||
latestVersion = ag
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Give the one we have as the newest, and then the new value to compare to
|
||||
func isVersionNewer(lastNewest, newPossibleNewest string) (isNewer bool) {
|
||||
// Better algorithm, turn V1.3.2.1 => 1321 and add any 0's on the end if the other number is longer
|
||||
if lastNewest == "" {
|
||||
return true
|
||||
}
|
||||
// Remove the V
|
||||
lastNewest = lastNewest[1:]
|
||||
newPossibleNewest = newPossibleNewest[1:]
|
||||
last := strings.Split(lastNewest, ".")
|
||||
new := strings.Split(newPossibleNewest, ".")
|
||||
for x := 0; x < len(last); x++ {
|
||||
// We have gotten to the end of the second string without being less, so we are greater, like a hotfix
|
||||
if x >= len(new) {
|
||||
return false
|
||||
}
|
||||
l, _ := strconv.Atoi(last[x])
|
||||
n, _ := strconv.Atoi(new[x])
|
||||
|
||||
if n > l {
|
||||
return true
|
||||
} else if l > n {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return len(new) > len(last)
|
||||
}
|
||||
|
||||
type DTCOuterObj struct {
|
||||
common.DTCInformation `bson:"Obj"`
|
||||
}
|
||||
|
||||
type PDXVersion struct {
|
||||
PDXVersion string `bson:"VodUniqueId"`
|
||||
CreatedTime time.Time `bson:"createDate"`
|
||||
UpdatedTime time.Time `bson:"updateDate"`
|
||||
}
|
||||
64
pkg/mongo/dtc_codes_test.go
Normal file
64
pkg/mongo/dtc_codes_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// A test to see if twe get the response form the database at all
|
||||
|
||||
func TestGetDTCFromMongo(t *testing.T){
|
||||
t.Skip()
|
||||
mg, err := GetPDXMongoClient()
|
||||
if err != nil{
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
|
||||
info, err := mg.GetDTCDefinitionByHexString("800156", "ACU")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
b, _ := json.Marshal(info)
|
||||
t.Log(string(b))
|
||||
}
|
||||
|
||||
func TestGetLatestPDXVersions(t *testing.T){
|
||||
t.Skip()
|
||||
mg, err := GetPDXMongoClient()
|
||||
|
||||
if err != nil{
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
version, err := mg.GetLatestPDXVersions()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if version.PDXVersion == ""{
|
||||
t.Fail()
|
||||
}
|
||||
t.Log(version)
|
||||
}
|
||||
|
||||
func TestIsVersionNewer(t *testing.T){
|
||||
if !isVersionNewer("V1.2.3", "V2.3.4"){
|
||||
t.Log("V1.2.3", "V2.3.4")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if !isVersionNewer("V1.2.3", "V1.2.3.4"){
|
||||
t.Log("V1.2.3", "V1.2.3.4")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if isVersionNewer("V1.2.3.4", "V1.2.3"){
|
||||
t.Log("V1.2.3.4", "V1.2.3")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if isVersionNewer("V1.3.4", "V1.3.3.3"){
|
||||
t.Log("V1.3.4", "V1.3.3.3")
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
9
pkg/mongo/error/errors.go
Normal file
9
pkg/mongo/error/errors.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package error
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
var ErrInvalidStruct = errors.New("invalid struct passed through parameters")
|
||||
var ErrInvalidNumberOfDocs = errors.New("invalid number of docs returned from db")
|
||||
|
||||
var ErrUnableToUpdate = errors.New("unable to edit doc in db")
|
||||
var ErrUnableToDelete = errors.New("unable to delete doc in db")
|
||||
15
pkg/mongo/fields.go
Normal file
15
pkg/mongo/fields.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
func getFieldsToIgnore(fields []string) bson.M {
|
||||
projection := bson.M{}
|
||||
|
||||
for _, field := range fields {
|
||||
projection[field] = 0
|
||||
}
|
||||
|
||||
return projection
|
||||
}
|
||||
21
pkg/mongo/fields_test.go
Normal file
21
pkg/mongo/fields_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
func TestGetFieldsToIgnore(t *testing.T) {
|
||||
got := getFieldsToIgnore([]string{"field1", "field2"})
|
||||
|
||||
expected := fmt.Sprintf("%+v", bson.M{
|
||||
"field1": 0,
|
||||
"field2": 0,
|
||||
})
|
||||
|
||||
if fmt.Sprintf("%+v", got) != expected {
|
||||
t.Errorf("getFieldsToIgnore = %+v; want %s", got, expected)
|
||||
}
|
||||
}
|
||||
455
pkg/mongo/fleets.go
Normal file
455
pkg/mongo/fleets.go
Normal file
@@ -0,0 +1,455 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"fiskerinc.com/modules/common"
|
||||
"fiskerinc.com/modules/db/queries"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
|
||||
"fiskerinc.com/modules/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
|
||||
}
|
||||
201
pkg/mongo/fleets_test.go
Normal file
201
pkg/mongo/fleets_test.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package mongo_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fiskerinc.com/modules/common"
|
||||
"fiskerinc.com/modules/db/queries"
|
||||
"fiskerinc.com/modules/mongo"
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
"fiskerinc.com/modules/utils/elptr"
|
||||
)
|
||||
|
||||
func TestNewFleetsCollection(t *testing.T) {
|
||||
client, err := mongo.NewClient(mongo.StandardDB)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestNewFleetsCollection", nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
mongo.NewFleetsCollection(mongo.NewCollection(client.Collection("fleets")))
|
||||
}
|
||||
|
||||
func TestFleetsAddFleet(t *testing.T) {
|
||||
collection := mongo.FleetsCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
err := collection.AddFleet(&mongo.Fleet{
|
||||
Name: "TEST-FLEET",
|
||||
LogLevel: 2,
|
||||
CANBus: common.CANBus{
|
||||
Enabled: true,
|
||||
DataLogger: true,
|
||||
DTCEnabled: elptr.ElPtr(true),
|
||||
MemBuffSize: 1 * 1024 * 1024,
|
||||
DiskBuffSize: 10 * 1024 * 1024,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestFleetsAddFleet", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFleetsFindFleet(t *testing.T) {
|
||||
collection := mongo.FleetsCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
_, err := collection.FindFleet(&mongo.Fleet{Name: "TEST-FLEET"})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestFleetsFindFleet", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFleetsUpdateFleet(t *testing.T) {
|
||||
collection := mongo.FleetsCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
fleet := mongo.Fleet{Name: "TEST-FlEET", CANBus: common.CANBus{Enabled: true, DataLogger: true, DTCEnabled: elptr.ElPtr(true)}, DebugMask: "12", IDPSEnabled: true}
|
||||
err := collection.UpdateFleet(&mongo.Fleet{Name: "TEST-FLEET"}, &fleet)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestFleetsUpdateFleet", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFleetsDeleteFleet(t *testing.T) {
|
||||
collection := mongo.FleetsCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
err := collection.AddFleet(&mongo.Fleet{Name: "TEST-FLEET"})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestFleetsDeleteFleet", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFleetsSelectFleets(t *testing.T) {
|
||||
collection := mongo.FleetsCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
_, err := collection.SelectFleets(&mongo.Fleet{Name: "TEST-FLEET"}, &queries.PageQueryOptions{Offset: 1, Limit: 1})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestFleetsSelectFleets", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFleetsGetFleetCount(t *testing.T) {
|
||||
collection := mongo.FleetsCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
_, err := collection.GetFleetCount(&mongo.Fleet{Name: "TEST-FLEET"})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestFleetsGetFleetCount", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFleetsGetVehiclesForFleet(t *testing.T) {
|
||||
collection := mongo.FleetsCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
_, err := collection.GetVehiclesForFleet("TEST-FLEET", "", &queries.PageQueryOptions{Offset: 1, Limit: 1})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestFleetsGetVehiclesForFleet", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFleetsGetVehiclesForFleetCount(t *testing.T) {
|
||||
collection := mongo.FleetsCollection{
|
||||
&mongo.MockCollection{
|
||||
AggregateObject: []struct{ Total int64 }{{1}},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := collection.GetVehiclesForFleetCount("TEST-FLEET", "")
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestFleetsGetVehiclesForFleetCount", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFleetsAddVehiclesToFleet(t *testing.T) {
|
||||
collection := mongo.FleetsCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
err := collection.AddVehiclesToFleet("TEST-FLEET", []string{"TESTVIN123"})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestFleetsAddVehiclesToFleet", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFleetsRemoveVehicleFromFleet(t *testing.T) {
|
||||
collection := mongo.FleetsCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
err := collection.DeleteVehiclesFromFleet("TEST-FLEET", []string{"TESTVIN123"})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestFleetsRemoveVehicleFromFleet", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFleetsGetFiltersForFleet(t *testing.T) {
|
||||
collection := mongo.FleetsCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
_, err := collection.GetFiltersForFleet("TEST-FLEET", &queries.PageQueryOptions{Offset: 1, Limit: 1})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestFleetsGetFiltersForFleet", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFleetsAddFilterToFleet(t *testing.T) {
|
||||
collection := mongo.FleetsCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
err := collection.AddFilterToFleet("TEST-FLEET",
|
||||
&common.CANFilter{CANID: "123", Interval: elptr.ElPtr(456)})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestFleetsAddFilterToFleet", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFleetsUpdateFilterForFleet(t *testing.T) {
|
||||
collection := mongo.FleetsCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
err := collection.UpdateFilterForFleet("TEST-FLEET", "123",
|
||||
&common.CANFilter{CANID: "123", Interval: elptr.ElPtr(789)})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestFleetsUpdateFilterForFleet", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFleetsDeleteFilterForleet(t *testing.T) {
|
||||
collection := mongo.FleetsCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
err := collection.DeleteFilterFromFleet("TEST-FLEET", "123")
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestFleetsDeleteFilterForleet", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFleetsGetFiltersForVehicle(t *testing.T) {
|
||||
collection := mongo.FleetsCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
_, err := collection.GetCANBusForVehicle("TESTVIN123")
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestFleetsGetFiltersForVehicle", nil, err)
|
||||
}
|
||||
}
|
||||
139
pkg/mongo/mock.go
Normal file
139
pkg/mongo/mock.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"fiskerinc.com/modules/db/queries"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
func NewMockClient() Client {
|
||||
c := &Conn{}
|
||||
c.SetVehicles(&VehiclesCollection{&MockCollection{}})
|
||||
c.SetFleets(&FleetsCollection{&MockCollection{}})
|
||||
return &mockClient{c}
|
||||
}
|
||||
|
||||
type mockClient struct {
|
||||
*Conn
|
||||
}
|
||||
|
||||
func (m *mockClient) Ping(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MockCollection is used as an embedded struct for mock tests
|
||||
type MockCollection struct {
|
||||
AggregateObject interface{}
|
||||
FindObject interface{}
|
||||
}
|
||||
|
||||
func (m *MockCollection) InsertOne(document interface{}) (interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockCollection) FindOne(filter interface{}, object interface{}, projection interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockCollection) ReplaceOne(filter interface{}, update interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockCollection) DeleteOne(document interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockCollection) UpdateOne(filter interface{}, update interface{}) (*mongo.UpdateResult, error) {
|
||||
return &mongo.UpdateResult{
|
||||
MatchedCount: 0,
|
||||
ModifiedCount: 1,
|
||||
UpsertedCount: 0,
|
||||
UpsertedID: []string{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockCollection) UpdateMany(filter interface{}, update interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockCollection) Find(filter interface{}, objects interface{}, options *queries.PageQueryOptions) error {
|
||||
data, err := json.Marshal(m.FindObject)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, objects)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockCollection) Count(filter interface{}) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (m *MockCollection) Aggregate(pipeline mongo.Pipeline, object interface{}) error {
|
||||
data, err := json.Marshal(m.AggregateObject)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, object)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// A separate client where mock collection have bunch of methods overwritten
|
||||
// specifically for certain unit tests like the one to validate DebugMask enablement.
|
||||
func NewMockMongoClient() Client {
|
||||
c := &Conn{}
|
||||
m := newMockVehicleCollection()
|
||||
c.SetVehicles(&VehiclesCollection{&m})
|
||||
c.SetFleets(&FleetsCollection{&MockCollection{}})
|
||||
return &mockMongoClient{c}
|
||||
}
|
||||
|
||||
type mockMongoClient struct {
|
||||
*Conn
|
||||
}
|
||||
|
||||
// It carries a specific vehicle inserted for mocking.
|
||||
type MockVehicleCollection struct {
|
||||
VehiclesCollection
|
||||
vehicle Vehicle
|
||||
}
|
||||
|
||||
func newMockVehicleCollection() MockVehicleCollection {
|
||||
return MockVehicleCollection{}
|
||||
}
|
||||
|
||||
func (m *MockVehicleCollection) InsertOne(document interface{}) (interface{}, error) {
|
||||
vh := document.(*Vehicle)
|
||||
m.vehicle = *vh
|
||||
return vh, nil
|
||||
}
|
||||
|
||||
func (m *MockVehicleCollection) FindOne(filter interface{}, object interface{}, projection interface{}) error {
|
||||
argVh := object.(*Vehicle)
|
||||
argVh.VIN = m.vehicle.VIN
|
||||
argVh.DebugMask = m.vehicle.DebugMask
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockVehicleCollection) FindVehicle(vf *Vehicle) (*Vehicle, error) {
|
||||
v := m.vehicle
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func (m *MockVehicleCollection) GetVehicles() VehiclesCollectionInterface {
|
||||
return m
|
||||
}
|
||||
30
pkg/mongo/sort.go
Normal file
30
pkg/mongo/sort.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
func AdaptOrder(orderString string, adaptMap map[string]string) string {
|
||||
for old, new := range adaptMap {
|
||||
orderString = strings.Replace(orderString, old, new, -1)
|
||||
}
|
||||
|
||||
return orderString
|
||||
}
|
||||
|
||||
func getOrder(queryOrder string) bson.M {
|
||||
order := bson.M{"_id": -1}
|
||||
orderSlice := strings.Split(queryOrder, " ")
|
||||
if len(orderSlice) > 1 {
|
||||
asc := 1
|
||||
if strings.ToLower(orderSlice[1]) == "desc" {
|
||||
asc = -1
|
||||
}
|
||||
|
||||
order = bson.M{orderSlice[0]: asc}
|
||||
}
|
||||
|
||||
return order
|
||||
}
|
||||
421
pkg/mongo/vehicles.go
Normal file
421
pkg/mongo/vehicles.go
Normal file
@@ -0,0 +1,421 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"fiskerinc.com/modules/common"
|
||||
"fiskerinc.com/modules/db/queries"
|
||||
|
||||
"fiskerinc.com/modules/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
|
||||
}
|
||||
164
pkg/mongo/vehicles_test.go
Normal file
164
pkg/mongo/vehicles_test.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package mongo_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fiskerinc.com/modules/common"
|
||||
"fiskerinc.com/modules/db/queries"
|
||||
"fiskerinc.com/modules/mongo"
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
"fiskerinc.com/modules/utils/elptr"
|
||||
)
|
||||
|
||||
func TestNewVehiclesCollection(t *testing.T) {
|
||||
client, err := mongo.NewClient(mongo.StandardDB)
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestNewVehiclesCollection", nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
mongo.NewVehiclesCollection(mongo.NewCollection(client.Collection("vehicles")))
|
||||
}
|
||||
|
||||
func TestVehiclesAddVehicle(t *testing.T) {
|
||||
collection := mongo.VehiclesCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
err := collection.AddVehicle(&mongo.Vehicle{VIN: "TESTVIN123", CANBus: common.CANBus{Enabled: true, DataLogger: true, DTCEnabled: elptr.ElPtr(true)}})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestVehiclesAddVehicle", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVehiclesFindVehicle(t *testing.T) {
|
||||
collection := mongo.VehiclesCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
_, err := collection.FindVehicle(&mongo.Vehicle{VIN: "TESTVIN123"})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestVehiclesFindVehicle", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVehiclesUpdateVehicle(t *testing.T) {
|
||||
collection := mongo.VehiclesCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
err := collection.AddVehicle(&mongo.Vehicle{VIN: "TESTVIN123", CANBus: common.CANBus{Enabled: true, DataLogger: true, DTCEnabled: elptr.ElPtr(false)}, DebugMask: "E"})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestVehiclesUpdateVehicle", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVehiclesDeleteVehicle(t *testing.T) {
|
||||
collection := mongo.VehiclesCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
err := collection.DeleteVehicle(&mongo.Vehicle{VIN: "TESTVIN123"})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestVehiclesDeleteVehicle", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVehiclesAddFilterToVehicle(t *testing.T) {
|
||||
collection := mongo.VehiclesCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
err := collection.AddFilterToVehicle("TESTVIN123",
|
||||
&common.CANFilter{CANID: "123", Interval: elptr.ElPtr(100)})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestAddFilterToVehicle", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVehiclesGetFiltersForVehicle(t *testing.T) {
|
||||
collection := mongo.VehiclesCollection{
|
||||
&mongo.MockCollection{
|
||||
AggregateObject: []mongo.Vehicle{{VIN: "TESTVIN123"}},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := collection.GetFiltersForVehicle("TESTVIN123", &queries.PageQueryOptions{Offset: 1, Limit: 1})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestVehiclesGetFiltersForVehicle", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVehiclesGetFleetsForVehicle(t *testing.T) {
|
||||
collection := mongo.VehiclesCollection{
|
||||
&mongo.MockCollection{
|
||||
AggregateObject: []struct {
|
||||
Fleets []string `bson:"fleets"`
|
||||
}{{Fleets: []string{"fleet1", "fleet2"}}}}}
|
||||
|
||||
_, err := collection.GetFleetsForVehicle(
|
||||
"TESTVIN123", "", &queries.PageQueryOptions{Offset: 1, Limit: 1})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestVehiclesGetFleetsForVehicle", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVehiclesGetFleetsCountForVehicle(t *testing.T) {
|
||||
collection := mongo.VehiclesCollection{
|
||||
&mongo.MockCollection{
|
||||
AggregateObject: []struct{ Total int64 }{{1}},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := collection.GetFleetCountForVehicle("TESTVIN123", "")
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestVehiclesGetFleetsCountForVehicle", nil, err)
|
||||
}
|
||||
}
|
||||
func TestVehiclesUpdateFilterForVehicle(t *testing.T) {
|
||||
collection := mongo.VehiclesCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
err := collection.UpdateFilterForVehicle("TESTVIN123", "123",
|
||||
&common.CANFilter{Interval: elptr.ElPtr(100)})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestVehiclesUpdateFilterForVehicle", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVehiclesDeleteFilterForVehicle(t *testing.T) {
|
||||
collection := mongo.VehiclesCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
err := collection.DeleteFilterForVehicle("TESTVIN123", "123")
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestVehiclesDeleteFilterForVehicle", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVehiclesGetFilterCountForVehicle(t *testing.T) {
|
||||
collection := mongo.VehiclesCollection{
|
||||
&mongo.MockCollection{
|
||||
AggregateObject: []struct{ Total int64 }{{1}},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := collection.GetFilterCountForVehicle("TESTVIN123")
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestVehiclesGetFilterCountForVehicle", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVehiclesUpdateFiltersForVehicle(t *testing.T) {
|
||||
collection := mongo.VehiclesCollection{
|
||||
&mongo.MockCollection{},
|
||||
}
|
||||
|
||||
err := collection.UpdateFiltersForVehicle("TESTVIN123", []common.CANFilter{{CANID: "123",
|
||||
Interval: elptr.ElPtr(100)}})
|
||||
if err != nil {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TestVehiclesUpdateFiltersForVehicle", nil, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user