Skip to content

Commit f7d7ca4

Browse files
committed
feat: added date range filter to pegout reports
1 parent 218be28 commit f7d7ca4

File tree

10 files changed

+482
-50
lines changed

10 files changed

+482
-50
lines changed

internal/adapters/dataproviders/database/mongo/pegout.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,3 +350,96 @@ func (repo *pegoutMongoRepository) UpsertPegoutDeposits(ctx context.Context, dep
350350
}
351351
return err
352352
}
353+
354+
func (repo *pegoutMongoRepository) GetQuotesByState(ctx context.Context, filter quote.GetPegoutQuotesByStateFilter) ([]quote.PegoutQuote, error) {
355+
dbCtx, cancel := context.WithTimeout(ctx, repo.conn.timeout)
356+
defer cancel()
357+
358+
quoteHashes, err := repo.getRetainedQuoteHashes(dbCtx, filter.States)
359+
if err != nil {
360+
return nil, err
361+
}
362+
363+
if len(quoteHashes) == 0 {
364+
return []quote.PegoutQuote{}, nil
365+
}
366+
367+
pegoutQuotes, err := repo.getPegoutQuotesByHashesAndDate(dbCtx, quoteHashes, filter.StartDate, filter.EndDate)
368+
if err != nil {
369+
return nil, err
370+
}
371+
372+
logDbInteraction(Read, pegoutQuotes)
373+
return pegoutQuotes, nil
374+
}
375+
376+
func (repo *pegoutMongoRepository) getRetainedQuoteHashes(ctx context.Context, states []quote.PegoutState) ([]string, error) {
377+
retainedCollection := repo.conn.Collection(RetainedPegoutQuoteCollection)
378+
retainedFilter := bson.D{
379+
primitive.E{
380+
Key: "state",
381+
Value: bson.D{primitive.E{Key: "$in", Value: states}},
382+
},
383+
}
384+
385+
projection := bson.D{
386+
primitive.E{Key: "quote_hash", Value: 1},
387+
primitive.E{Key: "_id", Value: 0},
388+
}
389+
390+
retainedCursor, err := retainedCollection.Find(ctx, retainedFilter, options.Find().SetProjection(projection))
391+
if err != nil {
392+
return nil, err
393+
}
394+
defer retainedCursor.Close(ctx)
395+
396+
var retainedResults []struct {
397+
QuoteHash string `bson:"quote_hash"`
398+
}
399+
400+
if err = retainedCursor.All(ctx, &retainedResults); err != nil {
401+
return nil, err
402+
}
403+
404+
quoteHashes := make([]string, len(retainedResults))
405+
for i, result := range retainedResults {
406+
quoteHashes[i] = result.QuoteHash
407+
}
408+
409+
return quoteHashes, nil
410+
}
411+
412+
func (repo *pegoutMongoRepository) getPegoutQuotesByHashesAndDate(
413+
ctx context.Context,
414+
quoteHashes []string,
415+
startDate, endDate uint32) ([]quote.PegoutQuote, error) {
416+
417+
pegoutCollection := repo.conn.Collection(PegoutQuoteCollection)
418+
pegoutFilter := bson.D{
419+
primitive.E{
420+
Key: "hash",
421+
Value: bson.D{primitive.E{Key: "$in", Value: quoteHashes}},
422+
},
423+
primitive.E{
424+
Key: "agreement_timestamp",
425+
Value: bson.D{
426+
primitive.E{Key: "$gte", Value: startDate},
427+
primitive.E{Key: "$lte", Value: endDate},
428+
},
429+
},
430+
}
431+
432+
sortOptions := options.Find().SetSort(bson.D{primitive.E{Key: "agreement_timestamp", Value: -1}})
433+
pegoutCursor, err := pegoutCollection.Find(ctx, pegoutFilter, sortOptions)
434+
if err != nil {
435+
return nil, err
436+
}
437+
defer pegoutCursor.Close(ctx)
438+
439+
var pegoutQuotes []quote.PegoutQuote
440+
if err = pegoutCursor.All(ctx, &pegoutQuotes); err != nil {
441+
return nil, err
442+
}
443+
444+
return pegoutQuotes, nil
445+
}

internal/adapters/dataproviders/database/mongo/pegout_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package mongo_test
22

33
import (
44
"context"
5+
"errors"
56
"github.com/btcsuite/btcd/btcjson"
67
"github.com/rsksmart/liquidity-provider-server/internal/adapters/dataproviders/database/mongo"
78
"github.com/rsksmart/liquidity-provider-server/internal/entities"
@@ -720,3 +721,115 @@ func TestPegoutMongoRepository_GetQuotes(t *testing.T) {
720721
assert.Nil(t, quotes)
721722
})
722723
}
724+
725+
// nolint:funlen
726+
func TestPegoutMongoRepository_GetQuotesByStates(t *testing.T) {
727+
t.Run("Successfully retrieves quotes", func(t *testing.T) {
728+
client, db := getClientAndDatabaseMocks()
729+
retainedCollection := &mocks.CollectionBindingMock{}
730+
pegoutCollection := &mocks.CollectionBindingMock{}
731+
732+
db.EXPECT().Collection(mongo.RetainedPegoutQuoteCollection).Return(retainedCollection)
733+
db.EXPECT().Collection(mongo.PegoutQuoteCollection).Return(pegoutCollection)
734+
735+
states := []quote.PegoutState{quote.PegoutStateRefundPegOutSucceeded}
736+
startDate := uint32(1727000000)
737+
endDate := uint32(1728000000)
738+
739+
expectedQuotes := []quote.PegoutQuote{testPegoutQuote}
740+
741+
retainedCollection.On("Find", mock.Anything, mock.MatchedBy(func(filter bson.D) bool {
742+
return true
743+
}), mock.Anything).Return(mongoDb.NewCursorFromDocuments([]any{testRetainedPegoutQuote}, nil, nil))
744+
745+
pegoutCollection.On("Find", mock.Anything, mock.MatchedBy(func(filter bson.D) bool {
746+
return true
747+
}), mock.Anything).Return(mongoDb.NewCursorFromDocuments([]any{testPegoutQuote}, nil, nil))
748+
749+
conn := mongo.NewConnection(client, time.Duration(1))
750+
repo := mongo.NewPegoutMongoRepository(conn)
751+
752+
filter := quote.GetPegoutQuotesByStateFilter{
753+
States: states,
754+
StartDate: startDate,
755+
EndDate: endDate,
756+
}
757+
result, err := repo.GetQuotesByState(context.Background(), filter)
758+
759+
require.NoError(t, err)
760+
assert.Equal(t, expectedQuotes, result)
761+
762+
retainedCollection.AssertExpectations(t)
763+
pegoutCollection.AssertExpectations(t)
764+
})
765+
766+
t.Run("Fails when retainedCollection.Find returns an error", func(t *testing.T) {
767+
client, db := getClientAndDatabaseMocks()
768+
retainedCollection := &mocks.CollectionBindingMock{}
769+
770+
db.EXPECT().Collection(mongo.RetainedPegoutQuoteCollection).Return(retainedCollection)
771+
772+
states := []quote.PegoutState{quote.PegoutStateRefundPegOutSucceeded}
773+
startDate := uint32(1727000000)
774+
endDate := uint32(1728000000)
775+
776+
expectedError := errors.New("database connection error")
777+
retainedCollection.On("Find", mock.Anything, mock.MatchedBy(func(filter bson.D) bool {
778+
return true
779+
}), mock.Anything).Return(nil, expectedError)
780+
781+
conn := mongo.NewConnection(client, time.Duration(1))
782+
repo := mongo.NewPegoutMongoRepository(conn)
783+
784+
filter := quote.GetPegoutQuotesByStateFilter{
785+
States: states,
786+
StartDate: startDate,
787+
EndDate: endDate,
788+
}
789+
result, err := repo.GetQuotesByState(context.Background(), filter)
790+
791+
require.Error(t, err)
792+
assert.Equal(t, expectedError, err)
793+
assert.Nil(t, result)
794+
795+
retainedCollection.AssertExpectations(t)
796+
})
797+
t.Run("Fails when pegoutCollection.Find returns an error", func(t *testing.T) {
798+
client, db := getClientAndDatabaseMocks()
799+
retainedCollection := &mocks.CollectionBindingMock{}
800+
pegoutCollection := &mocks.CollectionBindingMock{}
801+
802+
db.EXPECT().Collection(mongo.RetainedPegoutQuoteCollection).Return(retainedCollection)
803+
db.EXPECT().Collection(mongo.PegoutQuoteCollection).Return(pegoutCollection)
804+
805+
states := []quote.PegoutState{quote.PegoutStateRefundPegOutSucceeded}
806+
startDate := uint32(1727000000)
807+
endDate := uint32(1728000000)
808+
809+
retainedCollection.On("Find", mock.Anything, mock.MatchedBy(func(filter bson.D) bool {
810+
return true
811+
}), mock.Anything).Return(mongoDb.NewCursorFromDocuments([]any{testRetainedPegoutQuote}, nil, nil))
812+
813+
expectedError := errors.New("pegout collection query error")
814+
pegoutCollection.On("Find", mock.Anything, mock.MatchedBy(func(filter bson.D) bool {
815+
return true
816+
}), mock.Anything).Return(nil, expectedError)
817+
818+
conn := mongo.NewConnection(client, time.Duration(1))
819+
repo := mongo.NewPegoutMongoRepository(conn)
820+
821+
filter := quote.GetPegoutQuotesByStateFilter{
822+
States: states,
823+
StartDate: startDate,
824+
EndDate: endDate,
825+
}
826+
result, err := repo.GetQuotesByState(context.Background(), filter)
827+
828+
require.Error(t, err)
829+
assert.Equal(t, expectedError, err)
830+
assert.Nil(t, result)
831+
832+
retainedCollection.AssertExpectations(t)
833+
pegoutCollection.AssertExpectations(t)
834+
})
835+
}

internal/adapters/entrypoints/rest/handlers/get_reports_pegin.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,20 @@ import (
1111
// NewGetReportsPeginHandler
1212
// @Title Get Pegin Reports
1313
// @Description Get the last pegins on the API. Included in the management API.
14-
// @Param GetReportsPeginRequest body pkg.GetReportsPeginRequest true "Date range for the report with startDate and endDate"
14+
// @Param GetReportsPeginRequest body pkg.GetReportsPeginPegoutRequest true "Date range for the report with startDate and endDate"
1515
// @Success 200 pkg.GetPeginReportResponse
1616
// @Route /reports/pegin [get]
1717
func NewGetReportsPeginHandler(useCase *pegin.GetPeginReportUseCase) http.HandlerFunc {
1818
return func(w http.ResponseWriter, req *http.Request) {
19-
var requestBody pkg.GetReportsPeginRequest
19+
var requestBody pkg.GetReportsPeginPegoutRequest
2020
var err error
2121
if err = json.NewDecoder(req.Body).Decode(&requestBody); err != nil {
2222
jsonErr := rest.NewErrorResponseWithDetails("Invalid request body", rest.DetailsFromError(err), false)
2323
rest.JsonErrorResponse(w, http.StatusBadRequest, jsonErr)
2424
return
2525
}
2626

27-
if err = requestBody.ValidateGetReportsPeginRequest(); err != nil {
27+
if err = requestBody.ValidateGetReportsPeginPegoutRequest(); err != nil {
2828
jsonErr := rest.NewErrorResponseWithDetails("Validation error", rest.DetailsFromError(err), false)
2929
rest.JsonErrorResponse(w, http.StatusBadRequest, jsonErr)
3030
return

internal/adapters/entrypoints/rest/handlers/get_reports_pegout.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package handlers
22

33
import (
4+
"encoding/json"
45
"github.com/rsksmart/liquidity-provider-server/internal/adapters/entrypoints/rest"
56
"github.com/rsksmart/liquidity-provider-server/internal/usecases/pegout"
67
"github.com/rsksmart/liquidity-provider-server/pkg"
@@ -10,13 +11,33 @@ import (
1011
// NewGetReportsPegoutHandler
1112
// @Title Get Pegout Reports
1213
// @Description Get the last pegouts on the API. Included in the management API.
14+
// @Param GetReportsPeginRequest body pkg.GetReportsPeginPegoutRequest true "Date range for the report with startDate and endDate"
1315
// @Success 200 pkg.GetPegoutReportResponse
1416
// @Route /reports/pegout [get]
1517
func NewGetReportsPegoutHandler(useCase *pegout.GetPegoutReportUseCase) http.HandlerFunc {
1618
return func(w http.ResponseWriter, req *http.Request) {
19+
var requestBody pkg.GetReportsPeginPegoutRequest
1720
var err error
21+
if err = json.NewDecoder(req.Body).Decode(&requestBody); err != nil {
22+
jsonErr := rest.NewErrorResponseWithDetails("Invalid request body", rest.DetailsFromError(err), false)
23+
rest.JsonErrorResponse(w, http.StatusBadRequest, jsonErr)
24+
return
25+
}
26+
27+
if err = requestBody.ValidateGetReportsPeginPegoutRequest(); err != nil {
28+
jsonErr := rest.NewErrorResponseWithDetails("Validation error", rest.DetailsFromError(err), false)
29+
rest.JsonErrorResponse(w, http.StatusBadRequest, jsonErr)
30+
return
31+
}
32+
33+
startTime, endTime, err := requestBody.GetTimestamps()
34+
if err != nil {
35+
jsonErr := rest.NewErrorResponseWithDetails("Date conversion error", rest.DetailsFromError(err), false)
36+
rest.JsonErrorResponse(w, http.StatusBadRequest, jsonErr)
37+
return
38+
}
1839

19-
pegoutReport, err := useCase.Run(req.Context())
40+
pegoutReport, err := useCase.Run(req.Context(), startTime, endTime)
2041
if err != nil {
2142
jsonErr := rest.NewErrorResponseWithDetails(UnknownErrorMessage, rest.DetailsFromError(err), false)
2243
rest.JsonErrorResponse(w, http.StatusInternalServerError, jsonErr)

internal/entities/quote/pegout_quote.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,19 @@ type PegoutQuoteRepository interface {
4040
UpdateRetainedQuote(ctx context.Context, quote RetainedPegoutQuote) error
4141
UpdateRetainedQuotes(ctx context.Context, quotes []RetainedPegoutQuote) error
4242
GetRetainedQuoteByState(ctx context.Context, states ...PegoutState) ([]RetainedPegoutQuote, error)
43+
GetQuotesByState(ctx context.Context, filter GetPegoutQuotesByStateFilter) ([]PegoutQuote, error)
4344
// DeleteQuotes deletes both regular and retained quotes
4445
DeleteQuotes(ctx context.Context, quotes []string) (uint, error)
4546
UpsertPegoutDeposit(ctx context.Context, deposit PegoutDeposit) error
4647
UpsertPegoutDeposits(ctx context.Context, deposits []PegoutDeposit) error
4748
}
4849

50+
type GetPegoutQuotesByStateFilter struct {
51+
States []PegoutState
52+
StartDate uint32
53+
EndDate uint32
54+
}
55+
4956
type CreatedPegoutQuote struct {
5057
Hash string
5158
Quote PegoutQuote

internal/usecases/pegout/get_pegout_report.go

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"github.com/rsksmart/liquidity-provider-server/internal/entities"
66
"github.com/rsksmart/liquidity-provider-server/internal/entities/quote"
77
"github.com/rsksmart/liquidity-provider-server/internal/usecases"
8+
"time"
89
)
910

1011
type GetPegoutReportUseCase struct {
@@ -28,21 +29,29 @@ type GetPegoutReportResult struct {
2829
AverageFeePerQuote *entities.Wei
2930
}
3031

31-
func (useCase *GetPegoutReportUseCase) Run(ctx context.Context) (GetPegoutReportResult, error) {
32+
func (useCase *GetPegoutReportUseCase) Run(
33+
ctx context.Context,
34+
startDate time.Time,
35+
endDate time.Time,
36+
) (GetPegoutReportResult, error) {
3237
var err error
33-
var retained []quote.RetainedPegoutQuote
38+
var quotes []quote.PegoutQuote
3439
var minimumQuoteValue *entities.Wei
3540
var maximumQuoteValue *entities.Wei
3641
var averageQuoteValue *entities.Wei
3742
var totalFeesCollected *entities.Wei
3843
var averageFeePerQuote *entities.Wei
3944

40-
retained, err = useCase.pegoutQuoteRepository.GetRetainedQuoteByState(ctx, quote.PegoutStateRefundPegOutSucceeded)
41-
45+
filter := quote.GetPegoutQuotesByStateFilter{
46+
States: []quote.PegoutState{quote.PegoutStateRefundPegOutSucceeded},
47+
StartDate: uint32(startDate.Unix()),
48+
EndDate: uint32(endDate.Unix()),
49+
}
50+
quotes, err = useCase.pegoutQuoteRepository.GetQuotesByState(ctx, filter)
4251
if err != nil {
4352
return GetPegoutReportResult{}, usecases.WrapUseCaseError(usecases.GetPegoutReportId, err)
4453
}
45-
if len(retained) == 0 {
54+
if len(quotes) == 0 {
4655
return GetPegoutReportResult{
4756
NumberOfQuotes: 0,
4857
MinimumQuoteValue: entities.NewWei(0),
@@ -53,16 +62,6 @@ func (useCase *GetPegoutReportUseCase) Run(ctx context.Context) (GetPegoutReport
5362
}, nil
5463
}
5564

56-
quoteHashes := make([]string, 0)
57-
for _, q := range retained {
58-
quoteHashes = append(quoteHashes, q.QuoteHash)
59-
}
60-
61-
quotes, err := useCase.pegoutQuoteRepository.GetQuotes(ctx, quoteHashes)
62-
if err != nil {
63-
return GetPegoutReportResult{}, usecases.WrapUseCaseError(usecases.GetPegoutReportId, err)
64-
}
65-
6665
minimumQuoteValue = useCase.calculateMinimumQuoteValue(quotes)
6766
maximumQuoteValue = useCase.calculateMaximumQuoteValue(quotes)
6867
averageQuoteValue, err = useCase.calculateAverageQuoteValue(quotes)

0 commit comments

Comments
 (0)