diff --git a/carmaclient.go b/carmaclient.go new file mode 100644 index 0000000..92c9eb7 --- /dev/null +++ b/carmaclient.go @@ -0,0 +1,125 @@ +package carmaclient + +import ( + "fmt" + "io/ioutil" + "net/http" + "time" + + "bytes" + "encoding/json" + "github.com/moonwalker/logger" + "errors" +) + +const ( + timeout = 30 * time.Second + carmaAuthHeader = "X-Carma-Authentication-Token" +) + +type CarmaClient struct { + HTTPClient *http.Client + + carmaApiToken string + carmaOrganization int64 + carmaRESTURL string + carmaControllerURL string + + log logger.Logger + + common service + Triggers *TriggersService + Lists *ListsService + Properties *PropertiesService +} + +type service struct { + client *CarmaClient +} + +type carmaResponse struct { + statusCode int + data []byte + err error + msg string +} + +type carmaError struct { + Error string `json:"error"` +} + +func NewCarmaClient(organization int64, token, restURL, controllerURL string, log logger.Logger) (client *CarmaClient, err error) { + client = &CarmaClient{ + HTTPClient: &http.Client{ + Timeout: timeout, + }, + carmaApiToken: token, + carmaOrganization: organization, + carmaRESTURL: restURL, + carmaControllerURL: controllerURL, + log: log, + } + + client.common.client = client + + client.Triggers = (*TriggersService)(&client.common) + client.Lists = (*ListsService)(&client.common) + client.Properties = (*PropertiesService)(&client.common) + + return +} + +func (c CarmaClient) catchError(carmaResp *carmaResponse) { + carmaError := &carmaError{} + err := json.Unmarshal(carmaResp.data, carmaError) + if err != nil { + return + } + + carmaResp.err = errors.New(carmaError.Error) +} + +func (c CarmaClient) carmaRequest(endpoint string, method string, body interface{}) (carmaResp carmaResponse) { + client := http.Client{ + Timeout: 10 * time.Second, + } + + url := fmt.Sprintf("%s/%d/%s", c.carmaRESTURL, c.carmaOrganization, endpoint) + + b := new(bytes.Buffer) + if body != nil { + json.NewEncoder(b).Encode(body) + } + + req, err := http.NewRequest(method, url, b) + if err != nil { + carmaResp.err = err + return + } + + req.Header[carmaAuthHeader] = []string{c.carmaApiToken} + req.Header["Accept"] = []string{"application/json"} + req.Header["Content-Type"] = []string{"application/json"} + + resp, err := client.Do(req) + if err != nil { + carmaResp.err = err + return + } + + defer resp.Body.Close() + + carmaResp.statusCode = resp.StatusCode + + respBody, err := ioutil.ReadAll(resp.Body) + carmaResp.err = err + carmaResp.data = respBody + c.catchError(&carmaResp) + + return +} + +type RequestResponse struct { + HTTPStatusCode int + Error error +} diff --git a/dto/campaigntype.go b/dto/campaigntype.go new file mode 100644 index 0000000..4b30b06 --- /dev/null +++ b/dto/campaigntype.go @@ -0,0 +1,19 @@ +package dto + +type CampaignType int64 + +const ( + CampaignTypeEmail CampaignType = 1 + CampaignTypeSMS CampaignType = 2 + CampaignTypeDeleted CampaignType = 4 + CampaignTypeNormalApproved CampaignType = 5 + CampaignTypeIncomingSMS CampaignType = 6 + CampaignTypeSMSWithOADC CampaignType = 8 + CampaignTypeEmailWithAttachement CampaignType = 14 + CampaignTypeEmailSubdelivey CampaignType = 16 + CampaignTypeEmailMasterDelivery CampaignType = 17 + CampaignTypeSMSLinkMobilityDelivery CampaignType = 18 + CampaignTypeAppPushAndroidDelivery CampaignType = 19 + CampaignTypeAppPushIOSDelivery CampaignType = 20 + CampaignTypeAppPushWindowsDelivery CampaignType = 21 +) diff --git a/dto/campaignversion.go b/dto/campaignversion.go new file mode 100644 index 0000000..925011f --- /dev/null +++ b/dto/campaignversion.go @@ -0,0 +1,15 @@ +package dto + +type CampaignVersionDTO struct { + CampaignID int64 `json:"campaignId"` + CampaignName string `json:"campaignName"` + CampaignTypeID int64 `json:"campaignTypeId"` + Active bool `json:"active"` + Locked bool `json:"locked"` + DateCreated int64 `json:"dateCreated"` + DateModified int64 `json:"dateModified"` + UserCreated string `json:"userCreated"` + UserModified string `json:"userModified"` + Stats StatDTO `json:"statDtos"` + LastActivated int64 `json:"lastActivated"` +} diff --git a/dto/channeltype.go b/dto/channeltype.go new file mode 100644 index 0000000..ace3985 --- /dev/null +++ b/dto/channeltype.go @@ -0,0 +1,11 @@ +package dto + +type ChannelType int64 + +const ( + ChannelTypeUnknown ChannelType = 0 + ChannelTypeEmail ChannelType = 1 + ChannelTypeSMS ChannelType = 2 + ChannelTypePush ChannelType = 3 + ChannelTypeMixed ChannelType = 4 +) \ No newline at end of file diff --git a/dto/contact.go b/dto/contact.go new file mode 100644 index 0000000..6bdec8d --- /dev/null +++ b/dto/contact.go @@ -0,0 +1,36 @@ +package dto + +type ContactDTO struct { + ID int64 `json:"id,omitempty"` + ListID int64 `json:"listId"` + Country string `json:"country,omitempty"` + OriginalID string `json:"originalId"` + OriginalIDHashed int64 `json:"originalIDHashed,omitempty"` + FirstName string `json:"firstName,omitempty"` + LastName string `json:"lastName,omitempty"` + MiddleName string `json:"middleName,omitempty"` + EmailAddress string `json:"emailAddress,omitempty"` + Title string `json:"title,omitempty"` + DateOfBirth string `json:"dateOfBirth,omitempty"` + City string `json:"city,omitempty"` + ZipCode string `json:"zipcode,omitempty"` + Sex string `json:"sex,omitempty"` + MobileNumber string `json:"mobileNumber,omitempty"` + Unsubscribed bool `json:"unsubscribed"` + Bounced bool `json:"bounced"` + MobileUnsubscribed bool `json:"mobileUnsubscribed"` + AudioUnsubscribed bool `json:"audioUnsubscribed"` + PreferredContentVersionID int64 `json:"preferredContentVersionId,omitempty"` + OptOutDate int64 `json:"optOutDate"` + DateOfInvalidation int64 `json:"dateOfInvalidation"` + OptOutMobileDate int64 `json:"optOutMobileDate"` + OptOutAudioDate int64 `json:"optOutAudioDate"` + Active bool `json:"active"` + DateCreated int64 `json:"dateCreated,omitempty"` + DateModified int64 `json:"dateModified,omitempty"` + BlockedUntil int64 `json:"blockedUntil,omitempty"` + Properties map[string]string `json:"properties,omitempty"` + NamedProperties map[string]string `json:"namedProperties,omitempty"` + MobileNumberValid bool `json:"mobileNumberValid,omitempty"` + //deviceInfo (java.util.LinkedList, optional), +} diff --git a/dto/datatype.go b/dto/datatype.go new file mode 100644 index 0000000..62f66c8 --- /dev/null +++ b/dto/datatype.go @@ -0,0 +1,10 @@ +package dto + +type DataType string + +const ( + DataTypeString DataType = "STRING" + DataTypeInteger DataType = "INTEGER" + DataTypeBigInt DataType = "BIGINT" + DataTypeDateTime DataType = "DATETIME" +) diff --git a/dto/message.go b/dto/message.go new file mode 100644 index 0000000..aaf31fc --- /dev/null +++ b/dto/message.go @@ -0,0 +1,5 @@ +package dto + +type MessageDTO struct { + Message string `json:"message"` +} diff --git a/dto/property.go b/dto/property.go new file mode 100644 index 0000000..a003010 --- /dev/null +++ b/dto/property.go @@ -0,0 +1,16 @@ +package dto + +type PropertyDTO struct { + ID int64 `json:"id"` + PropertyTypeID PropertyType `json:"propertyTypeId"` // type of property + Name string `json:"name"` + Description string `json:"description,omitempty"` + DataType DataType `json:"dataType,omitempty"` // Data type to use for this property in Business Analytics export. Null means 'default', i.e. interpret value as a string + IncludeInBaExport bool `json:"includeInBaExport,omitempty"` // Whether this property should be included in Business Analytics export. Defaults to false + DateCreated string `json:"dateCreated"` // read only, populated by server + DateModified string `json:"dateModified"` // read only, populated by server + UserCreated string `json:"userCreated"` // read only, populated by server + UserModified string `json:"userModified"` // read only, populated by server + StaticProperty bool `json:"staticProperty"` // is this a static property + FileHeaders []string `json:"fileHeaders,omitempty"` +} diff --git a/dto/propertytype.go b/dto/propertytype.go new file mode 100644 index 0000000..f460b40 --- /dev/null +++ b/dto/propertytype.go @@ -0,0 +1,9 @@ +package dto + +type PropertyType int64 + +const ( + PropertyTypeEndUser PropertyType = 1 + PropertyTypeEncrypted PropertyType = 1 + PropertyTypeDeleted PropertyType = 1 +) diff --git a/dto/sendtrigger.go b/dto/sendtrigger.go new file mode 100644 index 0000000..e2e3adf --- /dev/null +++ b/dto/sendtrigger.go @@ -0,0 +1,12 @@ +package dto + +type SendTriggerDTO struct { + DeliveryTime string `json:"deliveryTime,omitempty"` // When is the trigger to be sent? Trigger will be sent immediately if the value is omitted, format shall be yyyy-MM-ddTHH:mm:ss-zzzz -zzzz is timezone difference from UCT sweden is +01000 , e.g. 2016-02-09T11:00:00+0100, + OptOut bool `json:"optOut,omitempty"` // if true the user will unsubscribed, + SaveProps bool `json:"saveProps,omitempty"` // True to update propertydata on the contact, + OriginalId string `json:"originalId"` // originalId of the contact the trigger is to be sent to, + CustomerId int64 `json:"customerId"` + CampaignId int64 `json:"campaignId,omitempty"` // If supplied the trigger willsend this delivery instead of the triggers active delivery, + Properties map[string]string `json:"properties"` // the data for the trigger, + ForceCreateMissingProperties bool `json:"forceCreateMissingProperties,omitempty"` // Automatically create missing properties. If false, missing property names will throw exception +} diff --git a/dto/stat.go b/dto/stat.go new file mode 100644 index 0000000..1ad9042 --- /dev/null +++ b/dto/stat.go @@ -0,0 +1,25 @@ +package dto + +type StatDTO struct { + ID int64 `json:"id"` + DeliveryID int64 `json:"deliveryId"` + Source int64 `json:"source"` + ProjectID int64 `json:"projectId"` + DeliveryTypeID int64 `json:"deliveryTypeId"` + Recipients int64 `json:"recipients"` + Sent int64 `json:"sent"` + Opened int64 `json:"opened"` + Clicked int64 `json:"clicked"` + OpenedInBrowser int64 `json:"openedInBrower"` + NonDeliverable int64 `json:"nonDeliverable"` + Unsubscribe int64 `json:"unsubscribed"` + NotBounced int64 `json:"notBounced"` + SoftBounced int64 `json:"softBounced"` + HardBounced int64 `json:"hardBounced"` + Complaints int64 `json:"complaints"` + ShareTarget int64 `json:"shareTarget"` + MailClick int64 `json:"mailClick"` + Conversions int64 `json:"conversions"` + UniqueClick int64 `json:"uniqueClick"` + Amount float64 `json:"amount"` +} diff --git a/dto/trigger.go b/dto/trigger.go new file mode 100644 index 0000000..21b4b65 --- /dev/null +++ b/dto/trigger.go @@ -0,0 +1,19 @@ +package dto + +type TriggerDTO struct { + ID int64 `json:"id"` + Type int64 `json:"type"` + ProjectID int64 `json:"projectId"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + ListID int64 `json:"listId"` + DateCreated int64 `json:"dateCreated,omitempty"` + DateModified int64 `json:"dateModified,omitempty"` + UserCreated string `json:"userCreated,omitempty"` + UserModified string `json:"userModified,omitempty"` + ChannelTypeID ChannelType `json:"channelTypeId"` + ActiveCampaign int64 `json:"activeCampaign"` + IgnoreOptOut bool `json:"ignoreOptOut,omitempty"` + CampaignVersions []CampaignVersionDTO `json:"campaignVersions,omitempty"` + Active bool `json:"active,omitempty"` +} diff --git a/lists.go b/lists.go new file mode 100644 index 0000000..e9aee2e --- /dev/null +++ b/lists.go @@ -0,0 +1,48 @@ +package carmaclient + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/moonwalker/carmaclient/dto" + "net/http" + "net/url" +) + +type ListsService service + +func (s *ListsService) GetContact(listID int64, originalID string) (*dto.ContactDTO, error) { + response := s.client.carmaRequest(fmt.Sprintf("lists/%d/contacts/%s", listID, url.QueryEscape(originalID)), http.MethodGet, nil) + if response.err != nil { + return nil, response.err + } + + if response.statusCode == http.StatusNotFound { + return nil, errors.New(http.StatusText(response.statusCode)) + } + + responseDTO := &dto.ContactDTO{} + + err := json.Unmarshal(response.data, responseDTO) + if err != nil { + return nil, err + } + + return responseDTO, nil +} + +func (s *ListsService) PutContactUpdate(listID int64, originalID string, contact dto.ContactDTO) (*dto.ContactDTO, error) { + response := s.client.carmaRequest(fmt.Sprintf("lists/%d/contacts/%s/update?force=true", listID, url.QueryEscape(originalID)), http.MethodPut, contact) + if response.err != nil { + return nil, response.err + } + + responseDTO := &dto.ContactDTO{} + + err := json.Unmarshal(response.data, responseDTO) + if err != nil { + return nil, err + } + + return responseDTO, nil +} diff --git a/properties.go b/properties.go new file mode 100644 index 0000000..add2a4a --- /dev/null +++ b/properties.go @@ -0,0 +1,29 @@ +package carmaclient + +import ( + "fmt" + "net/http" + "encoding/json" + + "github.com/moonwalker/carmaclient/dto" +) + +type PropertiesService service + +func (s *PropertiesService) GetProperties() (*[]dto.PropertyDTO, error) { + response := s.client.carmaRequest("properties", http.MethodGet, nil) + if response.err != nil { + return nil, response.err + } + + fmt.Println(string(response.data)) + + responseDTO := &[]dto.PropertyDTO{} + + err := json.Unmarshal(response.data, responseDTO) + if err != nil { + return nil, err + } + + return responseDTO, nil +} \ No newline at end of file diff --git a/triggers.go b/triggers.go new file mode 100644 index 0000000..1b51947 --- /dev/null +++ b/triggers.go @@ -0,0 +1,50 @@ +package carmaclient + +import ( + "encoding/json" + "net/http" + + "github.com/moonwalker/carmaclient/dto" + "fmt" + "errors" +) + +type TriggersService service + +func (s *TriggersService) GetTrigger(triggerID int64) (*dto.TriggerDTO, error) { + response := s.client.carmaRequest(fmt.Sprintf("triggers/%d", triggerID), http.MethodGet, nil) + if response.err != nil { + return nil, response.err + } + + fmt.Println(string(response.data)) + + responseDTO := &dto.TriggerDTO{} + + err := json.Unmarshal(response.data, responseDTO) + if err != nil { + return nil, err + } + + return responseDTO, nil +} + +func (s *TriggersService) PostTriggersMessages(triggerID int64, triggerDTO dto.SendTriggerDTO) (*dto.MessageDTO, error) { + response := s.client.carmaRequest(fmt.Sprintf("triggers/%d/messages", triggerID), http.MethodPost, triggerDTO) + if response.err != nil { + return nil, response.err + } + + if response.statusCode != http.StatusOK { + return nil, errors.New(http.StatusText(response.statusCode)) + } + + responseDTO := &dto.MessageDTO{} + + err := json.Unmarshal(response.data, responseDTO) + if err != nil { + return nil, err + } + + return responseDTO, nil +}