-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclient.go
207 lines (184 loc) · 6.15 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package gosail
import (
"bytes"
"crypto/md5"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"sort"
"time"
)
var allowedJobTypes = map[string]string{"export_list_data": "export_list_data"}
var apiBaseURL = "https://api.sailthru.com"
var apiURLGet = "%v/%v?json=%v&api_key=%v&sig=%v&format=%v"
var apiURLPost = "%v/%v?format=%v"
//SailThruClient Struct that contains key & hashing locations for sailthru calls
type SailThruClient struct {
apiKey string
secretKey string
jsonhashstring string
httpClient HTTPClienter
baseURL string
}
//Job struct that contains json marshalled data about a sailthru Job
type Job struct {
JobID string `json:"job_id"`
Name string `json:"name"`
List string `json:"list"`
Status string `json:"status"`
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
Filename string `json:"filename"`
Expired bool `json:"expired"`
ExportURL string `json:"export_url"`
}
//CreateJobResponse struct that contains the result of a Create Job Request.
type CreateJobResponse struct {
JobID string `json:"job_id"`
Name string `json:"name"`
List string `json:"list"`
Status string `json:"status"`
}
//APIConfig : struct that holds keys, url info for the SailThruAPI calls
type APIConfig struct {
APIKey string
SecretKey string
BaseURL string
}
type bodyJSON struct {
Body string
EscBody string
}
//NewSailThruClient func that creates a sailthruclient instance for calls to the SailThruAPI
func NewSailThruClient(client HTTPClienter, config APIConfig) SailThruClient {
sc := SailThruClient{config.APIKey, config.SecretKey, "%v%vjson%v", client, config.BaseURL}
return sc
}
func (sc *SailThruClient) getSignatureString(params map[string]string) string {
stringtohash := ""
keys := []string{}
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
for _, key := range keys {
stringtohash += params[key]
}
return fmt.Sprintf(sc.jsonhashstring, sc.secretKey, sc.apiKey, stringtohash)
}
func (sc *SailThruClient) getSigHash(strToHash string) string {
h := md5.New()
io.WriteString(h, strToHash)
sig := fmt.Sprintf("%x", h.Sum(nil))
return sig
}
func (sc *SailThruClient) getJSONStringBody(items map[string]interface{}) string {
jsonparams, _ := json.Marshal(items)
return string(jsonparams)
}
func (sc *SailThruClient) getJSONBody(data map[string]interface{}) bodyJSON {
b := bodyJSON{}
b.Body = sc.getJSONStringBody(data)
b.EscBody = url.QueryEscape(b.Body)
return b
}
func (sc *SailThruClient) getSigForJSONBody(params map[string]string) string {
str := sc.getSignatureString(params)
hash := sc.getSigHash(str)
return hash
}
func (sc *SailThruClient) getPostForm(items map[string]interface{}) url.Values {
jsonb := sc.getJSONBody(items)
data := map[string]string{"json": jsonb.Body}
sig := sc.getSigForJSONBody(data)
form := url.Values{}
form.Set("api_key", sc.apiKey)
form.Set("sig", sig)
form.Set("json", jsonb.Body)
form.Set("format", "json")
return form
}
//CreateJob Func that creates a sailthru job. Call must specify the type of job, the name of the list and the format of the returned data (json|xml)
//Keep in mind that CreateJob does not immediately return the contents of the job, it starts the job and returns a jobID. The status of the job is checked via the GetJob func
func (sc *SailThruClient) CreateJob(jobType string, listName string, fields map[string]map[string]interface{}, format string) (*CreateJobResponse, error) {
r := CreateJobResponse{}
if _, ok := allowedJobTypes[jobType]; !ok {
return nil, fmt.Errorf("Invalid jobType: %v", jobType)
}
posturl := fmt.Sprintf(apiURLPost, sc.baseURL, "job", format)
items := map[string]interface{}{"job": jobType, "list": listName, "fields": fields}
form := sc.getPostForm(items)
req, reqErr := http.NewRequest("POST", posturl, bytes.NewBufferString(form.Encode()))
if reqErr != nil {
return nil, reqErr
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, errDo := sc.httpClient.Do(req)
if errDo != nil {
return nil, errDo
}
defer resp.Body.Close()
body, errRead := ioutil.ReadAll(resp.Body)
if errRead != nil {
return nil, errRead
}
errJSON := json.Unmarshal(body, &r)
if errJSON != nil {
return nil, errJSON
}
return &r, nil
}
//GetJob Func that takes a jobID, which is returned by CreateJob and a format (json|xml) to get back the status of a CreateJob func call
func (sc *SailThruClient) GetJob(jobID string) (*Job, error) {
items := map[string]interface{}{"job_id": jobID}
jsonb := sc.getJSONBody(items)
data := map[string]string{"json": jsonb.Body}
sig := sc.getSigForJSONBody(data)
apiurl := fmt.Sprintf(apiURLGet, sc.baseURL, "job", jsonb.EscBody, sc.apiKey, sig, "json")
res, errHTTP := sc.httpClient.Get(apiurl)
if errHTTP != nil {
return nil, errHTTP
}
if res.StatusCode < 200 || res.StatusCode > 299 {
return nil, fmt.Errorf("Error Response: %v", res.Status)
}
output, _ := ioutil.ReadAll(res.Body)
job := Job{}
errJSON := json.Unmarshal([]byte(output), &job)
return &job, errJSON
}
//GetCSVData If the job has completed and it has not expired, this call will return the data in the CSV file the job created
func (sc *SailThruClient) GetCSVData(path string) (io.ReadCloser, error) {
res, errGet := sc.httpClient.Get(path)
if errGet != nil {
return nil, errGet
}
return res.Body, nil
}
//CreateJobAndReturnJob This will create the job, and then return the contents of the job, providing it does not timeout(value is seconds)
func (sc *SailThruClient) CreateJobAndReturnJob(jobType string, listName string, fields map[string]map[string]interface{}, format string, timeout int) (io.ReadCloser, error) {
cjresp, err := sc.CreateJob(jobType, listName, fields, format)
if err != nil {
return nil, err
}
timer := time.Tick(100 * time.Millisecond)
start := time.Now()
for now := range timer {
_ = now
j, errJ := sc.GetJob(cjresp.JobID)
if errJ != nil {
return nil, errJ
}
if j.Status == "completed" && !j.Expired {
return sc.GetCSVData(j.ExportURL)
}
delta := time.Now().Sub(start)
if delta.Seconds() > float64(timeout) {
break
}
}
return nil, fmt.Errorf("Timeout Error - Job not ready after %v seconds\n", timeout)
}