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 }