Skip to content

Commit 7876635

Browse files
committed
wip
1 parent b639ebd commit 7876635

34 files changed

+439
-86
lines changed

docs/api/README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3130,6 +3130,7 @@ Accept: application/json
31303130
```json
31313131
{
31323132
"name": "string",
3133+
"query": {},
31333134
"accountIDs": [
31343135
"string"
31353136
]
@@ -3214,6 +3215,8 @@ Accept: application/json
32143215
"id": "string",
32153216
"name": "string",
32163217
"createdAt": "2019-08-24T14:15:22Z",
3218+
"type": "string",
3219+
"query": {},
32173220
"poolAccounts": [
32183221
"string"
32193222
]
@@ -3266,6 +3269,8 @@ Accept: application/json
32663269
"id": "string",
32673270
"name": "string",
32683271
"createdAt": "2019-08-24T14:15:22Z",
3272+
"type": "string",
3273+
"query": {},
32693274
"poolAccounts": [
32703275
"string"
32713276
]
@@ -6335,6 +6340,7 @@ None ( Scopes: payments:read )
63356340
```json
63366341
{
63376342
"name": "string",
6343+
"query": {},
63386344
"accountIDs": [
63396345
"string"
63406346
]
@@ -6347,7 +6353,8 @@ None ( Scopes: payments:read )
63476353
|Name|Type|Required|Restrictions|Description|
63486354
|---|---|---|---|---|
63496355
|name|string|true|none|none|
6350-
|accountIDs|[string]|true|none|none|
6356+
|query|object|false|none|none|
6357+
|accountIDs|[string]|false|none|none|
63516358

63526359
<h2 id="tocS_V3CreatePoolResponse">V3CreatePoolResponse</h2>
63536360
<!-- backwards compatibility -->
@@ -6388,6 +6395,8 @@ None ( Scopes: payments:read )
63886395
"id": "string",
63896396
"name": "string",
63906397
"createdAt": "2019-08-24T14:15:22Z",
6398+
"type": "string",
6399+
"query": {},
63916400
"poolAccounts": [
63926401
"string"
63936402
]
@@ -6422,6 +6431,8 @@ None ( Scopes: payments:read )
64226431
"id": "string",
64236432
"name": "string",
64246433
"createdAt": "2019-08-24T14:15:22Z",
6434+
"type": "string",
6435+
"query": {},
64256436
"poolAccounts": [
64266437
"string"
64276438
]
@@ -6476,6 +6487,8 @@ None ( Scopes: payments:read )
64766487
"id": "string",
64776488
"name": "string",
64786489
"createdAt": "2019-08-24T14:15:22Z",
6490+
"type": "string",
6491+
"query": {},
64796492
"poolAccounts": [
64806493
"string"
64816494
]
@@ -6490,6 +6503,8 @@ None ( Scopes: payments:read )
64906503
|id|string|true|none|none|
64916504
|name|string|true|none|none|
64926505
|createdAt|string(date-time)|true|none|none|
6506+
|type|string|true|none|none|
6507+
|query|object|false|none|none|
64936508
|poolAccounts|[[V3AccountID](#schemav3accountid)]|true|none|none|
64946509

64956510
<h2 id="tocS_V3PoolBalances">V3PoolBalances</h2>

internal/api/services/pools_balances.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ func (s *Service) PoolsBalances(
2121
if err != nil {
2222
return nil, newStorageError(err, "cannot get pool")
2323
}
24+
25+
if pool.Type == models.POOL_TYPE_DYNAMIC {
26+
// populate the pool accounts from the query
27+
pool.PoolAccounts, err = s.populatePoolAccounts(ctx, pool)
28+
if err != nil {
29+
return nil, newStorageError(err, "cannot populate pool accounts")
30+
}
31+
}
32+
2433
res := make(map[string]*aggregatedBalance)
2534
for i := range pool.PoolAccounts {
2635
balances, err := s.storage.BalancesGetLatest(ctx, pool.PoolAccounts[i])

internal/api/services/pools_balances_at.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ package services
22

33
import (
44
"context"
5+
"encoding/json"
56
"math/big"
67
"time"
78

9+
"github.com/formancehq/go-libs/v3/bun/bunpaginate"
10+
"github.com/formancehq/go-libs/v3/query"
811
"github.com/formancehq/payments/internal/models"
12+
"github.com/formancehq/payments/internal/storage"
913
"github.com/google/uuid"
1014
)
1115

@@ -18,6 +22,15 @@ func (s *Service) PoolsBalancesAt(
1822
if err != nil {
1923
return nil, newStorageError(err, "cannot get pool")
2024
}
25+
26+
if pool.Type == models.POOL_TYPE_DYNAMIC {
27+
// populate the pool accounts from the query
28+
pool.PoolAccounts, err = s.populatePoolAccounts(ctx, pool)
29+
if err != nil {
30+
return nil, newStorageError(err, "cannot populate pool accounts")
31+
}
32+
}
33+
2134
res := make(map[string]*aggregatedBalance)
2235
for i := range pool.PoolAccounts {
2336
balances, err := s.storage.BalancesGetAt(ctx, pool.PoolAccounts[i], at)
@@ -51,3 +64,44 @@ func (s *Service) PoolsBalancesAt(
5164

5265
return balances, nil
5366
}
67+
68+
func (s *Service) populatePoolAccounts(ctx context.Context, pool *models.Pool) ([]models.AccountID, error) {
69+
queryJSON, err := json.Marshal(pool.Query)
70+
if err != nil {
71+
return nil, newStorageError(err, "cannot marshal pool query")
72+
}
73+
74+
qb, err := query.ParseJSON(string(queryJSON))
75+
if err != nil {
76+
return nil, newStorageError(err, "cannot parse pool query")
77+
}
78+
79+
q := storage.NewListAccountsQuery(
80+
bunpaginate.NewPaginatedQueryOptions(storage.AccountQuery{}).
81+
WithPageSize(100).
82+
WithQueryBuilder(qb),
83+
)
84+
85+
res := make([]models.AccountID, 0)
86+
for {
87+
cursor, err := s.storage.AccountsList(ctx, q)
88+
if err != nil {
89+
return nil, newStorageError(err, "cannot list accounts")
90+
}
91+
92+
for _, account := range cursor.Data {
93+
res = append(res, account.ID)
94+
}
95+
96+
if !cursor.HasMore {
97+
break
98+
}
99+
100+
err = bunpaginate.UnmarshalCursor(cursor.Next, &q)
101+
if err != nil {
102+
return nil, newStorageError(err, "cannot unmarshal cursor")
103+
}
104+
}
105+
106+
return res, nil
107+
}

internal/api/services/pools_balances_at_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ func TestPoolsBalancesAt(t *testing.T) {
9393
store.EXPECT().PoolsGet(gomock.Any(), id).Return(&models.Pool{
9494
ID: id,
9595
Name: "test",
96+
Type: models.POOL_TYPE_STATIC,
9697
CreatedAt: at,
9798
PoolAccounts: poolsAccount,
9899
}, test.poolsGetStorageErr)

internal/api/services/pools_balances_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ func TestPoolsBalancesLatest(t *testing.T) {
9393
ID: id,
9494
Name: "test",
9595
CreatedAt: time.Now().Add(-time.Hour),
96+
Type: models.POOL_TYPE_STATIC,
9697
PoolAccounts: poolsAccount,
9798
}, test.poolsGetStorageErr)
9899
if test.poolsGetStorageErr == nil {

internal/api/services/pools_get.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,13 @@ func (s *Service) PoolsGet(ctx context.Context, id uuid.UUID) (*models.Pool, err
1313
return nil, newStorageError(err, "cannot get pool")
1414
}
1515

16+
if p.Type == models.POOL_TYPE_DYNAMIC {
17+
// populate the pool accounts from the query
18+
p.PoolAccounts, err = s.populatePoolAccounts(ctx, p)
19+
if err != nil {
20+
return nil, newStorageError(err, "cannot populate pool accounts")
21+
}
22+
}
23+
1624
return p, nil
1725
}

internal/api/services/pools_list.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/formancehq/go-libs/v3/bun/bunpaginate"
77
"github.com/formancehq/payments/internal/models"
88
"github.com/formancehq/payments/internal/storage"
9+
"golang.org/x/sync/errgroup"
910
)
1011

1112
func (s *Service) PoolsList(ctx context.Context, query storage.ListPoolsQuery) (*bunpaginate.Cursor[models.Pool], error) {
@@ -14,5 +15,23 @@ func (s *Service) PoolsList(ctx context.Context, query storage.ListPoolsQuery) (
1415
return nil, newStorageError(err, "cannot list pools")
1516
}
1617

18+
errGroup, egCtx := errgroup.WithContext(ctx)
19+
for i := range ps.Data {
20+
index := i
21+
errGroup.Go(func() error {
22+
if ps.Data[index].Type == models.POOL_TYPE_DYNAMIC {
23+
ps.Data[index].PoolAccounts, err = s.populatePoolAccounts(egCtx, &ps.Data[index])
24+
if err != nil {
25+
return newStorageError(err, "cannot populate pool accounts")
26+
}
27+
}
28+
return nil
29+
})
30+
}
31+
32+
if err := errGroup.Wait(); err != nil {
33+
return nil, newStorageError(err, "cannot populate pool accounts")
34+
}
35+
1736
return ps, nil
1837
}

internal/api/services/pools_list_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import (
55
"fmt"
66
"testing"
77

8+
"github.com/formancehq/go-libs/v3/bun/bunpaginate"
89
"github.com/formancehq/payments/internal/connectors/engine"
10+
"github.com/formancehq/payments/internal/models"
911
"github.com/formancehq/payments/internal/storage"
1012
"github.com/stretchr/testify/require"
1113
gomock "go.uber.org/mock/gomock"
@@ -46,7 +48,7 @@ func TestPoolsList(t *testing.T) {
4648
for _, test := range tests {
4749
t.Run(test.name, func(t *testing.T) {
4850
query := storage.ListPoolsQuery{}
49-
store.EXPECT().PoolsList(gomock.Any(), query).Return(nil, test.err)
51+
store.EXPECT().PoolsList(gomock.Any(), query).Return(&bunpaginate.Cursor[models.Pool]{}, test.err)
5052
_, err := s.PoolsList(context.Background(), query)
5153
if test.expectedError == nil {
5254
require.NoError(t, err)

internal/api/v2/handler_pools_create.go

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,17 @@ import (
1818
)
1919

2020
type CreatePoolRequest struct {
21-
Name string `json:"name" validate:"required"`
22-
AccountIDs []string `json:"accountIDs" validate:"min=1,dive,accountID"`
21+
Name string `json:"name" validate:"required"`
22+
Query map[string]any `json:"query" validate:"required_without=AccountIDs"`
23+
AccountIDs []string `json:"accountIDs" validate:"required_without=Query,dive,accountID"`
2324
}
2425

2526
type PoolResponse struct {
26-
ID string `json:"id"`
27-
Name string `json:"name"`
28-
Accounts []string `json:"accounts"`
27+
ID string `json:"id"`
28+
Name string `json:"name"`
29+
Type models.PoolType `json:"type"`
30+
Query map[string]any `json:"query"`
31+
Accounts []string `json:"accounts"`
2932
}
3033

3134
func poolsCreate(backend backend.Backend, validator *validation.Validator) http.HandlerFunc {
@@ -55,18 +58,24 @@ func poolsCreate(backend backend.Backend, validator *validation.Validator) http.
5558
CreatedAt: time.Now().UTC(),
5659
}
5760

58-
accounts := make([]models.AccountID, len(CreatePoolRequest.AccountIDs))
59-
for i, accountID := range CreatePoolRequest.AccountIDs {
60-
aID, err := models.AccountIDFromString(accountID)
61-
if err != nil {
62-
otel.RecordError(span, err)
63-
api.BadRequest(w, ErrValidation, err)
64-
return
65-
}
61+
if len(CreatePoolRequest.Query) > 0 {
62+
pool.Type = models.POOL_TYPE_DYNAMIC
63+
pool.Query = CreatePoolRequest.Query
64+
} else {
65+
pool.Type = models.POOL_TYPE_STATIC
66+
accounts := make([]models.AccountID, len(CreatePoolRequest.AccountIDs))
67+
for i, accountID := range CreatePoolRequest.AccountIDs {
68+
aID, err := models.AccountIDFromString(accountID)
69+
if err != nil {
70+
otel.RecordError(span, err)
71+
api.BadRequest(w, ErrValidation, err)
72+
return
73+
}
6674

67-
accounts[i] = aID
75+
accounts[i] = aID
76+
}
77+
pool.PoolAccounts = accounts
6878
}
69-
pool.PoolAccounts = accounts
7079

7180
err = backend.PoolsCreate(ctx, pool)
7281
if err != nil {
@@ -78,6 +87,8 @@ func poolsCreate(backend backend.Backend, validator *validation.Validator) http.
7887
data := &PoolResponse{
7988
ID: pool.ID.String(),
8089
Name: pool.Name,
90+
Type: pool.Type,
91+
Query: pool.Query,
8192
Accounts: CreatePoolRequest.AccountIDs,
8293
}
8394

internal/api/v2/handler_pools_create_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,19 @@ var _ = Describe("API v2 Pools Create", func() {
7575
handlerFn(w, prepareJSONRequest(http.MethodPost, &cpr))
7676
assertExpectedResponse(w.Result(), http.StatusOK, "data")
7777
})
78+
79+
It("should return status ok on success with a query", func(ctx SpecContext) {
80+
m.EXPECT().PoolsCreate(gomock.Any(), gomock.Any()).Return(nil)
81+
cpr = CreatePoolRequest{
82+
Name: "name",
83+
Query: map[string]any{
84+
"$match": map[string]any{
85+
"account_id": accID.String(),
86+
},
87+
},
88+
}
89+
handlerFn(w, prepareJSONRequest(http.MethodPost, &cpr))
90+
assertExpectedResponse(w.Result(), http.StatusOK, "data")
91+
})
7892
})
7993
})

0 commit comments

Comments
 (0)