Skip to content

Commit

Permalink
Merge pull request #472 from bitcoin-sv/BUX-396/EndpointForCreatingCo…
Browse files Browse the repository at this point in the history
…ntact

Bux 396/endpoint for creating contact
  • Loading branch information
arkadiuszos4chain authored Mar 4, 2024
2 parents be747be + 5895c5a commit 8715537
Show file tree
Hide file tree
Showing 28 changed files with 696 additions and 30 deletions.
1 change: 1 addition & 0 deletions actions/contacts/contacts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package contacts
47 changes: 47 additions & 0 deletions actions/contacts/contacts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package contacts

import (
"testing"

"github.com/bitcoin-sv/spv-wallet/config"
"github.com/bitcoin-sv/spv-wallet/tests"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)

// TestSuite is for testing the entire package using real/mocked services
type TestSuite struct {
tests.TestSuite
}

// SetupSuite runs at the start of the suite
func (ts *TestSuite) SetupSuite() {
ts.BaseSetupSuite()
}

// TearDownSuite runs after the suite finishes
func (ts *TestSuite) TearDownSuite() {
ts.BaseTearDownSuite()
}

// SetupTest runs before each test
func (ts *TestSuite) SetupTest() {
ts.BaseSetupTest()

// Load the router & register routes
ts.Router = gin.Default()
require.NotNil(ts.T(), ts.Router)
routes := NewHandler(ts.AppConfig, ts.Services)
routes.RegisterAPIEndpoints(ts.Router.Group("/" + config.APIVersion))
}

// TearDownTest runs after each test
func (ts *TestSuite) TearDownTest() {
ts.BaseTearDownTest()
}

// TestTestSuite kick-starts all suite tests
func TestTestSuite(t *testing.T) {
suite.Run(t, new(TestSuite))
}
41 changes: 41 additions & 0 deletions actions/contacts/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package contacts

import (
"net/http"

"github.com/bitcoin-sv/spv-wallet/engine"
"github.com/bitcoin-sv/spv-wallet/mappings"
"github.com/gin-gonic/gin"
)

// create will make a new model using the services defined in the action object
// Create contact godoc
// @Summary Create contact
// @Description Create contact
// @Tags Contact
// @Produce json
// @Param fullName query string false "fullName"
// @Param paymail query string false "paymail"
// @Param pubKey query string false "pubKey"
// @Param metadata query string false "metadata"
// @Success 201
// @Router /v1/contact [post]
// @Security bux-auth-xpub
func (a *Action) create(c *gin.Context) {

fullName := c.GetString("full_name")
paymail := c.GetString("paymail")
pubKey := c.GetString("pubKey")

var requestBody CreateContact

contact, err := a.Services.SpvWalletEngine.NewContact(c.Request.Context(), fullName, paymail, pubKey, engine.WithMetadatas(requestBody.Metadata))
if err != nil {
c.JSON(http.StatusUnprocessableEntity, err.Error())
return
}

contract := mappings.MapToContactContract(contact)

c.JSON(http.StatusCreated, contract)
}
8 changes: 8 additions & 0 deletions actions/contacts/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package contacts

import "github.com/bitcoin-sv/spv-wallet/engine"

// CreateContact is the model for creating a contact
type CreateContact struct {
Metadata engine.Metadata `json:"metadata"`
}
25 changes: 25 additions & 0 deletions actions/contacts/routes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package contacts

import (
"github.com/bitcoin-sv/spv-wallet/actions"
"github.com/bitcoin-sv/spv-wallet/config"
"github.com/bitcoin-sv/spv-wallet/server/routes"
"github.com/gin-gonic/gin"
)

// Action is an extension of actions.Action for this package
type Action struct {
actions.Action
}

// NewHandler creates the specific package routes
func NewHandler(appConfig *config.AppConfig, services *config.AppServices) routes.APIEndpointsFunc {
action := &Action{actions.Action{AppConfig: appConfig, Services: services}}

apiEndpoints := routes.APIEndpointsFunc(func(router *gin.RouterGroup) {
contactGroup := router.Group("/contact")
contactGroup.POST("", action.create)
})

return apiEndpoints
}
34 changes: 34 additions & 0 deletions actions/contacts/routes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package contacts

import (
"testing"

"github.com/bitcoin-sv/spv-wallet/config"
"github.com/stretchr/testify/assert"
)

// TestBaseRegisterRoutes will test routes
func (ts *TestSuite) TestRegisterRoutes() {
ts.T().Run("test routes", func(t *testing.T) {
testCases := []struct {
method string
url string
}{
{"POST", "/" + config.APIVersion + "/contact"},
}

ts.Router.Routes()

for _, testCase := range testCases {
found := false
for _, routeInfo := range ts.Router.Routes() {
if testCase.url == routeInfo.Path && testCase.method == routeInfo.Method {
assert.NotNil(t, routeInfo.HandlerFunc)
found = true
break
}
}
assert.True(t, found)
}
})
}
7 changes: 7 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ type AppConfig struct {
TaskManager *TaskManagerConfig `json:"task_manager" mapstructure:"task_manager"`
// Metrics is a configuration for metrics in SPV Wallet.
Metrics *MetricsConfig `json:"metrics" mapstructure:"metrics"`
// ExperimentalFeatures is a configuration that allows to enable features that are considered experimental/non-production.
ExperimentalFeatures *ExperimentalConfig `json:"experimental_features" mapstructure:"experimental_features"`
}

// AuthenticationConfig is the configuration for Authentication
Expand Down Expand Up @@ -247,6 +249,11 @@ type MetricsConfig struct {
Enabled bool `json:"enabled" mapstructure:"enabled"`
}

// ExperimentalConfig represents a feature flag config.
type ExperimentalConfig struct {
PikeEnabled bool `json:"pike_enabled" mapstructure:"pike_enabled"`
}

// GetUserAgent will return the outgoing user agent
func (a *AppConfig) GetUserAgent() string {
return "SPV Wallet " + Version
Expand Down
39 changes: 23 additions & 16 deletions config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,23 @@ const DefaultAdminXpub = "xpub661MyMwAqRbcFgfmdkPgE2m5UjHXu9dj124DbaGLSjaqVESTWf

func getDefaultAppConfig() *AppConfig {
return &AppConfig{
Authentication: getAuthConfigDefaults(),
Cache: getCacheDefaults(),
Db: getDbDefaults(),
Debug: true,
DebugProfiling: true,
DisableITC: true,
ImportBlockHeaders: "",
Logging: getLoggingDefaults(),
NewRelic: getNewRelicDefaults(),
Nodes: getNodesDefaults(),
Notifications: getNotificationDefaults(),
Paymail: getPaymailDefaults(),
RequestLogging: true,
Server: getServerDefaults(),
TaskManager: getTaskManagerDefault(),
Metrics: getMetricsDefaults(),
Authentication: getAuthConfigDefaults(),
Cache: getCacheDefaults(),
Db: getDbDefaults(),
Debug: true,
DebugProfiling: true,
DisableITC: true,
ImportBlockHeaders: "",
Logging: getLoggingDefaults(),
NewRelic: getNewRelicDefaults(),
Nodes: getNodesDefaults(),
Notifications: getNotificationDefaults(),
Paymail: getPaymailDefaults(),
RequestLogging: true,
Server: getServerDefaults(),
TaskManager: getTaskManagerDefault(),
Metrics: getMetricsDefaults(),
ExperimentalFeatures: getExperimentalFeaturesConfig(),
}
}

Expand Down Expand Up @@ -177,3 +178,9 @@ func getMetricsDefaults() *MetricsConfig {
Enabled: false,
}
}

func getExperimentalFeaturesConfig() *ExperimentalConfig {
return &ExperimentalConfig{
PikeEnabled: false,
}
}
88 changes: 88 additions & 0 deletions engine/action_contact.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package engine

import (
"context"
"errors"
"fmt"
"github.com/bitcoin-sv/go-paymail"
"github.com/mrz1836/go-cachestore"
)

func (c *Client) NewContact(ctx context.Context, fullName, paymail, senderPubKey string, opts ...ModelOps) (*Contact, error) {
// Check for existing NewRelic transaction
ctx = c.GetOrStartTxn(ctx, "new_contact")

contact, err := getContact(ctx, fullName, paymail, senderPubKey, opts...)

if contact != nil {
return nil, errors.New("contact already exists")
}
if err != nil {
return nil, err
}

contact, err = newContact(
fullName,
paymail,
senderPubKey,
append(opts, c.DefaultModelOptions(
New(),
)...)...,
)

if err != nil {
return nil, err
}

capabilities, err := c.GetPaymailCapability(ctx, contact.Paymail)

if err != nil {
return nil, fmt.Errorf("failed to get contact paymail capability: %w", err)
}

pkiURL := capabilities.GetString("pki", "")

receiverPubKey, err := c.GetPubKeyFromPki(pkiURL, contact.Paymail)

contact.PubKey = receiverPubKey

contact.Status = notConfirmed

if err = contact.Save(ctx); err != nil {
return nil, err
}
return contact, nil
}

func (c *Client) GetPubKeyFromPki(pkiUrl, paymailAddress string) (string, error) {
if pkiUrl == "" {
return "", errors.New("pkiUrl should not be empty")
}
alias, domain, _ := paymail.SanitizePaymail(paymailAddress)
pc := c.PaymailClient()

pkiResponse, err := pc.GetPKI(pkiUrl, alias, domain)

if err != nil {
return "", fmt.Errorf("error getting public key from PKI: %w", err)
}
return pkiResponse.PubKey, nil
}

func (c *Client) GetPaymailCapability(ctx context.Context, paymailAddress string) (*paymail.CapabilitiesPayload, error) {
address := newPaymail(paymailAddress)

cs := c.Cachestore()
pc := c.PaymailClient()

capabilities, err := getCapabilities(ctx, cs, pc, address.Domain)

if err != nil {
if errors.Is(err, cachestore.ErrKeyNotFound) {
return nil, nil
}
return nil, err
}

return capabilities, nil
}
6 changes: 4 additions & 2 deletions engine/client_options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ func TestWithModels(t *testing.T) {
ModelXPub.String(), ModelAccessKey.String(),
ModelDraftTransaction.String(), ModelTransaction.String(),
ModelSyncTransaction.String(), ModelDestination.String(),
ModelUtxo.String(),
ModelUtxo.String(), ModelContact.String(),
}, tc.GetModelNames())
})

Expand All @@ -603,7 +603,7 @@ func TestWithModels(t *testing.T) {
ModelXPub.String(), ModelAccessKey.String(),
ModelDraftTransaction.String(), ModelTransaction.String(),
ModelSyncTransaction.String(), ModelDestination.String(),
ModelUtxo.String(), ModelPaymailAddress.String(),
ModelUtxo.String(), ModelContact.String(), ModelPaymailAddress.String(),
}, tc.GetModelNames())
})
}
Expand Down Expand Up @@ -797,6 +797,7 @@ func TestWithAutoMigrate(t *testing.T) {
ModelSyncTransaction.String(),
ModelDestination.String(),
ModelUtxo.String(),
ModelContact.String(),
}, tc.GetModelNames())
})

Expand All @@ -818,6 +819,7 @@ func TestWithAutoMigrate(t *testing.T) {
ModelSyncTransaction.String(),
ModelDestination.String(),
ModelUtxo.String(),
ModelContact.String(),
ModelPaymailAddress.String(),
}, tc.GetModelNames())
})
Expand Down
Loading

0 comments on commit 8715537

Please sign in to comment.