implemented backend wallets/ledger accounts listing
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@
|
|||||||
*.pb.gw.go
|
*.pb.gw.go
|
||||||
pubspec.lock
|
pubspec.lock
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
update_dep.sh
|
||||||
@@ -16,6 +16,8 @@ import (
|
|||||||
|
|
||||||
// Client exposes typed helpers around the ledger gRPC API.
|
// Client exposes typed helpers around the ledger gRPC API.
|
||||||
type Client interface {
|
type Client interface {
|
||||||
|
CreateAccount(ctx context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error)
|
||||||
|
ListAccounts(ctx context.Context, req *ledgerv1.ListAccountsRequest) (*ledgerv1.ListAccountsResponse, error)
|
||||||
PostCreditWithCharges(ctx context.Context, req *ledgerv1.PostCreditRequest) (*ledgerv1.PostResponse, error)
|
PostCreditWithCharges(ctx context.Context, req *ledgerv1.PostCreditRequest) (*ledgerv1.PostResponse, error)
|
||||||
PostDebitWithCharges(ctx context.Context, req *ledgerv1.PostDebitRequest) (*ledgerv1.PostResponse, error)
|
PostDebitWithCharges(ctx context.Context, req *ledgerv1.PostDebitRequest) (*ledgerv1.PostResponse, error)
|
||||||
TransferInternal(ctx context.Context, req *ledgerv1.TransferRequest) (*ledgerv1.PostResponse, error)
|
TransferInternal(ctx context.Context, req *ledgerv1.TransferRequest) (*ledgerv1.PostResponse, error)
|
||||||
@@ -29,6 +31,8 @@ type Client interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type grpcLedgerClient interface {
|
type grpcLedgerClient interface {
|
||||||
|
CreateAccount(ctx context.Context, in *ledgerv1.CreateAccountRequest, opts ...grpc.CallOption) (*ledgerv1.CreateAccountResponse, error)
|
||||||
|
ListAccounts(ctx context.Context, in *ledgerv1.ListAccountsRequest, opts ...grpc.CallOption) (*ledgerv1.ListAccountsResponse, error)
|
||||||
PostCreditWithCharges(ctx context.Context, in *ledgerv1.PostCreditRequest, opts ...grpc.CallOption) (*ledgerv1.PostResponse, error)
|
PostCreditWithCharges(ctx context.Context, in *ledgerv1.PostCreditRequest, opts ...grpc.CallOption) (*ledgerv1.PostResponse, error)
|
||||||
PostDebitWithCharges(ctx context.Context, in *ledgerv1.PostDebitRequest, opts ...grpc.CallOption) (*ledgerv1.PostResponse, error)
|
PostDebitWithCharges(ctx context.Context, in *ledgerv1.PostDebitRequest, opts ...grpc.CallOption) (*ledgerv1.PostResponse, error)
|
||||||
TransferInternal(ctx context.Context, in *ledgerv1.TransferRequest, opts ...grpc.CallOption) (*ledgerv1.PostResponse, error)
|
TransferInternal(ctx context.Context, in *ledgerv1.TransferRequest, opts ...grpc.CallOption) (*ledgerv1.PostResponse, error)
|
||||||
@@ -91,6 +95,18 @@ func (c *ledgerClient) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ledgerClient) CreateAccount(ctx context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error) {
|
||||||
|
ctx, cancel := c.callContext(ctx)
|
||||||
|
defer cancel()
|
||||||
|
return c.client.CreateAccount(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ledgerClient) ListAccounts(ctx context.Context, req *ledgerv1.ListAccountsRequest) (*ledgerv1.ListAccountsResponse, error) {
|
||||||
|
ctx, cancel := c.callContext(ctx)
|
||||||
|
defer cancel()
|
||||||
|
return c.client.ListAccounts(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ledgerClient) PostCreditWithCharges(ctx context.Context, req *ledgerv1.PostCreditRequest) (*ledgerv1.PostResponse, error) {
|
func (c *ledgerClient) PostCreditWithCharges(ctx context.Context, req *ledgerv1.PostCreditRequest) (*ledgerv1.PostResponse, error) {
|
||||||
ctx, cancel := c.callContext(ctx)
|
ctx, cancel := c.callContext(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|||||||
47
api/ledger/internal/service/ledger/list_accounts.go
Normal file
47
api/ledger/internal/service/ledger/list_accounts.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package ledger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tech/sendico/pkg/api/routers/gsresponse"
|
||||||
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
|
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Service) listAccountsResponder(_ context.Context, req *ledgerv1.ListAccountsRequest) gsresponse.Responder[ledgerv1.ListAccountsResponse] {
|
||||||
|
return func(ctx context.Context) (*ledgerv1.ListAccountsResponse, error) {
|
||||||
|
if s.storage == nil {
|
||||||
|
return nil, errStorageNotInitialized
|
||||||
|
}
|
||||||
|
if req == nil {
|
||||||
|
return nil, merrors.InvalidArgument("request is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
orgRefStr := strings.TrimSpace(req.GetOrganizationRef())
|
||||||
|
if orgRefStr == "" {
|
||||||
|
return nil, merrors.InvalidArgument("organization_ref is required")
|
||||||
|
}
|
||||||
|
orgRef, err := parseObjectID(orgRefStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// No pagination requested; return all accounts for the organization.
|
||||||
|
accounts, err := s.storage.Accounts().ListByOrganization(ctx, orgRef, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Warn("failed to list ledger accounts", zap.Error(err), zap.String("organizationRef", orgRef.Hex()))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &ledgerv1.ListAccountsResponse{
|
||||||
|
Accounts: make([]*ledgerv1.LedgerAccount, 0, len(accounts)),
|
||||||
|
}
|
||||||
|
for _, acc := range accounts {
|
||||||
|
resp.Accounts = append(resp.Accounts, toProtoAccount(acc))
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -81,6 +81,12 @@ func (s *Service) Register(router routers.GRPC) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListAccounts lists ledger accounts for an organization.
|
||||||
|
func (s *Service) ListAccounts(ctx context.Context, req *ledgerv1.ListAccountsRequest) (*ledgerv1.ListAccountsResponse, error) {
|
||||||
|
responder := s.listAccountsResponder(ctx, req)
|
||||||
|
return responder(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// CreateAccount provisions a new ledger account scoped to an organization.
|
// CreateAccount provisions a new ledger account scoped to an organization.
|
||||||
func (s *Service) CreateAccount(ctx context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error) {
|
func (s *Service) CreateAccount(ctx context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error) {
|
||||||
responder := s.createAccountResponder(ctx, req)
|
responder := s.createAccountResponder(ctx, req)
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ const (
|
|||||||
Roles Type = "roles" // Represents roles in access control
|
Roles Type = "roles" // Represents roles in access control
|
||||||
Storage Type = "storage" // Represents statuses of tasks or projects
|
Storage Type = "storage" // Represents statuses of tasks or projects
|
||||||
Tenants Type = "tenants" // Represents tenants managed in the system
|
Tenants Type = "tenants" // Represents tenants managed in the system
|
||||||
|
Wallets Type = "wallets" // Represents workflows for tasks or projects
|
||||||
Workflows Type = "workflows" // Represents workflows for tasks or projects
|
Workflows Type = "workflows" // Represents workflows for tasks or projects
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ service LedgerService {
|
|||||||
rpc GetBalance (GetBalanceRequest) returns (BalanceResponse);
|
rpc GetBalance (GetBalanceRequest) returns (BalanceResponse);
|
||||||
rpc GetJournalEntry (GetEntryRequest) returns (JournalEntryResponse);
|
rpc GetJournalEntry (GetEntryRequest) returns (JournalEntryResponse);
|
||||||
rpc GetStatement (GetStatementRequest) returns (StatementResponse);
|
rpc GetStatement (GetStatementRequest) returns (StatementResponse);
|
||||||
|
|
||||||
|
// Lists ledger accounts for an organization.
|
||||||
|
rpc ListAccounts (ListAccountsRequest) returns (ListAccountsResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
message CreateAccountRequest {
|
message CreateAccountRequest {
|
||||||
@@ -192,3 +195,11 @@ message StatementResponse {
|
|||||||
repeated JournalEntryResponse entries = 1;
|
repeated JournalEntryResponse entries = 1;
|
||||||
string next_cursor = 2;
|
string next_cursor = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ListAccountsRequest {
|
||||||
|
string organization_ref = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListAccountsResponse {
|
||||||
|
repeated LedgerAccount accounts = 1;
|
||||||
|
}
|
||||||
|
|||||||
@@ -85,6 +85,12 @@ api:
|
|||||||
chain: ARBITRUM_ONE
|
chain: ARBITRUM_ONE
|
||||||
token_symbol: USDT
|
token_symbol: USDT
|
||||||
contract_address: ""
|
contract_address: ""
|
||||||
|
ledger:
|
||||||
|
address: sendico_ledger:50052
|
||||||
|
address_env: LEDGER_ADDRESS
|
||||||
|
dial_timeout_seconds: 5
|
||||||
|
call_timeout_seconds: 5
|
||||||
|
insecure: true
|
||||||
|
|
||||||
app:
|
app:
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,15 @@ go 1.25.3
|
|||||||
|
|
||||||
replace github.com/tech/sendico/pkg => ../pkg
|
replace github.com/tech/sendico/pkg => ../pkg
|
||||||
|
|
||||||
|
replace github.com/tech/sendico/ledger => ../ledger
|
||||||
|
|
||||||
replace github.com/tech/sendico/chain/gateway => ../chain/gateway
|
replace github.com/tech/sendico/chain/gateway => ../chain/gateway
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aws/aws-sdk-go-v2 v1.40.0
|
github.com/aws/aws-sdk-go-v2 v1.40.0
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.1
|
github.com/aws/aws-sdk-go-v2/config v1.32.2
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.1
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.2
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.92.0
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.92.1
|
||||||
github.com/go-chi/chi/v5 v5.2.3
|
github.com/go-chi/chi/v5 v5.2.3
|
||||||
github.com/go-chi/cors v1.2.2
|
github.com/go-chi/cors v1.2.2
|
||||||
github.com/go-chi/jwtauth/v5 v5.3.3
|
github.com/go-chi/jwtauth/v5 v5.3.3
|
||||||
@@ -19,12 +21,14 @@ require (
|
|||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/tech/sendico/chain/gateway v0.1.0
|
github.com/tech/sendico/chain/gateway v0.1.0
|
||||||
|
github.com/tech/sendico/ledger v0.0.0-00010101000000-000000000000
|
||||||
github.com/tech/sendico/pkg v0.1.0
|
github.com/tech/sendico/pkg v0.1.0
|
||||||
github.com/testcontainers/testcontainers-go v0.33.0
|
github.com/testcontainers/testcontainers-go v0.33.0
|
||||||
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0
|
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0
|
||||||
go.mongodb.org/mongo-driver v1.17.6
|
go.mongodb.org/mongo-driver v1.17.6
|
||||||
go.uber.org/zap v1.27.1
|
go.uber.org/zap v1.27.1
|
||||||
golang.org/x/net v0.47.0
|
golang.org/x/net v0.47.0
|
||||||
|
google.golang.org/protobuf v1.36.10
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
moul.io/chizap v1.0.3
|
moul.io/chizap v1.0.3
|
||||||
)
|
)
|
||||||
@@ -49,10 +53,10 @@ require (
|
|||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.5 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.5 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 // indirect
|
github.com/aws/aws-sdk-go-v2/service/signin v1.0.2 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.4 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sso v1.30.5 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.9 // indirect
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.1 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.41.2 // indirect
|
||||||
github.com/aws/smithy-go v1.23.2 // indirect
|
github.com/aws/smithy-go v1.23.2 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/casbin/mongodb-adapter/v3 v3.7.0 // indirect
|
github.com/casbin/mongodb-adapter/v3 v3.7.0 // indirect
|
||||||
@@ -133,5 +137,4 @@ require (
|
|||||||
golang.org/x/text v0.31.0 // indirect
|
golang.org/x/text v0.31.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect
|
||||||
google.golang.org/grpc v1.77.0 // indirect
|
google.golang.org/grpc v1.77.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.10 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ github.com/aws/aws-sdk-go-v2 v1.40.0 h1:/WMUA0kjhZExjOQN2z3oLALDREea1A7TobfuiBrK
|
|||||||
github.com/aws/aws-sdk-go-v2 v1.40.0/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE=
|
github.com/aws/aws-sdk-go-v2 v1.40.0/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 h1:DHctwEM8P8iTXFxC/QK0MRjwEpWQeM9yzidCRjldUz0=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 h1:DHctwEM8P8iTXFxC/QK0MRjwEpWQeM9yzidCRjldUz0=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3/go.mod h1:xdCzcZEtnSTKVDOmUZs4l/j3pSV6rpo1WXl5ugNsL8Y=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3/go.mod h1:xdCzcZEtnSTKVDOmUZs4l/j3pSV6rpo1WXl5ugNsL8Y=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.1 h1:iODUDLgk3q8/flEC7ymhmxjfoAnBDwEEYEVyKZ9mzjU=
|
github.com/aws/aws-sdk-go-v2/config v1.32.2 h1:4liUsdEpUUPZs5WVapsJLx5NPmQhQdez7nYFcovrytk=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.1/go.mod h1:xoAgo17AGrPpJBSLg81W+ikM0cpOZG8ad04T2r+d5P0=
|
github.com/aws/aws-sdk-go-v2/config v1.32.2/go.mod h1:l0hs06IFz1eCT+jTacU/qZtC33nvcnLADAPL/XyrkZI=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.1 h1:JeW+EwmtTE0yXFK8SmklrFh/cGTTXsQJumgMZNlbxfM=
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.2 h1:qZry8VUyTK4VIo5aEdUcBjPZHL2v4FyQ3QEOaWcFLu4=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.1/go.mod h1:BOoXiStwTF+fT2XufhO0Efssbi1CNIO/ZXpZu87N0pw=
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.2/go.mod h1:YUqm5a1/kBnoK+/NY5WEiMocZihKSo15/tJdmdXnM5g=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 h1:PZHqQACxYb8mYgms4RZbhZG0a7dPW06xOjmaH0EJC/I=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 h1:PZHqQACxYb8mYgms4RZbhZG0a7dPW06xOjmaH0EJC/I=
|
||||||
@@ -32,16 +32,16 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 h1:FIouAnCE
|
|||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14/go.mod h1:UTwDc5COa5+guonQU8qBikJo1ZJ4ln2r1MkF7Dqag1E=
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14/go.mod h1:UTwDc5COa5+guonQU8qBikJo1ZJ4ln2r1MkF7Dqag1E=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14 h1:FzQE21lNtUor0Fb7QNgnEyiRCBlolLTX/Z1j65S7teM=
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14 h1:FzQE21lNtUor0Fb7QNgnEyiRCBlolLTX/Z1j65S7teM=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14/go.mod h1:s1ydyWG9pm3ZwmmYN21HKyG9WzAZhYVW85wMHs5FV6w=
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14/go.mod h1:s1ydyWG9pm3ZwmmYN21HKyG9WzAZhYVW85wMHs5FV6w=
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.92.0 h1:8FshVvnV2sr9kOSAbOnc/vwVmmAwMjOedKH6JW2ddPM=
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.92.1 h1:OgQy/+0+Kc3khtqiEOk23xQAglXi3Tj0y5doOxbi5tg=
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.92.0/go.mod h1:wYNqY3L02Z3IgRYxOBPH9I1zD9Cjh9hI5QOy/eOjQvw=
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.92.1/go.mod h1:wYNqY3L02Z3IgRYxOBPH9I1zD9Cjh9hI5QOy/eOjQvw=
|
||||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 h1:BDgIUYGEo5TkayOWv/oBLPphWwNm/A91AebUjAu5L5g=
|
github.com/aws/aws-sdk-go-v2/service/signin v1.0.2 h1:MxMBdKTYBjPQChlJhi4qlEueqB1p1KcbTEa7tD5aqPs=
|
||||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.1/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk=
|
github.com/aws/aws-sdk-go-v2/service/signin v1.0.2/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.4 h1:U//SlnkE1wOQiIImxzdY5PXat4Wq+8rlfVEw4Y7J8as=
|
github.com/aws/aws-sdk-go-v2/service/sso v1.30.5 h1:ksUT5KtgpZd3SAiFJNJ0AFEJVva3gjBmN7eXUZjzUwQ=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.4/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw=
|
github.com/aws/aws-sdk-go-v2/service/sso v1.30.5/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.9 h1:LU8S9W/mPDAU9q0FjCLi0TrCheLMGwzbRpvUMwYspcA=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 h1:GtsxyiF3Nd3JahRBJbxLCCdYW9ltGQYrFWg8XdkGDd8=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.9/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.1 h1:GdGmKtG+/Krag7VfyOXV17xjTCz0i9NT+JnqLTOI5nA=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.41.2 h1:a5UTtD4mHBU3t0o6aHQZFJTNKVfxFWfPX7J0Lr7G+uY=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.1/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.41.2/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso=
|
||||||
github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM=
|
github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM=
|
||||||
github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ type Config struct {
|
|||||||
Mw *mwa.Config `yaml:"middleware"`
|
Mw *mwa.Config `yaml:"middleware"`
|
||||||
Storage *fsc.Config `yaml:"storage"`
|
Storage *fsc.Config `yaml:"storage"`
|
||||||
ChainGateway *ChainGatewayConfig `yaml:"chain_gateway"`
|
ChainGateway *ChainGatewayConfig `yaml:"chain_gateway"`
|
||||||
|
Ledger *LedgerConfig `yaml:"ledger"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChainGatewayConfig struct {
|
type ChainGatewayConfig struct {
|
||||||
@@ -25,3 +26,11 @@ type ChainGatewayAssetConfig struct {
|
|||||||
TokenSymbol string `yaml:"token_symbol"`
|
TokenSymbol string `yaml:"token_symbol"`
|
||||||
ContractAddress string `yaml:"contract_address"`
|
ContractAddress string `yaml:"contract_address"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LedgerConfig struct {
|
||||||
|
Address string `yaml:"address"`
|
||||||
|
AddressEnv string `yaml:"address_env"`
|
||||||
|
DialTimeoutSeconds int `yaml:"dial_timeout_seconds"`
|
||||||
|
CallTimeoutSeconds int `yaml:"call_timeout_seconds"`
|
||||||
|
Insecure bool `yaml:"insecure"`
|
||||||
|
}
|
||||||
|
|||||||
106
api/server/interface/api/sresponse/ledger.go
Normal file
106
api/server/interface/api/sresponse/ledger.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package sresponse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tech/sendico/pkg/api/http/response"
|
||||||
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
|
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||||
|
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ledgerAccount struct {
|
||||||
|
LedgerAccountRef string `json:"ledgerAccountRef"`
|
||||||
|
OrganizationRef string `json:"organizationRef"`
|
||||||
|
AccountCode string `json:"accountCode"`
|
||||||
|
AccountType string `json:"accountType"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
AllowNegative bool `json:"allowNegative"`
|
||||||
|
IsSettlement bool `json:"isSettlement"`
|
||||||
|
Metadata map[string]string `json:"metadata,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ledgerAccountsResponse struct {
|
||||||
|
authResponse `json:",inline"`
|
||||||
|
Accounts []ledgerAccount `json:"accounts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ledgerMoney struct {
|
||||||
|
Amount string `json:"amount"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ledgerBalance struct {
|
||||||
|
LedgerAccountRef string `json:"ledgerAccountRef"`
|
||||||
|
Balance *ledgerMoney `json:"balance,omitempty"`
|
||||||
|
Version int64 `json:"version"`
|
||||||
|
LastUpdated time.Time `json:"lastUpdated,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ledgerBalanceResponse struct {
|
||||||
|
authResponse `json:",inline"`
|
||||||
|
Balance ledgerBalance `json:"balance"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func LedgerAccounts(logger mlogger.Logger, accounts []*ledgerv1.LedgerAccount, accessToken *TokenData) http.HandlerFunc {
|
||||||
|
dto := make([]ledgerAccount, 0, len(accounts))
|
||||||
|
for _, acc := range accounts {
|
||||||
|
dto = append(dto, toLedgerAccount(acc))
|
||||||
|
}
|
||||||
|
return response.Ok(logger, ledgerAccountsResponse{
|
||||||
|
Accounts: dto,
|
||||||
|
authResponse: authResponse{AccessToken: *accessToken},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func LedgerBalance(logger mlogger.Logger, resp *ledgerv1.BalanceResponse, accessToken *TokenData) http.HandlerFunc {
|
||||||
|
return response.Ok(logger, ledgerBalanceResponse{
|
||||||
|
Balance: toLedgerBalance(resp),
|
||||||
|
authResponse: authResponse{AccessToken: *accessToken},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func toLedgerAccount(acc *ledgerv1.LedgerAccount) ledgerAccount {
|
||||||
|
if acc == nil {
|
||||||
|
return ledgerAccount{}
|
||||||
|
}
|
||||||
|
return ledgerAccount{
|
||||||
|
LedgerAccountRef: acc.GetLedgerAccountRef(),
|
||||||
|
OrganizationRef: acc.GetOrganizationRef(),
|
||||||
|
AccountCode: acc.GetAccountCode(),
|
||||||
|
AccountType: acc.GetAccountType().String(),
|
||||||
|
Currency: acc.GetCurrency(),
|
||||||
|
Status: acc.GetStatus().String(),
|
||||||
|
AllowNegative: acc.GetAllowNegative(),
|
||||||
|
IsSettlement: acc.GetIsSettlement(),
|
||||||
|
Metadata: acc.GetMetadata(),
|
||||||
|
CreatedAt: acc.GetCreatedAt().AsTime(),
|
||||||
|
UpdatedAt: acc.GetUpdatedAt().AsTime(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toLedgerBalance(resp *ledgerv1.BalanceResponse) ledgerBalance {
|
||||||
|
if resp == nil {
|
||||||
|
return ledgerBalance{}
|
||||||
|
}
|
||||||
|
return ledgerBalance{
|
||||||
|
LedgerAccountRef: resp.GetLedgerAccountRef(),
|
||||||
|
Balance: toLedgerMoney(resp.GetBalance()),
|
||||||
|
Version: resp.GetVersion(),
|
||||||
|
LastUpdated: resp.GetLastUpdated().AsTime(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toLedgerMoney(m *moneyv1.Money) *ledgerMoney {
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &ledgerMoney{
|
||||||
|
Amount: m.GetAmount(),
|
||||||
|
Currency: m.GetCurrency(),
|
||||||
|
}
|
||||||
|
}
|
||||||
132
api/server/interface/api/sresponse/wallet.go
Normal file
132
api/server/interface/api/sresponse/wallet.go
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
package sresponse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tech/sendico/pkg/api/http/response"
|
||||||
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
|
gatewayv1 "github.com/tech/sendico/pkg/proto/chain/gateway/v1"
|
||||||
|
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||||
|
paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type walletAsset struct {
|
||||||
|
Chain string `json:"chain"`
|
||||||
|
TokenSymbol string `json:"tokenSymbol"`
|
||||||
|
ContractAddress string `json:"contractAddress"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type wallet struct {
|
||||||
|
WalletRef string `json:"walletRef"`
|
||||||
|
OrganizationRef string `json:"organizationRef"`
|
||||||
|
OwnerRef string `json:"ownerRef"`
|
||||||
|
Asset walletAsset `json:"asset"`
|
||||||
|
DepositAddress string `json:"depositAddress"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Metadata map[string]string `json:"metadata,omitempty"`
|
||||||
|
CreatedAt string `json:"createdAt,omitempty"`
|
||||||
|
UpdatedAt string `json:"updatedAt,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type walletsResponse struct {
|
||||||
|
authResponse `json:",inline"`
|
||||||
|
Wallets []wallet `json:"wallets"`
|
||||||
|
Page *paginationv1.CursorPageResponse `json:"page,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type walletMoney struct {
|
||||||
|
Amount string `json:"amount"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type walletBalance struct {
|
||||||
|
Available *walletMoney `json:"available,omitempty"`
|
||||||
|
PendingInbound *walletMoney `json:"pendingInbound,omitempty"`
|
||||||
|
PendingOutbound *walletMoney `json:"pendingOutbound,omitempty"`
|
||||||
|
CalculatedAt string `json:"calculatedAt,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type walletBalanceResponse struct {
|
||||||
|
authResponse `json:",inline"`
|
||||||
|
Balance walletBalance `json:"balance"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Wallets(logger mlogger.Logger, resp *gatewayv1.ListManagedWalletsResponse, accessToken *TokenData) http.HandlerFunc {
|
||||||
|
dto := walletsResponse{
|
||||||
|
Page: resp.GetPage(),
|
||||||
|
authResponse: authResponse{AccessToken: *accessToken},
|
||||||
|
}
|
||||||
|
dto.Wallets = make([]wallet, 0, len(resp.GetWallets()))
|
||||||
|
for _, w := range resp.GetWallets() {
|
||||||
|
dto.Wallets = append(dto.Wallets, toWallet(w))
|
||||||
|
}
|
||||||
|
return response.Ok(logger, dto)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WalletBalance(logger mlogger.Logger, bal *gatewayv1.WalletBalance, accessToken *TokenData) http.HandlerFunc {
|
||||||
|
return response.Ok(logger, walletBalanceResponse{
|
||||||
|
Balance: toWalletBalance(bal),
|
||||||
|
authResponse: authResponse{AccessToken: *accessToken},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func toWallet(w *gatewayv1.ManagedWallet) wallet {
|
||||||
|
if w == nil {
|
||||||
|
return wallet{}
|
||||||
|
}
|
||||||
|
asset := w.GetAsset()
|
||||||
|
chain := ""
|
||||||
|
token := ""
|
||||||
|
contract := ""
|
||||||
|
if asset != nil {
|
||||||
|
chain = asset.GetChain().String()
|
||||||
|
token = asset.GetTokenSymbol()
|
||||||
|
contract = asset.GetContractAddress()
|
||||||
|
}
|
||||||
|
return wallet{
|
||||||
|
WalletRef: w.GetWalletRef(),
|
||||||
|
OrganizationRef: w.GetOrganizationRef(),
|
||||||
|
OwnerRef: w.GetOwnerRef(),
|
||||||
|
Asset: walletAsset{
|
||||||
|
Chain: chain,
|
||||||
|
TokenSymbol: token,
|
||||||
|
ContractAddress: contract,
|
||||||
|
},
|
||||||
|
DepositAddress: w.GetDepositAddress(),
|
||||||
|
Status: w.GetStatus().String(),
|
||||||
|
Metadata: w.GetMetadata(),
|
||||||
|
CreatedAt: tsToString(w.GetCreatedAt()),
|
||||||
|
UpdatedAt: tsToString(w.GetUpdatedAt()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toWalletBalance(b *gatewayv1.WalletBalance) walletBalance {
|
||||||
|
if b == nil {
|
||||||
|
return walletBalance{}
|
||||||
|
}
|
||||||
|
return walletBalance{
|
||||||
|
Available: toMoney(b.GetAvailable()),
|
||||||
|
PendingInbound: toMoney(b.GetPendingInbound()),
|
||||||
|
PendingOutbound: toMoney(b.GetPendingOutbound()),
|
||||||
|
CalculatedAt: tsToString(b.GetCalculatedAt()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toMoney(m *moneyv1.Money) *walletMoney {
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &walletMoney{
|
||||||
|
Amount: m.GetAmount(),
|
||||||
|
Currency: m.GetCurrency(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func tsToString(ts *timestamppb.Timestamp) string {
|
||||||
|
if ts == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return ts.AsTime().UTC().Format(time.RFC3339)
|
||||||
|
}
|
||||||
11
api/server/interface/services/ledger/ledger.go
Normal file
11
api/server/interface/services/ledger/ledger.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package ledger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
|
"github.com/tech/sendico/server/interface/api"
|
||||||
|
"github.com/tech/sendico/server/internal/server/ledgerapiimp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Create(a api.API) (mservice.MicroService, error) {
|
||||||
|
return ledgerapiimp.CreateAPI(a)
|
||||||
|
}
|
||||||
11
api/server/interface/services/wallet/wallet.go
Normal file
11
api/server/interface/services/wallet/wallet.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package wallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
|
"github.com/tech/sendico/server/interface/api"
|
||||||
|
"github.com/tech/sendico/server/internal/server/walletapiimp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Create(a api.API) (mservice.MicroService, error) {
|
||||||
|
return walletapiimp.CreateAPI(a)
|
||||||
|
}
|
||||||
@@ -18,6 +18,8 @@ import (
|
|||||||
"github.com/tech/sendico/server/interface/services/organization"
|
"github.com/tech/sendico/server/interface/services/organization"
|
||||||
"github.com/tech/sendico/server/interface/services/permission"
|
"github.com/tech/sendico/server/interface/services/permission"
|
||||||
"github.com/tech/sendico/server/interface/services/site"
|
"github.com/tech/sendico/server/interface/services/site"
|
||||||
|
"github.com/tech/sendico/server/interface/services/wallet"
|
||||||
|
"github.com/tech/sendico/server/interface/services/ledger"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -83,6 +85,8 @@ func (a *APIImp) installServices() error {
|
|||||||
srvf = append(srvf, logo.Create)
|
srvf = append(srvf, logo.Create)
|
||||||
srvf = append(srvf, permission.Create)
|
srvf = append(srvf, permission.Create)
|
||||||
srvf = append(srvf, site.Create)
|
srvf = append(srvf, site.Create)
|
||||||
|
srvf = append(srvf, wallet.Create)
|
||||||
|
srvf = append(srvf, ledger.Create)
|
||||||
|
|
||||||
for _, v := range srvf {
|
for _, v := range srvf {
|
||||||
if err := a.addMicroservice(v); err != nil {
|
if err := a.addMicroservice(v); err != nil {
|
||||||
|
|||||||
52
api/server/internal/server/ledgerapiimp/balance.go
Normal file
52
api/server/internal/server/ledgerapiimp/balance.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package ledgerapiimp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tech/sendico/pkg/api/http/response"
|
||||||
|
"github.com/tech/sendico/pkg/model"
|
||||||
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
|
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||||
|
"github.com/tech/sendico/server/interface/api/sresponse"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *LedgerAPI) getBalance(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
||||||
|
orgRef, err := a.oph.GetRef(r)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to parse organization reference for ledger balance", zap.Error(err), zap.String(a.oph.Name(), a.oph.GetID(r)))
|
||||||
|
return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accountRef := strings.TrimSpace(a.aph.GetID(r))
|
||||||
|
if accountRef == "" {
|
||||||
|
return response.BadReference(a.logger, a.Name(), a.aph.Name(), a.aph.GetID(r), errors.New("ledger account reference is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
res, err := a.enf.Enforce(ctx, a.balancePerm, account.ID, orgRef, primitive.NilObjectID, model.ActionRead)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to check ledger balance access permissions", zap.Error(err), zap.String(a.oph.Name(), orgRef.Hex()), zap.String("ledger_account_ref", accountRef))
|
||||||
|
return response.Auto(a.logger, a.Name(), err)
|
||||||
|
}
|
||||||
|
if !res {
|
||||||
|
a.logger.Debug("Access denied when reading ledger balance", zap.String(a.oph.Name(), orgRef.Hex()), zap.String("ledger_account_ref", accountRef))
|
||||||
|
return response.AccessDenied(a.logger, a.Name(), "ledger balance read permission denied")
|
||||||
|
}
|
||||||
|
if a.client == nil {
|
||||||
|
return response.Internal(a.logger, mservice.Ledger, errors.New("ledger client is not configured"))
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := a.client.GetBalance(ctx, &ledgerv1.GetBalanceRequest{
|
||||||
|
LedgerAccountRef: accountRef,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to fetch ledger balance", zap.Error(err), zap.String("ledger_account_ref", accountRef))
|
||||||
|
return response.Auto(a.logger, mservice.Ledger, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sresponse.LedgerBalance(a.logger, resp, token)
|
||||||
|
}
|
||||||
46
api/server/internal/server/ledgerapiimp/list.go
Normal file
46
api/server/internal/server/ledgerapiimp/list.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package ledgerapiimp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/tech/sendico/pkg/api/http/response"
|
||||||
|
"github.com/tech/sendico/pkg/model"
|
||||||
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
|
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||||
|
"github.com/tech/sendico/server/interface/api/sresponse"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *LedgerAPI) listAccounts(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
||||||
|
orgRef, err := a.oph.GetRef(r)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to parse organization reference for ledger account list", zap.Error(err), zap.String(a.oph.Name(), a.oph.GetID(r)))
|
||||||
|
return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
res, err := a.enf.Enforce(ctx, a.permissionRef, account.ID, orgRef, primitive.NilObjectID, model.ActionRead)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to check ledger accounts access permissions", zap.Error(err), zap.String(a.oph.Name(), orgRef.Hex()))
|
||||||
|
return response.Auto(a.logger, a.Name(), err)
|
||||||
|
}
|
||||||
|
if !res {
|
||||||
|
a.logger.Debug("Access denied when listing ledger accounts", zap.String(a.oph.Name(), orgRef.Hex()))
|
||||||
|
return response.AccessDenied(a.logger, a.Name(), "ledger accounts read permission denied")
|
||||||
|
}
|
||||||
|
if a.client == nil {
|
||||||
|
return response.Internal(a.logger, mservice.Ledger, errors.New("ledger client is not configured"))
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := a.client.ListAccounts(ctx, &ledgerv1.ListAccountsRequest{
|
||||||
|
OrganizationRef: orgRef.Hex(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to list ledger accounts", zap.Error(err), zap.String("organization_ref", orgRef.Hex()))
|
||||||
|
return response.Auto(a.logger, mservice.Ledger, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sresponse.LedgerAccounts(a.logger, resp.GetAccounts(), token)
|
||||||
|
}
|
||||||
110
api/server/internal/server/ledgerapiimp/service.go
Normal file
110
api/server/internal/server/ledgerapiimp/service.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package ledgerapiimp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
ledgerclient "github.com/tech/sendico/ledger/client"
|
||||||
|
api "github.com/tech/sendico/pkg/api/http"
|
||||||
|
"github.com/tech/sendico/pkg/auth"
|
||||||
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
|
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||||
|
eapi "github.com/tech/sendico/server/interface/api"
|
||||||
|
mutil "github.com/tech/sendico/server/internal/mutil/param"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ledgerClient interface {
|
||||||
|
ListAccounts(ctx context.Context, req *ledgerv1.ListAccountsRequest) (*ledgerv1.ListAccountsResponse, error)
|
||||||
|
GetBalance(ctx context.Context, req *ledgerv1.GetBalanceRequest) (*ledgerv1.BalanceResponse, error)
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type LedgerAPI struct {
|
||||||
|
logger mlogger.Logger
|
||||||
|
client ledgerClient
|
||||||
|
enf auth.Enforcer
|
||||||
|
oph mutil.ParamHelper
|
||||||
|
aph mutil.ParamHelper
|
||||||
|
permissionRef primitive.ObjectID
|
||||||
|
balancePerm primitive.ObjectID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *LedgerAPI) Name() mservice.Type { return mservice.LedgerAccounts }
|
||||||
|
|
||||||
|
func (a *LedgerAPI) Finish(ctx context.Context) error {
|
||||||
|
if a.client != nil {
|
||||||
|
if err := a.client.Close(); err != nil {
|
||||||
|
a.logger.Warn("Failed to close ledger client", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateAPI(apiCtx eapi.API) (*LedgerAPI, error) {
|
||||||
|
p := &LedgerAPI{
|
||||||
|
logger: apiCtx.Logger().Named(mservice.LedgerAccounts),
|
||||||
|
enf: apiCtx.Permissions().Enforcer(),
|
||||||
|
oph: mutil.CreatePH(mservice.Organizations),
|
||||||
|
aph: mutil.CreatePH("ledger_account"),
|
||||||
|
}
|
||||||
|
|
||||||
|
desc, err := apiCtx.Permissions().GetPolicyDescription(context.Background(), mservice.LedgerAccounts)
|
||||||
|
if err != nil {
|
||||||
|
p.logger.Warn("Failed to fetch ledger accounts permission description", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.permissionRef = desc.ID
|
||||||
|
|
||||||
|
bdesc, err := apiCtx.Permissions().GetPolicyDescription(context.Background(), mservice.LedgerBalances)
|
||||||
|
if err != nil {
|
||||||
|
p.logger.Warn("Failed to fetch ledger balances permission description", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.balancePerm = bdesc.ID
|
||||||
|
|
||||||
|
if err := p.initLedgerClient(apiCtx.Config().Ledger); err != nil {
|
||||||
|
p.logger.Error("Failed to initialize ledger client", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/"), api.Get, p.listAccounts)
|
||||||
|
apiCtx.Register().AccountHandler(p.Name(), p.aph.AddRef(p.oph.AddRef("/"))+"/balance", api.Get, p.getBalance)
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *LedgerAPI) initLedgerClient(cfg *eapi.LedgerConfig) error {
|
||||||
|
if cfg == nil {
|
||||||
|
return merrors.InvalidArgument("ledger configuration is not provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
address := strings.TrimSpace(cfg.Address)
|
||||||
|
if address == "" {
|
||||||
|
address = strings.TrimSpace(os.Getenv(cfg.AddressEnv))
|
||||||
|
}
|
||||||
|
if address == "" {
|
||||||
|
return merrors.InvalidArgument(fmt.Sprintf("ledger address is not specified and address env %s is empty", cfg.AddressEnv))
|
||||||
|
}
|
||||||
|
|
||||||
|
clientCfg := ledgerclient.Config{
|
||||||
|
Address: address,
|
||||||
|
DialTimeout: time.Duration(cfg.DialTimeoutSeconds) * time.Second,
|
||||||
|
CallTimeout: time.Duration(cfg.CallTimeoutSeconds) * time.Second,
|
||||||
|
Insecure: cfg.Insecure,
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := ledgerclient.New(context.Background(), clientCfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.client = client
|
||||||
|
return nil
|
||||||
|
}
|
||||||
55
api/server/internal/server/walletapiimp/balance.go
Normal file
55
api/server/internal/server/walletapiimp/balance.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package walletapiimp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tech/sendico/pkg/api/http/response"
|
||||||
|
"github.com/tech/sendico/pkg/model"
|
||||||
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
|
gatewayv1 "github.com/tech/sendico/pkg/proto/chain/gateway/v1"
|
||||||
|
"github.com/tech/sendico/server/interface/api/sresponse"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *WalletAPI) getWalletBalance(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
||||||
|
orgRef, err := a.oph.GetRef(r)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to parse organization reference for wallet balance", zap.Error(err), zap.String(a.oph.Name(), a.oph.GetID(r)))
|
||||||
|
return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err)
|
||||||
|
}
|
||||||
|
walletRef := strings.TrimSpace(a.wph.GetID(r))
|
||||||
|
if walletRef == "" {
|
||||||
|
return response.BadReference(a.logger, a.Name(), a.wph.Name(), a.wph.GetID(r), errors.New("wallet reference is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
res, err := a.enf.Enforce(ctx, a.balancesPermissionRef, account.ID, orgRef, primitive.NilObjectID, model.ActionRead)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to check wallet balance permissions", zap.Error(err), zap.String(a.oph.Name(), orgRef.Hex()), zap.String("wallet_ref", walletRef))
|
||||||
|
return response.Auto(a.logger, a.Name(), err)
|
||||||
|
}
|
||||||
|
if !res {
|
||||||
|
a.logger.Debug("Access denied when reading wallet balance", zap.String(a.oph.Name(), orgRef.Hex()), zap.String("wallet_ref", walletRef))
|
||||||
|
return response.AccessDenied(a.logger, a.Name(), "wallet balance read permission denied")
|
||||||
|
}
|
||||||
|
if a.chainGateway == nil {
|
||||||
|
return response.Internal(a.logger, mservice.ChainGateway, errors.New("chain gateway client is not configured"))
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := a.chainGateway.GetWalletBalance(ctx, &gatewayv1.GetWalletBalanceRequest{WalletRef: walletRef})
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to fetch wallet balance", zap.Error(err), zap.String("wallet_ref", walletRef))
|
||||||
|
return response.Auto(a.logger, mservice.ChainGateway, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bal := resp.GetBalance()
|
||||||
|
if bal == nil {
|
||||||
|
a.logger.Warn("Wallet balance missing in response", zap.String("wallet_ref", walletRef))
|
||||||
|
return response.Auto(a.logger, mservice.ChainGateway, errors.New("wallet balance not available"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return sresponse.WalletBalance(a.logger, bal, token)
|
||||||
|
}
|
||||||
52
api/server/internal/server/walletapiimp/list.go
Normal file
52
api/server/internal/server/walletapiimp/list.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package walletapiimp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tech/sendico/pkg/api/http/response"
|
||||||
|
"github.com/tech/sendico/pkg/model"
|
||||||
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
|
gatewayv1 "github.com/tech/sendico/pkg/proto/chain/gateway/v1"
|
||||||
|
"github.com/tech/sendico/server/interface/api/sresponse"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *WalletAPI) listWallets(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
||||||
|
orgRef, err := a.oph.GetRef(r)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to parse organization reference for wallet list", zap.Error(err), zap.String(a.oph.Name(), a.oph.GetID(r)))
|
||||||
|
return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
res, err := a.enf.Enforce(ctx, a.walletsPermissionRef, account.ID, orgRef, primitive.NilObjectID, model.ActionRead)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to check chain wallet access permissions", zap.Error(err), zap.String(a.oph.Name(), orgRef.Hex()))
|
||||||
|
return response.Auto(a.logger, a.Name(), err)
|
||||||
|
}
|
||||||
|
if !res {
|
||||||
|
a.logger.Debug("Access denied when listing organization wallets", zap.String(a.oph.Name(), orgRef.Hex()))
|
||||||
|
return response.AccessDenied(a.logger, a.Name(), "wallets read permission denied")
|
||||||
|
}
|
||||||
|
if a.chainGateway == nil {
|
||||||
|
return response.Internal(a.logger, mservice.ChainGateway, errors.New("chain gateway client is not configured"))
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &gatewayv1.ListManagedWalletsRequest{
|
||||||
|
OrganizationRef: orgRef.Hex(),
|
||||||
|
}
|
||||||
|
if owner := strings.TrimSpace(r.URL.Query().Get("owner_ref")); owner != "" {
|
||||||
|
req.OwnerRef = owner
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := a.chainGateway.ListManagedWallets(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to list managed wallets", zap.Error(err), zap.String("organization_ref", orgRef.Hex()))
|
||||||
|
return response.Auto(a.logger, mservice.ChainGateway, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sresponse.Wallets(a.logger, resp, token)
|
||||||
|
}
|
||||||
115
api/server/internal/server/walletapiimp/service.go
Normal file
115
api/server/internal/server/walletapiimp/service.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package walletapiimp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
chaingatewayclient "github.com/tech/sendico/chain/gateway/client"
|
||||||
|
api "github.com/tech/sendico/pkg/api/http"
|
||||||
|
"github.com/tech/sendico/pkg/auth"
|
||||||
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
|
gatewayv1 "github.com/tech/sendico/pkg/proto/chain/gateway/v1"
|
||||||
|
eapi "github.com/tech/sendico/server/interface/api"
|
||||||
|
mutil "github.com/tech/sendico/server/internal/mutil/param"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WalletAPI struct {
|
||||||
|
logger mlogger.Logger
|
||||||
|
chainGateway chainWalletClient
|
||||||
|
enf auth.Enforcer
|
||||||
|
oph mutil.ParamHelper
|
||||||
|
wph mutil.ParamHelper
|
||||||
|
walletsPermissionRef primitive.ObjectID
|
||||||
|
balancesPermissionRef primitive.ObjectID
|
||||||
|
}
|
||||||
|
|
||||||
|
type chainWalletClient interface {
|
||||||
|
ListManagedWallets(ctx context.Context, req *gatewayv1.ListManagedWalletsRequest) (*gatewayv1.ListManagedWalletsResponse, error)
|
||||||
|
GetWalletBalance(ctx context.Context, req *gatewayv1.GetWalletBalanceRequest) (*gatewayv1.GetWalletBalanceResponse, error)
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *WalletAPI) Name() mservice.Type { return mservice.ChainWallets }
|
||||||
|
|
||||||
|
func (a *WalletAPI) Finish(ctx context.Context) error {
|
||||||
|
if a.chainGateway != nil {
|
||||||
|
if err := a.chainGateway.Close(); err != nil {
|
||||||
|
a.logger.Warn("Failed to close chain gateway client", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateAPI(apiCtx eapi.API) (*WalletAPI, error) {
|
||||||
|
p := &WalletAPI{
|
||||||
|
logger: apiCtx.Logger().Named(mservice.Wallets),
|
||||||
|
enf: apiCtx.Permissions().Enforcer(),
|
||||||
|
oph: mutil.CreatePH(mservice.Organizations),
|
||||||
|
wph: mutil.CreatePH(mservice.Wallets),
|
||||||
|
}
|
||||||
|
|
||||||
|
walletsPolicy, err := apiCtx.Permissions().GetPolicyDescription(context.Background(), mservice.ChainWallets)
|
||||||
|
if err != nil {
|
||||||
|
p.logger.Warn("Failed to fetch chain wallets permission policy description", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.walletsPermissionRef = walletsPolicy.ID
|
||||||
|
|
||||||
|
balancesPolicy, err := apiCtx.Permissions().GetPolicyDescription(context.Background(), mservice.ChainWalletBalances)
|
||||||
|
if err != nil {
|
||||||
|
p.logger.Warn("Failed to fetch chain wallet balances permission policy description", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.balancesPermissionRef = balancesPolicy.ID
|
||||||
|
|
||||||
|
cfg := apiCtx.Config()
|
||||||
|
if cfg == nil {
|
||||||
|
p.logger.Error("Failed to fetch service configuration")
|
||||||
|
return nil, merrors.InvalidArgument("No configuration provided")
|
||||||
|
}
|
||||||
|
if err := p.initChainGateway(cfg.ChainGateway); err != nil {
|
||||||
|
p.logger.Error("Failed to initialize chain gateway client", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/"), api.Get, p.listWallets)
|
||||||
|
apiCtx.Register().AccountHandler(p.Name(), p.wph.AddRef(p.oph.AddRef("/"))+"/balance", api.Get, p.getWalletBalance)
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *WalletAPI) initChainGateway(cfg *eapi.ChainGatewayConfig) error {
|
||||||
|
if cfg == nil {
|
||||||
|
return merrors.InvalidArgument("chain gateway configuration is not provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Address = strings.TrimSpace(cfg.Address)
|
||||||
|
if cfg.Address == "" {
|
||||||
|
cfg.Address = strings.TrimSpace(os.Getenv(cfg.AddressEnv))
|
||||||
|
}
|
||||||
|
if cfg.Address == "" {
|
||||||
|
return merrors.InvalidArgument(fmt.Sprintf("chain gateway address is not specified and address env %s is empty", cfg.AddressEnv))
|
||||||
|
}
|
||||||
|
|
||||||
|
clientCfg := chaingatewayclient.Config{
|
||||||
|
Address: cfg.Address,
|
||||||
|
DialTimeout: time.Duration(cfg.DialTimeoutSeconds) * time.Second,
|
||||||
|
CallTimeout: time.Duration(cfg.CallTimeoutSeconds) * time.Second,
|
||||||
|
Insecure: cfg.Insecure,
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := chaingatewayclient.New(context.Background(), clientCfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.chainGateway = client
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -88,6 +88,7 @@ LEDGER_COMPOSE_PROJECT=sendico-ledger
|
|||||||
LEDGER_SERVICE_NAME=sendico_ledger
|
LEDGER_SERVICE_NAME=sendico_ledger
|
||||||
LEDGER_GRPC_PORT=50052
|
LEDGER_GRPC_PORT=50052
|
||||||
LEDGER_METRICS_PORT=9401
|
LEDGER_METRICS_PORT=9401
|
||||||
|
LEDGER_ADDRESS=sendico_ledger:50520
|
||||||
|
|
||||||
# Ledger Mongo settings
|
# Ledger Mongo settings
|
||||||
LEDGER_MONGO_HOST=sendico_db1
|
LEDGER_MONGO_HOST=sendico_db1
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ services:
|
|||||||
NATS_PORT: ${NATS_PORT}
|
NATS_PORT: ${NATS_PORT}
|
||||||
NATS_USER: ${NATS_USER}
|
NATS_USER: ${NATS_USER}
|
||||||
NATS_PASSWORD: ${NATS_PASSWORD}
|
NATS_PASSWORD: ${NATS_PASSWORD}
|
||||||
CHAIN_GATEWAY_ADDRESS: ${CHAIN_GATEWAY_ADDRESS}
|
CHAIN_GATEWAY_ADDRESS: ${CHAIN_GATEWAY_SERVICE_NAME}:${CHAIN_GATEWAY_GRPC_PORT}
|
||||||
|
LEDGER_ADDRESS: ${LEDGER_SERVICE_NAME}:${LEDGER_GRPC_PORT}
|
||||||
MONGO_HOST: ${MONGO_HOST}
|
MONGO_HOST: ${MONGO_HOST}
|
||||||
MONGO_PORT: ${MONGO_PORT}
|
MONGO_PORT: ${MONGO_PORT}
|
||||||
MONGO_DATABASE: ${MONGO_DATABASE}
|
MONGO_DATABASE: ${MONGO_DATABASE}
|
||||||
|
|||||||
@@ -2,130 +2,89 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:pweb/models/wallet.dart';
|
import 'package:pweb/models/wallet.dart';
|
||||||
import 'package:pweb/services/wallets.dart';
|
import 'package:pweb/services/wallets.dart';
|
||||||
|
import 'package:pshared/provider/resource.dart';
|
||||||
|
import 'package:pshared/utils/exception.dart';
|
||||||
|
|
||||||
class WalletsProvider with ChangeNotifier {
|
class WalletsProvider with ChangeNotifier {
|
||||||
final WalletsService _service;
|
final WalletsService _service;
|
||||||
|
|
||||||
WalletsProvider(this._service);
|
WalletsProvider(this._service);
|
||||||
|
|
||||||
List<Wallet>? _wallets;
|
Resource<List<Wallet>> _resource = Resource(data: []);
|
||||||
bool _isLoading = false;
|
Resource<List<Wallet>> get resource => _resource;
|
||||||
String? _error;
|
|
||||||
Wallet? _selectedWallet;
|
|
||||||
final bool _isHidden = true;
|
|
||||||
|
|
||||||
List<Wallet>? get wallets => _wallets;
|
List<Wallet> get wallets => _resource.data ?? [];
|
||||||
bool get isLoading => _isLoading;
|
bool get isLoading => _resource.isLoading;
|
||||||
String? get error => _error;
|
Exception? get error => _resource.error;
|
||||||
|
|
||||||
|
Wallet? _selectedWallet;
|
||||||
Wallet? get selectedWallet => _selectedWallet;
|
Wallet? get selectedWallet => _selectedWallet;
|
||||||
|
final bool _isHidden = true;
|
||||||
bool get isHidden => _isHidden;
|
bool get isHidden => _isHidden;
|
||||||
|
|
||||||
|
bool _isRefreshingBalances = false;
|
||||||
|
bool get isRefreshingBalances => _isRefreshingBalances;
|
||||||
|
|
||||||
void selectWallet(Wallet wallet) {
|
void selectWallet(Wallet wallet) {
|
||||||
_selectedWallet = wallet;
|
_selectedWallet = wallet;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadData() async {
|
Future<void> loadWalletsWithBalances() async {
|
||||||
_isLoading = true;
|
_setResource(_resource.copyWith(isLoading: true, error: null));
|
||||||
_error = null;
|
|
||||||
notifyListeners();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_wallets = await _service.getWallets();
|
final base = await _service.getWallets();
|
||||||
} catch (e) {
|
final withBalances = <Wallet>[];
|
||||||
_error = e.toString();
|
for (final wallet in base) {
|
||||||
} finally {
|
try {
|
||||||
_isLoading = false;
|
final balance = await _service.getBalance(wallet.id);
|
||||||
notifyListeners();
|
withBalances.add(wallet.copyWith(balance: balance));
|
||||||
}
|
} catch (e) {
|
||||||
}
|
_setResource(_resource.copyWith(error: toException(e)));
|
||||||
|
withBalances.add(wallet);
|
||||||
Future<Wallet?> getWalletById(String walletId) async {
|
}
|
||||||
_isLoading = true;
|
|
||||||
_error = null;
|
|
||||||
notifyListeners();
|
|
||||||
|
|
||||||
try {
|
|
||||||
final wallet = await _service.getWallet(walletId);
|
|
||||||
return wallet;
|
|
||||||
} catch (e) {
|
|
||||||
_error = e.toString();
|
|
||||||
return null;
|
|
||||||
} finally {
|
|
||||||
_isLoading = false;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateName(String walletRef, String newName) {
|
|
||||||
final index = _wallets?.indexWhere((w) => w.id == walletRef);
|
|
||||||
if (index != null && index >= 0) {
|
|
||||||
_wallets![index] = _wallets![index].copyWith(name: newName);
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void updateBalance(String walletRef, double newBalance) {
|
|
||||||
final index = _wallets?.indexWhere((w) => w.id == walletRef);
|
|
||||||
if (index != null && index >= 0) {
|
|
||||||
_wallets![index] = _wallets![index].copyWith(balance: newBalance);
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateWallet(Wallet wallet) async {
|
|
||||||
try {
|
|
||||||
await _service.updateWallet();
|
|
||||||
final index = _wallets?.indexWhere((w) => w.id == wallet.id);
|
|
||||||
if (index != null && index >= 0) {
|
|
||||||
_wallets![index] = wallet;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
_setResource(Resource(data: withBalances, isLoading: false, error: _resource.error));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_error = e.toString();
|
_setResource(_resource.copyWith(isLoading: false, error: toException(e)));
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> refreshBalances() async {
|
||||||
|
if (wallets.isEmpty) return;
|
||||||
|
_isRefreshingBalances = true;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
Future<void> addWallet(Wallet wallet) async {
|
|
||||||
try {
|
try {
|
||||||
final newWallet = await _service.createWallet(); // Pass the wallet parameter
|
final updated = <Wallet>[];
|
||||||
_wallets = [...?_wallets, ]; // Add the new wallet
|
for (final wallet in wallets) {
|
||||||
notifyListeners();
|
final balance = await _service.getBalance(wallet.id);
|
||||||
|
updated.add(wallet.copyWith(balance: balance));
|
||||||
|
}
|
||||||
|
_setResource(_resource.copyWith(data: updated));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_error = e.toString();
|
_setResource(_resource.copyWith(error: toException(e)));
|
||||||
notifyListeners();
|
} finally {
|
||||||
}
|
_isRefreshingBalances = false;
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteWallet(String walletId) async {
|
|
||||||
try {
|
|
||||||
await _service.deleteWallet(); // Pass the walletId parameter
|
|
||||||
_wallets?.removeWhere((w) => w.id == walletId);
|
|
||||||
notifyListeners();
|
|
||||||
} catch (e) {
|
|
||||||
_error = e.toString();
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleVisibility(String walletId) {
|
void toggleVisibility(String walletId) {
|
||||||
final index = _wallets?.indexWhere((w) => w.id == walletId);
|
final index = wallets.indexWhere((w) => w.id == walletId);
|
||||||
if (index != null && index >= 0) {
|
if (index < 0) return;
|
||||||
final wallet = _wallets![index];
|
final wallet = wallets[index];
|
||||||
_wallets![index] = wallet.copyWith(isHidden: !wallet.isHidden);
|
final updated = wallet.copyWith(isHidden: !wallet.isHidden);
|
||||||
|
final next = List<Wallet>.from(wallets);
|
||||||
if (_selectedWallet?.id == walletId) {
|
next[index] = updated;
|
||||||
_selectedWallet = _wallets![index];
|
_setResource(_resource.copyWith(data: next));
|
||||||
}
|
if (_selectedWallet?.id == walletId) {
|
||||||
|
_selectedWallet = updated;
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
void _setResource(Resource<List<Wallet>> newResource) {
|
||||||
|
_resource = newResource;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,11 +4,7 @@ import 'package:pweb/models/wallet.dart';
|
|||||||
|
|
||||||
abstract class WalletsService {
|
abstract class WalletsService {
|
||||||
Future<List<Wallet>> getWallets();
|
Future<List<Wallet>> getWallets();
|
||||||
Future<List<Wallet>> updateWallet();
|
Future<double> getBalance(String walletRef);
|
||||||
Future<List<Wallet>> createWallet();
|
|
||||||
Future<List<Wallet>> deleteWallet();
|
|
||||||
|
|
||||||
Future<Wallet> getWallet(String walletRef);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockWalletsService implements WalletsService {
|
class MockWalletsService implements WalletsService {
|
||||||
@@ -31,11 +27,11 @@ class MockWalletsService implements WalletsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Wallet>> updateWallet() async => [];
|
Future<double> getBalance(String walletRef) async {
|
||||||
|
final wallet = _wallets.firstWhere(
|
||||||
@override
|
(w) => w.id == walletRef,
|
||||||
Future<List<Wallet>> createWallet() async => [];
|
orElse: () => throw Exception('Wallet not found'),
|
||||||
|
);
|
||||||
@override
|
return wallet.balance;
|
||||||
Future<List<Wallet>> deleteWallet() async => [];
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user