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  }