mirror of
https://github.com/warmbly/warmbly.git
synced 2026-08-18 16:01:18 +00:00
236 lines
8.8 KiB
Go
236 lines
8.8 KiB
Go
package models
|
|
|
|
// ProviderCapability is the static, code-defined descriptor the dashboard renders
|
|
// the onboarding + settings UI from, and that AutomationConfig.Validate checks
|
|
// against. Adding support for a new provider action is "add a descriptor entry +
|
|
// a handler", with no bespoke per-provider React. The descriptor only lists what
|
|
// Warmbly can actually execute today, so the UI never offers a dead action.
|
|
type ProviderCapability struct {
|
|
Provider IntegrationProvider `json:"provider"`
|
|
Directions []SyncDirection `json:"directions"`
|
|
Objects []CapabilityObject `json:"objects,omitempty"`
|
|
Actions []CapabilityAction `json:"actions,omitempty"`
|
|
Pickers []CapabilityPicker `json:"pickers,omitempty"`
|
|
|
|
// SupportsBookingLink is true for scheduling providers (Calendly, Cal.com):
|
|
// the connection stores a scheduling_url that contextual "Book a call"
|
|
// buttons open, prefilled with the contact's email/name.
|
|
SupportsBookingLink bool `json:"supports_booking_link"`
|
|
}
|
|
|
|
// FieldDef is one selectable field in the field-map editor (either a Warmbly
|
|
// source field or a provider destination field).
|
|
type FieldDef struct {
|
|
Key string `json:"key"`
|
|
Label string `json:"label"`
|
|
}
|
|
|
|
// CapabilityObject describes one provider object Warmbly can write (contact,
|
|
// person, lead, deal). WarmblyFields are the source fields a user can map FROM;
|
|
// ExternalFields are the provider destination fields they can map TO. When
|
|
// DynamicFields is true the destination list can be augmented by live discovery
|
|
// (Phase Later); today the static ExternalFields cover the common case.
|
|
type CapabilityObject struct {
|
|
Name string `json:"name"`
|
|
Label string `json:"label"`
|
|
DedupeKeys []string `json:"dedupe_keys"`
|
|
Required []string `json:"required"`
|
|
WarmblyFields []FieldDef `json:"warmbly_fields"`
|
|
ExternalFields []FieldDef `json:"external_fields"`
|
|
DynamicFields bool `json:"dynamic_fields"`
|
|
}
|
|
|
|
// CapabilityAction is a descriptor-level action that maps 1:1 to an
|
|
// IntegrationAction handler.
|
|
type CapabilityAction struct {
|
|
ID IntegrationAction `json:"id"`
|
|
Label string `json:"label"`
|
|
Description string `json:"description"`
|
|
Object string `json:"object,omitempty"`
|
|
NeedsPipeline bool `json:"needs_pipeline,omitempty"`
|
|
NeedsChannel bool `json:"needs_channel,omitempty"`
|
|
NeedsURL bool `json:"needs_url,omitempty"`
|
|
}
|
|
|
|
// CapabilityPicker is a value selector the UI renders (channel, pipeline, stage,
|
|
// owner). Endpoint is the discovery route key (empty = manual entry for now).
|
|
type CapabilityPicker struct {
|
|
Key string `json:"key"`
|
|
Label string `json:"label"`
|
|
Endpoint string `json:"endpoint,omitempty"`
|
|
DependsOn string `json:"depends_on,omitempty"`
|
|
}
|
|
|
|
// Action returns the descriptor for an action id, or nil.
|
|
func (p *ProviderCapability) Action(id IntegrationAction) *CapabilityAction {
|
|
for i := range p.Actions {
|
|
if p.Actions[i].ID == id {
|
|
return &p.Actions[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Object returns the descriptor for an object name, or nil.
|
|
func (p *ProviderCapability) Object(name string) *CapabilityObject {
|
|
for i := range p.Objects {
|
|
if p.Objects[i].Name == name {
|
|
return &p.Objects[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// warmblyContactFields are the Warmbly contact source fields offered in the
|
|
// field-map editor across every CRM provider.
|
|
func warmblyContactFields() []FieldDef {
|
|
return []FieldDef{
|
|
{Key: "email", Label: "Email"},
|
|
{Key: "first_name", Label: "First name"},
|
|
{Key: "last_name", Label: "Last name"},
|
|
{Key: "name", Label: "Full name"},
|
|
{Key: "company", Label: "Company"},
|
|
{Key: "phone", Label: "Phone"},
|
|
}
|
|
}
|
|
|
|
// CapabilityFor returns the descriptor for a provider, or nil if the provider
|
|
// has no configurable capability surface.
|
|
func CapabilityFor(provider IntegrationProvider) *ProviderCapability {
|
|
caps := Capabilities()
|
|
if c, ok := caps[provider]; ok {
|
|
return &c
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Capabilities is the static descriptor registry. It only describes actions with
|
|
// a real execAction handler, so the dashboard never renders a dead control.
|
|
func Capabilities() map[IntegrationProvider]ProviderCapability {
|
|
pushOnly := []SyncDirection{SyncDirectionPush}
|
|
|
|
hubspotContact := CapabilityObject{
|
|
Name: "contact", Label: "Contact",
|
|
DedupeKeys: []string{"email"}, Required: []string{"email"},
|
|
WarmblyFields: warmblyContactFields(),
|
|
ExternalFields: []FieldDef{
|
|
{Key: "email", Label: "Email"},
|
|
{Key: "firstname", Label: "First name"},
|
|
{Key: "lastname", Label: "Last name"},
|
|
{Key: "company", Label: "Company"},
|
|
{Key: "phone", Label: "Phone"},
|
|
{Key: "jobtitle", Label: "Job title"},
|
|
{Key: "website", Label: "Website"},
|
|
{Key: "lifecyclestage", Label: "Lifecycle stage"},
|
|
},
|
|
}
|
|
salesforceContact := CapabilityObject{
|
|
Name: "contact", Label: "Contact",
|
|
DedupeKeys: []string{"email"}, Required: []string{"LastName"},
|
|
WarmblyFields: warmblyContactFields(),
|
|
ExternalFields: []FieldDef{
|
|
{Key: "Email", Label: "Email"},
|
|
{Key: "FirstName", Label: "First name"},
|
|
{Key: "LastName", Label: "Last name"},
|
|
{Key: "Phone", Label: "Phone"},
|
|
{Key: "Title", Label: "Title"},
|
|
},
|
|
}
|
|
pipedrivePerson := CapabilityObject{
|
|
Name: "person", Label: "Person",
|
|
DedupeKeys: []string{"email"}, Required: []string{"name"},
|
|
WarmblyFields: warmblyContactFields(),
|
|
ExternalFields: []FieldDef{
|
|
{Key: "name", Label: "Name"},
|
|
{Key: "email", Label: "Email"},
|
|
{Key: "phone", Label: "Phone"},
|
|
},
|
|
}
|
|
closeLead := CapabilityObject{
|
|
Name: "lead", Label: "Lead",
|
|
DedupeKeys: []string{"email"}, Required: []string{"email"},
|
|
WarmblyFields: warmblyContactFields(),
|
|
ExternalFields: []FieldDef{
|
|
{Key: "name", Label: "Contact name"},
|
|
{Key: "email", Label: "Email"},
|
|
{Key: "phone", Label: "Phone"},
|
|
{Key: "company", Label: "Company / lead name"},
|
|
},
|
|
}
|
|
|
|
return map[IntegrationProvider]ProviderCapability{
|
|
IntegrationHubSpot: {
|
|
Provider: IntegrationHubSpot, Directions: pushOnly,
|
|
Objects: []CapabilityObject{hubspotContact},
|
|
Actions: []CapabilityAction{{
|
|
ID: IntegrationActionHubSpotUpsert, Label: "Create or update contact",
|
|
Description: "Upsert a HubSpot contact (matched by email) and log a timeline note.",
|
|
Object: "contact",
|
|
}},
|
|
},
|
|
IntegrationSalesforce: {
|
|
Provider: IntegrationSalesforce, Directions: pushOnly,
|
|
Objects: []CapabilityObject{salesforceContact},
|
|
Actions: []CapabilityAction{{
|
|
ID: IntegrationActionSalesforceUpsert, Label: "Create or update contact",
|
|
Description: "Upsert a Salesforce Contact (matched by email).",
|
|
Object: "contact",
|
|
}},
|
|
},
|
|
IntegrationPipedrive: {
|
|
Provider: IntegrationPipedrive, Directions: pushOnly,
|
|
Objects: []CapabilityObject{pipedrivePerson},
|
|
Actions: []CapabilityAction{{
|
|
ID: IntegrationActionPipedriveUpsert, Label: "Create or update person",
|
|
Description: "Upsert a Pipedrive person (matched by email).",
|
|
Object: "person",
|
|
}},
|
|
},
|
|
IntegrationClose: {
|
|
Provider: IntegrationClose, Directions: pushOnly,
|
|
Objects: []CapabilityObject{closeLead},
|
|
Actions: []CapabilityAction{{
|
|
ID: IntegrationActionCloseUpsert, Label: "Create or update lead",
|
|
Description: "Upsert a Close lead with an embedded contact (matched by email).",
|
|
Object: "lead",
|
|
}},
|
|
},
|
|
IntegrationSlack: {
|
|
Provider: IntegrationSlack, Directions: pushOnly,
|
|
Actions: []CapabilityAction{{
|
|
ID: IntegrationActionSlackNotify, Label: "Send a Slack message",
|
|
Description: "Post to a channel when the event fires.", NeedsChannel: true,
|
|
}},
|
|
Pickers: []CapabilityPicker{{Key: "channel", Label: "Channel"}},
|
|
},
|
|
IntegrationDiscord: {
|
|
Provider: IntegrationDiscord, Directions: pushOnly,
|
|
Actions: []CapabilityAction{{
|
|
ID: IntegrationActionDiscordNotify, Label: "Send a Discord message",
|
|
Description: "Post to the channel webhook when the event fires.", NeedsURL: true,
|
|
}},
|
|
},
|
|
IntegrationZapier: automationCapability(IntegrationZapier),
|
|
IntegrationMake: automationCapability(IntegrationMake),
|
|
IntegrationN8N: automationCapability(IntegrationN8N),
|
|
IntegrationCalendly: {
|
|
Provider: IntegrationCalendly, Directions: pushOnly, SupportsBookingLink: true,
|
|
},
|
|
IntegrationCalCom: {
|
|
Provider: IntegrationCalCom, Directions: pushOnly, SupportsBookingLink: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
// automationCapability is the shared descriptor for generic outbound-webhook
|
|
// automation providers (Zapier / Make / n8n): a single "send webhook" action.
|
|
func automationCapability(provider IntegrationProvider) ProviderCapability {
|
|
return ProviderCapability{
|
|
Provider: provider, Directions: []SyncDirection{SyncDirectionPush},
|
|
Actions: []CapabilityAction{{
|
|
ID: IntegrationActionGenericWebhookPing, Label: "Send to a webhook",
|
|
Description: "POST the event payload to your automation webhook.", NeedsURL: true,
|
|
}},
|
|
}
|
|
}
|