From dfad7fb335604cd17f2b4f71a6f9ae00ff6dc290 Mon Sep 17 00:00:00 2001 From: Stephan D Date: Mon, 22 Dec 2025 18:30:15 +0100 Subject: [PATCH] added fix for active indexed tokens + improved data structure for wallet description --- api/gateway/chain/go.mod | 2 +- api/gateway/chain/go.sum | 4 +- .../service/gateway/commands/wallet/create.go | 30 +++++- .../service/gateway/commands/wallet/proto.go | 23 +++++ api/gateway/chain/storage/model/wallet.go | 13 ++- .../db/internal/mongo/refreshtokensdb/db.go | 4 +- .../refreshtokensdb/refreshtokensdb_test.go | 95 ++++++++++++++++++- .../db/internal/mongo/repositoryimp/index.go | 3 + .../repositoryimp/index_integration_test.go | 83 ++++++++++++++++ api/pkg/db/repository/index/index.go | 11 ++- .../common/describable/v1/describable.proto | 11 +++ api/proto/gateway/chain/v1/chain.proto | 3 + api/server/interface/api/sresponse/wallet.go | 26 +++++ .../internal/api/routers/public/login.go | 4 +- .../pshared/lib/data/dto/wallet/wallet.dart | 4 + .../lib/data/mapper/wallet/wallet.dart | 6 +- .../lib/service/payment/quotation.dart | 4 +- frontend/pweb/lib/data/mappers/wallet_ui.dart | 7 +- 18 files changed, 307 insertions(+), 26 deletions(-) create mode 100644 api/pkg/db/internal/mongo/repositoryimp/index_integration_test.go create mode 100644 api/proto/common/describable/v1/describable.proto diff --git a/api/gateway/chain/go.mod b/api/gateway/chain/go.mod index e31c459..8bdbd69 100644 --- a/api/gateway/chain/go.mod +++ b/api/gateway/chain/go.mod @@ -22,7 +22,7 @@ require ( require ( github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251213223233-751f36331c62 // indirect + github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251222010151-8a13a32a690c // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.24.4 // indirect github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect diff --git a/api/gateway/chain/go.sum b/api/gateway/chain/go.sum index bcf2e08..00377ae 100644 --- a/api/gateway/chain/go.sum +++ b/api/gateway/chain/go.sum @@ -6,8 +6,8 @@ github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251213223233-751f36331c62 h1:Rge3uIIO891+nLqKTfMulCw+tWHtTl16Oudi0yUcAoE= -github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251213223233-751f36331c62/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI= +github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251222010151-8a13a32a690c h1:1HaIKi7tUhYKk05NOy2tgqtDky4aVXjCeTaBU7ziJZE= +github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251222010151-8a13a32a690c/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI= github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0= github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= diff --git a/api/gateway/chain/internal/service/gateway/commands/wallet/create.go b/api/gateway/chain/internal/service/gateway/commands/wallet/create.go index 90ce29b..83f2754 100644 --- a/api/gateway/chain/internal/service/gateway/commands/wallet/create.go +++ b/api/gateway/chain/internal/service/gateway/commands/wallet/create.go @@ -9,6 +9,7 @@ import ( "github.com/tech/sendico/gateway/chain/storage/model" "github.com/tech/sendico/pkg/api/routers/gsresponse" "github.com/tech/sendico/pkg/merrors" + pkgmodel "github.com/tech/sendico/pkg/model" "github.com/tech/sendico/pkg/mservice" chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1" "go.uber.org/zap" @@ -95,7 +96,31 @@ func (c *createManagedWalletCommand) Execute(ctx context.Context, req *chainv1.C return gsresponse.Internal[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.Internal("key manager returned empty address")) } + metadata := shared.CloneMetadata(req.GetMetadata()) + desc := req.GetDescribable() + name := strings.TrimSpace(desc.GetName()) + if name == "" { + name = strings.TrimSpace(metadata["name"]) + } + var description *string + if desc != nil && desc.Description != nil { + if trimmed := strings.TrimSpace(desc.GetDescription()); trimmed != "" { + description = &trimmed + } + } + if description == nil { + if trimmed := strings.TrimSpace(metadata["description"]); trimmed != "" { + description = &trimmed + } + } + if name == "" { + name = walletRef + } + wallet := &model.ManagedWallet{ + Describable: pkgmodel.Describable{ + Name: name, + }, IdempotencyKey: idempotencyKey, WalletRef: walletRef, OrganizationRef: organizationRef, @@ -106,7 +131,10 @@ func (c *createManagedWalletCommand) Execute(ctx context.Context, req *chainv1.C DepositAddress: strings.ToLower(keyInfo.Address), KeyReference: keyInfo.KeyID, Status: model.ManagedWalletStatusActive, - Metadata: shared.CloneMetadata(req.GetMetadata()), + Metadata: metadata, + } + if description != nil { + wallet.Describable.Description = description } created, err := c.deps.Storage.Wallets().Create(ctx, wallet) diff --git a/api/gateway/chain/internal/service/gateway/commands/wallet/proto.go b/api/gateway/chain/internal/service/gateway/commands/wallet/proto.go index 9906e2f..16ff0ad 100644 --- a/api/gateway/chain/internal/service/gateway/commands/wallet/proto.go +++ b/api/gateway/chain/internal/service/gateway/commands/wallet/proto.go @@ -1,8 +1,11 @@ package wallet import ( + "strings" + "github.com/tech/sendico/gateway/chain/internal/service/gateway/shared" "github.com/tech/sendico/gateway/chain/storage/model" + describablev1 "github.com/tech/sendico/pkg/proto/common/describable/v1" chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -16,6 +19,25 @@ func toProtoManagedWallet(wallet *model.ManagedWallet) *chainv1.ManagedWallet { TokenSymbol: wallet.TokenSymbol, ContractAddress: wallet.ContractAddress, } + name := strings.TrimSpace(wallet.Name) + if name == "" { + name = strings.TrimSpace(wallet.Metadata["name"]) + } + if name == "" { + name = wallet.WalletRef + } + description := "" + switch { + case wallet.Description != nil: + description = strings.TrimSpace(*wallet.Description) + default: + description = strings.TrimSpace(wallet.Metadata["description"]) + } + desc := &describablev1.Describable{Name: name} + if description != "" { + desc.Description = &description + } + return &chainv1.ManagedWallet{ WalletRef: wallet.WalletRef, OrganizationRef: wallet.OrganizationRef, @@ -26,6 +48,7 @@ func toProtoManagedWallet(wallet *model.ManagedWallet) *chainv1.ManagedWallet { Metadata: shared.CloneMetadata(wallet.Metadata), CreatedAt: timestamppb.New(wallet.CreatedAt.UTC()), UpdatedAt: timestamppb.New(wallet.UpdatedAt.UTC()), + Describable: desc, } } diff --git a/api/gateway/chain/storage/model/wallet.go b/api/gateway/chain/storage/model/wallet.go index 7080b26..8edb5c1 100644 --- a/api/gateway/chain/storage/model/wallet.go +++ b/api/gateway/chain/storage/model/wallet.go @@ -5,6 +5,7 @@ import ( "time" "github.com/tech/sendico/pkg/db/storable" + pkgmodel "github.com/tech/sendico/pkg/model" "github.com/tech/sendico/pkg/mservice" moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1" ) @@ -19,7 +20,8 @@ const ( // ManagedWallet represents a user-controlled on-chain wallet managed by the service. type ManagedWallet struct { - storable.Base `bson:",inline" json:",inline"` + storable.Base `bson:",inline" json:",inline"` + pkgmodel.Describable `bson:",inline" json:",inline"` IdempotencyKey string `bson:"idempotencyKey" json:"idempotencyKey"` WalletRef string `bson:"walletRef" json:"walletRef"` @@ -77,6 +79,15 @@ func (m *ManagedWallet) Normalize() { m.WalletRef = strings.TrimSpace(m.WalletRef) m.OrganizationRef = strings.TrimSpace(m.OrganizationRef) m.OwnerRef = strings.TrimSpace(m.OwnerRef) + m.Name = strings.TrimSpace(m.Name) + if m.Description != nil { + desc := strings.TrimSpace(*m.Description) + if desc == "" { + m.Description = nil + } else { + m.Description = &desc + } + } m.Network = strings.TrimSpace(strings.ToLower(m.Network)) m.TokenSymbol = strings.TrimSpace(strings.ToUpper(m.TokenSymbol)) m.ContractAddress = strings.TrimSpace(strings.ToLower(m.ContractAddress)) diff --git a/api/pkg/db/internal/mongo/refreshtokensdb/db.go b/api/pkg/db/internal/mongo/refreshtokensdb/db.go index b015662..06d232d 100644 --- a/api/pkg/db/internal/mongo/refreshtokensdb/db.go +++ b/api/pkg/db/internal/mongo/refreshtokensdb/db.go @@ -37,7 +37,9 @@ func Create(logger mlogger.Logger, db *mongo.Database) (*RefreshTokenDB, error) {Field: "clientId", Sort: ri.Asc}, {Field: "deviceId", Sort: ri.Asc}, }, - Unique: true, + Unique: true, + Name: "unique_active_session", + PartialFilter: repository.Filter(IsRevokedField, false), }); err != nil { p.Logger.Error("Failed to create unique account/client/device index", zap.Error(err)) return nil, err diff --git a/api/pkg/db/internal/mongo/refreshtokensdb/refreshtokensdb_test.go b/api/pkg/db/internal/mongo/refreshtokensdb/refreshtokensdb_test.go index edc13c3..b5e57a6 100644 --- a/api/pkg/db/internal/mongo/refreshtokensdb/refreshtokensdb_test.go +++ b/api/pkg/db/internal/mongo/refreshtokensdb/refreshtokensdb_test.go @@ -10,23 +10,29 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/tech/sendico/pkg/db/internal/mongo/refreshtokensdb" "github.com/tech/sendico/pkg/db/repository" "github.com/tech/sendico/pkg/db/repository/builder" "github.com/tech/sendico/pkg/merrors" factory "github.com/tech/sendico/pkg/mlogger/factory" "github.com/tech/sendico/pkg/model" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/mongodb" "github.com/testcontainers/testcontainers-go/wait" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) func setupTestDB(t *testing.T) (*refreshtokensdb.RefreshTokenDB, func()) { + db, _, cleanup := setupTestDBWithMongo(t) + return db, cleanup +} + +func setupTestDBWithMongo(t *testing.T) (*refreshtokensdb.RefreshTokenDB, *mongo.Database, func()) { // mark as helper for better test failure reporting t.Helper() @@ -62,7 +68,7 @@ func setupTestDB(t *testing.T) (*refreshtokensdb.RefreshTokenDB, func()) { _ = mongoContainer.Terminate(termCtx) } - return db, cleanup + return db, database, cleanup } func createTestRefreshToken(accountRef primitive.ObjectID, clientID, deviceID, token string) *model.RefreshToken { @@ -332,6 +338,63 @@ func TestRefreshTokenDB_SessionReplacement(t *testing.T) { _, err = db.GetByCRT(ctx, secondCRT) require.NoError(t, err) }) + + t.Run("Create_After_GlobalRevocation_AllowsNewActive", func(t *testing.T) { + userID := primitive.NewObjectID() + clientID := "web-app" + deviceID := "user-laptop" + + firstToken := createTestRefreshToken(userID, clientID, deviceID, "revoked_token_123") + err := db.Create(ctx, firstToken) + require.NoError(t, err) + require.NotNil(t, firstToken.GetID()) + + // Global revoke (deviceID empty) — all tokens should be revoked + err = db.RevokeAll(ctx, userID, "") + require.NoError(t, err) + + var revoked model.RefreshToken + err = db.Get(ctx, *firstToken.GetID(), &revoked) + require.NoError(t, err) + assert.True(t, revoked.IsRevoked) + + // Creating a new token for the same account/client/device must succeed and produce an active token + reissueToken := createTestRefreshToken(userID, clientID, deviceID, "new_token_after_revocation") + err = db.Create(ctx, reissueToken) + require.NoError(t, err) + + newCRT := &model.ClientRefreshToken{ + SessionIdentifier: model.SessionIdentifier{ + ClientID: clientID, + DeviceID: deviceID, + }, + RefreshToken: "new_token_after_revocation", + } + _, err = db.GetByCRT(ctx, newCRT) + require.NoError(t, err) + + // Old token must remain unusable + oldCRT := &model.ClientRefreshToken{ + SessionIdentifier: model.SessionIdentifier{ + ClientID: clientID, + DeviceID: deviceID, + }, + RefreshToken: "revoked_token_123", + } + _, err = db.GetByCRT(ctx, oldCRT) + assert.Error(t, err) + + // Both records exist: revoked + new active + query := repository.Query(). + Filter(repository.AccountField(), userID). + And( + repository.Query().Comparison(repository.Field("clientId"), builder.Eq, clientID), + repository.Query().Comparison(repository.Field("deviceId"), builder.Eq, deviceID), + ) + ids, err := db.Repository.ListIDs(ctx, query) + require.NoError(t, err) + assert.Len(t, ids, 2) + }) } func TestRefreshTokenDB_ClientManagement(t *testing.T) { @@ -637,3 +700,29 @@ func TestRefreshTokenDB_DatabaseIndexes(t *testing.T) { assert.Len(t, ids, 5) // Should find 5 non-revoked tokens }) } + +func TestRefreshTokenDB_IndexPartialUniqueActiveSession(t *testing.T) { + db, database, cleanup := setupTestDBWithMongo(t) + defer cleanup() + + ctx := context.Background() + + cursor, err := database.Collection(db.Repository.Collection()).Indexes().List(ctx) + require.NoError(t, err) + defer cursor.Close(ctx) + + found := false + for cursor.Next(ctx) { + var idx bson.M + require.NoError(t, cursor.Decode(&idx)) + if idx["name"] == "unique_active_session" { + found = true + assert.Equal(t, true, idx["unique"]) + + partial, ok := idx["partialFilterExpression"].(bson.M) + require.True(t, ok) + assert.Equal(t, bson.M{"isRevoked": false}, partial) + } + } + assert.True(t, found, "unique_active_session index not found") +} diff --git a/api/pkg/db/internal/mongo/repositoryimp/index.go b/api/pkg/db/internal/mongo/repositoryimp/index.go index 7313aa7..3df08bd 100644 --- a/api/pkg/db/internal/mongo/repositoryimp/index.go +++ b/api/pkg/db/internal/mongo/repositoryimp/index.go @@ -41,6 +41,9 @@ func (r *MongoRepository) CreateIndex(def *ri.Definition) error { if def.Name != "" { opts.SetName(def.Name) } + if def.PartialFilter != nil { + opts.SetPartialFilterExpression(def.PartialFilter.BuildQuery()) + } _, err := r.collection.Indexes().CreateOne( context.Background(), diff --git a/api/pkg/db/internal/mongo/repositoryimp/index_integration_test.go b/api/pkg/db/internal/mongo/repositoryimp/index_integration_test.go new file mode 100644 index 0000000..54efca4 --- /dev/null +++ b/api/pkg/db/internal/mongo/repositoryimp/index_integration_test.go @@ -0,0 +1,83 @@ +//go:build integration +// +build integration + +package repositoryimp_test + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tech/sendico/pkg/db/repository" + ri "github.com/tech/sendico/pkg/db/repository/index" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/mongodb" + "github.com/testcontainers/testcontainers-go/wait" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +func TestCreateIndex_WithPartialFilter(t *testing.T) { + startCtx, startCancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer startCancel() + + mongoContainer, err := mongodb.Run(startCtx, + "mongo:latest", + mongodb.WithUsername("root"), + mongodb.WithPassword("password"), + testcontainers.WithWaitStrategy(wait.ForListeningPort("27017/tcp").WithStartupTimeout(2*time.Minute)), + ) + require.NoError(t, err) + + mongoURI, err := mongoContainer.ConnectionString(startCtx) + require.NoError(t, err) + + client, err := mongo.Connect(startCtx, options.Client().ApplyURI(mongoURI)) + require.NoError(t, err) + defer client.Disconnect(context.Background()) + + database := client.Database("test_partial_index_" + t.Name()) + defer database.Drop(context.Background()) + + repo := repository.CreateMongoRepository(database, "partial_index_items") + + def := &ri.Definition{ + Keys: []ri.Key{ + {Field: "field", Sort: ri.Asc}, + }, + Unique: true, + Name: "partial_unique_field_true", + PartialFilter: repository.Filter("flag", treu), + } + + require.NoError(t, repo.CreateIndex(def)) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + cursor, err := database.Collection(repo.Collection()).Indexes().List(ctx) + require.NoError(t, err) + defer cursor.Close(ctx) + + found := false + for cursor.Next(ctx) { + var idx bson.M + require.NoError(t, cursor.Decode(&idx)) + if idx["name"] == def.Name { + found = true + assert.Equal(t, true, idx["unique"]) + assert.Equal(t, bson.M{"field": int32(1)}, idx["key"]) + partial, ok := idx["partialFilterExpression"].(bson.M) + require.True(t, ok) + assert.Equal(t, bson.M{"flag": true}, partial) + } + } + assert.True(t, found, "partial unique index was not created") + + termCtx, termCancel := context.WithTimeout(context.Background(), 30*time.Second) + defer termCancel() + _ = mongoContainer.Terminate(termCtx) +} diff --git a/api/pkg/db/repository/index/index.go b/api/pkg/db/repository/index/index.go index c441549..06e7122 100644 --- a/api/pkg/db/repository/index/index.go +++ b/api/pkg/db/repository/index/index.go @@ -1,5 +1,7 @@ package repository +import "github.com/tech/sendico/pkg/db/repository/builder" + type Sort int8 const ( @@ -14,8 +16,9 @@ type Key struct { } type Definition struct { - Keys []Key // mandatory, at least one element - Unique bool // unique constraint? - TTL *int32 // seconds; nil means “no TTL” - Name string // optional explicit name + Keys []Key // mandatory, at least one element + Unique bool // unique constraint? + TTL *int32 // seconds; nil means “no TTL” + Name string // optional explicit name + PartialFilter builder.Query // optional: partialFilterExpression for conditional indexes } diff --git a/api/proto/common/describable/v1/describable.proto b/api/proto/common/describable/v1/describable.proto new file mode 100644 index 0000000..6c6cb56 --- /dev/null +++ b/api/proto/common/describable/v1/describable.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package common.describable.v1; + +option go_package = "github.com/tech/sendico/pkg/proto/common/describable/v1;describablev1"; + +// Describable captures a name/description pair reusable across resources. +message Describable { + string name = 1; + optional string description = 2; +} diff --git a/api/proto/gateway/chain/v1/chain.proto b/api/proto/gateway/chain/v1/chain.proto index 8ecc390..83fbd46 100644 --- a/api/proto/gateway/chain/v1/chain.proto +++ b/api/proto/gateway/chain/v1/chain.proto @@ -7,6 +7,7 @@ option go_package = "github.com/tech/sendico/pkg/proto/gateway/chain/v1;chainv1" import "google/protobuf/timestamp.proto"; import "common/money/v1/money.proto"; import "common/pagination/v1/cursor.proto"; +import "common/describable/v1/describable.proto"; // Supported blockchain networks for the managed wallets. enum ChainNetwork { @@ -57,6 +58,7 @@ message ManagedWallet { map metadata = 7; google.protobuf.Timestamp created_at = 8; google.protobuf.Timestamp updated_at = 9; + common.describable.v1.Describable describable = 10; } message CreateManagedWalletRequest { @@ -65,6 +67,7 @@ message CreateManagedWalletRequest { string owner_ref = 3; Asset asset = 4; map metadata = 5; + common.describable.v1.Describable describable = 6; } message CreateManagedWalletResponse { diff --git a/api/server/interface/api/sresponse/wallet.go b/api/server/interface/api/sresponse/wallet.go index f5d0374..52c248b 100644 --- a/api/server/interface/api/sresponse/wallet.go +++ b/api/server/interface/api/sresponse/wallet.go @@ -2,6 +2,7 @@ package sresponse import ( "net/http" + "strings" "time" "github.com/tech/sendico/pkg/api/http/response" @@ -26,6 +27,8 @@ type wallet struct { DepositAddress string `json:"depositAddress"` Status string `json:"status"` Metadata map[string]string `json:"metadata,omitempty"` + Name string `json:"name"` + Description *string `json:"description,omitempty"` CreatedAt string `json:"createdAt,omitempty"` UpdatedAt string `json:"updatedAt,omitempty"` } @@ -80,6 +83,27 @@ func toWallet(w *chainv1.ManagedWallet) wallet { token = asset.GetTokenSymbol() contract = asset.GetContractAddress() } + name := "" + if d := w.GetDescribable(); d != nil { + name = strings.TrimSpace(d.GetName()) + } + if name == "" { + name = strings.TrimSpace(w.GetMetadata()["name"]) + } + if name == "" { + name = w.GetWalletRef() + } + var description *string + if d := w.GetDescribable(); d != nil && d.Description != nil { + if trimmed := strings.TrimSpace(d.GetDescription()); trimmed != "" { + description = &trimmed + } + } + if description == nil { + if trimmed := strings.TrimSpace(w.GetMetadata()["description"]); trimmed != "" { + description = &trimmed + } + } return wallet{ WalletRef: w.GetWalletRef(), OrganizationRef: w.GetOrganizationRef(), @@ -92,6 +116,8 @@ func toWallet(w *chainv1.ManagedWallet) wallet { DepositAddress: w.GetDepositAddress(), Status: w.GetStatus().String(), Metadata: w.GetMetadata(), + Name: name, + Description: description, CreatedAt: tsToString(w.GetCreatedAt()), UpdatedAt: tsToString(w.GetUpdatedAt()), } diff --git a/api/server/internal/api/routers/public/login.go b/api/server/internal/api/routers/public/login.go index a4eaab6..ab7c5de 100644 --- a/api/server/internal/api/routers/public/login.go +++ b/api/server/internal/api/routers/public/login.go @@ -53,9 +53,7 @@ func (pr *PublicRouter) logUserIn(ctx context.Context, _ *http.Request, req *sre pr.logger.Warn("Failed to create login confirmation code", zap.Error(err)) return response.Internal(pr.logger, pr.service, err) } - pr.logger.Info("Login confirmation code issued", - zap.String("destination", pr.maskEmail(account.Login)), - zap.String("account", account.Login)) + pr.logger.Info("Login confirmation code issued", zap.String("destination", pr.maskEmail(account.Login))) return sresponse.LoginPending(pr.logger, account, &pendingToken, pr.maskEmail(account.Login), int(time.Until(rec.ExpiresAt).Seconds())) } diff --git a/frontend/pshared/lib/data/dto/wallet/wallet.dart b/frontend/pshared/lib/data/dto/wallet/wallet.dart index fe740ea..3f04cc4 100644 --- a/frontend/pshared/lib/data/dto/wallet/wallet.dart +++ b/frontend/pshared/lib/data/dto/wallet/wallet.dart @@ -13,6 +13,8 @@ class WalletDTO { final WalletAssetDTO asset; final String depositAddress; final String status; + final String name; + final String? description; final Map? metadata; final String? createdAt; final String? updatedAt; @@ -24,6 +26,8 @@ class WalletDTO { required this.asset, required this.depositAddress, required this.status, + required this.name, + this.description, this.metadata, this.createdAt, this.updatedAt, diff --git a/frontend/pshared/lib/data/mapper/wallet/wallet.dart b/frontend/pshared/lib/data/mapper/wallet/wallet.dart index da4352f..3ee2b4e 100644 --- a/frontend/pshared/lib/data/mapper/wallet/wallet.dart +++ b/frontend/pshared/lib/data/mapper/wallet/wallet.dart @@ -24,8 +24,10 @@ extension WalletDTOMapper on WalletDTO { balance: balance?.toDomain(), availableMoney: balance?.available?.toDomain(), describable: newDescribable( - name: metadata?['name'] ?? 'Crypto Wallet', - description: metadata?['description'], + name: name.isNotEmpty ? name : (metadata?['name'] ?? 'Crypto Wallet'), + description: (description != null && description!.isNotEmpty) + ? description + : metadata?['description'], ), ); } diff --git a/frontend/pshared/lib/service/payment/quotation.dart b/frontend/pshared/lib/service/payment/quotation.dart index 93ba559..99a5155 100644 --- a/frontend/pshared/lib/service/payment/quotation.dart +++ b/frontend/pshared/lib/service/payment/quotation.dart @@ -26,11 +26,11 @@ class QuotationService { return PaymentQuoteResponse.fromJson(response).quote.toDomain(); } - static Future getMultipleQuotation(String organizationRef, QuotePaymentsRequest request) async { + static Future getMultiQuotation(String organizationRef, QuotePaymentsRequest request) async { _logger.fine('Quoting payments for organization $organizationRef'); final response = await AuthorizationService.getPOSTResponse( _objectType, - '/quote-multiple/$organizationRef', + '/multiquote/$organizationRef', request.toJson(), ); return PaymentQuotesResponse.fromJson(response).quote.toDomain(); diff --git a/frontend/pweb/lib/data/mappers/wallet_ui.dart b/frontend/pweb/lib/data/mappers/wallet_ui.dart index f938338..8dec139 100644 --- a/frontend/pweb/lib/data/mappers/wallet_ui.dart +++ b/frontend/pweb/lib/data/mappers/wallet_ui.dart @@ -1,8 +1,6 @@ import 'package:pshared/models/wallet/wallet.dart' as domain; import 'package:pshared/models/currency.dart'; -import 'package:pshared/models/describable.dart'; - import 'package:pweb/models/wallet.dart'; @@ -26,10 +24,7 @@ extension WalletUiMapper on domain.WalletModel { network: asset.chain, tokenSymbol: asset.tokenSymbol, contractAddress: asset.contractAddress, - describable: newDescribable( - name: metadata?['name'] ?? 'Crypto Wallet', - description: metadata?['description'], - ), + describable: describable, ); } }