From b008e619e7431014736ae85850bf378c49d60cca Mon Sep 17 00:00:00 2001 From: Stephen Blackstone Date: Mon, 13 Mar 2023 16:34:27 -0400 Subject: [PATCH] Add support for crm/v3/schemas and crm/v3/properties --- crm.go | 16 ++- crm_properties.go | 66 ++++++++++++ crm_properties_test.go | 82 +++++++++++++++ crm_schemas.go | 64 ++++++++++++ crm_schemas_test.go | 221 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 446 insertions(+), 3 deletions(-) create mode 100644 crm_properties.go create mode 100644 crm_properties_test.go create mode 100644 crm_schemas.go create mode 100644 crm_schemas_test.go diff --git a/crm.go b/crm.go index b502ffb..7eac54a 100644 --- a/crm.go +++ b/crm.go @@ -9,9 +9,11 @@ const ( ) type CRM struct { - Contact ContactService - Deal DealService - Imports CrmImportsService + Contact ContactService + Deal DealService + Imports CrmImportsService + Schemas CrmSchemasService + Properties CrmPropertiesService } func newCRM(c *Client) *CRM { @@ -29,5 +31,13 @@ func newCRM(c *Client) *CRM { crmImportsPath: fmt.Sprintf("%s/%s", crmPath, crmImportsBasePath), client: c, }, + Schemas: &CrmSchemasServiceOp{ + crmSchemasPath: fmt.Sprintf("%s/%s", crmPath, crmSchemasPath), + client: c, + }, + Properties: &CrmPropertiesServiceOp{ + crmPropertiesPath: fmt.Sprintf("%s/%s", crmPath, crmPropertiesPath), + client: c, + }, } } diff --git a/crm_properties.go b/crm_properties.go new file mode 100644 index 0000000..4cd691f --- /dev/null +++ b/crm_properties.go @@ -0,0 +1,66 @@ +package hubspot + +import "fmt" + +const ( + crmPropertiesPath = "properties" +) + +// CrmPropertiesService is an interface of CRM crm/v3/properties interface.. +// Reference: https://developers.hubspot.com/docs/api/crm/properties +type CrmPropertiesService interface { + List(string) (map[string]interface{}, error) + Create(string, map[string]interface{}) (map[string]interface{}, error) + Get(string, string) (map[string]interface{}, error) + Delete(string, string) error + Update(string, string, map[string]interface{}) (map[string]interface{}, error) +} + +// CrmPropertiesServiceOp handles communication with the CRM properties endpoint. +type CrmPropertiesServiceOp struct { + client *Client + crmPropertiesPath string +} + +var _ CrmPropertiesService = (*CrmPropertiesServiceOp)(nil) + +func (s *CrmPropertiesServiceOp) List(objectType string) (map[string]interface{}, error) { + path := fmt.Sprintf("%s/%s", s.crmPropertiesPath, objectType) + resource := make(map[string]interface{}) + if err := s.client.Get(path, &resource, nil); err != nil { + return nil, err + } + return resource, nil +} + +func (s *CrmPropertiesServiceOp) Get(objectType, propertyName string) (map[string]interface{}, error) { + path := fmt.Sprintf("%s/%s/%s", s.crmPropertiesPath, objectType, propertyName) + resource := make(map[string]interface{}) + if err := s.client.Get(path, &resource, nil); err != nil { + return nil, err + } + return resource, nil +} + +func (s *CrmPropertiesServiceOp) Create(objectType string, reqData map[string]interface{}) (map[string]interface{}, error) { + resource := make(map[string]interface{}) + path := fmt.Sprintf("%s/%s", s.crmPropertiesPath, objectType) + if err := s.client.Post(path, reqData, &resource); err != nil { + return nil, err + } + return resource, nil +} + +func (s *CrmPropertiesServiceOp) Delete(objectType string, propertyName string) error { + path := fmt.Sprintf("%s/%s/%s", s.crmPropertiesPath, objectType, propertyName) + return s.client.Delete(path) +} + +func (s *CrmPropertiesServiceOp) Update(objectType string, propertyName string, reqData map[string]interface{}) (map[string]interface{}, error) { + resource := make(map[string]interface{}) + path := fmt.Sprintf("%s/%s/%s", s.crmPropertiesPath, objectType, propertyName) + if err := s.client.Patch(path, reqData, &resource); err != nil { + return nil, err + } + return resource, nil +} diff --git a/crm_properties_test.go b/crm_properties_test.go new file mode 100644 index 0000000..8695a13 --- /dev/null +++ b/crm_properties_test.go @@ -0,0 +1,82 @@ +package hubspot + +import ( + "os" + "testing" +) + +func TestListCrmProperties(t *testing.T) { + t.SkipNow() + cli, _ := NewClient(SetPrivateAppToken(os.Getenv("PRIVATE_APP_TOKEN"))) + // Use crm_schemas:TestCreate() to generate this... + res, err := cli.CRM.Properties.List("cars") + if err != nil { + t.Error(err) + } + + if _, ok := res["results"]; !ok { + t.Error("expected results key in reslt") + } + +} + +func TestGetCrmProperty(t *testing.T) { + t.SkipNow() + + cli, _ := NewClient(SetPrivateAppToken(os.Getenv("PRIVATE_APP_TOKEN"))) + // Use crm_schemas:TestCreate() to generate this... + res, err := cli.CRM.Properties.Get("cars", "model") + if err != nil { + t.Error(err) + } + + if name, ok := res["name"]; !ok { + t.Error("expected result to have a key named name") + } else if name != "model" { + t.Errorf("expedcted name to be model, got %s", name) + } + +} + +func TestCreateProperty(t *testing.T) { + t.SkipNow() + cli, _ := NewClient(SetPrivateAppToken(os.Getenv("PRIVATE_APP_TOKEN"))) + newProp := make(map[string]interface{}) + newProp["name"] = "mileage" + newProp["label"] = "Mileage" + newProp["type"] = "number" + newProp["fieldType"] = "number" + newProp["groupName"] = "cars_information" + + _, err := cli.CRM.Properties.Create("cars", newProp) + if err != nil { + t.Error(err) + return + } +} + +func TestUpdateProperty(t *testing.T) { + t.SkipNow() + cli, _ := NewClient(SetPrivateAppToken(os.Getenv("PRIVATE_APP_TOKEN"))) + updateProp := make(map[string]interface{}) + updateProp["label"] = "Mileage - updated" + + res, err := cli.CRM.Properties.Update("cars", "mileage", updateProp) + if err != nil { + t.Error(err) + return + } + + if newLabel, ok := res["label"]; !ok || newLabel != "Mileage - updated" { + t.Errorf("expected updated label") + } +} + +func TestDeleteProperty(t *testing.T) { + t.SkipNow() + cli, _ := NewClient(SetPrivateAppToken(os.Getenv("PRIVATE_APP_TOKEN"))) + err := cli.CRM.Properties.Delete("cars", "mileage") + if err != nil { + t.Error(err) + } +} diff --git a/crm_schemas.go b/crm_schemas.go new file mode 100644 index 0000000..b56a4d5 --- /dev/null +++ b/crm_schemas.go @@ -0,0 +1,64 @@ +package hubspot + +import "fmt" + +const ( + crmSchemasPath = "schemas" +) + +// CrmSchemas is an interface of CRM crm/v3/schemas interface.. +// Reference: https://developers.hubspot.com/docs/api/crm/crm-custom-objects +type CrmSchemasService interface { + List() (map[string]interface{}, error) + Create(map[string]interface{}) (map[string]interface{}, error) + Get(string) (map[string]interface{}, error) + Delete(string, bool) error + Update(string, map[string]interface{}) (map[string]interface{}, error) +} + +// CrmSchemasServiceOp handles communication with the CRM schemas endpoint. +type CrmSchemasServiceOp struct { + client *Client + crmSchemasPath string +} + +var _ CrmSchemasService = (*CrmSchemasServiceOp)(nil) + +func (s *CrmSchemasServiceOp) List() (map[string]interface{}, error) { + resource := make(map[string]interface{}) + if err := s.client.Get(s.crmSchemasPath, &resource, nil); err != nil { + return nil, err + } + return resource, nil +} + +func (s *CrmSchemasServiceOp) Create(reqData map[string]interface{}) (map[string]interface{}, error) { + resource := make(map[string]interface{}) + if err := s.client.Post(s.crmSchemasPath, reqData, &resource); err != nil { + return nil, err + } + return resource, nil +} + +func (s *CrmSchemasServiceOp) Get(schemaIdentifier string) (map[string]interface{}, error) { + resource := make(map[string]interface{}) + path := fmt.Sprintf("%s/%s", s.crmSchemasPath, schemaIdentifier) + if err := s.client.Get(path, &resource, nil); err != nil { + return nil, err + } + return resource, nil +} + +func (s *CrmSchemasServiceOp) Delete(schemaIdentifier string, archived bool) error { + path := fmt.Sprintf("%s/%s?archived=%t", s.crmSchemasPath, schemaIdentifier, archived) + return s.client.Delete(path) +} + +func (s *CrmSchemasServiceOp) Update(schemaIdentifier string, reqData map[string]interface{}) (map[string]interface{}, error) { + resource := make(map[string]interface{}) + path := fmt.Sprintf("%s/%s", s.crmSchemasPath, schemaIdentifier) + if err := s.client.Patch(path, reqData, &resource); err != nil { + return nil, err + } + return resource, nil +} diff --git a/crm_schemas_test.go b/crm_schemas_test.go new file mode 100644 index 0000000..b9c6513 --- /dev/null +++ b/crm_schemas_test.go @@ -0,0 +1,221 @@ +package hubspot + +import ( + "encoding/json" + "fmt" + "os" + "testing" +) + +func TestGetSchemas(t *testing.T) { + t.SkipNow() + + cli, _ := NewClient(SetPrivateAppToken(os.Getenv("PRIVATE_APP_TOKEN"))) + res, err := cli.CRM.Schemas.List() + if err != nil { + t.Error(err) + } + _, ok := res["results"] + if !ok { + t.Errorf("expected List schemas to respond with results key on success") + } +} + +func TestCreateSchema(t *testing.T) { + // t.SkipNow() + // this example is from Hubspot.. + var exampleJson string = ` + { + "name": "cars", + "labels": { + "singular": "Car", + "plural": "Cars" + }, + "primaryDisplayProperty": "model", + "secondaryDisplayProperties": [ + "make" + ], + "searchableProperties": [ + "year", + "make", + "vin", + "model" + ], + "requiredProperties": [ + "year", + "make", + "vin", + "model" + ], + "properties": [ + { + "name": "condition", + "label": "Condition", + "type": "enumeration", + "fieldType": "select", + "options": [ + { + "label": "New", + "value": "new" + }, + { + "label": "Used", + "value": "used" + } + ] + }, + { + "name": "date_received", + "label": "Date received", + "type": "date", + "fieldType": "date" + }, + { + "name": "year", + "label": "Year", + "type": "number", + "fieldType": "number" + }, + { + "name": "make", + "label": "Make", + "type": "string", + "fieldType": "text" + }, + { + "name": "model", + "label": "Model", + "type": "string", + "fieldType": "text" + }, + { + "name": "vin", + "label": "VIN", + "type": "string", + "hasUniqueValue": true, + "fieldType": "text" + }, + { + "name": "price", + "label": "Price", + "type": "number", + "fieldType": "number" + }, + { + "name": "notes", + "label": "Notes", + "type": "string", + "fieldType": "text" + } + ], + "associatedObjects": [ + "CONTACT" + ] + } + ` + + req := make(map[string]interface{}) + err := json.Unmarshal([]byte(exampleJson), &req) + if err != nil { + t.Error(err) + } + + cli, _ := NewClient(SetPrivateAppToken(os.Getenv("PRIVATE_APP_TOKEN"))) + res, err := cli.CRM.Schemas.Create(req) + if err != nil { + t.Error(err) + } + + _, ok := res["id"] + if !ok { + t.Errorf("expected post schema result to have an id field") + } +} + +func TestGetSchema(t *testing.T) { + t.SkipNow() + cli, _ := NewClient(SetPrivateAppToken(os.Getenv("PRIVATE_APP_TOKEN"))) + res, err := cli.CRM.Schemas.Get("cars") + if err != nil { + t.Error(err) + } + fmt.Printf("%+v\n", res) + + _, ok := res["id"] + if !ok { + t.Errorf("expected post schema result to have an id field") + } +} + +func TestDeleteSchema(t *testing.T) { + t.SkipNow() + cli, _ := NewClient(SetPrivateAppToken(os.Getenv("PRIVATE_APP_TOKEN"))) + + res, err := cli.CRM.Schemas.Get("cars") + if err != nil { + t.Error(err) + } + + qName, ok := res["fullyQualifiedName"] + if !ok { + t.Error(err) + } + err = cli.CRM.Schemas.Delete(qName.(string), false) + if err != nil { + t.Error(err) + } + + err = cli.CRM.Schemas.Delete(qName.(string), true) + if err != nil { + t.Error(err) + } + +} + +func TestUpdateSchema(t *testing.T) { + t.SkipNow() + // Note: You need to wait some time after calling create before calling Update as it'll return an error message. + req := make(map[string]interface{}) + req["primaryDisplayProperty"] = "year" + + cli, _ := NewClient(SetPrivateAppToken(os.Getenv("PRIVATE_APP_TOKEN"))) + + res, err := cli.CRM.Schemas.Get("cars") + if err != nil { + t.Error(err) + return + } + + primaryDisplayProperty, ok := res["primaryDisplayProperty"] + if !ok { + t.Error(err) + return + } + + if primaryDisplayProperty != "model" { + t.Error("expected primaryDisplayProperty to be model before update") + return + } + + qName, ok := res["fullyQualifiedName"] + if !ok { + t.Error(err) + return + } + + res, err = cli.CRM.Schemas.Update(qName.(string), req) + if err != nil { + t.Error(err) + return + } + + primaryDisplayProperty, ok = res["primaryDisplayProperty"] + if !ok { + t.Error(err) + } + + if primaryDisplayProperty != "year" { + t.Errorf("expected primaryDisplayProperty to be year after update, got %s", primaryDisplayProperty.(string)) + } + +}