github.com/companieshouse/lfp-pay-api@v0.0.0-20230203133422-0ca455cd79f9/dao/mongo.go (about) 1 package dao 2 3 import ( 4 "context" 5 "errors" 6 "os" 7 "time" 8 9 "github.com/companieshouse/chs.go/log" 10 "github.com/companieshouse/lfp-pay-api-core/models" 11 "github.com/companieshouse/lfp-pay-api/e5" 12 "go.mongodb.org/mongo-driver/bson" 13 "go.mongodb.org/mongo-driver/bson/primitive" 14 "go.mongodb.org/mongo-driver/mongo" 15 "go.mongodb.org/mongo-driver/mongo/options" 16 ) 17 18 var client *mongo.Client 19 20 func getMongoClient(mongoDBURL string) *mongo.Client { 21 if client != nil { 22 return client 23 } 24 25 ctx := context.Background() 26 27 clientOptions := options.Client().ApplyURI(mongoDBURL) 28 client, err := mongo.Connect(ctx, clientOptions) 29 30 // assume the caller of this func cannot handle the case where there is no database connection so the prog must 31 // crash here as the service cannot continue. 32 if err != nil { 33 log.Error(err) 34 os.Exit(1) 35 } 36 37 // check we can connect to the mongodb instance. failure here should result in a crash. 38 pingContext, cancel := context.WithDeadline(ctx, time.Now().Add(5*time.Second)) 39 defer cancel() 40 err = client.Ping(pingContext, nil) 41 if err != nil { 42 log.Error(errors.New("ping to mongodb timed out. please check the connection to mongodb and that it is running")) 43 os.Exit(1) 44 } 45 46 log.Info("connected to mongodb successfully") 47 48 return client 49 } 50 51 // MongoDatabaseInterface is an interface that describes the mongodb driver 52 type MongoDatabaseInterface interface { 53 Collection(name string, opts ...*options.CollectionOptions) *mongo.Collection 54 } 55 56 func getMongoDatabase(mongoDBURL, databaseName string) MongoDatabaseInterface { 57 return getMongoClient(mongoDBURL).Database(databaseName) 58 } 59 60 // MongoService is an implementation of the Service interface using MongoDB as the backend driver. 61 type MongoService struct { 62 db MongoDatabaseInterface 63 CollectionName string 64 } 65 66 // SaveE5Error will update the resource by flagging an error in e5 for a particular action 67 func (m *MongoService) SaveE5Error(companyNumber, reference string, action e5.Action) error { 68 dao, err := m.GetPayableResource(companyNumber, reference) 69 if err != nil { 70 log.Error(err, log.Data{"company_number": companyNumber, "lfp_reference": reference}) 71 return err 72 } 73 74 filter := bson.M{"_id": dao.ID} 75 update := bson.D{ 76 { 77 "$set", bson.D{ 78 {"e5_command_error", string(action)}, 79 }, 80 }, 81 } 82 83 collection := m.db.Collection(m.CollectionName) 84 85 log.Debug("updating e5 command error in mongo document", log.Data{"_id": dao.ID}) 86 87 _, err = collection.UpdateOne(context.Background(), filter, update) 88 if err != nil { 89 log.Error(err, log.Data{"_id": dao.ID, "company_number": dao.CompanyNumber, "reference": dao.Reference}) 90 return err 91 } 92 93 return nil 94 } 95 96 // CreatePayableResource will store the payable request into the database 97 func (m *MongoService) CreatePayableResource(dao *models.PayableResourceDao) error { 98 99 dao.ID = primitive.NewObjectID() 100 101 collection := m.db.Collection(m.CollectionName) 102 _, err := collection.InsertOne(context.Background(), dao) 103 if err != nil { 104 log.Error(err) 105 return err 106 } 107 108 return nil 109 } 110 111 // GetPayableResource gets the payable request from the database 112 func (m *MongoService) GetPayableResource(companyNumber, reference string) (*models.PayableResourceDao, error) { 113 var resource models.PayableResourceDao 114 115 collection := m.db.Collection(m.CollectionName) 116 dbResource := collection.FindOne(context.Background(), bson.M{"reference": reference, "company_number": companyNumber}) 117 118 err := dbResource.Err() 119 if err != nil { 120 if err == mongo.ErrNoDocuments { 121 log.Debug("no payable resource found", log.Data{"company_number": companyNumber, "reference": reference}) 122 return nil, nil 123 } 124 log.Error(err, log.Data{"company_number": companyNumber, "reference": reference}) 125 return nil, err 126 } 127 128 err = dbResource.Decode(&resource) 129 130 if err != nil { 131 log.Error(err, log.Data{"company_number": companyNumber, "reference": reference}) 132 return nil, err 133 } 134 135 return &resource, nil 136 } 137 138 // UpdatePaymentDetails will save the document back to Mongo 139 func (m *MongoService) UpdatePaymentDetails(dao *models.PayableResourceDao) error { 140 filter := bson.M{"_id": dao.ID} 141 142 update := bson.D{ 143 { 144 "$set", bson.D{ 145 {"data.payment.status", dao.Data.Payment.Status}, 146 {"data.payment.reference", dao.Data.Payment.Reference}, 147 {"data.payment.paid_at", dao.Data.Payment.PaidAt}, 148 {"data.payment.amount", dao.Data.Payment.Amount}, 149 }, 150 }, 151 } 152 153 collection := m.db.Collection(m.CollectionName) 154 155 log.Debug("updating payment details in mongo document", log.Data{"_id": dao.ID}) 156 157 _, err := collection.UpdateOne(context.Background(), filter, update) 158 if err != nil { 159 log.Error(err, log.Data{"_id": dao.ID, "company_number": dao.CompanyNumber, "reference": dao.Reference}) 160 return err 161 } 162 163 log.Debug("updated payment details in mongo document", log.Data{"_id": dao.ID}) 164 165 return nil 166 } 167 168 // Shutdown is a hook that can be used to clean up db resources 169 func (m *MongoService) Shutdown() { 170 if client != nil { 171 err := client.Disconnect(context.Background()) 172 if err != nil { 173 log.Error(err) 174 return 175 } 176 log.Info("disconnected from mongodb successfully") 177 } 178 }