Skip to content

Commit

Permalink
payments: get endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-burghardt committed Jun 24, 2024
1 parent f41bed0 commit 1f58eac
Show file tree
Hide file tree
Showing 10 changed files with 412 additions and 27 deletions.
57 changes: 43 additions & 14 deletions internal/data/payments.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ type PaymentModel struct {
}

type Payment struct {
OperationID int64 `db:"operation_id"`
OperationType string `db:"operation_type"`
TransactionID int64 `db:"transaction_id"`
TransactionHash string `db:"transaction_hash"`
FromAddress string `db:"from_address"`
ToAddress string `db:"to_address"`
SrcAssetCode string `db:"src_asset_code"`
SrcAssetIssuer string `db:"src_asset_issuer"`
SrcAmount int64 `db:"src_amount"`
DestAssetCode string `db:"dest_asset_code"`
DestAssetIssuer string `db:"dest_asset_issuer"`
DestAmount int64 `db:"dest_amount"`
CreatedAt time.Time `db:"created_at"`
Memo *string `db:"memo"`
OperationID int64 `db:"operation_id" json:"operationId"`
OperationType string `db:"operation_type" json:"operationType"`
TransactionID int64 `db:"transaction_id" json:"transactionId"`
TransactionHash string `db:"transaction_hash" json:"transactionHash"`
FromAddress string `db:"from_address" json:"fromAddress"`
ToAddress string `db:"to_address" json:"toAddress"`
SrcAssetCode string `db:"src_asset_code" json:"srcAssetCode"`
SrcAssetIssuer string `db:"src_asset_issuer" json:"srcAssetIssuer"`
SrcAmount int64 `db:"src_amount" json:"srcAmount"`
DestAssetCode string `db:"dest_asset_code" json:"destAssetCode"`
DestAssetIssuer string `db:"dest_asset_issuer" json:"destAssetIssuer"`
DestAmount int64 `db:"dest_amount" json:"destAmount"`
CreatedAt time.Time `db:"created_at" json:"createdAt"`
Memo *string `db:"memo" json:"memo"`
}

func (m *PaymentModel) GetLatestLedgerSynced(ctx context.Context, cursorName string) (uint32, error) {
Expand Down Expand Up @@ -91,3 +91,32 @@ func (m *PaymentModel) AddPayment(ctx context.Context, tx db.Transaction, paymen

return nil
}

func (m *PaymentModel) GetPayments(ctx context.Context, address string, afterID int64, sortOrder SortOrder, limit int) ([]Payment, error) {
if !sortOrder.IsValid() {
return nil, fmt.Errorf("invalid sort value: %s", sortOrder)
}

const query = `
SELECT * FROM ingest_payments
WHERE
($1 = '' OR $1 IN (from_address, to_address)) AND
($2 = 0 OR CASE
WHEN $3 = 'DESC' THEN operation_id < $2
ELSE operation_id > $2
END)
ORDER BY
CASE
WHEN $3 = 'DESC' THEN -operation_id
ELSE operation_id
END
LIMIT $4
`
var payments []Payment
err := m.DB.SelectContext(ctx, &payments, query, address, afterID, sortOrder, limit)
if err != nil {
return nil, fmt.Errorf("fetching payments: %w", err)
}

return payments, nil
}
64 changes: 64 additions & 0 deletions internal/data/payments_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,67 @@ func TestPaymentModelUpdateLatestLedgerSynced(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, uint32(123), lastSyncedLedger)
}

func TestPaymentModelGetPayments(t *testing.T) {
dbt := dbtest.Open(t)
defer dbt.Close()
dbConnectionPool, err := db.OpenDBConnectionPool(dbt.DSN)
require.NoError(t, err)
defer dbConnectionPool.Close()

ctx := context.Background()
m := &PaymentModel{
DB: dbConnectionPool,
}

dbPayments := []Payment{
{OperationID: 1, OperationType: "OperationTypePayment", TransactionID: 11, TransactionHash: "c370ff20144e4c96b17432b8d14664c1", FromAddress: "GAZ37ZO4TU3H", ToAddress: "GDD2HQO6IOFT", SrcAssetCode: "XLM", SrcAssetIssuer: "", SrcAmount: 10, DestAssetCode: "XLM", DestAssetIssuer: "", DestAmount: 10, CreatedAt: time.Date(2024, 6, 21, 0, 0, 0, 0, time.UTC), Memo: nil},
{OperationID: 2, OperationType: "OperationTypePayment", TransactionID: 22, TransactionHash: "30850d8fc7d1439782885103390cd975", FromAddress: "GBZ5Q56JKHJQ", ToAddress: "GASV72SENBSY", SrcAssetCode: "XLM", SrcAssetIssuer: "", SrcAmount: 20, DestAssetCode: "XLM", DestAssetIssuer: "", DestAmount: 20, CreatedAt: time.Date(2024, 6, 22, 0, 0, 0, 0, time.UTC), Memo: nil},
{OperationID: 3, OperationType: "OperationTypePayment", TransactionID: 33, TransactionHash: "d9521ed7057d4d1e9b9dd22ab515cbf1", FromAddress: "GAYFAYPOECBT", ToAddress: "GDWDPNMALNIT", SrcAssetCode: "XLM", SrcAssetIssuer: "", SrcAmount: 30, DestAssetCode: "XLM", DestAssetIssuer: "", DestAmount: 30, CreatedAt: time.Date(2024, 6, 23, 0, 0, 0, 0, time.UTC), Memo: nil},
{OperationID: 4, OperationType: "OperationTypePayment", TransactionID: 44, TransactionHash: "2af98496a86741c6a6814200e06027fd", FromAddress: "GACKTNR2QQXU", ToAddress: "GBZ5KUZHAAVI", SrcAssetCode: "USDC", SrcAssetIssuer: "GAHLU7PDIQMZ", SrcAmount: 40, DestAssetCode: "USDC", DestAssetIssuer: "GAHLU7PDIQMZ", DestAmount: 40, CreatedAt: time.Date(2024, 6, 24, 0, 0, 0, 0, time.UTC), Memo: nil},
{OperationID: 5, OperationType: "OperationTypePayment", TransactionID: 55, TransactionHash: "edfab36f9f104c4fb74b549de44cfbcc", FromAddress: "GA4CMYJEC5W5", ToAddress: "GAZ37ZO4TU3H", SrcAssetCode: "USDC", SrcAssetIssuer: "GAHLU7PDIQMZ", SrcAmount: 50, DestAssetCode: "USDC", DestAssetIssuer: "GAHLU7PDIQMZ", DestAmount: 50, CreatedAt: time.Date(2024, 6, 25, 0, 0, 0, 0, time.UTC), Memo: nil},
}

const query = `INSERT INTO ingest_payments (operation_id, operation_type, transaction_id, transaction_hash, from_address, to_address, src_asset_code, src_asset_issuer, src_amount, dest_asset_code, dest_asset_issuer, dest_amount, created_at, memo) VALUES (:operation_id, :operation_type, :transaction_id, :transaction_hash, :from_address, :to_address, :src_asset_code, :src_asset_issuer, :src_amount, :dest_asset_code, :dest_asset_issuer, :dest_amount, :created_at, :memo);`
_, err = dbConnectionPool.NamedExecContext(ctx, query, dbPayments)
require.NoError(t, err)

t.Run("no_filter_desc", func(t *testing.T) {
payments, err := m.GetPayments(ctx, "", 0, DESC, 2)
require.NoError(t, err)

assert.Equal(t, []Payment{
dbPayments[4],
dbPayments[3],
}, payments)
})

t.Run("no_filter_asc", func(t *testing.T) {
payments, err := m.GetPayments(ctx, "", 0, ASC, 2)
require.NoError(t, err)

assert.Equal(t, []Payment{
dbPayments[0],
dbPayments[1],
}, payments)
})

t.Run("afterid_desc", func(t *testing.T) {
payments, err := m.GetPayments(ctx, "", dbPayments[3].OperationID, DESC, 2)
require.NoError(t, err)

assert.Equal(t, []Payment{
dbPayments[2],
dbPayments[1],
}, payments)
})

t.Run("afterid_asc", func(t *testing.T) {
payments, err := m.GetPayments(ctx, "", dbPayments[3].OperationID, ASC, 2)
require.NoError(t, err)

assert.Equal(t, []Payment{
dbPayments[4],
}, payments)
})
}
12 changes: 12 additions & 0 deletions internal/data/query_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package data

type SortOrder string

const (
ASC SortOrder = "ASC"
DESC SortOrder = "DESC"
)

func (o SortOrder) IsValid() bool {
return o == ASC || o == DESC
}
1 change: 1 addition & 0 deletions internal/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ var _ Transaction = (*sqlx.Tx)(nil)
type SQLExecuter interface {
DriverName() string
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
NamedExecContext(ctx context.Context, query string, arg interface{}) (sql.Result, error)
GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error
sqlx.PreparerContext
sqlx.QueryerContext
Expand Down
8 changes: 1 addition & 7 deletions internal/serve/httphandler/account_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"errors"
"net/http"

"github.com/stellar/go/support/http/httpdecode"
"github.com/stellar/go/support/render/httpjson"
"github.com/stellar/go/txnbuild"
"github.com/stellar/wallet-backend/internal/entities"
Expand Down Expand Up @@ -35,12 +34,7 @@ func (h AccountHandler) SponsorAccountCreation(rw http.ResponseWriter, req *http
ctx := req.Context()

var reqBody SponsorAccountCreationRequest
if err := httpdecode.DecodeJSON(req, &reqBody); err != nil {
httperror.BadRequest("", nil).Render(rw)
return
}

httpErr := ValidateRequestBody(ctx, reqBody)
httpErr := DecodeJSONAndValidate(ctx, req, &reqBody)
if httpErr != nil {
httpErr.Render(rw)
return
Expand Down
33 changes: 33 additions & 0 deletions internal/serve/httphandler/payments_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package httphandler
import (
"net/http"

"github.com/stellar/go/support/render/httpjson"
"github.com/stellar/wallet-backend/internal/data"
"github.com/stellar/wallet-backend/internal/serve/httperror"
)
Expand Down Expand Up @@ -48,3 +49,35 @@ func (h PaymentsHandler) UnsubscribeAddress(w http.ResponseWriter, r *http.Reque
return
}
}

type PaymentsRequest struct {
Address string `query:"address" validate:"public_key"`
AfterID int64 `query:"afterId"`
Sort data.SortOrder `query:"sort" validate:"oneof=ASC DESC"`
Limit int `query:"limit" validate:"gt=0"`
}

type PaymentsResponse struct {
Payments []data.Payment `json:"payments"`
}

func (h PaymentsHandler) GetPayments(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

reqQuery := PaymentsRequest{Sort: data.DESC, Limit: 50}
httpErr := DecodeQueryAndValidate(ctx, r, &reqQuery)
if httpErr != nil {
httpErr.Render(w)
return
}

payments, err := h.Models.Payments.GetPayments(ctx, reqQuery.Address, reqQuery.AfterID, reqQuery.Sort, reqQuery.Limit)
if err != nil {
httperror.InternalServerError(ctx, "", err, nil).Render(w)
return
}

httpjson.Render(w, PaymentsResponse{
Payments: payments,
}, httpjson.JSON)
}
Loading

0 comments on commit 1f58eac

Please sign in to comment.