Repository: amp-labs/connectors
Pull request #1512 (2025-03-21 11:11:19)

[CON-1484] feat: Add Podium Metadata Connector
Tests:
<img width="1229" alt="Screenshot 2025-03-21 at 13 19 29" src="https://github.com/user-attachments/assets/ce0a6e92-deee-41ff-ad3c-766bfb472f07" />



Patch:
diff --git a/providers/podium/connector.go b/providers/podium/connector.go
new file mode 100644
index 00000000..dc69a019
--- /dev/null
+++ b/providers/podium/connector.go
@@ -0,0 +1,42 @@
+package podium
+
+import (
+	"github.com/amp-labs/connectors/common"
+	"github.com/amp-labs/connectors/internal/components"
+	"github.com/amp-labs/connectors/internal/components/operations"
+	"github.com/amp-labs/connectors/internal/components/schema"
+	"github.com/amp-labs/connectors/providers"
+)
+
+const restAPIVersion = "v4"
+
+type Connector struct {
+	// Basic connector
+	*components.Connector
+
+	// Require authenticated client
+	common.RequireAuthenticatedClient
+
+	// Supported operations
+	components.SchemaProvider
+}
+
+func NewConnector(params common.Parameters) (*Connector, error) {
+	return components.Initialize(providers.Podium, params, constructor)
+}
+
+func constructor(base *components.Connector) (*Connector, error) {
+	connector := &Connector{Connector: base}
+
+	// Set the metadata provider for the connector
+	connector.SchemaProvider = schema.NewObjectSchemaProvider(
+		connector.HTTPClient().Client,
+		schema.FetchModeParallel,
+		operations.SingleObjectMetadataHandlers{
+			BuildRequest:  connector.buildSingleObjectMetadataRequest,
+			ParseResponse: connector.parseSingleObjectMetadataResponse,
+		},
+	)
+
+	return connector, nil
+}
diff --git a/providers/podium/handlers.go b/providers/podium/handlers.go
new file mode 100644
index 00000000..ccf19cbc
--- /dev/null
+++ b/providers/podium/handlers.go
@@ -0,0 +1,62 @@
+package podium
+
+import (
+	"context"
+	"net/http"
+
+	"github.com/amp-labs/connectors/common"
+	"github.com/amp-labs/connectors/common/naming"
+	"github.com/amp-labs/connectors/common/urlbuilder"
+)
+
+const (
+	limitQuery       = "limit"
+	cursorQuery      = "cursor"
+	metadataPageSize = "1"
+)
+
+type readResponse struct {
+	Data     []map[string]any `json:"data"`
+	Metadata map[string]any   `json:"metadata"`
+}
+
+func (c *Connector) buildSingleObjectMetadataRequest(ctx context.Context, objectName string) (*http.Request, error) {
+	url, err := urlbuilder.New(c.ProviderInfo().BaseURL, restAPIVersion, objectName)
+	if err != nil {
+		return nil, err
+	}
+
+	// Limit response to 1 record data.
+	url.WithQueryParam(limitQuery, metadataPageSize)
+
+	return http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil)
+}
+
+func (c *Connector) parseSingleObjectMetadataResponse(
+	ctx context.Context,
+	objectName string,
+	request *http.Request,
+	response *common.JSONHTTPResponse,
+) (*common.ObjectMetadata, error) {
+	objectMetadata := common.ObjectMetadata{
+		FieldsMap:   make(map[string]string),
+		DisplayName: naming.CapitalizeFirstLetterEveryWord(objectName),
+	}
+
+	data, err := common.UnmarshalJSON[readResponse](response)
+	if err != nil {
+		return nil, common.ErrFailedToUnmarshalBody
+	}
+
+	if len(data.Data) == 0 {
+		return nil, common.ErrMissingExpectedValues
+	}
+
+	firstRecord := data.Data[0]
+
+	for fld := range firstRecord {
+		objectMetadata.FieldsMap[fld] = fld
+	}
+
+	return &objectMetadata, nil
+}


Test patch:
diff --git a/test/podium/connector.go b/test/podium/connector.go
new file mode 100644
index 00000000..ddd3cd51
--- /dev/null
+++ b/test/podium/connector.go
@@ -0,0 +1,50 @@
+package podium
+
+import (
+	"context"
+	"net/http"
+
+	"github.com/amp-labs/connectors/common"
+	"github.com/amp-labs/connectors/common/scanning/credscanning"
+	"github.com/amp-labs/connectors/providers"
+	"github.com/amp-labs/connectors/providers/podium"
+	"github.com/amp-labs/connectors/test/utils"
+	"golang.org/x/oauth2"
+)
+
+func GetConnector(ctx context.Context) *podium.Connector {
+	filePath := credscanning.LoadPath(providers.Podium)
+	reader := utils.MustCreateProvCredJSON(filePath, true, false)
+
+	client, err := common.NewOAuthHTTPClient(ctx,
+		common.WithOAuthClient(http.DefaultClient),
+		common.WithOAuthConfig(getConfig(reader)),
+		common.WithOAuthToken(reader.GetOauthToken()),
+	)
+	if err != nil {
+		utils.Fail(err.Error())
+	}
+
+	conn, err := podium.NewConnector(common.Parameters{
+		AuthenticatedClient: client,
+	})
+	if err != nil {
+		utils.Fail("error creating connector", "error", err)
+	}
+
+	return conn
+}
+
+func getConfig(reader *credscanning.ProviderCredentials) *oauth2.Config {
+	cfg := oauth2.Config{
+		ClientID:     reader.Get(credscanning.Fields.ClientId),
+		ClientSecret: reader.Get(credscanning.Fields.ClientSecret),
+		Endpoint: oauth2.Endpoint{
+			AuthURL:   "https://api.podium.com/oauth/authorize",
+			TokenURL:  "https://api.podium.com/oauth/token",
+			AuthStyle: oauth2.AuthStyleAutoDetect,
+		},
+	}
+
+	return &cfg
+}
diff --git a/test/podium/metadata/main.go b/test/podium/metadata/main.go
new file mode 100644
index 00000000..aaf75d28
--- /dev/null
+++ b/test/podium/metadata/main.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/amp-labs/connectors/test/podium"
+	"github.com/amp-labs/connectors/test/utils"
+)
+
+func main() {
+	if err := run(); err != nil {
+		utils.Fail(err.Error())
+	}
+}
+
+func run() error {
+	ctx := context.Background()
+	connector := podium.GetConnector(ctx)
+
+	m, err := connector.ListObjectMetadata(ctx, []string{"locations", "campaigns", "contacts"})
+	if err != nil {
+		return err
+	}
+
+	// Print the results
+	fmt.Println("Results: ", m.Result)
+	fmt.Println("Errors: ", m.Errors)
+
+	return nil
+}