Files
Adrian Gallagher 4651af5767 modernise: Run new gopls modernise tool against the codebase and fix minor issues (#1826)
* modernise: Run new gopls modernise tool against codebase

* Address shazbert's nits

* apichecker, gctcli: Simplify HTML scraping functions and improve depth limit handling

* refactor: Create minSyncInterval const and update order book limit handling for binance and binanceUS

* refactor: Various slice usage improvements and rename TODO

* tranches: Revert deleteByID changes due to performance decrease

Shazbert was a F1 driver in a past lifetime 🏎️

* tranches: Simply retrieve copy

Thanks to shazbert

* documentation: Sort contributors list by contributions

* tranches: Remove deadcode in deleteByID
2025-03-21 09:17:10 +11:00

426 lines
13 KiB
Go

package withdraw
import (
"context"
"database/sql"
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/common"
"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"
)
// 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.Errorln(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) {
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
}
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.Errorln(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.Errorln(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.Errorln(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 any, 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
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 {
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 {
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, common.ErrNoResults
}
return resp, nil
}