diff --git a/providers/hubspot/README.md b/providers/hubspot/README.md index 6eecbea6e5..61178d690b 100644 --- a/providers/hubspot/README.md +++ b/providers/hubspot/README.md @@ -60,3 +60,7 @@ result, err := client.Search(context.Background(), hubspot.SearchParams{ }, }) ``` + +# Docs + +[Hubspot CRM](https://developers.hubspot.com/docs/guides/crm/understanding-the-crm) diff --git a/providers/hubspot/connector.go b/providers/hubspot/connector.go index 1ff5fae407..c87073cfd4 100644 --- a/providers/hubspot/connector.go +++ b/providers/hubspot/connector.go @@ -1,14 +1,17 @@ package hubspot import ( - "context" - "github.com/amp-labs/connectors" "github.com/amp-labs/connectors/common" "github.com/amp-labs/connectors/internal/components" + "github.com/amp-labs/connectors/internal/components/deleter" + "github.com/amp-labs/connectors/internal/components/operations" "github.com/amp-labs/connectors/providers" - "github.com/amp-labs/connectors/providers/hubspot/internal/crm" + "github.com/amp-labs/connectors/providers/hubspot/internal/crm/associations" + "github.com/amp-labs/connectors/providers/hubspot/internal/crm/batch" "github.com/amp-labs/connectors/providers/hubspot/internal/crm/core" + "github.com/amp-labs/connectors/providers/hubspot/internal/crm/custom" + "github.com/amp-labs/connectors/providers/hubspot/internal/crm/search" ) // Connector implements the HubSpot integration. @@ -29,10 +32,14 @@ type Connector struct { // Provides access to an authenticated client. common.RequireAuthenticatedClient - // Temporary grouping. - // TODO this adapter contents should dissolve into this connector. - // The idea of CRM module should cease to exist. Hubspot is but one entity. - delegate *crm.Adapter + // Operations + components.Deleter + + // These delegate complex functionality to keep Connector modular and prevent code bloat. + customAdapter *custom.Adapter // used for connectors.UpsertMetadataConnector capabilities. + batchAdapter *batch.Adapter // used for connectors.BatchWriteConnector capabilities. + searchStrategy *search.Strategy // used for connectors.SearchConnector capabilities. + associationsFiller associations.Filler } var _ connectors.WebhookVerifierConnector = &Connector{} @@ -40,12 +47,10 @@ var _ connectors.WebhookVerifierConnector = &Connector{} // NewConnector returns a new Hubspot connector. // Hubspot connector still owns CRM functionality. Not every CRM feature is located under `crm` package. func NewConnector(params common.ConnectorParams) (*Connector, error) { - return components.Initialize(providers.Hubspot, params, func(base *components.Connector) (*Connector, error) { - return constructor(base, ¶ms) - }) + return components.Initialize(providers.Hubspot, params, constructor) } -func constructor(base *components.Connector, params *common.ConnectorParams) (*Connector, error) { +func constructor(base *components.Connector) (*Connector, error) { connector := &Connector{ Connector: base, } @@ -54,16 +59,26 @@ func constructor(base *components.Connector, params *common.ConnectorParams) (*C // Check method in the internal package "custom", method "readGroupName" which relies on error casting. connector.SetErrorHandler(core.InterpretJSONError) - var err error + connector.Deleter = deleter.NewHTTPDeleter( + connector.HTTPClient().Client, + components.NewEmptyEndpointRegistry(), + connector.ProviderContext.Module(), + operations.DeleteHandlers{ + BuildRequest: connector.buildDeleteRequest, + ParseResponse: connector.parseDeleteResponse, + ErrorHandler: core.InterpretJSONError, + }, + ) - connector.delegate, err = crm.NewAdapter(params) - if err != nil { - return nil, err - } + connector.customAdapter = custom.NewAdapter(connector.JSONHTTPClient(), connector.ModuleInfo()) + associationsStrategy := associations.NewStrategy( + connector.JSONHTTPClient(), connector.ModuleInfo(), connector.ProviderInfo(), + ) + connector.associationsFiller = associationsStrategy + connector.batchAdapter = batch.NewAdapter(connector.HTTPClient(), connector.ModuleInfo(), associationsStrategy) + connector.searchStrategy = search.NewStrategy( + connector.JSONHTTPClient(), connector.ModuleInfo(), connector.associationsFiller, + ) return connector, nil } - -func (c *Connector) Search(ctx context.Context, params *common.SearchParams) (*common.SearchResult, error) { - return c.delegate.Search(ctx, params) -} diff --git a/providers/hubspot/internal/crm/delete.go b/providers/hubspot/delete.go similarity index 74% rename from providers/hubspot/internal/crm/delete.go rename to providers/hubspot/delete.go index 74927717ff..bb349feaca 100644 --- a/providers/hubspot/internal/crm/delete.go +++ b/providers/hubspot/delete.go @@ -1,4 +1,4 @@ -package crm +package hubspot import ( "context" @@ -9,8 +9,8 @@ import ( "github.com/amp-labs/connectors/internal/httpkit" ) -func (a *Adapter) buildDeleteRequest(ctx context.Context, params common.DeleteParams) (*http.Request, error) { - url, err := a.getDeleteURL(params.ObjectName, params.RecordId) +func (c *Connector) buildDeleteRequest(ctx context.Context, params common.DeleteParams) (*http.Request, error) { + url, err := c.getDeleteURL(params.ObjectName, params.RecordId) if err != nil { return nil, err } @@ -23,7 +23,7 @@ func (a *Adapter) buildDeleteRequest(ctx context.Context, params common.DeletePa return req, nil } -func (a *Adapter) parseDeleteResponse( +func (c *Connector) parseDeleteResponse( ctx context.Context, params common.DeleteParams, request *http.Request, diff --git a/providers/hubspot/internal/crm/README.md b/providers/hubspot/internal/crm/README.md deleted file mode 100644 index 87ae55516e..0000000000 --- a/providers/hubspot/internal/crm/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Module - -This package implements a HubSpot connector for the **CRM module**, -allowing integration with the -[HubSpot CRM APIs](https://developers.hubspot.com/docs/guides/crm/understanding-the-crm). diff --git a/providers/hubspot/internal/crm/adapter.go b/providers/hubspot/internal/crm/adapter.go deleted file mode 100644 index ef0c737c2d..0000000000 --- a/providers/hubspot/internal/crm/adapter.go +++ /dev/null @@ -1,95 +0,0 @@ -package crm - -import ( - "context" - - "github.com/amp-labs/connectors/common" - "github.com/amp-labs/connectors/common/urlbuilder" - "github.com/amp-labs/connectors/internal/components" - "github.com/amp-labs/connectors/internal/components/deleter" - "github.com/amp-labs/connectors/internal/components/operations" - "github.com/amp-labs/connectors/providers" - "github.com/amp-labs/connectors/providers/hubspot/internal/crm/associations" - "github.com/amp-labs/connectors/providers/hubspot/internal/crm/batch" - "github.com/amp-labs/connectors/providers/hubspot/internal/crm/core" - "github.com/amp-labs/connectors/providers/hubspot/internal/crm/custom" - "github.com/amp-labs/connectors/providers/hubspot/internal/crm/search" -) - -// Adapter handles CRUD operations (at the moment: delete only) against HubSpot's REST API. -// It abstracts API endpoint construction, versioning, and JSON response processing -// specific to the HubSpot CRUD feature. -type Adapter struct { - // Basic connector - *components.Connector - - // Require authenticated client - common.RequireAuthenticatedClient - - // Supported operations - components.Deleter - - // CRM module sub-adapters - // These delegate specialized subsets of CRM functionality to keep Connector modular and prevent code bloat. - customAdapter *custom.Adapter // used for connectors.UpsertMetadataConnector capabilities. - batchAdapter *batch.Adapter // used for connectors.BatchWriteConnector capabilities. - searchStrategy *search.Strategy // used for connectors.SearchConnector capabilities. - AssociationsFiller associations.Filler -} - -// NewAdapter creates a new crm Adapter configured to work with Hubspot's APIs. -func NewAdapter(params *common.ConnectorParams) (*Adapter, error) { - return components.Initialize(providers.Hubspot, *params, constructor) -} - -func constructor(base *components.Connector) (*Adapter, error) { - adapter := &Adapter{Connector: base} - - adapter.Deleter = deleter.NewHTTPDeleter( - adapter.HTTPClient().Client, - components.NewEmptyEndpointRegistry(), - adapter.ProviderContext.Module(), - operations.DeleteHandlers{ - BuildRequest: adapter.buildDeleteRequest, - ParseResponse: adapter.parseDeleteResponse, - ErrorHandler: core.InterpretJSONError, - }, - ) - - adapter.SetErrorHandler(core.InterpretJSONError) - adapter.customAdapter = custom.NewAdapter(adapter.JSONHTTPClient(), adapter.ModuleInfo()) - associationsStrategy := associations.NewStrategy(adapter.JSONHTTPClient(), - adapter.ModuleInfo(), adapter.ProviderInfo()) - adapter.AssociationsFiller = associationsStrategy - adapter.batchAdapter = batch.NewAdapter(adapter.HTTPClient(), adapter.ModuleInfo(), associationsStrategy) - adapter.searchStrategy = search.NewStrategy( - adapter.JSONHTTPClient(), adapter.ModuleInfo(), adapter.AssociationsFiller, - ) - - return adapter, nil -} - -func (a *Adapter) UpsertMetadata( - ctx context.Context, params *common.UpsertMetadataParams, -) (*common.UpsertMetadataResult, error) { - return a.customAdapter.UpsertMetadata(ctx, params) -} - -func (a *Adapter) BatchWrite( - ctx context.Context, params *common.BatchWriteParam, -) (*common.BatchWriteResult, error) { - return a.batchAdapter.BatchWrite(ctx, params) -} - -func (a *Adapter) Search(ctx context.Context, params *common.SearchParams) (*common.SearchResult, error) { - return a.searchStrategy.Search(ctx, params) -} - -func (a *Adapter) getModuleURL() string { - return a.ModuleInfo().BaseURL -} - -// https://developers.hubspot.com/docs/api-reference/latest/crm/objects/contacts/delete-contact -func (a *Adapter) getDeleteURL(objectName, recordID string) (*urlbuilder.URL, error) { - return urlbuilder.New(a.getModuleURL(), "objects", core.APIVersion2026March, objectName, recordID) -} diff --git a/providers/hubspot/metadata.go b/providers/hubspot/metadata.go index 917a39e431..2f1ff8afc9 100644 --- a/providers/hubspot/metadata.go +++ b/providers/hubspot/metadata.go @@ -30,7 +30,7 @@ type objectMetadataError struct { func (c *Connector) UpsertMetadata( ctx context.Context, params *common.UpsertMetadataParams, ) (*common.UpsertMetadataResult, error) { - return c.delegate.UpsertMetadata(ctx, params) + return c.customAdapter.UpsertMetadata(ctx, params) } // ListObjectMetadata returns object metadata for each object name provided. diff --git a/providers/hubspot/metadata_test.go b/providers/hubspot/metadata_test.go index 184b18dd53..a0957273b7 100644 --- a/providers/hubspot/metadata_test.go +++ b/providers/hubspot/metadata_test.go @@ -397,9 +397,6 @@ func constructTestConnector(serverURL string) (*Connector, error) { // for testing we want to redirect calls to our mock server connector.SetUnitTestMockServerBaseURL(serverURL) - if connector.delegate != nil { - connector.delegate.SetUnitTestMockServerBaseURL(serverURL) - } return connector, nil } diff --git a/providers/hubspot/read.go b/providers/hubspot/read.go index 7790fdf942..7822c59f9d 100644 --- a/providers/hubspot/read.go +++ b/providers/hubspot/read.go @@ -83,7 +83,7 @@ func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common core.GetRecords, core.GetNextRecordsURL, associations.CreateDataMarshallerWithAssociations( - ctx, c.delegate.AssociationsFiller, config.ObjectName, config.AssociatedObjects), + ctx, c.associationsFiller, config.ObjectName, config.AssociatedObjects), config.Fields, ) } diff --git a/providers/hubspot/record.go b/providers/hubspot/record.go index 8850d4e9b8..4158c9f16a 100644 --- a/providers/hubspot/record.go +++ b/providers/hubspot/record.go @@ -82,7 +82,7 @@ func (c *Connector) GetRecordsByIds( } marshaller := associations.CreateDataMarshallerWithAssociations( - ctx, c.delegate.AssociationsFiller, objectName, associationsList, + ctx, c.associationsFiller, objectName, associationsList, ) return marshaller(records, fields) diff --git a/providers/hubspot/search.go b/providers/hubspot/search.go index 99322067bf..aa82f7f2f5 100644 --- a/providers/hubspot/search.go +++ b/providers/hubspot/search.go @@ -24,6 +24,10 @@ const ( searchPageSize = "200" ) +func (c *Connector) Search(ctx context.Context, params *common.SearchParams) (*common.SearchResult, error) { + return c.searchStrategy.Search(ctx, params) +} + // ReadUsingSearchAPI uses the POST /search endpoint to filter object records and return the result. // This endpoint has a limit of 10,000 records. If the result has more than 10,000 records, // the caller should employ sorting to paginate through the result on the client side. @@ -72,7 +76,7 @@ func (c *Connector) ReadUsingSearchAPI(ctx context.Context, config SearchParams) core.GetRecords, core.GetNextRecordsAfter, associations.CreateDataMarshallerWithAssociations( - ctx, c.delegate.AssociationsFiller, config.ObjectName, config.AssociatedObjects), + ctx, c.associationsFiller, config.ObjectName, config.AssociatedObjects), config.Fields, ) } diff --git a/providers/hubspot/url.go b/providers/hubspot/url.go index 00513c5ddd..a219ee1c16 100644 --- a/providers/hubspot/url.go +++ b/providers/hubspot/url.go @@ -93,3 +93,8 @@ func (c *Connector) getURLFromRoot(relativePath string) string { func (c *Connector) getRootProviderURL() string { return c.ProviderInfo().BaseURL } + +// https://developers.hubspot.com/docs/api-reference/latest/crm/objects/contacts/delete-contact +func (c *Connector) getDeleteURL(objectName, recordID string) (*urlbuilder.URL, error) { + return urlbuilder.New(c.ModuleInfo().BaseURL, "objects", core.APIVersion2026March, objectName, recordID) +} diff --git a/providers/hubspot/write.go b/providers/hubspot/write.go index 87c2790515..adcb4c5642 100644 --- a/providers/hubspot/write.go +++ b/providers/hubspot/write.go @@ -5,7 +5,6 @@ import ( "fmt" "strings" - "github.com/amp-labs/connectors" "github.com/amp-labs/connectors/common" "github.com/amp-labs/connectors/common/logging" "github.com/amp-labs/connectors/internal/datautils" @@ -74,9 +73,5 @@ func (c *Connector) Write(ctx context.Context, config common.WriteParams) (*comm } func (c *Connector) BatchWrite(ctx context.Context, params *common.BatchWriteParam) (*common.BatchWriteResult, error) { - return c.delegate.BatchWrite(ctx, params) -} - -func (c *Connector) Delete(ctx context.Context, params connectors.DeleteParams) (*connectors.DeleteResult, error) { - return c.delegate.Delete(ctx, params) + return c.batchAdapter.BatchWrite(ctx, params) }