From ffcd47d8ede19940e66aa91efa1120cccb002b04 Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Fri, 20 Jun 2025 11:12:38 +0200 Subject: [PATCH] feat: add some pagination options --- docs/api/README.md | 28 ++++- go.mod | 1 + go.sum | 2 + internal/README.md | 2 +- internal/account.go | 2 +- internal/api/common/errors.go | 13 +++ internal/api/v2/common.go | 17 +++ internal/api/v2/controllers_accounts_list.go | 10 +- internal/api/v2/controllers_ledgers_list.go | 10 +- internal/api/v2/controllers_logs_list.go | 9 +- .../api/v2/controllers_transactions_list.go | 10 +- internal/api/v2/controllers_volumes.go | 8 +- internal/api/v2/views_test.go | 2 + .../controller/ledger/controller_default.go | 2 +- internal/controller/ledger/store.go | 2 +- .../controller/ledger/store_generated_test.go | 8 +- internal/storage/common/errors.go | 19 ++++ internal/storage/common/resource.go | 4 + internal/storage/common/schema.go | 8 +- internal/storage/ledger/accounts.go | 6 +- internal/storage/ledger/accounts_test.go | 6 +- internal/storage/ledger/balances_test.go | 4 +- internal/storage/ledger/resource_accounts.go | 4 +- .../ledger/resource_aggregated_balances.go | 2 +- internal/storage/ledger/resource_logs.go | 4 +- .../storage/ledger/resource_transactions.go | 4 +- internal/storage/ledger/resource_volumes.go | 7 +- internal/storage/ledger/volumes_test.go | 4 +- internal/storage/system/resource_ledgers.go | 2 +- openapi.yaml | 20 ++++ openapi/v2.yaml | 23 ++++ pkg/client/.speakeasy/gen.lock | 9 +- pkg/client/.speakeasy/logs/naming.log | 4 +- .../docs/models/components/v2account.md | 1 + pkg/client/docs/models/operations/order.md | 2 + .../v2getvolumeswithbalancesrequest.md | 1 + .../operations/v2listaccountsrequest.md | 1 + .../models/operations/v2listledgersrequest.md | 1 + .../models/operations/v2listlogsrequest.md | 1 + .../operations/v2listtransactionsrequest.md | 3 +- pkg/client/docs/sdks/v2/README.md | 5 + pkg/client/models/components/v2account.go | 24 ++++ .../operations/v2getvolumeswithbalances.go | 13 ++- .../models/operations/v2listaccounts.go | 17 ++- pkg/client/models/operations/v2listledgers.go | 13 ++- pkg/client/models/operations/v2listlogs.go | 15 ++- .../models/operations/v2listtransactions.go | 25 ++++- .../.speakeasy/logs/naming.log | 4 +- test/e2e/api_accounts_list_test.go | 104 ++++++++++-------- test/e2e/api_accounts_metadata_test.go | 24 ++-- test/e2e/api_logs_list_test.go | 12 ++ test/e2e/api_transactions_create_test.go | 3 + test/e2e/api_transactions_list_test.go | 30 +++++ test/e2e/api_volumes_test.go | 14 +-- tools/generator/go.sum | 2 + 55 files changed, 412 insertions(+), 159 deletions(-) diff --git a/docs/api/README.md b/docs/api/README.md index d364eb1eaa..2d901bf5cf 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -136,6 +136,7 @@ Accept: application/json |---|---|---|---|---| |pageSize|query|integer(int64)|false|The maximum number of results to return per page.| |cursor|query|string|false|Parameter used in pagination requests. Maximum page size is set to 15.| +|sort|query|string|false|Sort results using a field name and order (ascending or descending). | |body|body|object|true|none| #### Detailed descriptions @@ -147,6 +148,9 @@ Set to the value of next for the next page of results. Set to the value of previous for the previous page of results. No other parameters can be set when this parameter is set. +**sort**: Sort results using a field name and order (ascending or descending). +Format: `:`, where `` is the field name and `` is either `asc` or `desc`. + > Example responses > 200 Response @@ -737,6 +741,7 @@ List accounts from a ledger, sorted by address in descending order. |cursor|query|string|false|Parameter used in pagination requests. Maximum page size is set to 15.| |expand|query|string|false|none| |pit|query|string(date-time)|false|none| +|sort|query|string|false|Sort results using a field name and order (ascending or descending). | |body|body|object|true|none| #### Detailed descriptions @@ -748,6 +753,9 @@ Set to the value of next for the next page of results. Set to the value of previous for the previous page of results. No other parameters can be set when this parameter is set. +**sort**: Sort results using a field name and order (ascending or descending). +Format: `:`, where `` is the field name and `` is either `asc` or `desc`. + > Example responses > 200 Response @@ -765,6 +773,7 @@ No other parameters can be set when this parameter is set. "metadata": { "admin": "true" }, + "firstUsage": "2023-01-01T00:00:00Z", "volumes": { "USD": { "input": 100, @@ -849,6 +858,7 @@ Accept: application/json "metadata": { "admin": "true" }, + "firstUsage": "2023-01-01T00:00:00Z", "volumes": { "USD": { "input": 100, @@ -1151,8 +1161,9 @@ List transactions from a ledger, sorted by id in descending order. |cursor|query|string|false|Parameter used in pagination requests. Maximum page size is set to 15.| |expand|query|string|false|none| |pit|query|string(date-time)|false|none| -|order|query|string|false|none| +|order|query|string|false|Deprecated: Use sort param| |reverse|query|boolean|false|none| +|sort|query|string|false|Sort results using a field name and order (ascending or descending). | |body|body|object|true|none| #### Detailed descriptions @@ -1164,6 +1175,9 @@ Set to the value of next for the next page of results. Set to the value of previous for the previous page of results. No other parameters can be set when this parameter is set. +**sort**: Sort results using a field name and order (ascending or descending). +Format: `:`, where `` is the field name and `` is either `asc` or `desc`. + #### Enumerated Values |Parameter|Value| @@ -1862,6 +1876,7 @@ Accept: application/json |startTime|query|string(date-time)|false|none| |insertionDate|query|boolean|false|Use insertion date instead of effective date| |groupBy|query|integer(int64)|false|Group volumes and balance by the level of the segment of the address| +|sort|query|string|false|Sort results using a field name and order (ascending or descending). | |body|body|object|true|none| #### Detailed descriptions @@ -1873,6 +1888,9 @@ Set to the value of next for the next page of results. Set to the value of previous for the previous page of results. No other parameters can be set when this parameter is set. +**sort**: Sort results using a field name and order (ascending or descending). +Format: `:`, where `` is the field name and `` is either `asc` or `desc`. + > Example responses > 200 Response @@ -1941,6 +1959,7 @@ List the logs from a ledger, sorted by ID in descending order. |pageSize|query|integer(int64)|false|The maximum number of results to return per page.| |cursor|query|string|false|Parameter used in pagination requests. Maximum page size is set to 15.| |pit|query|string(date-time)|false|none| +|sort|query|string|false|Sort results using a field name and order (ascending or descending). | |body|body|object|true|none| #### Detailed descriptions @@ -1952,6 +1971,9 @@ Set to the value of next for the next page of results. Set to the value of previous for the previous page of results. No other parameters can be set when this parameter is set. +**sort**: Sort results using a field name and order (ascending or descending). +Format: `:`, where `` is the field name and `` is either `asc` or `desc`. + > Example responses > 200 Response @@ -2101,6 +2123,7 @@ Authorization ( Scopes: ledger:write ) "metadata": { "admin": "true" }, + "firstUsage": "2023-01-01T00:00:00Z", "volumes": { "USD": { "input": 100, @@ -2311,6 +2334,7 @@ Authorization ( Scopes: ledger:write ) "metadata": { "admin": "true" }, + "firstUsage": "2023-01-01T00:00:00Z", "volumes": { "USD": { "input": 100, @@ -2491,6 +2515,7 @@ Authorization ( Scopes: ledger:write ) "metadata": { "admin": "true" }, + "firstUsage": "2023-01-01T00:00:00Z", "volumes": { "USD": { "input": 100, @@ -2526,6 +2551,7 @@ Authorization ( Scopes: ledger:write ) |address|string|true|none|none| |metadata|object|true|none|none| |ยป **additionalProperties**|string|false|none|none| +|firstUsage|string(date-time)|false|none|none| |volumes|[V2Volumes](#schemav2volumes)|false|none|none| |effectiveVolumes|[V2Volumes](#schemav2volumes)|false|none|none| diff --git a/go.mod b/go.mod index 051664a6c6..07b201f37f 100644 --- a/go.mod +++ b/go.mod @@ -51,6 +51,7 @@ require ( require ( github.com/formancehq/go-libs/v3 v3.0.0-20250610201819-3bfa67aab0e2 + github.com/iancoleman/strcase v0.3.0 github.com/robfig/cron/v3 v3.0.1 github.com/spf13/viper v1.20.1 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index 4953288776..5e6da771f2 100644 --- a/go.sum +++ b/go.sum @@ -194,6 +194,8 @@ github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFO github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= diff --git a/internal/README.md b/internal/README.md index b47fc8267c..a686fb5832 100644 --- a/internal/README.md +++ b/internal/README.md @@ -232,7 +232,7 @@ type Account struct { Address string `json:"address" bun:"address"` Metadata metadata.Metadata `json:"metadata" bun:"metadata,type:jsonb,default:'{}'"` - FirstUsage time.Time `json:"-" bun:"first_usage,nullzero"` + FirstUsage time.Time `json:"firstUsage" bun:"first_usage,nullzero"` InsertionDate time.Time `json:"-" bun:"insertion_date,nullzero"` UpdatedAt time.Time `json:"-" bun:"updated_at,nullzero"` Volumes VolumesByAssets `json:"volumes,omitempty" bun:"volumes,scanonly"` diff --git a/internal/account.go b/internal/account.go index d46ccb751c..4d6845a56f 100644 --- a/internal/account.go +++ b/internal/account.go @@ -16,7 +16,7 @@ type Account struct { Address string `json:"address" bun:"address"` Metadata metadata.Metadata `json:"metadata" bun:"metadata,type:jsonb,default:'{}'"` - FirstUsage time.Time `json:"-" bun:"first_usage,nullzero"` + FirstUsage time.Time `json:"firstUsage" bun:"first_usage,nullzero"` InsertionDate time.Time `json:"-" bun:"insertion_date,nullzero"` UpdatedAt time.Time `json:"-" bun:"updated_at,nullzero"` Volumes VolumesByAssets `json:"volumes,omitempty" bun:"volumes,scanonly"` diff --git a/internal/api/common/errors.go b/internal/api/common/errors.go index ed6741ceda..0076eb4fb8 100644 --- a/internal/api/common/errors.go +++ b/internal/api/common/errors.go @@ -2,6 +2,7 @@ package common import ( "errors" + storagecommon "github.com/formancehq/ledger/internal/storage/common" "net/http" "github.com/formancehq/go-libs/v3/api" @@ -52,6 +53,18 @@ func HandleCommonWriteErrors(w http.ResponseWriter, r *http.Request, err error) } } +func HandleCommonPaginationErrors(w http.ResponseWriter, r *http.Request, err error) { + switch { + case errors.Is(err, storagecommon.ErrInvalidQuery{}) || + errors.Is(err, ledgercontroller.ErrMissingFeature{}) || + errors.Is(err, storagecommon.ErrNotPaginatedField{}): + api.BadRequest(w, ErrValidation, err) + default: + HandleCommonErrors(w, r, err) + } +} + + func InternalServerError(w http.ResponseWriter, r *http.Request, err error) { otlp.RecordError(r.Context(), err) logging.FromContext(r.Context()).Error(err) diff --git a/internal/api/v2/common.go b/internal/api/v2/common.go index 3e6b9e20b8..9252dcefab 100644 --- a/internal/api/v2/common.go +++ b/internal/api/v2/common.go @@ -1,9 +1,11 @@ package v2 import ( + "fmt" . "github.com/formancehq/go-libs/v3/collectionutils" "github.com/formancehq/ledger/internal/api/common" storagecommon "github.com/formancehq/ledger/internal/storage/common" + "github.com/iancoleman/strcase" "io" "net/http" "strings" @@ -89,6 +91,21 @@ func getPaginatedQuery[Options any]( return nil, err } + if sort := r.URL.Query().Get("sort"); sort != "" { + parts := strings.SplitN(sort, ":", 2) + column = strcase.ToSnake(parts[0]) + if len(parts) > 1 { + switch { + case strings.ToLower(parts[1]) == "desc": + order = bunpaginate.OrderDesc + case strings.ToLower(parts[1]) == "asc": + order = bunpaginate.OrderAsc + default: + return nil, fmt.Errorf("invalid order: %s", parts[1]) + } + } + } + return &storagecommon.InitialPaginatedQuery[Options]{ Column: column, Order: &order, diff --git a/internal/api/v2/controllers_accounts_list.go b/internal/api/v2/controllers_accounts_list.go index 5d9956dc00..7a3e2f6a61 100644 --- a/internal/api/v2/controllers_accounts_list.go +++ b/internal/api/v2/controllers_accounts_list.go @@ -1,13 +1,10 @@ package v2 import ( - "errors" "github.com/formancehq/go-libs/v3/api" "github.com/formancehq/go-libs/v3/bun/bunpaginate" ledger "github.com/formancehq/ledger/internal" "github.com/formancehq/ledger/internal/api/common" - ledgercontroller "github.com/formancehq/ledger/internal/controller/ledger" - storagecommon "github.com/formancehq/ledger/internal/storage/common" "net/http" ) @@ -23,12 +20,7 @@ func listAccounts(paginationConfig common.PaginationConfig) http.HandlerFunc { cursor, err := l.ListAccounts(r.Context(), query) if err != nil { - switch { - case errors.Is(err, storagecommon.ErrInvalidQuery{}) || errors.Is(err, ledgercontroller.ErrMissingFeature{}): - api.BadRequest(w, common.ErrValidation, err) - default: - common.HandleCommonErrors(w, r, err) - } + common.HandleCommonPaginationErrors(w, r, err) return } diff --git a/internal/api/v2/controllers_ledgers_list.go b/internal/api/v2/controllers_ledgers_list.go index d7cd782fcb..7e76c4a84b 100644 --- a/internal/api/v2/controllers_ledgers_list.go +++ b/internal/api/v2/controllers_ledgers_list.go @@ -2,13 +2,10 @@ package v2 import ( "github.com/formancehq/ledger/internal/api/common" - storagecommon "github.com/formancehq/ledger/internal/storage/common" "net/http" - "errors" "github.com/formancehq/go-libs/v3/api" "github.com/formancehq/go-libs/v3/bun/bunpaginate" - ledgercontroller "github.com/formancehq/ledger/internal/controller/ledger" "github.com/formancehq/ledger/internal/controller/system" ) @@ -23,12 +20,7 @@ func listLedgers(b system.Controller, paginationConfig common.PaginationConfig) ledgers, err := b.ListLedgers(r.Context(), rq) if err != nil { - switch { - case errors.Is(err, storagecommon.ErrInvalidQuery{}) || errors.Is(err, ledgercontroller.ErrMissingFeature{}): - api.BadRequest(w, common.ErrValidation, err) - default: - common.HandleCommonErrors(w, r, err) - } + common.HandleCommonPaginationErrors(w, r, err) return } diff --git a/internal/api/v2/controllers_logs_list.go b/internal/api/v2/controllers_logs_list.go index 6a999e5aad..6a130cd756 100644 --- a/internal/api/v2/controllers_logs_list.go +++ b/internal/api/v2/controllers_logs_list.go @@ -1,12 +1,10 @@ package v2 import ( - "errors" "github.com/formancehq/go-libs/v3/api" "github.com/formancehq/go-libs/v3/bun/bunpaginate" ledger "github.com/formancehq/ledger/internal" "github.com/formancehq/ledger/internal/api/common" - storagecommon "github.com/formancehq/ledger/internal/storage/common" "net/http" ) @@ -22,12 +20,7 @@ func listLogs(paginationConfig common.PaginationConfig) http.HandlerFunc { cursor, err := l.ListLogs(r.Context(), rq) if err != nil { - switch { - case errors.Is(err, storagecommon.ErrInvalidQuery{}): - api.BadRequest(w, common.ErrValidation, err) - default: - common.HandleCommonErrors(w, r, err) - } + common.HandleCommonPaginationErrors(w, r, err) return } diff --git a/internal/api/v2/controllers_transactions_list.go b/internal/api/v2/controllers_transactions_list.go index c788dd843f..791d6a4b30 100644 --- a/internal/api/v2/controllers_transactions_list.go +++ b/internal/api/v2/controllers_transactions_list.go @@ -1,13 +1,10 @@ package v2 import ( - "errors" "github.com/formancehq/go-libs/v3/api" "github.com/formancehq/go-libs/v3/bun/bunpaginate" ledger "github.com/formancehq/ledger/internal" "github.com/formancehq/ledger/internal/api/common" - ledgercontroller "github.com/formancehq/ledger/internal/controller/ledger" - storagecommon "github.com/formancehq/ledger/internal/storage/common" "net/http" ) @@ -33,12 +30,7 @@ func listTransactions(paginationConfig common.PaginationConfig) http.HandlerFunc cursor, err := l.ListTransactions(r.Context(), rq) if err != nil { - switch { - case errors.Is(err, storagecommon.ErrInvalidQuery{}) || errors.Is(err, ledgercontroller.ErrMissingFeature{}): - api.BadRequest(w, common.ErrValidation, err) - default: - common.HandleCommonErrors(w, r, err) - } + common.HandleCommonPaginationErrors(w, r, err) return } diff --git a/internal/api/v2/controllers_volumes.go b/internal/api/v2/controllers_volumes.go index c872bb9ff5..fef1e21e65 100644 --- a/internal/api/v2/controllers_volumes.go +++ b/internal/api/v2/controllers_volumes.go @@ -1,7 +1,6 @@ package v2 import ( - "errors" "github.com/formancehq/go-libs/v3/api" "github.com/formancehq/go-libs/v3/bun/bunpaginate" "github.com/formancehq/go-libs/v3/time" @@ -76,12 +75,7 @@ func readVolumes(paginationConfig common.PaginationConfig) http.HandlerFunc { cursor, err := l.GetVolumesWithBalances(r.Context(), rq) if err != nil { - switch { - case errors.Is(err, storagecommon.ErrInvalidQuery{}) || errors.Is(err, ledgercontroller.ErrMissingFeature{}): - api.BadRequest(w, common.ErrValidation, err) - default: - common.HandleCommonErrors(w, r, err) - } + common.HandleCommonPaginationErrors(w, r, err) return } diff --git a/internal/api/v2/views_test.go b/internal/api/v2/views_test.go index db9e16d86a..4e85bdaf25 100644 --- a/internal/api/v2/views_test.go +++ b/internal/api/v2/views_test.go @@ -381,6 +381,7 @@ func TestAccountRender(t *testing.T) { "balance": float64(-100), }, }, + "firstUsage": now.Format(time.RFC3339Nano), }, }, { @@ -407,6 +408,7 @@ func TestAccountRender(t *testing.T) { "balance": "-100", }, }, + "firstUsage": now.Format(time.RFC3339Nano), }, }, } { diff --git a/internal/controller/ledger/controller_default.go b/internal/controller/ledger/controller_default.go index b39fb1642a..047baac7a0 100644 --- a/internal/controller/ledger/controller_default.go +++ b/internal/controller/ledger/controller_default.go @@ -274,7 +274,7 @@ func (ctrl *DefaultController) importLog(ctx context.Context, store Store, log l logging.FromContext(ctx).Debugf("Saving metadata of account %s", payload.TargetID) if err := store.UpdateAccountsMetadata(ctx, ledger.AccountMetadata{ payload.TargetID.(string): payload.Metadata, - }); err != nil { + }, log.Date); err != nil { return nil, fmt.Errorf("failed to update account metadata: %w", err) } } diff --git a/internal/controller/ledger/store.go b/internal/controller/ledger/store.go index 65372f2c4b..c75a40267b 100644 --- a/internal/controller/ledger/store.go +++ b/internal/controller/ledger/store.go @@ -42,7 +42,7 @@ type Store interface { RevertTransaction(ctx context.Context, id uint64, at time.Time) (*ledger.Transaction, bool, error) UpdateTransactionMetadata(ctx context.Context, transactionID uint64, m metadata.Metadata, at time.Time) (*ledger.Transaction, bool, error) DeleteTransactionMetadata(ctx context.Context, transactionID uint64, key string, at time.Time) (*ledger.Transaction, bool, error) - UpdateAccountsMetadata(ctx context.Context, m map[string]metadata.Metadata) error + UpdateAccountsMetadata(ctx context.Context, m map[string]metadata.Metadata, at time.Time) error // UpsertAccount returns a boolean indicating if the account was upserted UpsertAccounts(ctx context.Context, accounts ...*ledger.Account) error DeleteAccountMetadata(ctx context.Context, address, key string) error diff --git a/internal/controller/ledger/store_generated_test.go b/internal/controller/ledger/store_generated_test.go index f8508d2ebd..c17ea9ce97 100644 --- a/internal/controller/ledger/store_generated_test.go +++ b/internal/controller/ledger/store_generated_test.go @@ -311,17 +311,17 @@ func (mr *MockStoreMockRecorder) Transactions() *gomock.Call { } // UpdateAccountsMetadata mocks base method. -func (m_2 *MockStore) UpdateAccountsMetadata(ctx context.Context, m map[string]metadata.Metadata) error { +func (m_2 *MockStore) UpdateAccountsMetadata(ctx context.Context, m map[string]metadata.Metadata, at time.Time) error { m_2.ctrl.T.Helper() - ret := m_2.ctrl.Call(m_2, "UpdateAccountsMetadata", ctx, m) + ret := m_2.ctrl.Call(m_2, "UpdateAccountsMetadata", ctx, m, at) ret0, _ := ret[0].(error) return ret0 } // UpdateAccountsMetadata indicates an expected call of UpdateAccountsMetadata. -func (mr *MockStoreMockRecorder) UpdateAccountsMetadata(ctx, m any) *gomock.Call { +func (mr *MockStoreMockRecorder) UpdateAccountsMetadata(ctx, m, at any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAccountsMetadata", reflect.TypeOf((*MockStore)(nil).UpdateAccountsMetadata), ctx, m) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAccountsMetadata", reflect.TypeOf((*MockStore)(nil).UpdateAccountsMetadata), ctx, m, at) } // UpdateTransactionMetadata mocks base method. diff --git a/internal/storage/common/errors.go b/internal/storage/common/errors.go index 5ca6ab7c80..0fba87c2d6 100644 --- a/internal/storage/common/errors.go +++ b/internal/storage/common/errors.go @@ -20,3 +20,22 @@ func NewErrInvalidQuery(msg string, args ...any) ErrInvalidQuery { msg: fmt.Sprintf(msg, args...), } } + +type ErrNotPaginatedField struct { + field string +} + +func (e ErrNotPaginatedField) Error() string { + return fmt.Sprintf("field %s is not paginated", e.field) +} + +func (e ErrNotPaginatedField) Is(err error) bool { + _, ok := err.(ErrNotPaginatedField) + return ok +} + +func newErrNotPaginatedField(field string) ErrNotPaginatedField { + return ErrNotPaginatedField{ + field: field, + } +} \ No newline at end of file diff --git a/internal/storage/common/resource.go b/internal/storage/common/resource.go index a64d3da5a5..3f98654d45 100644 --- a/internal/storage/common/resource.go +++ b/internal/storage/common/resource.go @@ -284,6 +284,10 @@ func (r *PaginatedResourceRepository[ResourceType, OptionsType]) Paginate( return nil, fmt.Errorf("invalid property '%s' for pagination", v.Column) } + if !field.IsPaginated { + return nil, newErrNotPaginatedField(v.Column) + } + if field.Type.IsPaginated() { paginationQuery = ColumnPaginatedQuery[OptionsType]{ InitialPaginatedQuery: v, diff --git a/internal/storage/common/schema.go b/internal/storage/common/schema.go index 1a3c08f4d1..9e5c4ae846 100644 --- a/internal/storage/common/schema.go +++ b/internal/storage/common/schema.go @@ -24,7 +24,8 @@ type FieldType interface { type Field struct { Aliases []string - Type FieldType + Type FieldType + IsPaginated bool } func (f Field) WithAliases(aliases ...string) Field { @@ -32,6 +33,11 @@ func (f Field) WithAliases(aliases ...string) Field { return f } +func (f Field) Paginated() Field { + f.IsPaginated = true + return f +} + func (f Field) matchKey(name, key string) bool { if key == name { return true diff --git a/internal/storage/ledger/accounts.go b/internal/storage/ledger/accounts.go index 692c9bc75b..944b3b257a 100644 --- a/internal/storage/ledger/accounts.go +++ b/internal/storage/ledger/accounts.go @@ -5,12 +5,14 @@ import ( "fmt" . "github.com/formancehq/go-libs/v3/collectionutils" "github.com/formancehq/go-libs/v3/pointer" + "github.com/formancehq/go-libs/v3/time" "github.com/formancehq/ledger/internal/tracing" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "regexp" "strings" + "github.com/formancehq/go-libs/v3/metadata" "github.com/formancehq/go-libs/v3/platform/postgres" ledger "github.com/formancehq/ledger/internal" @@ -20,7 +22,7 @@ var ( balanceRegex = regexp.MustCompile(`balance\[(.*)]`) ) -func (store *Store) UpdateAccountsMetadata(ctx context.Context, m map[string]metadata.Metadata) error { +func (store *Store) UpdateAccountsMetadata(ctx context.Context, m map[string]metadata.Metadata, at time.Time) error { _, err := tracing.TraceWithMetric( ctx, "UpdateAccountsMetadata", @@ -44,6 +46,7 @@ func (store *Store) UpdateAccountsMetadata(ctx context.Context, m map[string]met Account: ledger.Account{ Address: account, Metadata: accountMetadata, + FirstUsage: at, }, AddressArray: strings.Split(account, ":"), }) @@ -55,6 +58,7 @@ func (store *Store) UpdateAccountsMetadata(ctx context.Context, m map[string]met On("conflict (ledger, address) do update"). Set("metadata = accounts.metadata || excluded.metadata"). Set("updated_at = excluded.updated_at"). + Set("first_usage = case when excluded.first_usage < accounts.first_usage then excluded.first_usage else accounts.first_usage end"). Where("not accounts.metadata @> excluded.metadata"). Exec(ctx) if err != nil { diff --git a/internal/storage/ledger/accounts_test.go b/internal/storage/ledger/accounts_test.go index 4c7db2796b..1c98cf19fe 100644 --- a/internal/storage/ledger/accounts_test.go +++ b/internal/storage/ledger/accounts_test.go @@ -49,7 +49,7 @@ func TestAccountsList(t *testing.T) { "orders:2": { "foo": "bar", }, - })) + }, time.Time{})) err = store.CommitTransaction(ctx, pointer.For(ledger.NewTransaction(). WithPostings(ledger.NewPosting("world", "account:1", "USD", big.NewInt(100))). @@ -299,7 +299,7 @@ func TestAccountsUpdateMetadata(t *testing.T) { require.NoError(t, store.UpdateAccountsMetadata(ctx, map[string]metadata.Metadata{ "bank": m, - })) + }, time.Time{})) account, err := store.Accounts().GetOne(context.Background(), common.ResourceQuery[any]{ Builder: query.Match("address", "bank"), @@ -330,7 +330,7 @@ func TestAccountsGet(t *testing.T) { "multi": { "category": "gold", }, - })) + }, time.Time{})) tx2 := pointer.For(ledger.NewTransaction().WithPostings( ledger.NewPosting("world", "multi", "USD/2", big.NewInt(0)), diff --git a/internal/storage/ledger/balances_test.go b/internal/storage/ledger/balances_test.go index c56936b01f..dbafe9ef37 100644 --- a/internal/storage/ledger/balances_test.go +++ b/internal/storage/ledger/balances_test.go @@ -196,7 +196,7 @@ func TestBalancesAggregates(t *testing.T) { "users:2": { "category": "premium", }, - })) + }, time.Time{})) require.NoError(t, store.DeleteAccountMetadata(ctx, "users:2", "category")) @@ -210,7 +210,7 @@ func TestBalancesAggregates(t *testing.T) { "world": { "world": "bar", }, - })) + }, time.Time{})) t.Run("aggregate on all", func(t *testing.T) { t.Parallel() diff --git a/internal/storage/ledger/resource_accounts.go b/internal/storage/ledger/resource_accounts.go index 236b2750d3..e2d5321e30 100644 --- a/internal/storage/ledger/resource_accounts.go +++ b/internal/storage/ledger/resource_accounts.go @@ -16,8 +16,8 @@ type accountsResourceHandler struct { func (h accountsResourceHandler) Schema() common.EntitySchema { return common.EntitySchema{ Fields: map[string]common.Field{ - "address": common.NewStringField(), - "first_usage": common.NewDateField(), + "address": common.NewStringField().Paginated(), + "first_usage": common.NewDateField().Paginated(), "balance": common.NewNumericMapField(), "metadata": common.NewStringMapField(), }, diff --git a/internal/storage/ledger/resource_aggregated_balances.go b/internal/storage/ledger/resource_aggregated_balances.go index db9c1f1d83..3e137aa9b0 100644 --- a/internal/storage/ledger/resource_aggregated_balances.go +++ b/internal/storage/ledger/resource_aggregated_balances.go @@ -15,7 +15,7 @@ type aggregatedBalancesResourceRepositoryHandler struct { func (h aggregatedBalancesResourceRepositoryHandler) Schema() common.EntitySchema { return common.EntitySchema{ Fields: map[string]common.Field{ - "address": common.NewStringField(), + "address": common.NewStringField().Paginated(), "metadata": common.NewStringMapField(), }, } diff --git a/internal/storage/ledger/resource_logs.go b/internal/storage/ledger/resource_logs.go index 9107be1bda..06d0135b29 100644 --- a/internal/storage/ledger/resource_logs.go +++ b/internal/storage/ledger/resource_logs.go @@ -14,8 +14,8 @@ type logsResourceHandler struct { func (h logsResourceHandler) Schema() common.EntitySchema { return common.EntitySchema{ Fields: map[string]common.Field{ - "date": common.NewDateField(), - "id": common.NewNumericField(), + "date": common.NewDateField().Paginated(), + "id": common.NewNumericField().Paginated(), }, } } diff --git a/internal/storage/ledger/resource_transactions.go b/internal/storage/ledger/resource_transactions.go index 8a627a053d..9d5abc1398 100644 --- a/internal/storage/ledger/resource_transactions.go +++ b/internal/storage/ledger/resource_transactions.go @@ -19,9 +19,9 @@ func (h transactionsResourceHandler) Schema() common.EntitySchema { "account": common.NewStringField(), "source": common.NewStringField(), "destination": common.NewStringField(), - "timestamp": common.NewDateField(), + "timestamp": common.NewDateField().Paginated(), "metadata": common.NewStringMapField(), - "id": common.NewNumericField(), + "id": common.NewNumericField().Paginated(), "reference": common.NewStringField(), }, } diff --git a/internal/storage/ledger/resource_volumes.go b/internal/storage/ledger/resource_volumes.go index afff4b004b..bf1dfda905 100644 --- a/internal/storage/ledger/resource_volumes.go +++ b/internal/storage/ledger/resource_volumes.go @@ -18,10 +18,11 @@ func (h volumesResourceHandler) Schema() common.EntitySchema { return common.EntitySchema{ Fields: map[string]common.Field{ "address": common.NewStringField(). - WithAliases("account"), - "balance": common.NewNumericMapField(), + WithAliases("account"). + Paginated(), + "balance": common.NewNumericMapField(), "first_usage": common.NewDateField(), - "metadata": common.NewStringMapField(), + "metadata": common.NewStringMapField(), }, } } diff --git a/internal/storage/ledger/volumes_test.go b/internal/storage/ledger/volumes_test.go index 795d6687a7..90fba3c028 100644 --- a/internal/storage/ledger/volumes_test.go +++ b/internal/storage/ledger/volumes_test.go @@ -46,7 +46,7 @@ func TestVolumesList(t *testing.T) { "world": { "foo": "bar", }, - })) + }, time.Time{})) tx1 := ledger.NewTransaction(). WithPostings(ledger.NewPosting("world", "account:1", "USD", big.NewInt(100))). @@ -542,7 +542,7 @@ func TestVolumesAggregate(t *testing.T) { "account:1:1": { "foo": "bar", }, - })) + }, time.Time{})) t.Run("Aggregation Volumes with balance for GroupLvl 0", func(t *testing.T) { t.Parallel() diff --git a/internal/storage/system/resource_ledgers.go b/internal/storage/system/resource_ledgers.go index 1228386f85..7cff225fed 100644 --- a/internal/storage/system/resource_ledgers.go +++ b/internal/storage/system/resource_ledgers.go @@ -23,7 +23,7 @@ func (h ledgersResourceHandler) Schema() common.EntitySchema { "features": common.NewStringMapField(), "metadata": common.NewStringMapField(), "name": common.NewStringField(), - "id": common.NewNumericField(), + "id": common.NewNumericField().Paginated(), }, } } diff --git a/openapi.yaml b/openapi.yaml index 4a71d929d3..1943e893fd 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1352,6 +1352,7 @@ paths: schema: type: string example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== + - $ref: "#/components/parameters/sort" requestBody: required: true content: @@ -1692,6 +1693,7 @@ paths: schema: type: string format: date-time + - $ref: "#/components/parameters/sort" requestBody: required: true content: @@ -2016,6 +2018,8 @@ paths: - name: order in: query required: false + deprecated: true + description: "Deprecated: Use sort param" schema: type: string enum: @@ -2025,6 +2029,7 @@ paths: required: false schema: type: boolean + - $ref: "#/components/parameters/sort" requestBody: required: true content: @@ -2440,6 +2445,7 @@ paths: format: int64 minimum: 0 maximum: 1000 + - $ref: "#/components/parameters/sort" requestBody: required: true content: @@ -2509,6 +2515,7 @@ paths: schema: type: string format: date-time + - $ref: "#/components/parameters/sort" requestBody: required: true content: @@ -3384,6 +3391,10 @@ components: type: string example: admin: "true" + firstUsage: + type: string + format: date-time + example: "2023-01-01T00:00:00Z" volumes: $ref: "#/components/schemas/V2Volumes" effectiveVolumes: @@ -3953,5 +3964,14 @@ components: file: type: string format: binary + parameters: + sort: + name: sort + in: query + description: "Sort results using a field name and order (ascending or descending). \nFormat: `:`, where `` is the field name and `` is either `asc` or `desc`.\n" + required: false + schema: + type: string + example: id:desc servers: - url: http://localhost:8080/ diff --git a/openapi/v2.yaml b/openapi/v2.yaml index 5ce569c812..0190a60ef7 100644 --- a/openapi/v2.yaml +++ b/openapi/v2.yaml @@ -91,6 +91,7 @@ paths: schema: type: string example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== + - $ref: "#/components/parameters/sort" requestBody: required: true content: @@ -431,6 +432,7 @@ paths: schema: type: string format: date-time + - $ref: "#/components/parameters/sort" requestBody: required: true content: @@ -756,6 +758,8 @@ paths: - name: order in: query required: false + deprecated: true + description: "Deprecated: Use sort param" schema: type: string enum: @@ -765,6 +769,7 @@ paths: required: false schema: type: boolean + - $ref: "#/components/parameters/sort" requestBody: required: true content: @@ -1183,6 +1188,7 @@ paths: format: int64 minimum: 0 maximum: 1000 + - $ref: "#/components/parameters/sort" requestBody: required: true content: @@ -1252,6 +1258,7 @@ paths: schema: type: string format: date-time + - $ref: "#/components/parameters/sort" requestBody: required: true content: @@ -1345,6 +1352,18 @@ components: tokenUrl: "/oauth/token" refreshUrl: "/oauth/token" scopes: {} + + parameters: + sort: + name: sort + in: query + description: | + Sort results using a field name and order (ascending or descending). + Format: `:`, where `` is the field name and `` is either `asc` or `desc`. + required: false + schema: + type: string + example: id:desc schemas: V2AccountsCursorResponse: @@ -1540,6 +1559,10 @@ components: type: string example: admin: "true" + firstUsage: + type: string + format: date-time + example: "2023-01-01T00:00:00Z" volumes: $ref: "#/components/schemas/V2Volumes" effectiveVolumes: diff --git a/pkg/client/.speakeasy/gen.lock b/pkg/client/.speakeasy/gen.lock index 244bae39c9..de2fdc30f3 100644 --- a/pkg/client/.speakeasy/gen.lock +++ b/pkg/client/.speakeasy/gen.lock @@ -1,7 +1,7 @@ lockVersion: 2.0.0 id: a9ac79e1-e429-4ee3-96c4-ec973f19bec3 management: - docChecksum: cf53669174b68bdafc357d6575035712 + docChecksum: 57791e55791395e191ae00df11e3d8aa docVersion: v2 speakeasyVersion: 1.563.0 generationVersion: 2.629.1 @@ -652,6 +652,7 @@ examples: query: pageSize: 100 cursor: "aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ==" + sort: "id:desc" requestBody: application/json: {"key": "", "key1": "", "key2": ""} responses: @@ -746,6 +747,7 @@ examples: query: pageSize: 100 cursor: "aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ==" + sort: "id:desc" requestBody: application/json: {} responses: @@ -761,7 +763,7 @@ examples: address: "users:001" responses: "200": - application/json: {"data": {"address": "users:001", "metadata": {"admin": "true"}, "volumes": {"USD": {"input": 100, "output": 10, "balance": 90}, "EUR": {"input": 100, "output": 10, "balance": 90}}, "effectiveVolumes": {"USD": {"input": 100, "output": 10, "balance": 90}, "EUR": {"input": 100, "output": 10, "balance": 90}}}} + application/json: {"data": {"address": "users:001", "metadata": {"admin": "true"}, "firstUsage": "2023-01-01T00:00:00Z", "volumes": {"USD": {"input": 100, "output": 10, "balance": 90}, "EUR": {"input": 100, "output": 10, "balance": 90}}, "effectiveVolumes": {"USD": {"input": 100, "output": 10, "balance": 90}, "EUR": {"input": 100, "output": 10, "balance": 90}}}} default: application/json: {"errorCode": "VALIDATION", "errorMessage": "[VALIDATION] invalid 'cursor' query param", "details": "https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9"} v2AddMetadataToAccount: @@ -815,6 +817,7 @@ examples: query: pageSize: 100 cursor: "aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ==" + sort: "id:desc" requestBody: application/json: {} responses: @@ -904,6 +907,7 @@ examples: pageSize: 100 cursor: "aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ==" groupBy: 3 + sort: "id:desc" requestBody: application/json: {"key": ""} responses: @@ -919,6 +923,7 @@ examples: query: pageSize: 100 cursor: "aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ==" + sort: "id:desc" requestBody: application/json: {} responses: diff --git a/pkg/client/.speakeasy/logs/naming.log b/pkg/client/.speakeasy/logs/naming.log index 5f7c47ec2a..3bb64195da 100644 --- a/pkg/client/.speakeasy/logs/naming.log +++ b/pkg/client/.speakeasy/logs/naming.log @@ -88,7 +88,7 @@ ListLogsResponse (HttpMeta: HTTPMetadata, LogsCursorResponse: LogsCursorResponse Log (id: integer, type: enum, data: map ...) Type (enum: NEW_TRANSACTION, SET_METADATA) V2 (SDK empty) -V2ListLedgersRequest (pageSize: integer, cursor: string, RequestBody: map) +V2ListLedgersRequest (pageSize: integer, cursor: string, sort: string ...) V2ListLedgersResponse (HttpMeta: HTTPMetadata, V2LedgerListResponse: V2LedgerListResponse) V2LedgerListResponse (cursor: class) V2LedgerListResponseCursor (pageSize: integer, hasMore: boolean, previous: string ...) @@ -144,7 +144,7 @@ V2ListAccountsRequest (ledger: string, pageSize: integer, cursor: string ...) V2ListAccountsResponse (HttpMeta: HTTPMetadata, V2AccountsCursorResponse: V2AccountsCursorResponse) V2AccountsCursorResponse (cursor: class) V2AccountsCursorResponseCursor (pageSize: integer, hasMore: boolean, previous: string ...) - V2Account (address: string, metadata: map, volumes: map ...) + V2Account (address: string, metadata: map, firstUsage: date-time ...) V2GetAccountRequest (ledger: string, address: string, expand: string ...) V2GetAccountResponse (HttpMeta: HTTPMetadata, V2AccountResponse: V2AccountResponse) V2AccountResponse (data: V2Account) diff --git a/pkg/client/docs/models/components/v2account.md b/pkg/client/docs/models/components/v2account.md index 91b9e84361..4983530397 100644 --- a/pkg/client/docs/models/components/v2account.md +++ b/pkg/client/docs/models/components/v2account.md @@ -7,5 +7,6 @@ | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | `Address` | *string* | :heavy_check_mark: | N/A | users:001 | | `Metadata` | map[string]*string* | :heavy_check_mark: | N/A | {
"admin": "true"
} | +| `FirstUsage` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | 2023-01-01T00:00:00Z | | `Volumes` | map[string][components.V2Volume](../../models/components/v2volume.md) | :heavy_minus_sign: | N/A | {
"USD": {
"input": 100,
"output": 10,
"balance": 90
},
"EUR": {
"input": 100,
"output": 10,
"balance": 90
}
} | | `EffectiveVolumes` | map[string][components.V2Volume](../../models/components/v2volume.md) | :heavy_minus_sign: | N/A | {
"USD": {
"input": 100,
"output": 10,
"balance": 90
},
"EUR": {
"input": 100,
"output": 10,
"balance": 90
}
} | \ No newline at end of file diff --git a/pkg/client/docs/models/operations/order.md b/pkg/client/docs/models/operations/order.md index b29bc2b6b5..27ec199a5b 100644 --- a/pkg/client/docs/models/operations/order.md +++ b/pkg/client/docs/models/operations/order.md @@ -1,5 +1,7 @@ # Order +Deprecated: Use sort param + ## Values diff --git a/pkg/client/docs/models/operations/v2getvolumeswithbalancesrequest.md b/pkg/client/docs/models/operations/v2getvolumeswithbalancesrequest.md index 3fe0b41724..3843ed4d61 100644 --- a/pkg/client/docs/models/operations/v2getvolumeswithbalancesrequest.md +++ b/pkg/client/docs/models/operations/v2getvolumeswithbalancesrequest.md @@ -12,4 +12,5 @@ | `StartTime` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | | `InsertionDate` | **bool* | :heavy_minus_sign: | Use insertion date instead of effective date | | | `GroupBy` | **int64* | :heavy_minus_sign: | Group volumes and balance by the level of the segment of the address | 3 | +| `Sort` | **string* | :heavy_minus_sign: | Sort results using a field name and order (ascending or descending).
Format: `:`, where `` is the field name and `` is either `asc` or `desc`.
| id:desc | | `RequestBody` | map[string]*any* | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/pkg/client/docs/models/operations/v2listaccountsrequest.md b/pkg/client/docs/models/operations/v2listaccountsrequest.md index a95415b46d..b8a9b9b223 100644 --- a/pkg/client/docs/models/operations/v2listaccountsrequest.md +++ b/pkg/client/docs/models/operations/v2listaccountsrequest.md @@ -10,4 +10,5 @@ | `Cursor` | **string* | :heavy_minus_sign: | Parameter used in pagination requests. Maximum page size is set to 15.
Set to the value of next for the next page of results.
Set to the value of previous for the previous page of results.
No other parameters can be set when this parameter is set.
| aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== | | `Expand` | **string* | :heavy_minus_sign: | N/A | | | `Pit` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | +| `Sort` | **string* | :heavy_minus_sign: | Sort results using a field name and order (ascending or descending).
Format: `:`, where `` is the field name and `` is either `asc` or `desc`.
| id:desc | | `RequestBody` | map[string]*any* | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/pkg/client/docs/models/operations/v2listledgersrequest.md b/pkg/client/docs/models/operations/v2listledgersrequest.md index 98097ed5a6..050ff35b33 100644 --- a/pkg/client/docs/models/operations/v2listledgersrequest.md +++ b/pkg/client/docs/models/operations/v2listledgersrequest.md @@ -7,4 +7,5 @@ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PageSize` | **int64* | :heavy_minus_sign: | The maximum number of results to return per page.
| 100 | | `Cursor` | **string* | :heavy_minus_sign: | Parameter used in pagination requests. Maximum page size is set to 15.
Set to the value of next for the next page of results.
Set to the value of previous for the previous page of results.
No other parameters can be set when this parameter is set.
| aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== | +| `Sort` | **string* | :heavy_minus_sign: | Sort results using a field name and order (ascending or descending).
Format: `:`, where `` is the field name and `` is either `asc` or `desc`.
| id:desc | | `RequestBody` | map[string]*any* | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/pkg/client/docs/models/operations/v2listlogsrequest.md b/pkg/client/docs/models/operations/v2listlogsrequest.md index 249554cc74..ae35476501 100644 --- a/pkg/client/docs/models/operations/v2listlogsrequest.md +++ b/pkg/client/docs/models/operations/v2listlogsrequest.md @@ -9,4 +9,5 @@ | `PageSize` | **int64* | :heavy_minus_sign: | The maximum number of results to return per page.
| 100 | | `Cursor` | **string* | :heavy_minus_sign: | Parameter used in pagination requests. Maximum page size is set to 15.
Set to the value of next for the next page of results.
Set to the value of previous for the previous page of results.
No other parameters can be set when this parameter is set.
| aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== | | `Pit` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | +| `Sort` | **string* | :heavy_minus_sign: | Sort results using a field name and order (ascending or descending).
Format: `:`, where `` is the field name and `` is either `asc` or `desc`.
| id:desc | | `RequestBody` | map[string]*any* | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/pkg/client/docs/models/operations/v2listtransactionsrequest.md b/pkg/client/docs/models/operations/v2listtransactionsrequest.md index 3cf4170e5f..28d0dcd131 100644 --- a/pkg/client/docs/models/operations/v2listtransactionsrequest.md +++ b/pkg/client/docs/models/operations/v2listtransactionsrequest.md @@ -10,6 +10,7 @@ | `Cursor` | **string* | :heavy_minus_sign: | Parameter used in pagination requests. Maximum page size is set to 15.
Set to the value of next for the next page of results.
Set to the value of previous for the previous page of results.
No other parameters can be set when this parameter is set.
| aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== | | `Expand` | **string* | :heavy_minus_sign: | N/A | | | `Pit` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | -| `Order` | [*operations.Order](../../models/operations/order.md) | :heavy_minus_sign: | N/A | | +| ~~`Order`~~ | [*operations.Order](../../models/operations/order.md) | :heavy_minus_sign: | : warning: ** DEPRECATED **: This will be removed in a future release, please migrate away from it as soon as possible.

Deprecated: Use sort param | | | `Reverse` | **bool* | :heavy_minus_sign: | N/A | | +| `Sort` | **string* | :heavy_minus_sign: | Sort results using a field name and order (ascending or descending).
Format: `:`, where `` is the field name and `` is either `asc` or `desc`.
| id:desc | | `RequestBody` | map[string]*any* | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/pkg/client/docs/sdks/v2/README.md b/pkg/client/docs/sdks/v2/README.md index af054c1308..be6c012b20 100644 --- a/pkg/client/docs/sdks/v2/README.md +++ b/pkg/client/docs/sdks/v2/README.md @@ -62,6 +62,7 @@ func main() { res, err := s.Ledger.V2.ListLedgers(ctx, operations.V2ListLedgersRequest{ PageSize: client.Int64(100), Cursor: client.String("aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ=="), + Sort: client.String("id:desc"), RequestBody: map[string]any{ "key": "", "key1": "", @@ -564,6 +565,7 @@ func main() { Ledger: "ledger001", PageSize: client.Int64(100), Cursor: client.String("aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ=="), + Sort: client.String("id:desc"), RequestBody: map[string]any{ }, @@ -935,6 +937,7 @@ func main() { Ledger: "ledger001", PageSize: client.Int64(100), Cursor: client.String("aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ=="), + Sort: client.String("id:desc"), RequestBody: map[string]any{ }, @@ -1413,6 +1416,7 @@ func main() { Cursor: client.String("aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ=="), Ledger: "ledger001", GroupBy: client.Int64(3), + Sort: client.String("id:desc"), RequestBody: map[string]any{ "key": "", }, @@ -1477,6 +1481,7 @@ func main() { Ledger: "ledger001", PageSize: client.Int64(100), Cursor: client.String("aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ=="), + Sort: client.String("id:desc"), RequestBody: map[string]any{ }, diff --git a/pkg/client/models/components/v2account.go b/pkg/client/models/components/v2account.go index 8d8c58f652..644579aaf5 100644 --- a/pkg/client/models/components/v2account.go +++ b/pkg/client/models/components/v2account.go @@ -2,13 +2,30 @@ package components +import ( + "github.com/formancehq/ledger/pkg/client/internal/utils" + "time" +) + type V2Account struct { Address string `json:"address"` Metadata map[string]string `json:"metadata"` + FirstUsage *time.Time `json:"firstUsage,omitempty"` Volumes map[string]V2Volume `json:"volumes,omitempty"` EffectiveVolumes map[string]V2Volume `json:"effectiveVolumes,omitempty"` } +func (v V2Account) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(v, "", false) +} + +func (v *V2Account) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { + return err + } + return nil +} + func (o *V2Account) GetAddress() string { if o == nil { return "" @@ -23,6 +40,13 @@ func (o *V2Account) GetMetadata() map[string]string { return o.Metadata } +func (o *V2Account) GetFirstUsage() *time.Time { + if o == nil { + return nil + } + return o.FirstUsage +} + func (o *V2Account) GetVolumes() map[string]V2Volume { if o == nil { return nil diff --git a/pkg/client/models/operations/v2getvolumeswithbalances.go b/pkg/client/models/operations/v2getvolumeswithbalances.go index 12d82d0c80..fdc770a278 100644 --- a/pkg/client/models/operations/v2getvolumeswithbalances.go +++ b/pkg/client/models/operations/v2getvolumeswithbalances.go @@ -25,7 +25,11 @@ type V2GetVolumesWithBalancesRequest struct { // Use insertion date instead of effective date InsertionDate *bool `queryParam:"style=form,explode=true,name=insertionDate"` // Group volumes and balance by the level of the segment of the address - GroupBy *int64 `queryParam:"style=form,explode=true,name=groupBy"` + GroupBy *int64 `queryParam:"style=form,explode=true,name=groupBy"` + // Sort results using a field name and order (ascending or descending). + // Format: `:`, where `` is the field name and `` is either `asc` or `desc`. + // + Sort *string `queryParam:"style=form,explode=true,name=sort"` RequestBody map[string]any `request:"mediaType=application/json"` } @@ -89,6 +93,13 @@ func (o *V2GetVolumesWithBalancesRequest) GetGroupBy() *int64 { return o.GroupBy } +func (o *V2GetVolumesWithBalancesRequest) GetSort() *string { + if o == nil { + return nil + } + return o.Sort +} + func (o *V2GetVolumesWithBalancesRequest) GetRequestBody() map[string]any { if o == nil { return map[string]any{} diff --git a/pkg/client/models/operations/v2listaccounts.go b/pkg/client/models/operations/v2listaccounts.go index fcd5f8ec92..4fa73ee2d9 100644 --- a/pkg/client/models/operations/v2listaccounts.go +++ b/pkg/client/models/operations/v2listaccounts.go @@ -19,9 +19,13 @@ type V2ListAccountsRequest struct { // Set to the value of previous for the previous page of results. // No other parameters can be set when this parameter is set. // - Cursor *string `queryParam:"style=form,explode=true,name=cursor"` - Expand *string `queryParam:"style=form,explode=true,name=expand"` - Pit *time.Time `queryParam:"style=form,explode=true,name=pit"` + Cursor *string `queryParam:"style=form,explode=true,name=cursor"` + Expand *string `queryParam:"style=form,explode=true,name=expand"` + Pit *time.Time `queryParam:"style=form,explode=true,name=pit"` + // Sort results using a field name and order (ascending or descending). + // Format: `:`, where `` is the field name and `` is either `asc` or `desc`. + // + Sort *string `queryParam:"style=form,explode=true,name=sort"` RequestBody map[string]any `request:"mediaType=application/json"` } @@ -71,6 +75,13 @@ func (o *V2ListAccountsRequest) GetPit() *time.Time { return o.Pit } +func (o *V2ListAccountsRequest) GetSort() *string { + if o == nil { + return nil + } + return o.Sort +} + func (o *V2ListAccountsRequest) GetRequestBody() map[string]any { if o == nil { return map[string]any{} diff --git a/pkg/client/models/operations/v2listledgers.go b/pkg/client/models/operations/v2listledgers.go index 3b33a664a5..6a24e17655 100644 --- a/pkg/client/models/operations/v2listledgers.go +++ b/pkg/client/models/operations/v2listledgers.go @@ -15,7 +15,11 @@ type V2ListLedgersRequest struct { // Set to the value of previous for the previous page of results. // No other parameters can be set when this parameter is set. // - Cursor *string `queryParam:"style=form,explode=true,name=cursor"` + Cursor *string `queryParam:"style=form,explode=true,name=cursor"` + // Sort results using a field name and order (ascending or descending). + // Format: `:`, where `` is the field name and `` is either `asc` or `desc`. + // + Sort *string `queryParam:"style=form,explode=true,name=sort"` RequestBody map[string]any `request:"mediaType=application/json"` } @@ -33,6 +37,13 @@ func (o *V2ListLedgersRequest) GetCursor() *string { return o.Cursor } +func (o *V2ListLedgersRequest) GetSort() *string { + if o == nil { + return nil + } + return o.Sort +} + func (o *V2ListLedgersRequest) GetRequestBody() map[string]any { if o == nil { return map[string]any{} diff --git a/pkg/client/models/operations/v2listlogs.go b/pkg/client/models/operations/v2listlogs.go index 2d45360d79..9352e5d955 100644 --- a/pkg/client/models/operations/v2listlogs.go +++ b/pkg/client/models/operations/v2listlogs.go @@ -19,8 +19,12 @@ type V2ListLogsRequest struct { // Set to the value of previous for the previous page of results. // No other parameters can be set when this parameter is set. // - Cursor *string `queryParam:"style=form,explode=true,name=cursor"` - Pit *time.Time `queryParam:"style=form,explode=true,name=pit"` + Cursor *string `queryParam:"style=form,explode=true,name=cursor"` + Pit *time.Time `queryParam:"style=form,explode=true,name=pit"` + // Sort results using a field name and order (ascending or descending). + // Format: `:`, where `` is the field name and `` is either `asc` or `desc`. + // + Sort *string `queryParam:"style=form,explode=true,name=sort"` RequestBody map[string]any `request:"mediaType=application/json"` } @@ -63,6 +67,13 @@ func (o *V2ListLogsRequest) GetPit() *time.Time { return o.Pit } +func (o *V2ListLogsRequest) GetSort() *string { + if o == nil { + return nil + } + return o.Sort +} + func (o *V2ListLogsRequest) GetRequestBody() map[string]any { if o == nil { return map[string]any{} diff --git a/pkg/client/models/operations/v2listtransactions.go b/pkg/client/models/operations/v2listtransactions.go index d57edc487a..1de950d71d 100644 --- a/pkg/client/models/operations/v2listtransactions.go +++ b/pkg/client/models/operations/v2listtransactions.go @@ -10,6 +10,7 @@ import ( "time" ) +// Order - Deprecated: Use sort param type Order string const ( @@ -44,11 +45,18 @@ type V2ListTransactionsRequest struct { // Set to the value of previous for the previous page of results. // No other parameters can be set when this parameter is set. // - Cursor *string `queryParam:"style=form,explode=true,name=cursor"` - Expand *string `queryParam:"style=form,explode=true,name=expand"` - Pit *time.Time `queryParam:"style=form,explode=true,name=pit"` - Order *Order `queryParam:"style=form,explode=true,name=order"` - Reverse *bool `queryParam:"style=form,explode=true,name=reverse"` + Cursor *string `queryParam:"style=form,explode=true,name=cursor"` + Expand *string `queryParam:"style=form,explode=true,name=expand"` + Pit *time.Time `queryParam:"style=form,explode=true,name=pit"` + // Deprecated: Use sort param + // + // Deprecated: This will be removed in a future release, please migrate away from it as soon as possible. + Order *Order `queryParam:"style=form,explode=true,name=order"` + Reverse *bool `queryParam:"style=form,explode=true,name=reverse"` + // Sort results using a field name and order (ascending or descending). + // Format: `:`, where `` is the field name and `` is either `asc` or `desc`. + // + Sort *string `queryParam:"style=form,explode=true,name=sort"` RequestBody map[string]any `request:"mediaType=application/json"` } @@ -112,6 +120,13 @@ func (o *V2ListTransactionsRequest) GetReverse() *bool { return o.Reverse } +func (o *V2ListTransactionsRequest) GetSort() *string { + if o == nil { + return nil + } + return o.Sort +} + func (o *V2ListTransactionsRequest) GetRequestBody() map[string]any { if o == nil { return map[string]any{} diff --git a/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log b/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log index 5f7c47ec2a..3bb64195da 100644 --- a/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log +++ b/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log @@ -88,7 +88,7 @@ ListLogsResponse (HttpMeta: HTTPMetadata, LogsCursorResponse: LogsCursorResponse Log (id: integer, type: enum, data: map ...) Type (enum: NEW_TRANSACTION, SET_METADATA) V2 (SDK empty) -V2ListLedgersRequest (pageSize: integer, cursor: string, RequestBody: map) +V2ListLedgersRequest (pageSize: integer, cursor: string, sort: string ...) V2ListLedgersResponse (HttpMeta: HTTPMetadata, V2LedgerListResponse: V2LedgerListResponse) V2LedgerListResponse (cursor: class) V2LedgerListResponseCursor (pageSize: integer, hasMore: boolean, previous: string ...) @@ -144,7 +144,7 @@ V2ListAccountsRequest (ledger: string, pageSize: integer, cursor: string ...) V2ListAccountsResponse (HttpMeta: HTTPMetadata, V2AccountsCursorResponse: V2AccountsCursorResponse) V2AccountsCursorResponse (cursor: class) V2AccountsCursorResponseCursor (pageSize: integer, hasMore: boolean, previous: string ...) - V2Account (address: string, metadata: map, volumes: map ...) + V2Account (address: string, metadata: map, firstUsage: date-time ...) V2GetAccountRequest (ledger: string, address: string, expand: string ...) V2GetAccountResponse (HttpMeta: HTTPMetadata, V2AccountResponse: V2AccountResponse) V2AccountResponse (data: V2Account) diff --git a/test/e2e/api_accounts_list_test.go b/test/e2e/api_accounts_list_test.go index ff99003b7e..423ffdb099 100644 --- a/test/e2e/api_accounts_list_test.go +++ b/test/e2e/api_accounts_list_test.go @@ -136,10 +136,8 @@ var _ = Context("Ledger accounts list API tests", func() { accountsCursorResponse := response.V2AccountsCursorResponse.Cursor.Data Expect(accountsCursorResponse).To(HaveLen(3)) - Expect(accountsCursorResponse[0]).To(Equal(components.V2Account{ - Address: "foo:bar", - Metadata: metadata2, - })) + Expect(accountsCursorResponse[0].Address).To(Equal("foo:bar")) + Expect(accountsCursorResponse[0].Metadata).To(Equal(metadata2)) Expect(accountsCursorResponse[1]).To(Equal(components.V2Account{ Address: "foo:foo", Metadata: metadata1, @@ -150,10 +148,12 @@ var _ = Context("Ledger accounts list API tests", func() { Balance: bigInt, }, }, + FirstUsage: ×tamp, })) Expect(accountsCursorResponse[2]).To(Equal(components.V2Account{ - Address: "world", - Metadata: metadata.Metadata{}, + Address: "world", + Metadata: metadata.Metadata{}, + FirstUsage: ×tamp, Volumes: map[string]components.V2Volume{ "USD": { Output: bigInt, @@ -163,6 +163,18 @@ var _ = Context("Ledger accounts list API tests", func() { }, })) }) + It("should be listed on api while paginating on first usage", func(specContext SpecContext) { + _, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.ListAccounts( + ctx, + operations.V2ListAccountsRequest{ + Ledger: "default", + Expand: pointer.For("volumes"), + Sort: pointer.For("first_usage:asc"), + }, + ) + Expect(err).ToNot(HaveOccurred()) + }) + It("should be listed on api using address filters", func(specContext SpecContext) { response, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.ListAccounts( ctx, @@ -179,14 +191,10 @@ var _ = Context("Ledger accounts list API tests", func() { accountsCursorResponse := response.V2AccountsCursorResponse.Cursor.Data Expect(accountsCursorResponse).To(HaveLen(2)) - Expect(accountsCursorResponse[0]).To(Equal(components.V2Account{ - Address: "foo:bar", - Metadata: metadata2, - })) - Expect(accountsCursorResponse[1]).To(Equal(components.V2Account{ - Address: "foo:foo", - Metadata: metadata1, - })) + Expect(accountsCursorResponse[1].Address).To(Equal("foo:foo")) + Expect(accountsCursorResponse[1].Metadata).To(Equal(metadata1)) + Expect(accountsCursorResponse[0].Address).To(Equal("foo:bar")) + Expect(accountsCursorResponse[0].Metadata).To(Equal(metadata2)) response, err = Wait(specContext, DeferClient(testServer)).Ledger.V2.ListAccounts( ctx, @@ -203,10 +211,9 @@ var _ = Context("Ledger accounts list API tests", func() { accountsCursorResponse = response.V2AccountsCursorResponse.Cursor.Data Expect(accountsCursorResponse).To(HaveLen(1)) - Expect(accountsCursorResponse[0]).To(Equal(components.V2Account{ - Address: "foo:foo", - Metadata: metadata1, - })) + + Expect(accountsCursorResponse[0].Address).To(Equal("foo:foo")) + Expect(accountsCursorResponse[0].Metadata).To(Equal(metadata1)) }) It("should be listed on api using metadata filters", func(specContext SpecContext) { response, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.ListAccounts( @@ -224,10 +231,8 @@ var _ = Context("Ledger accounts list API tests", func() { accountsCursorResponse := response.V2AccountsCursorResponse.Cursor.Data Expect(accountsCursorResponse).To(HaveLen(1)) - Expect(accountsCursorResponse[0]).To(Equal(components.V2Account{ - Address: "foo:foo", - Metadata: metadata1, - })) + Expect(accountsCursorResponse[0].Address).To(Equal("foo:foo")) + Expect(accountsCursorResponse[0].Metadata).To(Equal(metadata1)) }) It("should be listable on api using $not filter", func(specContext SpecContext) { response, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.ListAccounts( @@ -315,16 +320,31 @@ var _ = Context("Ledger accounts list API tests", func() { response *operations.V2ListAccountsResponse err error ) - BeforeEach(func(specContext SpecContext) { - response, err = Wait(specContext, DeferClient(testServer)).Ledger.V2.ListAccounts( - ctx, - operations.V2ListAccountsRequest{ - Ledger: "default", - PageSize: pointer.For(pageSize), - }, - ) + listAccounts := func(specContext SpecContext, request operations.V2ListAccountsRequest) *operations.V2ListAccountsResponse { + GinkgoHelper() + response, err = Wait(specContext, DeferClient(testServer)).Ledger.V2.ListAccounts(ctx, request) Expect(err).ToNot(HaveOccurred()) + // We don't have firstUsage value, so we set it to nil to be able to compare accounts list + for ind, account := range response.V2AccountsCursorResponse.Cursor.Data { + account.FirstUsage = nil + response.V2AccountsCursorResponse.Cursor.Data[ind] = account + } + + return response + } + BeforeEach(func(specContext SpecContext) { + response = listAccounts(specContext, operations.V2ListAccountsRequest{ + Ledger: "default", + PageSize: pointer.For(pageSize), + }) + + // We don't have firstUsage value, so we set it to nil to be able to compare accounts list + for ind, account := range response.V2AccountsCursorResponse.Cursor.Data { + account.FirstUsage = nil + response.V2AccountsCursorResponse.Cursor.Data[ind] = account + } + Expect(response.V2AccountsCursorResponse.Cursor.HasMore).To(BeTrue()) Expect(response.V2AccountsCursorResponse.Cursor.Previous).To(BeNil()) Expect(response.V2AccountsCursorResponse.Cursor.Next).NotTo(BeNil()) @@ -335,14 +355,10 @@ var _ = Context("Ledger accounts list API tests", func() { }) When("following next cursor", func() { BeforeEach(func(specContext SpecContext) { - response, err = Wait(specContext, DeferClient(testServer)).Ledger.V2.ListAccounts( - ctx, - operations.V2ListAccountsRequest{ - Cursor: response.V2AccountsCursorResponse.Cursor.Next, - Ledger: "default", - }, - ) - Expect(err).ToNot(HaveOccurred()) + response = listAccounts(specContext, operations.V2ListAccountsRequest{ + Cursor: response.V2AccountsCursorResponse.Cursor.Next, + Ledger: "default", + }) }) It("should return next page", func() { Expect(response.V2AccountsCursorResponse.Cursor.PageSize).To(Equal(pageSize)) @@ -351,14 +367,10 @@ var _ = Context("Ledger accounts list API tests", func() { }) When("following previous cursor", func() { BeforeEach(func(specContext SpecContext) { - response, err = Wait(specContext, DeferClient(testServer)).Ledger.V2.ListAccounts( - ctx, - operations.V2ListAccountsRequest{ - Ledger: "default", - Cursor: response.V2AccountsCursorResponse.Cursor.Previous, - }, - ) - Expect(err).ToNot(HaveOccurred()) + response = listAccounts(specContext, operations.V2ListAccountsRequest{ + Ledger: "default", + Cursor: response.V2AccountsCursorResponse.Cursor.Previous, + }) }) It("should return first page", func() { Expect(response.V2AccountsCursorResponse.Cursor.PageSize).To(Equal(pageSize)) diff --git a/test/e2e/api_accounts_metadata_test.go b/test/e2e/api_accounts_metadata_test.go index 893f01f0f8..670fdb1b3b 100644 --- a/test/e2e/api_accounts_metadata_test.go +++ b/test/e2e/api_accounts_metadata_test.go @@ -74,10 +74,8 @@ var _ = Context("Ledger accounts metadata API tests", func() { ) Expect(err).ToNot(HaveOccurred()) - Expect(response.V2AccountResponse.Data).Should(Equal(components.V2Account{ - Address: "foo", - Metadata: metadata, - })) + Expect(response.V2AccountResponse.Data.Address).Should(Equal("foo")) + Expect(response.V2AccountResponse.Data.Metadata).ShouldNot(BeEmpty()) }) Context("Then updating the metadata", func() { var ( @@ -106,10 +104,8 @@ var _ = Context("Ledger accounts metadata API tests", func() { ) Expect(err).ToNot(HaveOccurred()) - Expect(response.V2AccountResponse.Data).Should(Equal(components.V2Account{ - Address: "foo", - Metadata: newMetadata, - })) + Expect(response.V2AccountResponse.Data.Address).Should(Equal("foo")) + Expect(response.V2AccountResponse.Data.Metadata).Should(Equal(newMetadata)) }) }) It("should trigger a new event", func() { @@ -129,10 +125,8 @@ var _ = Context("Ledger accounts metadata API tests", func() { ) Expect(err).ToNot(HaveOccurred()) - Expect(response.V2AccountResponse.Data).Should(Equal(components.V2Account{ - Address: "foo", - Metadata: map[string]string{}, - })) + Expect(response.V2AccountResponse.Data.Address).Should(Equal("foo")) + Expect(response.V2AccountResponse.Data.Metadata).Should(BeEmpty()) }) Context("then adding with empty metadata", func() { It("should be OK", func(specContext SpecContext) { @@ -158,10 +152,8 @@ var _ = Context("Ledger accounts metadata API tests", func() { ) Expect(err).ToNot(HaveOccurred()) - Expect(response.V2AccountResponse.Data).Should(Equal(components.V2Account{ - Address: "foo", - Metadata: map[string]string{}, - })) + Expect(response.V2AccountResponse.Data.Address).Should(Equal("foo")) + Expect(response.V2AccountResponse.Data.Metadata).Should(BeEmpty()) }) }) }) diff --git a/test/e2e/api_logs_list_test.go b/test/e2e/api_logs_list_test.go index ebf4513342..c3138a6122 100644 --- a/test/e2e/api_logs_list_test.go +++ b/test/e2e/api_logs_list_test.go @@ -123,6 +123,18 @@ var _ = Context("Ledger logs list API tests", func() { ) Expect(err).ToNot(HaveOccurred()) }) + When("paginating on date", func() { + It("should be ok", func(specContext SpecContext) { + _, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.ListLogs( + ctx, + operations.V2ListLogsRequest{ + Ledger: "default", + Sort: pointer.For("date:asc"), + }, + ) + Expect(err).ToNot(HaveOccurred()) + }) + }) It("should be listed on api with ListLogs", func(specContext SpecContext) { response, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.ListLogs( ctx, diff --git a/test/e2e/api_transactions_create_test.go b/test/e2e/api_transactions_create_test.go index 5be4018c85..421550cab5 100644 --- a/test/e2e/api_transactions_create_test.go +++ b/test/e2e/api_transactions_create_test.go @@ -124,6 +124,7 @@ var _ = Context("Ledger transactions create API tests", func() { Metadata: map[string]string{ "clientType": "silver", }, + FirstUsage: account.V2AccountResponse.Data.FirstUsage, })) account, err = Wait(specContext, DeferClient(testServer)).Ledger.V2.GetAccount(ctx, operations.V2GetAccountRequest{ @@ -136,6 +137,7 @@ var _ = Context("Ledger transactions create API tests", func() { Metadata: map[string]string{ "status": "pending", }, + FirstUsage: account.V2AccountResponse.Data.FirstUsage, })) }) }) @@ -277,6 +279,7 @@ var _ = Context("Ledger transactions create API tests", func() { Balance: big.NewInt(100), }, }, + FirstUsage: &response.V2GetTransactionResponse.Data.Timestamp, })) By("should trigger a new event", func() { Eventually(events).Should(Receive(Event(ledgerevents.EventTypeCommittedTransactions, WithPayload(bus.CommittedTransactions{ diff --git a/test/e2e/api_transactions_list_test.go b/test/e2e/api_transactions_list_test.go index b3f3de67b0..6da2e382dd 100644 --- a/test/e2e/api_transactions_list_test.go +++ b/test/e2e/api_transactions_list_test.go @@ -127,6 +127,35 @@ var _ = Context("Ledger transactions list API tests", func() { Expect(rsp.V2TransactionsCursorResponse.Cursor.Data).To(Equal(expectedTxs)) }) }) + When("listing transaction while paginating on timestamp", func() { + var ( + rsp *operations.V2ListTransactionsResponse + err error + ) + JustBeforeEach(func(specContext SpecContext) { + rsp, err = Wait(specContext, DeferClient(testServer)).Ledger.V2.ListTransactions( + ctx, + operations.V2ListTransactionsRequest{ + Ledger: "default", + PageSize: pointer.For(pageSize), + Expand: pointer.For("volumes,effectiveVolumes"), + Reverse: pointer.For(true), + Sort: pointer.For("timestamp:desc"), + }, + ) + Expect(err).ToNot(HaveOccurred()) + }) + It("Should be ok", func() { + Expect(rsp.V2TransactionsCursorResponse.Cursor.PageSize).To(Equal(pageSize)) + sortedByTimestamp := transactions[:] + sort.SliceStable(sortedByTimestamp, func(i, j int) bool { + return sortedByTimestamp[i].Timestamp.Before(sortedByTimestamp[j].Timestamp) + }) + page := sortedByTimestamp[pageSize:] + slices.Reverse(page) + Expect(rsp.V2TransactionsCursorResponse.Cursor.Data).To(Equal(page)) + }) + }) When("listing transactions using a page size of 5", func() { var ( rsp *operations.V2ListTransactionsResponse @@ -208,6 +237,7 @@ var _ = Context("Ledger transactions list API tests", func() { }) Context("with effective ordering", func() { BeforeEach(func() { + //nolint:staticcheck req.Order = pointer.For(operations.OrderEffective) }) It("Should be ok, and returns transactions ordered by effective timestamp", func() { diff --git a/test/e2e/api_volumes_test.go b/test/e2e/api_volumes_test.go index ebc865d730..137e8f45bd 100644 --- a/test/e2e/api_volumes_test.go +++ b/test/e2e/api_volumes_test.go @@ -83,7 +83,7 @@ var _ = Context("Ledger accounts list API tests", func() { } }) - When("Get current Volumes and Balances From origin of time till now (insertion-date)", func() { + When("Get current volumes and balances from origin of time till now (insertion-date)", func() { It("should be ok", func(specContext SpecContext) { response, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.GetVolumesWithBalances( ctx, @@ -112,7 +112,7 @@ var _ = Context("Ledger accounts list API tests", func() { }) }) - When("Get Volumes and Balances From oot til oot+2 hours (effectiveDate) ", func() { + When("Get volumes and balances from oot til oot+2 hours (effectiveDate) ", func() { It("should be ok", func(specContext SpecContext) { response, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.GetVolumesWithBalances( @@ -140,7 +140,7 @@ var _ = Context("Ledger accounts list API tests", func() { }) }) - When("Get Volumes and Balances Filter by address account", func() { + When("Get volumes and balances filter by address account", func() { It("should be ok", func(specContext SpecContext) { response, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.GetVolumesWithBalances( ctx, @@ -167,7 +167,7 @@ var _ = Context("Ledger accounts list API tests", func() { }) }) - When("Get Volumes and Balances Filter by address account a,d and end-time now effective", func() { + When("Get volumes and balances filter by address account a,d and end-time now effective", func() { It("should be ok", func(specContext SpecContext) { response, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.GetVolumesWithBalances( ctx, @@ -195,7 +195,7 @@ var _ = Context("Ledger accounts list API tests", func() { }) }) - When("Get Volumes and Balances Filter by address account which doesn't exist", func() { + When("Get volumes and balances filter by address account which doesn't exist", func() { It("should be ok", func(specContext SpecContext) { response, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.GetVolumesWithBalances( ctx, @@ -213,7 +213,7 @@ var _ = Context("Ledger accounts list API tests", func() { }) }) - When("Get Volumes and Balances Filter With futures dates empty", func() { + When("Get volumes and balances filter With futures dates empty", func() { It("should be ok", func(specContext SpecContext) { response, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.GetVolumesWithBalances( ctx, @@ -228,7 +228,7 @@ var _ = Context("Ledger accounts list API tests", func() { }) }) - When("Get Volumes and Balances Filter by address account aggregation by level 1", func() { + When("Get volumes and balances filter by address account aggregation by level 1", func() { It("should be ok", func(specContext SpecContext) { response, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.GetVolumesWithBalances( ctx, diff --git a/tools/generator/go.sum b/tools/generator/go.sum index 0e1badb527..fff69dc09b 100644 --- a/tools/generator/go.sum +++ b/tools/generator/go.sum @@ -172,6 +172,8 @@ github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISH github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=