Files
gocryptotrader/database/repository/withdraw/withdraw.go
Adrian Gallagher 9a4eb9de84 CI: Fix golangci-lint linter issues, add prealloc linter and bump version depends for Go 1.18 (#915)
* Bump CI versions

* Specifically set go version as 1.17.x bumps it to 1.18

* Another

* Adjust AppVeyor

* Part 1 of linter issues

* Part 2

* Fix various linters and improvements

* Part 3

* Finishing touches

* Tests and EqualFold

* Fix nitterinos plus bonus requester jobs bump for exchanges with large number of tests

* Fix nitterinos and bump golangci-lint timeout for AppVeyor

* Address nits, ensure all books are returned on err due to syncer regression

* Fix the wiggins

* Fix duplication

* Fix nitterinos
2022-04-20 13:45:15 +10:00

431 lines
13 KiB
Go

package withdraw
import (
"context"
"database/sql"
"errors"
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/database"
modelPSQL "github.com/thrasher-corp/gocryptotrader/database/models/postgres"
modelSQLite "github.com/thrasher-corp/gocryptotrader/database/models/sqlite3"
"github.com/thrasher-corp/gocryptotrader/database/repository"
exchangeDB "github.com/thrasher-corp/gocryptotrader/database/repository/exchange"
"github.com/thrasher-corp/gocryptotrader/log"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
"github.com/thrasher-corp/sqlboiler/boil"
"github.com/thrasher-corp/sqlboiler/queries/qm"
)
var (
// ErrNoResults is the error returned if no results are found
ErrNoResults = errors.New("no results found")
)
// Event stores Withdrawal Response details in database
func Event(res *withdraw.Response) {
if database.DB.SQL == nil {
return
}
ctx := context.Background()
ctx = boil.SkipTimestamps(ctx)
exchangeUUID, err := exchangeDB.UUIDByName(res.Exchange.Name)
if err != nil {
log.Error(log.DatabaseMgr, err)
return
}
res.Exchange.Name = exchangeUUID.String()
tx, err := database.DB.SQL.BeginTx(ctx, nil)
if err != nil {
log.Errorf(log.DatabaseMgr, "Event transaction being failed: %v", err)
return
}
if repository.GetSQLDialect() == database.DBSQLite3 {
err = addSQLiteEvent(ctx, tx, res)
} else {
err = addPSQLEvent(ctx, tx, res)
}
if err != nil {
log.Errorf(log.DatabaseMgr, "Event insert failed: %v", err)
err = tx.Rollback()
if err != nil {
log.Errorf(log.DatabaseMgr, "Event Transaction rollback failed: %v", err)
}
return
}
err = tx.Commit()
if err != nil {
log.Errorf(log.DatabaseMgr, "Event Transaction commit failed: %v", err)
return
}
}
func addPSQLEvent(ctx context.Context, tx *sql.Tx, res *withdraw.Response) (err error) {
var tempEvent = modelPSQL.WithdrawalHistory{
ExchangeNameID: res.Exchange.Name,
ExchangeID: res.Exchange.ID,
Status: res.Exchange.Status,
Currency: res.RequestDetails.Currency.String(),
Amount: res.RequestDetails.Amount,
WithdrawType: int(res.RequestDetails.Type),
}
if res.RequestDetails.Description != "" {
tempEvent.Description.SetValid(res.RequestDetails.Description)
}
err = tempEvent.Insert(ctx, tx, boil.Infer())
if err != nil {
log.Errorf(log.DatabaseMgr, "Event Insert failed: %v", err)
err = tx.Rollback()
if err != nil {
log.Errorf(log.DatabaseMgr, "Rollback failed: %v", err)
}
return
}
if res.RequestDetails.Type == withdraw.Fiat {
fiatEvent := &modelPSQL.WithdrawalFiat{
BankName: res.RequestDetails.Fiat.Bank.BankName,
BankAddress: res.RequestDetails.Fiat.Bank.BankAddress,
BankAccountName: res.RequestDetails.Fiat.Bank.AccountName,
BankAccountNumber: res.RequestDetails.Fiat.Bank.AccountNumber,
BSB: res.RequestDetails.Fiat.Bank.BSBNumber,
SwiftCode: res.RequestDetails.Fiat.Bank.SWIFTCode,
Iban: res.RequestDetails.Fiat.Bank.IBAN,
}
err = tempEvent.SetWithdrawalFiatWithdrawalFiats(ctx, tx, true, fiatEvent)
if err != nil {
log.Errorf(log.DatabaseMgr, "Event Insert failed: %v", err)
err = tx.Rollback()
if err != nil {
log.Errorf(log.DatabaseMgr, "Rollback failed: %v", err)
}
return
}
}
if res.RequestDetails.Type == withdraw.Crypto {
cryptoEvent := &modelPSQL.WithdrawalCrypto{
Address: res.RequestDetails.Crypto.Address,
Fee: res.RequestDetails.Crypto.FeeAmount,
}
if res.RequestDetails.Crypto.AddressTag != "" {
cryptoEvent.AddressTag.SetValid(res.RequestDetails.Crypto.AddressTag)
}
err = tempEvent.AddWithdrawalCryptoWithdrawalCryptos(ctx, tx, true, cryptoEvent)
if err != nil {
log.Errorf(log.DatabaseMgr, "Event Insert failed: %v", err)
err = tx.Rollback()
if err != nil {
log.Errorf(log.DatabaseMgr, "Rollback failed: %v", err)
}
return
}
}
realID, _ := uuid.FromString(tempEvent.ID)
res.ID = realID
return nil
}
func addSQLiteEvent(ctx context.Context, tx *sql.Tx, res *withdraw.Response) (err error) {
newUUID, errUUID := uuid.NewV4()
if errUUID != nil {
log.Errorf(log.DatabaseMgr, "Failed to generate UUID: %v", errUUID)
err = tx.Rollback()
if err != nil {
log.Errorf(log.DatabaseMgr, "Rollback failed: %v", err)
}
return
}
var tempEvent = modelSQLite.WithdrawalHistory{
ID: newUUID.String(),
ExchangeNameID: res.Exchange.Name,
ExchangeID: res.Exchange.ID,
Status: res.Exchange.Status,
Currency: res.RequestDetails.Currency.String(),
Amount: res.RequestDetails.Amount,
WithdrawType: int64(res.RequestDetails.Type),
}
if res.RequestDetails.Description != "" {
tempEvent.Description.SetValid(res.RequestDetails.Description)
}
err = tempEvent.Insert(ctx, tx, boil.Infer())
if err != nil {
log.Errorf(log.DatabaseMgr, "Event Insert failed: %v", err)
err = tx.Rollback()
if err != nil {
log.Errorf(log.DatabaseMgr, "Rollback failed: %v", err)
}
return
}
if res.RequestDetails.Type == withdraw.Fiat {
fiatEvent := &modelSQLite.WithdrawalFiat{
BankName: res.RequestDetails.Fiat.Bank.BankName,
BankAddress: res.RequestDetails.Fiat.Bank.BankAddress,
BankAccountName: res.RequestDetails.Fiat.Bank.AccountName,
BankAccountNumber: res.RequestDetails.Fiat.Bank.AccountNumber,
BSB: res.RequestDetails.Fiat.Bank.BSBNumber,
SwiftCode: res.RequestDetails.Fiat.Bank.SWIFTCode,
Iban: res.RequestDetails.Fiat.Bank.IBAN,
}
err = tempEvent.AddWithdrawalFiats(ctx, tx, true, fiatEvent)
if err != nil {
log.Errorf(log.DatabaseMgr, "Event Insert failed: %v", err)
err = tx.Rollback()
if err != nil {
log.Errorf(log.DatabaseMgr, "Rollback failed: %v", err)
}
return
}
}
if res.RequestDetails.Type == withdraw.Crypto {
cryptoEvent := &modelSQLite.WithdrawalCrypto{
Address: res.RequestDetails.Crypto.Address,
Fee: res.RequestDetails.Crypto.FeeAmount,
}
if res.RequestDetails.Crypto.AddressTag != "" {
cryptoEvent.AddressTag.SetValid(res.RequestDetails.Crypto.AddressTag)
}
err = tempEvent.AddWithdrawalCryptos(ctx, tx, true, cryptoEvent)
if err != nil {
log.Errorf(log.DatabaseMgr, "Event Insert failed: %v", err)
err = tx.Rollback()
if err != nil {
log.Errorf(log.DatabaseMgr, "Rollback failed: %v", err)
}
return
}
}
res.ID = newUUID
return nil
}
// GetEventByUUID return requested withdraw information by ID
func GetEventByUUID(id string) (*withdraw.Response, error) {
resp, err := getByColumns(generateWhereQuery([]string{"id"}, []string{id}, 1))
if err != nil {
return nil, err
}
return resp[0], nil
}
// GetEventsByExchange returns all withdrawal requests by exchange
func GetEventsByExchange(exchange string, limit int) ([]*withdraw.Response, error) {
exch, err := exchangeDB.UUIDByName(exchange)
if err != nil {
log.Error(log.DatabaseMgr, err)
return nil, err
}
return getByColumns(generateWhereQuery([]string{"exchange_name_id"}, []string{exch.String()}, limit))
}
// GetEventByExchangeID return requested withdraw information by Exchange ID
func GetEventByExchangeID(exchange, id string) (*withdraw.Response, error) {
exch, err := exchangeDB.UUIDByName(exchange)
if err != nil {
log.Error(log.DatabaseMgr, err)
return nil, err
}
resp, err := getByColumns(generateWhereQuery([]string{"exchange_name_id", "exchange_id"}, []string{exch.String(), id}, 1))
if err != nil {
return nil, err
}
return resp[0], err
}
// GetEventsByDate returns requested withdraw information by date range
func GetEventsByDate(exchange string, start, end time.Time, limit int) ([]*withdraw.Response, error) {
betweenQuery := generateWhereBetweenQuery("created_at", start, end, limit)
if exchange == "" {
return getByColumns(betweenQuery)
}
exch, err := exchangeDB.UUIDByName(exchange)
if err != nil {
log.Error(log.DatabaseMgr, err)
return nil, err
}
return getByColumns(append(generateWhereQuery([]string{"exchange_name_id"}, []string{exch.String()}, 0), betweenQuery...))
}
func generateWhereQuery(columns, id []string, limit int) []qm.QueryMod {
x := len(columns)
if limit > 0 {
x++
}
queries := make([]qm.QueryMod, 0, x)
if limit > 0 {
queries = append(queries, qm.Limit(limit))
}
for x := range columns {
queries = append(queries, qm.Where(columns[x]+"= ?", id[x]))
}
return queries
}
func generateWhereBetweenQuery(column string, start, end interface{}, limit int) []qm.QueryMod {
return []qm.QueryMod{
qm.Limit(limit),
qm.Where(column+" BETWEEN ? AND ?", start, end),
}
}
func getByColumns(q []qm.QueryMod) ([]*withdraw.Response, error) {
if database.DB.SQL == nil {
return nil, database.ErrDatabaseSupportDisabled
}
var resp []*withdraw.Response
var ctx = context.Background()
if repository.GetSQLDialect() == database.DBSQLite3 {
v, err := modelSQLite.WithdrawalHistories(q...).All(ctx, database.DB.SQL)
if err != nil {
return nil, err
}
for x := range v {
var tempResp = &withdraw.Response{}
var newUUID uuid.UUID
newUUID, err = uuid.FromString(v[x].ID)
if err != nil {
return nil, err
}
tempResp.ID = newUUID
tempResp.Exchange.ID = v[x].ExchangeID
tempResp.Exchange.Status = v[x].Status
tempResp.RequestDetails = withdraw.Request{
Currency: currency.NewCode(v[x].Currency),
Description: v[x].Description.String,
Amount: v[x].Amount,
Type: withdraw.RequestType(v[x].WithdrawType),
}
exchangeName, err := v[x].ExchangeName().One(ctx, database.DB.SQL)
if err != nil {
log.Errorf(log.DatabaseMgr, "Unable to get exchange name")
tempUUID, errUUID := uuid.FromString(v[x].ExchangeNameID)
if errUUID != nil {
log.Errorf(log.DatabaseMgr, "invalid exchange name UUID for record %v", v[x].ID)
} else {
tempResp.Exchange.UUID = tempUUID
}
} else {
tempResp.Exchange.Name = exchangeName.Name
}
createdAtTime, err := time.Parse(time.RFC3339, v[x].CreatedAt)
if err != nil {
log.Errorf(log.DatabaseMgr, "record: %v has an incorrect time format ( %v ) - defaulting to empty time: %v", tempResp.ID, v[x].CreatedAt, err)
tempResp.CreatedAt = time.Time{}
} else {
tempResp.CreatedAt = createdAtTime
}
updatedAtTime, err := time.Parse(time.RFC3339, v[x].UpdatedAt)
if err != nil {
log.Errorf(log.DatabaseMgr, "record: %v has an incorrect time format ( %v ) - defaulting to empty time: %v", tempResp.ID, v[x].UpdatedAt, err)
tempResp.UpdatedAt = time.Time{}
} else {
tempResp.UpdatedAt = updatedAtTime
}
if withdraw.RequestType(v[x].WithdrawType) == withdraw.Crypto {
x, err := v[x].WithdrawalCryptos().One(ctx, database.DB.SQL)
if err != nil {
return nil, err
}
tempResp.RequestDetails.Crypto.Address = x.Address
tempResp.RequestDetails.Crypto.AddressTag = x.AddressTag.String
tempResp.RequestDetails.Crypto.FeeAmount = x.Fee
} else {
x, err := v[x].WithdrawalFiats().One(ctx, database.DB.SQL)
if err != nil {
return nil, err
}
tempResp.RequestDetails.Fiat.Bank.AccountName = x.BankAccountName
tempResp.RequestDetails.Fiat.Bank.AccountNumber = x.BankAccountNumber
tempResp.RequestDetails.Fiat.Bank.IBAN = x.Iban
tempResp.RequestDetails.Fiat.Bank.SWIFTCode = x.SwiftCode
tempResp.RequestDetails.Fiat.Bank.BSBNumber = x.BSB
}
resp = append(resp, tempResp)
}
} else {
v, err := modelPSQL.WithdrawalHistories(q...).All(ctx, database.DB.SQL)
if err != nil {
return nil, err
}
for x := range v {
var tempResp = &withdraw.Response{}
newUUID, _ := uuid.FromString(v[x].ID)
tempResp.ID = newUUID
tempResp.Exchange.ID = v[x].ExchangeID
tempResp.Exchange.Status = v[x].Status
tempResp.RequestDetails = withdraw.Request{
Currency: currency.NewCode(v[x].Currency),
Description: v[x].Description.String,
Amount: v[x].Amount,
Type: withdraw.RequestType(v[x].WithdrawType),
}
tempResp.CreatedAt = v[x].CreatedAt
tempResp.UpdatedAt = v[x].UpdatedAt
exchangeName, err := v[x].ExchangeName().One(ctx, database.DB.SQL)
if err != nil {
log.Errorf(log.DatabaseMgr, "Unable to get exchange name")
tempUUID, errUUID := uuid.FromString(v[x].ExchangeNameID)
if errUUID != nil {
log.Errorf(log.DatabaseMgr, "invalid exchange name UUID for record %v", v[x].ID)
} else {
tempResp.Exchange.UUID = tempUUID
}
} else {
tempResp.Exchange.Name = exchangeName.Name
}
if withdraw.RequestType(v[x].WithdrawType) == withdraw.Crypto {
x, err := v[x].WithdrawalCryptoWithdrawalCryptos().One(ctx, database.DB.SQL)
if err != nil {
return nil, err
}
tempResp.RequestDetails.Crypto.Address = x.Address
tempResp.RequestDetails.Crypto.AddressTag = x.AddressTag.String
tempResp.RequestDetails.Crypto.FeeAmount = x.Fee
} else if withdraw.RequestType(v[x].WithdrawType) == withdraw.Fiat {
x, err := v[x].WithdrawalFiatWithdrawalFiats().One(ctx, database.DB.SQL)
if err != nil {
return nil, err
}
tempResp.RequestDetails.Fiat.Bank.AccountName = x.BankAccountName
tempResp.RequestDetails.Fiat.Bank.AccountNumber = x.BankAccountNumber
tempResp.RequestDetails.Fiat.Bank.IBAN = x.Iban
tempResp.RequestDetails.Fiat.Bank.SWIFTCode = x.SwiftCode
tempResp.RequestDetails.Fiat.Bank.BSBNumber = x.BSB
}
resp = append(resp, tempResp)
}
}
if len(resp) == 0 {
return nil, ErrNoResults
}
return resp, nil
}