diff --git a/domain/config/config_service.go b/domain/config/config_service.go new file mode 100644 index 0000000..b456aa1 --- /dev/null +++ b/domain/config/config_service.go @@ -0,0 +1,75 @@ +package config + +import ( + "slices" + "sync" + + backendconfig "github.com/bitcoin-sv/spv-wallet-web-backend/config" + "github.com/bitcoin-sv/spv-wallet-web-backend/domain/users" + "github.com/bitcoin-sv/spv-wallet/models" + "github.com/rs/zerolog" + "github.com/spf13/viper" +) + +type ConfigService struct { + adminWalletClient users.AdminWalletClient + log *zerolog.Logger + + sharedConfig *models.SharedConfig + publicConfig *PublicConfig + mutex sync.Mutex +} + +func NewConfigService(adminWalletClient users.AdminWalletClient, log *zerolog.Logger) *ConfigService { + return &ConfigService{ + adminWalletClient: adminWalletClient, + log: log, + sharedConfig: nil, + publicConfig: nil, + } +} + +// GetSharedConfig returns shared config. +// If shared config is not cached, it will be fetched from the spv-wallet. +// SharedConfig should not be exposed to the public - use PublicConfig instead. +func (s *ConfigService) GetSharedConfig() *models.SharedConfig { + if s.sharedConfig != nil { + return s.sharedConfig + } + + s.mutex.Lock() + defer s.mutex.Unlock() + model, err := s.adminWalletClient.GetSharedConfig() + if err != nil { + s.log.Error().Err(err).Msg("Failed to get shared config") + return nil + } + s.sharedConfig = model + return s.sharedConfig +} + +// GetPublicConfig returns public config. +func (s *ConfigService) GetPublicConfig() *PublicConfig { + if s.publicConfig != nil { + return s.publicConfig + } + shared := s.GetSharedConfig() + if shared == nil { + return nil + } + + s.publicConfig = s.makePublicConfig(shared) + return s.publicConfig +} + +func (s *ConfigService) makePublicConfig(shared *models.SharedConfig) *PublicConfig { + configuredPaymailDomain := viper.GetString(backendconfig.EnvPaymailDomain) + if !slices.Contains(shared.PaymilDomains, configuredPaymailDomain) { + s.log.Warn().Str("configuredPaymailDomain", configuredPaymailDomain).Msg("Configured paymail domain is not in the list of paymail domains from SPV Wallet") + } + + return &PublicConfig{ + PaymilDomain: configuredPaymailDomain, + ExperimentalFeatures: shared.ExperimentalFeatures, + } +} diff --git a/domain/config/public_config.go b/domain/config/public_config.go new file mode 100644 index 0000000..458ac17 --- /dev/null +++ b/domain/config/public_config.go @@ -0,0 +1,8 @@ +package config + +import "github.com/bitcoin-sv/spv-wallet/models" + +type PublicConfig struct { + PaymilDomain string `json:"paymail_domain"` + ExperimentalFeatures models.ExperimentalConfig `json:"experimental_features"` +} diff --git a/domain/services.go b/domain/services.go index 3f88772..d362ec1 100644 --- a/domain/services.go +++ b/domain/services.go @@ -2,6 +2,7 @@ package domain import ( db_users "github.com/bitcoin-sv/spv-wallet-web-backend/data/users" + "github.com/bitcoin-sv/spv-wallet-web-backend/domain/config" "github.com/bitcoin-sv/spv-wallet-web-backend/domain/transactions" "github.com/bitcoin-sv/spv-wallet-web-backend/domain/users" "github.com/bitcoin-sv/spv-wallet-web-backend/transports/spvwallet" @@ -14,6 +15,7 @@ type Services struct { UsersService *users.UserService TransactionsService *transactions.TransactionService WalletClientFactory users.WalletClientFactory + ConfigService *config.ConfigService } // NewServices creates services instance. @@ -31,5 +33,6 @@ func NewServices(usersRepo *db_users.UsersRepository, log *zerolog.Logger) (*Ser UsersService: uService, TransactionsService: transactions.NewTransactionService(adminWalletClient, walletClientFactory, log), WalletClientFactory: walletClientFactory, + ConfigService: config.NewConfigService(adminWalletClient, log), }, nil } diff --git a/domain/users/interfaces.go b/domain/users/interfaces.go index 227cd3c..6a1a20a 100644 --- a/domain/users/interfaces.go +++ b/domain/users/interfaces.go @@ -75,6 +75,7 @@ type ( AdminWalletClient interface { RegisterXpub(xpriv *bip32.ExtendedKey) (string, error) RegisterPaymail(alias, xpub string) (string, error) + GetSharedConfig() (*models.SharedConfig, error) } // WalletClientFactory defines methods to create user and admin clients. diff --git a/transports/http/endpoints/api/config/endpoints.go b/transports/http/endpoints/api/config/endpoints.go new file mode 100644 index 0000000..4ec0741 --- /dev/null +++ b/transports/http/endpoints/api/config/endpoints.go @@ -0,0 +1,40 @@ +package config + +import ( + "github.com/bitcoin-sv/spv-wallet-web-backend/domain" + router "github.com/bitcoin-sv/spv-wallet-web-backend/transports/http/endpoints/routes" + "github.com/gin-gonic/gin" +) + +type handler struct { + services *domain.Services +} + +func NewHandler(s *domain.Services) router.RootEndpoints { + handler := &handler{ + services: s, + } + prefix := "/api/v1" + + rootEndpoints := router.RootEndpointsFunc(func(router *gin.RouterGroup) { + router.GET(prefix+"/config", handler.getPublicConfig) + }) + + return rootEndpoints +} + +// getConfig returns config fields exposed to clients. +// +// @Summary Get config returns config fields exposed to clients +// @Tags sharedconfig +// @Produce json +// @Success 200 {object} PublicConfig +// @Router /api/v1/config [get] +func (h *handler) getPublicConfig(c *gin.Context) { + pubConf := h.services.ConfigService.GetPublicConfig() + if pubConf == nil { + c.JSON(500, "Failed to get public config") + return + } + c.JSON(200, pubConf) +} diff --git a/transports/http/endpoints/wallet_endpoints.go b/transports/http/endpoints/wallet_endpoints.go index 1d246c6..8670e8d 100644 --- a/transports/http/endpoints/wallet_endpoints.go +++ b/transports/http/endpoints/wallet_endpoints.go @@ -13,6 +13,7 @@ import ( "github.com/bitcoin-sv/spv-wallet-web-backend/transports/http/auth" "github.com/bitcoin-sv/spv-wallet-web-backend/transports/http/endpoints/api/access" + "github.com/bitcoin-sv/spv-wallet-web-backend/transports/http/endpoints/api/config" "github.com/bitcoin-sv/spv-wallet-web-backend/transports/http/endpoints/api/transactions" "github.com/bitcoin-sv/spv-wallet-web-backend/transports/http/endpoints/api/users" router "github.com/bitcoin-sv/spv-wallet-web-backend/transports/http/endpoints/routes" @@ -31,6 +32,7 @@ func SetupWalletRoutes(s *domain.Services, db *sql.DB, log *zerolog.Logger, ws w routes := []interface{}{ swagger.NewHandler(), status.NewHandler(), + config.NewHandler(s), usersRootEndpoints, usersApiEndpoints, accessRootEndpoints, diff --git a/transports/spvwallet/admin_client.go b/transports/spvwallet/admin_client.go index 46b3343..9d53d3c 100644 --- a/transports/spvwallet/admin_client.go +++ b/transports/spvwallet/admin_client.go @@ -64,3 +64,12 @@ func (c *AdminWalletClient) RegisterPaymail(alias, xpub string) (string, error) } return address, nil } + +func (c *AdminWalletClient) GetSharedConfig() (*models.SharedConfig, error) { + sharedConfig, err := c.client.AdminGetSharedConfig(context.Background()) + if err != nil { + c.log.Error().Msgf("Error while getting shared config: %v", err.Error()) + return nil, err + } + return sharedConfig, nil +}