Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion providers/hubspot.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const Hubspot Provider = "hubspot"
const (
// ModuleHubspotCRM is the module used for accessing standard CRM objects.
ModuleHubspotCRM common.ModuleID = "crm"
// ModuleHubspotMarketing is the module used for Marketing Hub product.
ModuleHubspotMarketing common.ModuleID = "marketing"
)

func init() { //nolint:funlen
Expand Down Expand Up @@ -78,6 +80,16 @@ func init() { //nolint:funlen
},
},
},
ModuleHubspotMarketing: {
BaseURL: "https://api.hubapi.com",
DisplayName: "HubSpot Marketing",
Support: Support{
Delete: false,
Read: false,
Subscribe: false,
Write: false,
},
},
},
Media: &Media{
DarkMode: &MediaTypeDarkMode{
Expand All @@ -101,7 +113,8 @@ func init() { //nolint:funlen
{
Name: "ownerId",
ModuleDependencies: &ModuleDependencies{
ModuleHubspotCRM: ModuleDependency{},
ModuleHubspotCRM: ModuleDependency{},
ModuleHubspotMarketing: ModuleDependency{},
},
},
},
Expand Down
14 changes: 12 additions & 2 deletions providers/hubspot/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import (
"github.com/amp-labs/connectors/internal/components"
"github.com/amp-labs/connectors/providers"
"github.com/amp-labs/connectors/providers/hubspot/internal/crm"
"github.com/amp-labs/connectors/providers/hubspot/internal/crm/core"
"github.com/amp-labs/connectors/providers/hubspot/internal/marketing"
"github.com/amp-labs/connectors/providers/hubspot/internal/shared"
)

// Connector provides integration with Hubspot provider.
Expand All @@ -26,6 +27,8 @@ type Connector struct {
// crmAdapter handles the core Hubspot CRM module.
// It provides dedicated support for HubspotCRM-specific functionality.
crmAdapter *crm.Adapter
// marketingAdapter handles Hubspot's Marketing Hub product.
marketingAdapter *marketing.Adapter
}

var _ connectors.WebhookVerifierConnector = &Connector{}
Expand All @@ -45,7 +48,7 @@ func constructor(base *components.Connector, params *common.ConnectorParams) (*C

// Note: error handler must return common.HTTPError.
// Check method in the internal package "custom", method "readGroupName" which relies on error casting.
connector.SetErrorHandler(core.InterpretJSONError)
connector.SetErrorHandler(shared.InterpretJSONError)

var err error
if connector.Module() == providers.ModuleHubspotCRM {
Expand All @@ -55,6 +58,13 @@ func constructor(base *components.Connector, params *common.ConnectorParams) (*C
}
}

if connector.Module() == providers.ModuleHubspotMarketing {
connector.marketingAdapter, err = marketing.NewAdapter(params)
if err != nil {
return nil, err
}
}

return connector, nil
}

Expand Down
8 changes: 4 additions & 4 deletions providers/hubspot/internal/crm/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import (
"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"
"github.com/amp-labs/connectors/providers/hubspot/internal/shared"
)

// Adapter handles CRUD operations (at the moment: delete only) against HubSpot's REST API.
Expand Down Expand Up @@ -52,11 +52,11 @@ func constructor(base *components.Connector) (*Adapter, error) {
operations.DeleteHandlers{
BuildRequest: adapter.buildDeleteRequest,
ParseResponse: adapter.parseDeleteResponse,
ErrorHandler: core.InterpretJSONError,
ErrorHandler: shared.InterpretJSONError,
},
)

adapter.SetErrorHandler(core.InterpretJSONError)
adapter.SetErrorHandler(shared.InterpretJSONError)
adapter.customAdapter = custom.NewAdapter(adapter.JSONHTTPClient(), adapter.ModuleInfo())
associationsStrategy := associations.NewStrategy(adapter.JSONHTTPClient(),
adapter.ModuleInfo(), adapter.ProviderInfo())
Expand Down Expand Up @@ -91,5 +91,5 @@ func (a *Adapter) getModuleURL() string {

// 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)
return urlbuilder.New(a.getModuleURL(), "objects", shared.APIVersion2026March, objectName, recordID)
}
4 changes: 2 additions & 2 deletions providers/hubspot/internal/crm/associations/marshaller.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import (

"github.com/amp-labs/connectors/common"
"github.com/amp-labs/connectors/common/readhelper"
"github.com/amp-labs/connectors/providers/hubspot/internal/crm/core"
"github.com/amp-labs/connectors/providers/hubspot/internal/shared"
)

func CreateDataMarshallerWithAssociations(
ctx context.Context, filler Filler,
objectName string, associatedObjects []string,
) common.MarshalFromNodeFunc {
return readhelper.ChainedMarshaller(
core.GetDataMarshaller(),
shared.GetDataMarshaller(),
// Enhance records with associations by fetching these relationships.
func(rows []common.ReadResultRow) error {
return filler.FillAssociations(ctx, objectName, associatedObjects, rows)
Expand Down
8 changes: 4 additions & 4 deletions providers/hubspot/internal/crm/associations/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"github.com/amp-labs/connectors/common"
"github.com/amp-labs/connectors/common/urlbuilder"
"github.com/amp-labs/connectors/providers"
"github.com/amp-labs/connectors/providers/hubspot/internal/crm/core"
"github.com/amp-labs/connectors/providers/hubspot/internal/shared"
)

type Strategy struct {
Expand All @@ -29,7 +29,7 @@ func NewStrategy(
// https://developers.hubspot.com/docs/api-reference/latest/crm/associations/associate-records/batch/get-associations
func (s Strategy) getReadAssociationsURL(fromObject, toObject string) (*urlbuilder.URL, error) {
return urlbuilder.New(s.moduleInfo.BaseURL,
"associations", core.APIVersion2026March, fromObject, toObject, "batch/read")
"associations", shared.APIVersion2026March, fromObject, toObject, "batch/read")
}

// getCreateAssociationsURL builds the Hubspot endpoint to create associations between 2 object types.
Expand All @@ -38,13 +38,13 @@ func (s Strategy) getReadAssociationsURL(fromObject, toObject string) (*urlbuild
// https://developers.hubspot.com/docs/api-reference/latest/crm/associations/associate-records/batch/create-associations-labeled
func (s Strategy) getCreateAssociationsURL(fromObject, toObject string) (*urlbuilder.URL, error) {
return urlbuilder.New(s.moduleInfo.BaseURL,
"associations", core.APIVersion2026March, fromObject, toObject, "batch/create")
"associations", shared.APIVersion2026March, fromObject, toObject, "batch/create")
}

// getReadObjectSchema builds the Hubspot endpoint to get schema definition for an object.
// Response includes properties and associations defined on that object.
//
// https://developers.hubspot.com/docs/api-reference/latest/crm/objects/schemas/get-schema
func (s Strategy) getReadObjectSchema(objectName string) (*urlbuilder.URL, error) {
return urlbuilder.New(s.providerInfo.BaseURL, "crm-object-schemas", core.APIVersion2026March, "schemas", objectName)
return urlbuilder.New(s.providerInfo.BaseURL, "crm-object-schemas", shared.APIVersion2026March, "schemas", objectName)
}
6 changes: 3 additions & 3 deletions providers/hubspot/internal/crm/batch/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/amp-labs/connectors/internal/httpkit"
"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/core"
"github.com/amp-labs/connectors/providers/hubspot/internal/shared"
)

// Adapter handles batched record operations (create/update) against HubSpot's REST API.
Expand Down Expand Up @@ -64,13 +64,13 @@ func (a *Adapter) getModuleURL() string {
// nolint:lll
// Contacts example: https://developers.hubspot.com/docs/api-reference/latest/crm/objects/contacts/batch/create-contacts
func (a *Adapter) getCreateURL(objectName common.ObjectName) (*urlbuilder.URL, error) {
return urlbuilder.New(a.getModuleURL(), "objects", core.APIVersion2026March, objectName.String(), "batch/create")
return urlbuilder.New(a.getModuleURL(), "objects", shared.APIVersion2026March, objectName.String(), "batch/create")
}

// getUpdateURL builds the HubSpot batch update endpoint for the given object type.
//
// nolint:lll
// Contacts example: https://developers.hubspot.com/docs/api-reference/latest/crm/objects/contacts/batch/update-contacts
func (a *Adapter) getUpdateURL(objectName common.ObjectName) (*urlbuilder.URL, error) {
return urlbuilder.New(a.getModuleURL(), "objects", core.APIVersion2026March, objectName.String(), "batch/update")
return urlbuilder.New(a.getModuleURL(), "objects", shared.APIVersion2026March, objectName.String(), "batch/update")
}
10 changes: 5 additions & 5 deletions providers/hubspot/internal/crm/custom/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"github.com/amp-labs/connectors/common"
"github.com/amp-labs/connectors/common/urlbuilder"
"github.com/amp-labs/connectors/providers"
"github.com/amp-labs/connectors/providers/hubspot/internal/crm/core"
"github.com/amp-labs/connectors/providers/hubspot/internal/shared"
)

type Adapter struct {
Expand All @@ -22,23 +22,23 @@ func NewAdapter(client *common.JSONHTTPClient, moduleInfo *providers.ModuleInfo)
// https://developers.hubspot.com/docs/api-reference/latest/crm/properties/batch/create-properties
// Note: Version APIVersion2026March is NOT FOUND at the moment for this endpoint. Using older V3.
func (a *Adapter) getPropertyBatchCreateURL(objectName string) (*urlbuilder.URL, error) {
return urlbuilder.New(a.moduleInfo.BaseURL, core.APIVersion3, "properties", objectName, "/batch/create")
return urlbuilder.New(a.moduleInfo.BaseURL, shared.APIVersion3, "properties", objectName, "/batch/create")
}

// https://developers.hubspot.com/docs/api-reference/latest/crm/properties/update-property
// Note: Version APIVersion2026March is NOT FOUND at the moment for this endpoint. Using older V3.
func (a *Adapter) getPropertyUpdateURL(objectName, propertyName string) (*urlbuilder.URL, error) {
return urlbuilder.New(a.moduleInfo.BaseURL, core.APIVersion3, "properties", objectName, propertyName)
return urlbuilder.New(a.moduleInfo.BaseURL, shared.APIVersion3, "properties", objectName, propertyName)
}

// https://developers.hubspot.com/docs/api-reference/latest/crm/properties/property-groups/get-property
// Note: Version APIVersion2026March is NOT FOUND at the moment for this endpoint. Using older V3.
func (a *Adapter) getPropertyGroupNameURL(objectName, groupName string) (*urlbuilder.URL, error) {
return urlbuilder.New(a.moduleInfo.BaseURL, core.APIVersion3, "properties", objectName, "groups", groupName)
return urlbuilder.New(a.moduleInfo.BaseURL, shared.APIVersion3, "properties", objectName, "groups", groupName)
}

// https://developers.hubspot.com/docs/api-reference/latest/crm/properties/property-groups/create-property
// Note: Version APIVersion2026March is NOT FOUND at the moment for this endpoint. Using older V3.
func (a *Adapter) getPropertyGroupNameCreationURL(objectName string) (*urlbuilder.URL, error) {
return urlbuilder.New(a.moduleInfo.BaseURL, core.APIVersion3, "properties", objectName, "groups")
return urlbuilder.New(a.moduleInfo.BaseURL, shared.APIVersion3, "properties", objectName, "groups")
}
6 changes: 3 additions & 3 deletions providers/hubspot/internal/crm/search/adhocSearchAPI.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"strconv"

"github.com/amp-labs/connectors/common"
"github.com/amp-labs/connectors/providers/hubspot/internal/crm/core"
"github.com/amp-labs/connectors/providers/hubspot/internal/shared"
)

// searchCRM is intended for objects outside HubSpot's ObjectAPI.
Expand Down Expand Up @@ -41,7 +41,7 @@ func (s Strategy) searchViaNonstandardSearchAPI(
return common.ParseResult(
rsp,
common.ExtractOptionalRecordsFromPath(params.ObjectName),
core.GetNextRecordsURLCRM,
shared.GetNextRecordsURLCRM,
common.GetMarshaledData,
params.Fields,
)
Expand All @@ -59,7 +59,7 @@ func makeNonstandardSearchPayload(params *common.SearchParams) (nonstandardSearc
}
}

pageSize := core.DefaultPageSizeInt
pageSize := shared.DefaultPageSizeInt
if params.Limit != 0 {
pageSize = params.Limit
}
Expand Down
6 changes: 3 additions & 3 deletions providers/hubspot/internal/crm/search/canonicalSearchAPI.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (

"github.com/amp-labs/connectors/common"
"github.com/amp-labs/connectors/providers/hubspot/internal/crm/associations"
"github.com/amp-labs/connectors/providers/hubspot/internal/crm/core"
"github.com/amp-labs/connectors/providers/hubspot/internal/shared"
)

// https://developers.hubspot.com/docs/api/crm/search
Expand All @@ -26,8 +26,8 @@ func (s Strategy) searchViaObjectAPI(ctx context.Context, params *common.SearchP

return common.ParseResult(
rsp,
core.GetRecords,
core.GetNextRecordsAfter,
shared.GetRecords,
shared.GetNextRecordsAfter,
associations.CreateDataMarshallerWithAssociations(
ctx, s.associationsFiller, params.ObjectName, params.AssociatedObjects,
),
Expand Down
4 changes: 2 additions & 2 deletions providers/hubspot/internal/crm/search/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (

"github.com/amp-labs/connectors/common"
"github.com/amp-labs/connectors/common/logging"
"github.com/amp-labs/connectors/providers/hubspot/internal/crm/core"
"github.com/amp-labs/connectors/providers/hubspot/internal/shared"
)

const (
Expand Down Expand Up @@ -39,7 +39,7 @@ func (s Strategy) Search(ctx context.Context, params *common.SearchParams) (*com
// Search has two execution paths:
// - ObjectAPI: for core CRM objects supported by the canonical ObjectAPI endpoint.
// - Non-ObjectAPI: for CRM objects not supported by ObjectAPI, which use separate endpoints (e.g., Lists).
if core.ObjectsWithoutPropertiesAPISupport.Has(params.ObjectName) {
if shared.ObjectsWithoutPropertiesAPISupport.Has(params.ObjectName) {
return s.searchViaNonstandardSearchAPI(ctx, params)
}

Expand Down
6 changes: 3 additions & 3 deletions providers/hubspot/internal/crm/search/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/amp-labs/connectors/common/urlbuilder"
"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/core"
"github.com/amp-labs/connectors/providers/hubspot/internal/shared"
)

type Strategy struct {
Expand All @@ -34,14 +34,14 @@ func (s Strategy) getModuleURL(paths ...string) (*urlbuilder.URL, error) {

// https://developers.hubspot.com/docs/api-reference/latest/crm/search-the-crm#make-a-search-request
func (s Strategy) getObjectsAPISearchURL(objectName string) (*urlbuilder.URL, error) {
return s.getModuleURL("objects", core.APIVersion2026March, objectName, "search")
return s.getModuleURL("objects", shared.APIVersion2026March, objectName, "search")
}

func (s Strategy) getSearchURL(objectName string) (*urlbuilder.URL, error) {
switch objectName {
case "lists":
// https://developers.hubspot.com/docs/api-reference/latest/crm/lists/guide#retrieve-by-searching-list-details
return s.getModuleURL("lists", core.APIVersion2026March, "search")
return s.getModuleURL("lists", shared.APIVersion2026March, "search")
default:
return nil, fmt.Errorf("%w: search not supported for %v", common.ErrObjectNotSupported, objectName)
}
Expand Down
74 changes: 74 additions & 0 deletions providers/hubspot/internal/marketing/adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package marketing

import (
"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/internal/components/reader"
"github.com/amp-labs/connectors/internal/components/schema"
"github.com/amp-labs/connectors/internal/components/writer"
"github.com/amp-labs/connectors/providers"
"github.com/amp-labs/connectors/providers/hubspot/internal/shared"
)

// Adapter handles CRUD operations against HubSpot's Marketing Hub product.
type Adapter struct {
// Basic connector
*components.Connector

// Require authenticated client
common.RequireAuthenticatedClient

// Supported operations
components.SchemaProvider
components.Reader
components.Writer
components.Deleter
}

// NewAdapter creates a new Marketing Adapter configured to work with Hubspot's Marketing 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.SchemaProvider = schema.NewOpenAPISchemaProvider(adapter.ProviderContext.Module(), Schemas)

adapter.Reader = reader.NewHTTPReader(
adapter.HTTPClient().Client,
components.NewEmptyEndpointRegistry(),
adapter.ProviderContext.Module(),
operations.ReadHandlers{
BuildRequest: adapter.buildReadRequest,
ParseResponse: adapter.parseReadResponse,
ErrorHandler: shared.InterpretJSONError,
},
)

adapter.Writer = writer.NewHTTPWriter(
adapter.HTTPClient().Client,
components.NewEmptyEndpointRegistry(),
adapter.ProviderContext.Module(),
operations.WriteHandlers{
BuildRequest: adapter.buildWriteRequest,
ParseResponse: adapter.parseWriteResponse,
ErrorHandler: shared.InterpretJSONError,
},
)

adapter.Deleter = deleter.NewHTTPDeleter(
adapter.HTTPClient().Client,
components.NewEmptyEndpointRegistry(),
adapter.ProviderContext.Module(),
operations.DeleteHandlers{
BuildRequest: adapter.buildDeleteRequest,
ParseResponse: adapter.parseDeleteResponse,
ErrorHandler: shared.InterpretJSONError,
},
)

return adapter, nil
}
Loading
Loading