diff --git a/actions/access_keys/access_keys_test.go b/actions/access_keys/access_keys_test.go index c0601295b..4f036852a 100644 --- a/actions/access_keys/access_keys_test.go +++ b/actions/access_keys/access_keys_test.go @@ -5,8 +5,6 @@ import ( "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" ) @@ -29,9 +27,6 @@ func (ts *TestSuite) TearDownSuite() { 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)) } diff --git a/actions/admin/admin_test.go b/actions/admin/admin_test.go index 99141ea8e..0499bb6eb 100644 --- a/actions/admin/admin_test.go +++ b/actions/admin/admin_test.go @@ -5,8 +5,6 @@ import ( "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" ) @@ -30,8 +28,6 @@ func (ts *TestSuite) SetupTest() { ts.BaseSetupTest() // Load the router & register routes - ts.Router = gin.Default() - require.NotNil(ts.T(), ts.Router) adminRoutes := NewHandler(ts.AppConfig, ts.Services) adminRoutes.RegisterAdminEndpoints(ts.Router.Group("/" + config.APIVersion)) } diff --git a/actions/base/base_test.go b/actions/base/base_test.go index 8160008a6..651d78dc8 100644 --- a/actions/base/base_test.go +++ b/actions/base/base_test.go @@ -4,8 +4,6 @@ import ( "testing" "github.com/bitcoin-sv/spv-wallet/tests" - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -29,8 +27,6 @@ func (ts *TestSuite) SetupTest() { ts.BaseSetupTest() // Load the router & register routes - ts.Router = gin.Default() - require.NotNil(ts.T(), ts.Router) routes := NewHandler() routes.RegisterBaseEndpoints(ts.Router.Group("")) } diff --git a/actions/contacts/contacts.go b/actions/contacts/contacts.go deleted file mode 100644 index c849a8836..000000000 --- a/actions/contacts/contacts.go +++ /dev/null @@ -1 +0,0 @@ -package contacts diff --git a/actions/contacts/contacts_test.go b/actions/contacts/contacts_test.go index d28a00b02..840e54c52 100644 --- a/actions/contacts/contacts_test.go +++ b/actions/contacts/contacts_test.go @@ -5,8 +5,6 @@ import ( "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" ) @@ -30,8 +28,6 @@ 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)) } diff --git a/actions/contacts/create.go b/actions/contacts/create.go index faf4b7c28..92d6b0ef6 100644 --- a/actions/contacts/create.go +++ b/actions/contacts/create.go @@ -5,6 +5,7 @@ import ( "github.com/bitcoin-sv/spv-wallet/engine" "github.com/bitcoin-sv/spv-wallet/mappings" + "github.com/bitcoin-sv/spv-wallet/server/auth" "github.com/gin-gonic/gin" ) @@ -23,19 +24,26 @@ import ( // @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 contact engine.Contact + + err := c.ShouldBindJSON(&contact) + + if err != nil { + c.JSON(http.StatusUnprocessableEntity, err.Error()) + return + } + + pubKey := c.GetString(auth.ParamXPubKey) var requestBody CreateContact - contact, err := a.Services.SpvWalletEngine.NewContact(c.Request.Context(), fullName, paymail, pubKey, engine.WithMetadatas(requestBody.Metadata)) + newContact, err := a.Services.SpvWalletEngine.NewContact(c.Request.Context(), contact.FullName, contact.Paymail, pubKey, engine.WithMetadatas(requestBody.Metadata)) if err != nil { c.JSON(http.StatusUnprocessableEntity, err.Error()) return } - contract := mappings.MapToContactContract(contact) + contract := mappings.MapToContactContract(newContact) c.JSON(http.StatusCreated, contract) } diff --git a/actions/contacts/models.go b/actions/contacts/models.go index 70abc8f94..8cac123cd 100644 --- a/actions/contacts/models.go +++ b/actions/contacts/models.go @@ -1,8 +1,21 @@ package contacts -import "github.com/bitcoin-sv/spv-wallet/engine" +import ( + "github.com/bitcoin-sv/spv-wallet/engine" + "github.com/bitcoin-sv/spv-wallet/models" +) // CreateContact is the model for creating a contact type CreateContact struct { Metadata engine.Metadata `json:"metadata"` } + +// UpdateContact is the model for updating a contact +type UpdateContact struct { + XPubID string `json:"xpub_id"` + FullName string `json:"full_name"` + Paymail string `json:"paymail"` + PubKey string `json:"pubKey"` + Status models.ContactStatus `json:"status"` + Metadata engine.Metadata `json:"metadata"` +} diff --git a/actions/contacts/routes.go b/actions/contacts/routes.go index d5b80154a..93124c368 100644 --- a/actions/contacts/routes.go +++ b/actions/contacts/routes.go @@ -19,6 +19,11 @@ func NewHandler(appConfig *config.AppConfig, services *config.AppServices) route apiEndpoints := routes.APIEndpointsFunc(func(router *gin.RouterGroup) { contactGroup := router.Group("/contact") contactGroup.POST("", action.create) + contactGroup.PATCH("", action.update) + + contactsGroup := router.Group("/contacts") + contactsGroup.GET("", action.search) + }) return apiEndpoints diff --git a/actions/contacts/routes_test.go b/actions/contacts/routes_test.go index e73c1b269..33bb58530 100644 --- a/actions/contacts/routes_test.go +++ b/actions/contacts/routes_test.go @@ -7,14 +7,15 @@ import ( "github.com/stretchr/testify/assert" ) -// TestBaseRegisterRoutes will test routes -func (ts *TestSuite) TestRegisterRoutes() { +// TestContactsRegisterRoutes will test routes +func (ts *TestSuite) TestContactsRegisterRoutes() { ts.T().Run("test routes", func(t *testing.T) { testCases := []struct { method string url string }{ {"POST", "/" + config.APIVersion + "/contact"}, + {"GET", "/" + config.APIVersion + "/contacts"}, } ts.Router.Routes() diff --git a/actions/contacts/search.go b/actions/contacts/search.go new file mode 100644 index 000000000..d9856ffb8 --- /dev/null +++ b/actions/contacts/search.go @@ -0,0 +1,64 @@ +package contacts + +import ( + "net/http" + + "github.com/bitcoin-sv/spv-wallet/actions" + "github.com/bitcoin-sv/spv-wallet/engine" + "github.com/bitcoin-sv/spv-wallet/mappings" + "github.com/bitcoin-sv/spv-wallet/models" + "github.com/bitcoin-sv/spv-wallet/server/auth" + "github.com/gin-gonic/gin" +) + +// Search will fetch a list of contacts +// Get contacts godoc +// @Summary Search contacts +// @Description Search contacts +// @Tags Contact +// @Produce json +// @Param page query int false "page" +// @Param page_size query int false "page_size" +// @Param order_by_field query string false "order_by_field" +// @Param sort_direction query string false "sort_direction" +// @Param conditions query string false "conditions" +// @Success 200 +// @Router /v1/contacts [get] +// @Security x-auth-xpub +func (a *Action) search(c *gin.Context) { + reqXPubID := c.GetString(auth.ParamXPubHashKey) + + params := c.Request.URL.Query() + + queryParams, metadata, _, err := actions.GetQueryParameters(c) + if err != nil { + c.JSON(http.StatusExpectationFailed, err.Error()) + return + } + + dbConditions := make(map[string]interface{}) + + for key, value := range params { + dbConditions[key] = value + } + + dbConditions["xpub_id"] = reqXPubID + + var contacts []*engine.Contact + if contacts, err = a.Services.SpvWalletEngine.GetContacts( + c.Request.Context(), + metadata, + &dbConditions, + queryParams, + ); err != nil { + c.JSON(http.StatusExpectationFailed, err.Error()) + return + } + + contactContracts := make([]*models.Contact, 0) + for _, contact := range contacts { + contactContracts = append(contactContracts, mappings.MapToContactContract(contact)) + } + + c.JSON(http.StatusOK, contactContracts) +} diff --git a/actions/contacts/update.go b/actions/contacts/update.go new file mode 100644 index 000000000..7c8429550 --- /dev/null +++ b/actions/contacts/update.go @@ -0,0 +1,47 @@ +package contacts + +import ( + "net/http" + + "github.com/bitcoin-sv/spv-wallet/engine" + "github.com/bitcoin-sv/spv-wallet/mappings" + "github.com/bitcoin-sv/spv-wallet/server/auth" + "github.com/gin-gonic/gin" +) + +// update will update an existing model +// Update Contact godoc +// @Summary Update contact +// @Description Update contact +// @Tags Contacts +// @Produce json +// @Param metadata body string true "Contacts Metadata" +// @Success 200 +// @Router /v1/contact [patch] +// @Security x-auth-xpub +func (a *Action) update(c *gin.Context) { + reqXPubID := c.GetString(auth.ParamXPubHashKey) + + var requestBody UpdateContact + + if err := c.ShouldBindJSON(&requestBody); err != nil { + c.JSON(http.StatusBadRequest, err.Error()) + return + } + + if requestBody.XPubID == "" { + c.JSON(http.StatusBadRequest, "Id is missing") + } + + contact, err := a.Services.SpvWalletEngine.UpdateContact(c.Request.Context(), requestBody.FullName, requestBody.PubKey, reqXPubID, requestBody.Paymail, engine.ContactStatus(requestBody.Status), engine.WithMetadatas(requestBody.Metadata)) + + if err != nil { + c.JSON(http.StatusExpectationFailed, err.Error()) + return + } + + contract := mappings.MapToContactContract(contact) + + c.JSON(http.StatusOK, contract) + +} diff --git a/actions/destinations/destination_test.go b/actions/destinations/destination_test.go index f373ae9f6..f7e891c7c 100644 --- a/actions/destinations/destination_test.go +++ b/actions/destinations/destination_test.go @@ -5,8 +5,6 @@ import ( "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" ) @@ -30,8 +28,6 @@ func (ts *TestSuite) SetupTest() { ts.BaseSetupTest() // Load the router & register routes - ts.Router = gin.Default() - require.NotNil(ts.T(), ts.Router) basicRoutes, apiRoutes := NewHandler(ts.AppConfig, ts.Services) basicRoutes.RegisterBasicEndpoints(ts.Router.Group("/" + config.APIVersion)) apiRoutes.RegisterAPIEndpoints(ts.Router.Group("/" + config.APIVersion)) diff --git a/actions/transactions/transaction_test.go b/actions/transactions/transaction_test.go index 968934f30..fb9f36db3 100644 --- a/actions/transactions/transaction_test.go +++ b/actions/transactions/transaction_test.go @@ -5,8 +5,6 @@ import ( "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" ) @@ -30,8 +28,6 @@ func (ts *TestSuite) SetupTest() { ts.BaseSetupTest() // Load the router & register routes - ts.Router = gin.Default() - require.NotNil(ts.T(), ts.Router) basicRoutes, apiRoutes, callbackRoutes := NewHandler(ts.AppConfig, ts.Services) basicRoutes.RegisterBasicEndpoints(ts.Router.Group("/" + config.APIVersion)) apiRoutes.RegisterAPIEndpoints(ts.Router.Group("/" + config.APIVersion)) diff --git a/actions/utxos/utxo_test.go b/actions/utxos/utxo_test.go index d3f65e313..c04029a44 100644 --- a/actions/utxos/utxo_test.go +++ b/actions/utxos/utxo_test.go @@ -5,8 +5,6 @@ import ( "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" ) @@ -30,8 +28,6 @@ func (ts *TestSuite) SetupTest() { ts.BaseSetupTest() // Load the router & register routes - ts.Router = gin.Default() - require.NotNil(ts.T(), ts.Router) apiRoutes := NewHandler(ts.AppConfig, ts.Services) apiRoutes.RegisterAPIEndpoints(ts.Router.Group("/" + config.APIVersion)) } diff --git a/actions/xpubs/xpubs_test.go b/actions/xpubs/xpubs_test.go index 3d6ce6628..3c3467231 100644 --- a/actions/xpubs/xpubs_test.go +++ b/actions/xpubs/xpubs_test.go @@ -5,8 +5,6 @@ import ( "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" ) @@ -30,8 +28,6 @@ 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)) } diff --git a/config/config_test.go b/config/config_test.go index f0dc7152b..b82d9b7a1 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -4,17 +4,18 @@ import ( "context" "testing" - "github.com/bitcoin-sv/spv-wallet/logging" "github.com/mrz1836/go-cachestore" "github.com/mrz1836/go-datastore" + "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // newTestConfig will make a new test config func newTestConfig(t *testing.T) (ac *AppConfig) { - defaultLogger := logging.GetDefaultLogger() - ac, err := Load(defaultLogger) + nop := zerolog.Nop() + ac, err := Load(&nop) + require.NoError(t, err) require.NotNil(t, ac) return diff --git a/config/load.go b/config/load.go index ec860c315..9df859d3f 100644 --- a/config/load.go +++ b/config/load.go @@ -45,13 +45,25 @@ func Load(logger *zerolog.Logger) (appConfig *AppConfig, err error) { if err != nil { logger.Error().Msg("Unable to decode App Config to json") } else { - fmt.Printf("loaded config: %s", cfg) + logger.Debug().Msgf("loaded config: %s", cfg) } } return appConfig, nil } +// LoadForTest returns test AppConfig +func LoadForTest() (appConfig *AppConfig) { + + appConfig = getDefaultAppConfig() + appConfig.Debug = false + appConfig.DebugProfiling = false + appConfig.Logging.Level = zerolog.LevelErrorValue + appConfig.Logging.Format = "console" + + return appConfig +} + func setDefaults() error { viper.SetDefault(ConfigFilePathKey, DefaultConfigFilePath) diff --git a/docker-compose.yml b/docker-compose.yml index 6092948ff..b5447d2d7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -176,7 +176,6 @@ configs: frontend-env-config: content: | { - "paymailDomain": "${RUN_PAYMAIL_DOMAIN}", "apiUrl": "http${RUN_SECURED_PROTOCOL_SUFFIX}://${RUN_API_DOMAIN}", "wsUrl": "ws${RUN_SECURED_PROTOCOL_SUFFIX}://${RUN_API_DOMAIN}/api/websocket" } diff --git a/engine/action_contact.go b/engine/action_contact.go index 429fccd01..784d69c7c 100644 --- a/engine/action_contact.go +++ b/engine/action_contact.go @@ -6,13 +6,14 @@ import ( "fmt" "github.com/bitcoin-sv/go-paymail" "github.com/mrz1836/go-cachestore" + "github.com/mrz1836/go-datastore" ) 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...) + contact, err := getContact(ctx, fullName, paymail, senderPubKey, c.DefaultModelOptions()...) if contact != nil { return nil, errors.New("contact already exists") @@ -27,6 +28,7 @@ func (c *Client) NewContact(ctx context.Context, fullName, paymail, senderPubKey senderPubKey, append(opts, c.DefaultModelOptions( New(), + WithXPub(senderPubKey), )...)..., ) @@ -54,6 +56,40 @@ func (c *Client) NewContact(ctx context.Context, fullName, paymail, senderPubKey return contact, nil } +func (c *Client) UpdateContact(ctx context.Context, fullName, pubKey, xPubID, paymailAddr string, status ContactStatus, opts ...ModelOps) (*Contact, error) { + contact, err := getContactByXPubIdAndRequesterPubKey(ctx, xPubID, paymailAddr, opts...) + + if err != nil { + return nil, fmt.Errorf("failed to get contact: %w", err) + } + + if contact == nil { + return nil, fmt.Errorf("contact not found") + } + + if fullName != "" { + contact.FullName = fullName + } + + if pubKey != "" { + contact.PubKey = pubKey + } + + if status != "" { + contact.Status = status + } + + if paymailAddr != "" { + contact.Paymail = paymailAddr + } + + 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") @@ -86,3 +122,16 @@ func (c *Client) GetPaymailCapability(ctx context.Context, paymailAddress string return capabilities, nil } + +func (c *Client) GetContacts(ctx context.Context, metadata *Metadata, conditions *map[string]interface{}, queryParams *datastore.QueryParams, opts ...ModelOps) ([]*Contact, error) { + + ctx = c.GetOrStartTxn(ctx, "get_contacts") + + contacts, err := getContacts(ctx, metadata, conditions, queryParams, c.DefaultModelOptions(opts...)...) + + if err != nil { + return nil, err + } + + return contacts, nil +} diff --git a/engine/definitions.go b/engine/definitions.go index d4b9c679b..ac530f965 100644 --- a/engine/definitions.go +++ b/engine/definitions.go @@ -94,7 +94,11 @@ const ( bumpField = "bump" fullNameField = "full_name" paymailField = "paymail" - senderXPubField = "pub_key" + + // TODO: check + xPubKeyField = "pub_key" + senderXPubField = "pub_key" + contactStatus = "status" // Universal statuses statusCanceled = "canceled" diff --git a/engine/errors.go b/engine/errors.go index e3c304db6..064e71b2e 100644 --- a/engine/errors.go +++ b/engine/errors.go @@ -207,3 +207,6 @@ var ErrEmptyContactFullName = errors.New("full_name is empty") // ErrEmptyContactPubKey when pubKey is empty var ErrEmptyContactPubKey = errors.New("pubKey is empty") + +// ErrEmptyContactPaymail when paymail is empty +var ErrEmptyContactPaymail = errors.New("paymail is empty") diff --git a/engine/interface.go b/engine/interface.go index 757c67bdc..780d398c6 100644 --- a/engine/interface.go +++ b/engine/interface.go @@ -59,6 +59,10 @@ type ClientService interface { type ContactService interface { NewContact(ctx context.Context, fullName, paymail, pubKey string, opts ...ModelOps) (*Contact, error) + + UpdateContact(ctx context.Context, fullName, pubKey, xPubID, paymail string, status ContactStatus, opts ...ModelOps) (*Contact, error) + + GetContacts(ctx context.Context, metadata *Metadata, conditions *map[string]interface{}, queryParams *datastore.QueryParams, opts ...ModelOps) ([]*Contact, error) } // DestinationService is the destination actions diff --git a/engine/model_contact.go b/engine/model_contact.go index 65a276849..1c6897422 100644 --- a/engine/model_contact.go +++ b/engine/model_contact.go @@ -3,6 +3,8 @@ package engine import ( "context" "errors" + "fmt" + "github.com/bitcoin-sv/go-paymail" "github.com/bitcoin-sv/spv-wallet/engine/utils" "github.com/mrz1836/go-datastore" @@ -17,7 +19,7 @@ type Contact struct { XpubID string `json:"xpub_id" toml:"xpub_id" yaml:"xpub_id" gorm:"<-:create;type:char(64);foreignKey:XpubID;reference:ID;index;comment:This is the related xPub" bson:"xpub_id"` FullName string `json:"full_name" toml:"full_name" yaml:"full_name" gorm:"<-create;comment:This is the contact's full name" bson:"full_name"` Paymail string `json:"paymail" toml:"paymail" yaml:"paymail" gorm:"<-create;comment:This is the paymail address alias@domain.com" bson:"paymail"` - PubKey string `json:"pub_key" toml:"pub_key" yaml:"pub_key" gorm:"<-:create;index;comment:This is the related public key" bson:"pub_key"` + PubKey string `json:"pub_key" toml:"pub_key" yaml:"pub_key" gorm:"<-:create;index;comment:This is the related to receiver public key" bson:"pub_key"` Status ContactStatus `json:"status" toml:"status" yaml:"status" gorm:"<-create;type:varchar(20);default:not confirmed;comment:This is the contact status" bson:"status"` } @@ -26,21 +28,25 @@ func newContact(fullName, paymailAddress, senderPubKey string, opts ...ModelOps) return nil, ErrEmptyContactFullName } - err := paymail.ValidatePaymail(paymailAddress) + if senderPubKey == "" { + return nil, ErrEmptyContactPubKey + } - if err != nil { - return nil, err + if paymailAddress == "" { + return nil, ErrEmptyContactPaymail } - if senderPubKey == "" { - return nil, ErrEmptyContactPubKey + sanitizedPaymail, err := paymail.ValidateAndSanitisePaymail(paymailAddress, false) + + if err != nil { + return nil, err } xPubId := utils.Hash(senderPubKey) - id := utils.Hash(xPubId + paymailAddress) + id := utils.Hash(senderPubKey + sanitizedPaymail.Address) - contact := &Contact{ID: id, XpubID: xPubId, Model: *NewBaseModel(ModelContact, opts...), FullName: fullName, Paymail: paymailAddress} + contact := &Contact{ID: id, XpubID: xPubId, Model: *NewBaseModel(ModelContact, opts...), FullName: fullName, Paymail: sanitizedPaymail.Address} return contact, nil } @@ -54,9 +60,11 @@ func getContact(ctx context.Context, fullName, paymailAddress, senderPubKey stri contact.enrich(ModelContact, opts...) + _, _, sanitizedAddress := paymail.SanitizePaymail(paymailAddress) + conditions := map[string]interface{}{ senderXPubField: senderPubKey, - paymailField: paymailAddress, + paymailField: sanitizedAddress, } if err := Get(ctx, contact, conditions, false, defaultDatabaseReadTimeout, false); err != nil { @@ -69,6 +77,48 @@ func getContact(ctx context.Context, fullName, paymailAddress, senderPubKey stri return contact, nil } +func getContactByXPubIdAndRequesterPubKey(ctx context.Context, xPubId, paymailAddr string, opts ...ModelOps) (*Contact, error) { + + if xPubId == "" { + return nil, fmt.Errorf("xpub_id is empty") + } + + if paymailAddr == "" { + return nil, fmt.Errorf("paymail address is empty") + } + contact := &Contact{ + XpubID: xPubId, + Paymail: paymailAddr, + } + + contact.enrich(ModelContact, opts...) + + conditions := map[string]interface{}{ + xPubIDField: xPubId, + paymailField: paymailAddr, + } + + if err := Get(ctx, contact, conditions, false, defaultDatabaseReadTimeout, false); err != nil { + if errors.Is(err, datastore.ErrNoResults) { + return nil, nil + } + return nil, err + } + + return contact, nil +} + +func getContacts(ctx context.Context, metadata *Metadata, conditions *map[string]interface{}, queryParams *datastore.QueryParams, opts ...ModelOps) ([]*Contact, error) { + contacts := make([]*Contact, 0) + + if err := getModelsByConditions(ctx, ModelContact, &contacts, metadata, conditions, queryParams, opts...); err != nil { + return nil, err + } + + return contacts, nil + +} + func (c *Contact) GetModelName() string { return ModelContact.String() } diff --git a/engine/model_contact_test.go b/engine/model_contact_test.go index 9273cb52c..fd85f1ad5 100644 --- a/engine/model_contact_test.go +++ b/engine/model_contact_test.go @@ -1,16 +1,24 @@ package engine import ( + "testing" + "github.com/bitcoin-sv/spv-wallet/engine/utils" + "github.com/mrz1836/go-datastore" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" ) const ( fullName = "John Doe" paymailTest = "test@paymail.com" senderPubKey = "senderPubKey" + + xpubKey = "xpub661MyMwAqRbcEp7YgDpGXquSF2NW3GBAU3SXTikFT1nkxHGbxjG9RgGxr9X3D4AYsJ6ZqYjMGcdUsPDQZoeibKECs5d56f1w9rfF3QrAAu9" + xPubId = "62910a1ecbc7728afad563ab3f8aa70568ed934d1e0383cb1bbbfb1bc8f2afe5" + paymailAddr = "test.test@mail.test" + + xPubID = "62910a1ecbc7728afad563ab3f8aa70568ed934d1e0383cb1bbbfb1bc8f2afe5" ) func Test_newContact(t *testing.T) { @@ -18,11 +26,10 @@ func Test_newContact(t *testing.T) { contact, err := newContact(fullName, paymailTest, senderPubKey) require.NoError(t, err) require.NotNil(t, contact) - assert.Equal(t, fullName, contact.FullName) assert.Equal(t, paymailTest, contact.Paymail) assert.Equal(t, utils.Hash(senderPubKey), contact.XpubID) - assert.Equal(t, utils.Hash(contact.XpubID+paymailTest), contact.ID) + assert.Equal(t, utils.Hash(senderPubKey+paymailTest), contact.ID) }) t.Run("empty full_name", func(t *testing.T) { @@ -37,14 +44,7 @@ func Test_newContact(t *testing.T) { contact, err := newContact(fullName, "", senderPubKey) require.Nil(t, contact) - require.ErrorContains(t, err, "paymail address failed format validation") - }) - - t.Run("invalid paymail", func(t *testing.T) { - contact, err := newContact(fullName, "testata", senderPubKey) - - require.Nil(t, contact) - require.ErrorContains(t, err, "paymail address failed format validation") + require.EqualError(t, err, ErrEmptyContactPaymail.Error()) }) t.Run("empty pubKey", func(t *testing.T) { @@ -102,3 +102,129 @@ func Test_getContact(t *testing.T) { require.NoError(t, err) }) } + +func Test_getContactByXPubIdAndPubKey(t *testing.T) { + t.Run("valid xPubId and paymailAddr", func(t *testing.T) { + ctx, client, deferMe := CreateTestSQLiteClient(t, false, false, withTaskManagerMockup()) + defer deferMe() + var opts []ModelOps + createdContact, err := newContact( + fullName, + paymailAddr, + xpubKey, + append(opts, client.DefaultModelOptions( + New(), + )...)..., + ) + createdContact.PubKey = "testPubKey" + err = createdContact.Save(ctx) + + contact, err := getContactByXPubIdAndRequesterPubKey(ctx, createdContact.XpubID, createdContact.Paymail, client.DefaultModelOptions()...) + + require.NotNil(t, contact) + require.NoError(t, err) + }) + + t.Run("empty xPubId", func(t *testing.T) { + ctx, client, deferMe := CreateTestSQLiteClient(t, false, false, withTaskManagerMockup()) + defer deferMe() + + var opts []ModelOps + createdContact, err := newContact( + fullName, + paymailAddr, + xpubKey, + append(opts, client.DefaultModelOptions( + New(), + )...)..., + ) + createdContact.PubKey = "testPubKey" + err = createdContact.Save(ctx) + + contact, err := getContactByXPubIdAndRequesterPubKey(ctx, "", createdContact.Paymail, client.DefaultModelOptions()...) + + require.Nil(t, contact) + require.Error(t, err) + }) + + t.Run("empty paymailAddr", func(t *testing.T) { + ctx, client, deferMe := CreateTestSQLiteClient(t, false, false, withTaskManagerMockup()) + defer deferMe() + + var opts []ModelOps + createdContact, err := newContact( + fullName, + paymailAddr, + xpubKey, + append(opts, client.DefaultModelOptions( + New(), + )...)..., + ) + createdContact.PubKey = "testPubKey" + err = createdContact.Save(ctx) + + contact, err := getContactByXPubIdAndRequesterPubKey(ctx, createdContact.XpubID, "", client.DefaultModelOptions()...) + + require.Nil(t, contact) + require.Error(t, err) + }) +} +func Test_getContacts(t *testing.T) { + t.Run("status 'not confirmed'", func(t *testing.T) { + ctx, client, deferMe := CreateTestSQLiteClient(t, false, false, withTaskManagerMockup()) + defer deferMe() + + var metadata *Metadata + + dbConditions := map[string]interface{}{ + xPubIDField: xPubID, + contactStatus: notConfirmed, + } + + var queryParams *datastore.QueryParams + + contacts, err := getContacts(ctx, metadata, &dbConditions, queryParams, client.DefaultModelOptions()...) + + require.NoError(t, err) + assert.NotNil(t, contacts) + }) + + t.Run("status 'confirmed'", func(t *testing.T) { + ctx, client, deferMe := CreateTestSQLiteClient(t, false, false, withTaskManagerMockup()) + defer deferMe() + + var metadata *Metadata + + dbConditions := make(map[string]interface{}) + + var queryParams *datastore.QueryParams + + (dbConditions)[xPubIDField] = xPubID + (dbConditions)[contactStatus] = confirmed + + contacts, err := getContacts(ctx, metadata, &dbConditions, queryParams, client.DefaultModelOptions()...) + + require.NoError(t, err) + assert.Equal(t, 0, len(contacts)) + }) + + t.Run("status 'awaiting acceptance'", func(t *testing.T) { + ctx, client, deferMe := CreateTestSQLiteClient(t, false, false, withTaskManagerMockup()) + defer deferMe() + + var metadata *Metadata + + dbConditions := make(map[string]interface{}) + + var queryParams *datastore.QueryParams + + (dbConditions)[xPubIDField] = xPubID + (dbConditions)[contactStatus] = awaitingAcceptance + + contacts, err := getContacts(ctx, metadata, &dbConditions, queryParams, client.DefaultModelOptions()...) + + require.NoError(t, err) + assert.Equal(t, 0, len(contacts)) + + }) +} diff --git a/engine/models_test.go b/engine/models_test.go index acc10e7ec..52a99e091 100644 --- a/engine/models_test.go +++ b/engine/models_test.go @@ -29,9 +29,7 @@ func TestModelName_String(t *testing.T) { assert.Equal(t, "transaction", ModelTransaction.String()) assert.Equal(t, "utxo", ModelUtxo.String()) assert.Equal(t, "xpub", ModelXPub.String()) - assert.Equal(t, "contact", ModelContact.String()) - assert.Len(t, AllModelNames, 10) }) } diff --git a/server/auth/middleware.go b/server/auth/middleware.go index cea488aca..660bf639d 100644 --- a/server/auth/middleware.go +++ b/server/auth/middleware.go @@ -135,6 +135,7 @@ func SignatureMiddleware(appConfig *config.AppConfig, requireSigning, adminRequi return func(c *gin.Context) { if c.Request.Body == nil { c.AbortWithStatusJSON(http.StatusUnauthorized, "missing body") + return } defer func() { _ = c.Request.Body.Close() diff --git a/server/server_test.go b/server/server_test.go index 6fa8706ad..61e41d0af 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -11,7 +11,6 @@ import ( "github.com/bitcoin-sv/spv-wallet/engine/utils" "github.com/bitcoin-sv/spv-wallet/models" "github.com/bitcoin-sv/spv-wallet/tests" - "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -40,9 +39,6 @@ func (ts *TestSuite) TearDownSuite() { func (ts *TestSuite) SetupTest() { ts.BaseSetupTest() - // Load the router & register routes - ts.Router = gin.Default() - require.NotNil(ts.T(), ts.Router) SetupServerRoutes(ts.AppConfig, ts.Services, ts.Router) } diff --git a/tests/tests.go b/tests/tests.go index 04ab76d3a..118100342 100644 --- a/tests/tests.go +++ b/tests/tests.go @@ -7,7 +7,9 @@ import ( "github.com/bitcoin-sv/spv-wallet/config" "github.com/bitcoin-sv/spv-wallet/logging" + "github.com/bitcoin-sv/spv-wallet/server/auth" "github.com/gin-gonic/gin" + "github.com/rs/zerolog" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -22,11 +24,7 @@ type TestSuite struct { // BaseSetupSuite runs at the start of the suite func (ts *TestSuite) BaseSetupSuite() { - // Load the configuration - defaultLogger := logging.GetDefaultLogger() - var err error - ts.AppConfig, err = config.Load(defaultLogger) - require.NoError(ts.T(), err) + ts.AppConfig = config.LoadForTest() } // BaseTearDownSuite runs after the suite finishes @@ -46,6 +44,16 @@ func (ts *TestSuite) BaseTearDownSuite() { func (ts *TestSuite) BaseSetupTest() { // Load the services var err error + nop := zerolog.Nop() + + gin.SetMode(gin.ReleaseMode) + engine := gin.New() + engine.Use(logging.GinMiddleware(&nop), gin.Recovery()) + engine.Use(auth.CorsMiddleware()) + + ts.Router = engine + require.NotNil(ts.T(), ts.Router) + ts.Services, err = ts.AppConfig.LoadTestServices(context.Background()) require.NoError(ts.T(), err) }