-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontroller.go
158 lines (127 loc) · 3.81 KB
/
controller.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
package gozabo
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"strconv"
"time"
"github.com/pkg/errors"
)
const SandboxURL = "https://api.zabo.com/sandbox-v1"
const LiveURL = "https://api.zabo.com/v1"
type Config struct {
ClientID string `mapstructure:"clientID"`
APIKey string `mapstructure:"apiKey"`
APISecret string `mapstructure:"apiSecret"`
Sandbox bool `mapstructure:"sandbox"`
}
// Controller is the struct for budget insight controller
type Controller struct {
config Config
httpClient *http.Client
whListeners WebhooksListeners
}
// New create new budget insight controller
func NewController(config Config, listeners WebhooksListeners) *Controller {
return &Controller{
config: config,
httpClient: &http.Client{},
whListeners: listeners,
}
}
// apiErrors map api errors by errors code
var apiErrors = map[string]error{
"invalid_client": ErrAPIInvalidClientID,
"invalid_grant": ErrAPIInvalidGrant,
}
var (
ErrAPIUnhandled = errors.New("unhandled budget insight api error")
ErrAPIInvalidClientID = errors.New("invalid budget insight client id")
ErrAPIInvalidGrant = errors.New("invalid budget insight grant")
)
func (ctrl *Controller) request(method string, route string, queryParams map[string]string, requestData interface{}, token string, responseData interface{}) (err error) {
URL := ctrl.getURL(route)
log.Printf("%s at %s", method, URL)
var req *http.Request
var requestBody []byte
if requestData != nil {
if method != http.MethodPost && method != http.MethodPut {
return fmt.Errorf("request data can't be sended with %s", method)
}
requestBody, err := json.Marshal(requestData)
if err != nil {
return errors.Wrap(err, "failed to marshal request data")
}
log.Printf("json data: %s", requestBody)
req, err = http.NewRequest(method, URL, bytes.NewBuffer(requestBody))
if err != nil {
return errors.Wrap(err, "failed to create request")
}
} else {
req, err = http.NewRequest(method, URL, nil)
if err != nil {
return errors.Wrap(err, "failed to create request")
}
}
timestamp := strconv.Itoa(int(time.Now().Unix()))
// manage signature as described at https://zabo.com/docs/#signing-requests
sigHash := hmac.New(sha256.New, []byte(ctrl.config.APISecret))
sigHash.Write([]byte(timestamp + URL + string(requestBody)))
sig := hex.EncodeToString(sigHash.Sum(nil))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Zabo-Key", ctrl.config.APIKey)
req.Header.Set("X-Zabo-Sig", sig)
req.Header.Set("X-Zabo-Timestamp", ctrl.config.APIKey)
q := req.URL.Query()
for key, value := range queryParams {
q.Add(key, value)
}
req.URL.RawQuery = q.Encode()
resp, err := ctrl.httpClient.Do(req)
if err != nil {
return errors.Wrap(err, "failed to do request")
}
defer resp.Body.Close()
log.Printf("response status: %s", resp.Status)
success := resp.StatusCode >= 200 && resp.StatusCode < 300
if responseData != nil || !success {
var errData ErrorResponse
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return errors.Wrap(err, "failed to read response body")
}
log.Printf("response body : %s", body)
dataToDecode := responseData
if !success {
dataToDecode = &errData
}
err = json.Unmarshal(body, dataToDecode)
if err != nil {
return errors.Wrap(err, "failed to read response body json")
}
if !success {
// manage error data
apiErr, errHandled := apiErrors[errData.Code]
if !errHandled {
apiErr = errors.Wrap(ErrAPIUnhandled, errData.Code)
}
return errors.Wrap(apiErr, errData.Description)
}
}
return err
}
func (ctrl *Controller) getURL(route string) string {
var baseURL string
if ctrl.config.Sandbox {
baseURL = SandboxURL
} else {
baseURL = LiveURL
}
return fmt.Sprintf("%s%s", baseURL, route)
}