github.com/mundipagg/boleto-api@v0.0.0-20230620145841-3f9ec742599f/db/mongodb.go (about)

     1  package db
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"errors"
     7  	"fmt"
     8  	"net"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/mundipagg/boleto-api/config"
    13  	"github.com/mundipagg/boleto-api/log"
    14  	"github.com/mundipagg/boleto-api/models"
    15  	"github.com/mundipagg/boleto-api/util"
    16  	"go.mongodb.org/mongo-driver/bson"
    17  	"go.mongodb.org/mongo-driver/bson/primitive"
    18  	"go.mongodb.org/mongo-driver/mongo"
    19  	"go.mongodb.org/mongo-driver/mongo/options"
    20  	"go.mongodb.org/mongo-driver/mongo/readpref"
    21  	"go.mongodb.org/mongo-driver/mongo/writeconcern"
    22  
    23  	mongoCheck "github.com/mundipagg/healthcheck-go/checks/mongo"
    24  )
    25  
    26  var (
    27  	conn              *mongo.Client // is concurrent safe: https://github.com/mongodb/mongo-go-driver/blob/master/mongo/client.go#L46
    28  	ConnectionTimeout = 10 * time.Second
    29  	mu                sync.RWMutex
    30  )
    31  
    32  const (
    33  	NotFoundDoc = "mongo: no documents in result"
    34  	InvalidPK   = "invalid pk"
    35  	emptyConn   = "Connection is empty"
    36  )
    37  
    38  // CheckMongo checks if Mongo is up and running
    39  func CheckMongo() error {
    40  	_, err := CreateMongo()
    41  	if err != nil {
    42  		return err
    43  	}
    44  
    45  	return ping()
    46  }
    47  
    48  // CreateMongo cria uma nova instancia de conexão com o mongodb
    49  func CreateMongo() (*mongo.Client, error) {
    50  	mu.Lock()
    51  	defer mu.Unlock()
    52  
    53  	if conn != nil {
    54  		return conn, nil
    55  	}
    56  
    57  	ctx, cancel := context.WithTimeout(context.Background(), ConnectionTimeout)
    58  	defer cancel()
    59  
    60  	var err error
    61  	l := log.CreateLog()
    62  	conn, err = mongo.Connect(ctx, getClientOptions())
    63  	if err != nil {
    64  		l.Error(err.Error(), "mongodb.CreateMongo - Error creating mongo connection")
    65  		return conn, err
    66  	}
    67  
    68  	return conn, nil
    69  }
    70  
    71  func getClientOptions() *options.ClientOptions {
    72  	mongoURL := config.Get().MongoURL
    73  	mongoTimeoutConnection := config.Get().MongoTimeoutConnection
    74  
    75  	co := options.Client()
    76  	co.SetRetryWrites(true)
    77  	co.SetWriteConcern(writeconcern.New(writeconcern.WMajority()))
    78  
    79  	co.SetConnectTimeout(time.Duration(mongoTimeoutConnection) * time.Second)
    80  	co.SetMaxConnIdleTime(10 * time.Second)
    81  	co.SetMaxPoolSize(512)
    82  
    83  	if config.Get().ForceTLS {
    84  		co.SetTLSConfig(&tls.Config{})
    85  	}
    86  
    87  	return co.ApplyURI(mongoURL).SetAuth(mongoCredential())
    88  }
    89  
    90  func mongoCredential() options.Credential {
    91  	user := config.Get().MongoUser
    92  	password := config.Get().MongoPassword
    93  	var database string
    94  	if config.Get().MongoAuthSource != "" {
    95  		database = config.Get().MongoAuthSource
    96  	} else {
    97  		database = config.Get().MongoDatabase
    98  	}
    99  
   100  	credential := options.Credential{
   101  		Username:   user,
   102  		Password:   password,
   103  		AuthSource: database,
   104  	}
   105  
   106  	if config.Get().ForceTLS {
   107  		credential.AuthMechanism = "SCRAM-SHA-1"
   108  	}
   109  
   110  	return credential
   111  }
   112  
   113  //SaveBoleto salva um boleto no mongoDB
   114  func SaveBoleto(boleto models.BoletoView) error {
   115  	ctx, cancel := context.WithTimeout(context.Background(), ConnectionTimeout)
   116  	defer cancel()
   117  
   118  	l := log.CreateLog()
   119  	conn, err := CreateMongo()
   120  	if err != nil {
   121  		l.Error(err.Error(), fmt.Sprintf("mongodb.CreateMongo - Error creating mongo connection while saving boleto %v", boleto))
   122  		return err
   123  	}
   124  
   125  	collection := conn.Database(config.Get().MongoDatabase).Collection(config.Get().MongoBoletoCollection)
   126  	_, err = collection.InsertOne(ctx, boleto)
   127  
   128  	return err
   129  }
   130  
   131  //GetBoletoByID busca um boleto pelo ID que vem na URL
   132  //O retorno será um objeto BoletoView, o tempo decorrido da operação (em milisegundos) e algum erro ocorrido durante a operação
   133  func GetBoletoByID(id, pk string) (models.BoletoView, int64, error) {
   134  	start := time.Now()
   135  
   136  	result := models.BoletoView{}
   137  
   138  	ctx, cancel := context.WithTimeout(context.Background(), ConnectionTimeout)
   139  	defer cancel()
   140  
   141  	l := log.CreateLog()
   142  	conn, err := CreateMongo()
   143  	if err != nil {
   144  		l.Error(err.Error(), fmt.Sprintf("mongodb.GetBoletoByID - Error creating mongo connection for id %s and pk %s", id, pk))
   145  		return result, time.Since(start).Milliseconds(), err
   146  	}
   147  	collection := conn.Database(config.Get().MongoDatabase).Collection(config.Get().MongoBoletoCollection)
   148  
   149  	for i := 0; i <= config.Get().RetryNumberGetBoleto; i++ {
   150  
   151  		var filter primitive.M
   152  		if len(id) == 24 {
   153  			d, err := primitive.ObjectIDFromHex(id)
   154  			if err != nil {
   155  				return result, time.Since(start).Milliseconds(), fmt.Errorf("Error: %s\n", err)
   156  			}
   157  			filter = bson.M{"_id": d}
   158  		} else {
   159  			filter = bson.M{"id": id}
   160  		}
   161  		err = collection.FindOne(ctx, filter).Decode(&result)
   162  
   163  		if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
   164  			continue
   165  		} else {
   166  			break
   167  		}
   168  	}
   169  
   170  	if err != nil {
   171  		return models.BoletoView{}, time.Since(start).Milliseconds(), err
   172  	} else if !hasValidKey(result, pk) {
   173  		return models.BoletoView{}, time.Since(start).Milliseconds(), errors.New(InvalidPK)
   174  	}
   175  
   176  	// Changing dates as LocalDateTime, in order to keep the same time.Time attributes the mgo used return
   177  	result.Boleto.Title.ExpireDateTime = util.TimeToLocalTime(result.Boleto.Title.ExpireDateTime)
   178  	result.Boleto.Title.CreateDate = util.TimeToLocalTime(result.Boleto.Title.CreateDate)
   179  	result.CreateDate = util.TimeToLocalTime(result.CreateDate)
   180  
   181  	return result, time.Since(start).Milliseconds(), nil
   182  }
   183  
   184  //GetUserCredentials Busca as Credenciais dos Usuários
   185  func GetUserCredentials() ([]models.Credentials, error) {
   186  	result := []models.Credentials{}
   187  
   188  	ctx, cancel := context.WithTimeout(context.Background(), ConnectionTimeout)
   189  	defer cancel()
   190  
   191  	l := log.CreateLog()
   192  	conn, err := CreateMongo()
   193  	if err != nil {
   194  		l.Error(err.Error(), "mongodb.GetUserCredentials - Error creating mongo connection")
   195  		return result, err
   196  	}
   197  	collection := conn.Database(config.Get().MongoDatabase).Collection(config.Get().MongoCredentialsCollection)
   198  
   199  	cur, err := collection.Find(ctx, bson.M{})
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  
   204  	err = cur.All(ctx, &result)
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  
   209  	return result, nil
   210  }
   211  
   212  // GetTokenByClientIDAndIssuerBank fetches a token by clientID and issuerBank
   213  func GetTokenByClientIDAndIssuerBank(clientID, issuerBank string) (models.Token, error) {
   214  	result := models.Token{}
   215  	if clientID == "" || issuerBank == "" {
   216  		return result, fmt.Errorf("fields clientID and issuerBank cannot be empty")
   217  	}
   218  
   219  	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
   220  	defer cancel()
   221  
   222  	conn, err := CreateMongo()
   223  	if err != nil {
   224  		return result, err
   225  	}
   226  
   227  	filter := bson.D{
   228  		primitive.E{Key: "clientid", Value: clientID},
   229  		primitive.E{Key: "issuerbank", Value: issuerBank},
   230  	}
   231  	opts := options.FindOne().SetSort(bson.D{primitive.E{Key: "createdat", Value: -1}})
   232  
   233  	collection := conn.Database(config.Get().MongoDatabase).Collection(config.Get().MongoTokenCollection)
   234  	err = collection.FindOne(ctx, filter, opts).Decode(&result)
   235  	if err != nil {
   236  		return result, err
   237  	}
   238  
   239  	if time.Now().After(result.CreatedAt.Add(time.Duration(config.Get().TokenSafeDurationInMinutes) * time.Minute)) { // safety margin
   240  		result = models.Token{} // a little help for GC
   241  	}
   242  
   243  	return result, nil
   244  }
   245  
   246  // SaveToken saves an access token at mongoDB
   247  func SaveToken(token models.Token) error {
   248  	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
   249  	defer cancel()
   250  
   251  	conn, err := CreateMongo()
   252  	if err != nil {
   253  		return err
   254  	}
   255  
   256  	collection := conn.Database(config.Get().MongoDatabase).Collection(config.Get().MongoTokenCollection)
   257  	_, err = collection.InsertOne(ctx, token)
   258  
   259  	return err
   260  }
   261  
   262  func hasValidKey(r models.BoletoView, pk string) bool {
   263  	return r.SecretKey == "" || r.PublicKey == pk
   264  }
   265  
   266  func ping() error {
   267  	if conn == nil {
   268  		return fmt.Errorf(emptyConn)
   269  	}
   270  
   271  	ctx, cancel := context.WithTimeout(context.Background(), ConnectionTimeout)
   272  	defer cancel()
   273  
   274  	err := conn.Ping(ctx, readpref.Primary())
   275  	if err != nil {
   276  		return err
   277  	}
   278  
   279  	return nil
   280  }
   281  
   282  func CloseConnection() error {
   283  	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
   284  	defer cancel()
   285  
   286  	return conn.Disconnect(ctx)
   287  }
   288  
   289  func GetDatabaseConfiguration() *mongoCheck.Config {
   290  	return &mongoCheck.Config{
   291  		Url:        config.Get().MongoURL,
   292  		User:       config.Get().MongoUser,
   293  		Password:   config.Get().MongoPassword,
   294  		Database:   config.Get().MongoDatabase,
   295  		AuthSource: config.Get().MongoAuthSource,
   296  		Timeout:    config.Get().MongoTimeoutConnection,
   297  		ForceTLS:   config.Get().ForceTLS,
   298  	}
   299  }