1package state
2
3import (
4 "bytes"
5 "crypto/hmac"
6 "crypto/sha256"
7 "encoding/hex"
8 "encoding/json"
9 "fmt"
10 "net/http"
11 "net/url"
12 "time"
13)
14
15type SignerTransport struct {
16 Secret string
17}
18
19func (s SignerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
20 timestamp := time.Now().Format(time.RFC3339)
21 mac := hmac.New(sha256.New, []byte(s.Secret))
22 message := req.Method + req.URL.Path + timestamp
23 mac.Write([]byte(message))
24 signature := hex.EncodeToString(mac.Sum(nil))
25 req.Header.Set("X-Signature", signature)
26 req.Header.Set("X-Timestamp", timestamp)
27 return http.DefaultTransport.RoundTrip(req)
28}
29
30type SignedClient struct {
31 Secret string
32 Url *url.URL
33 client *http.Client
34}
35
36func NewSignedClient(domain, secret string, dev bool) (*SignedClient, error) {
37 client := &http.Client{
38 Timeout: 5 * time.Second,
39 Transport: SignerTransport{
40 Secret: secret,
41 },
42 }
43
44 scheme := "https"
45 if dev {
46 scheme = "http"
47 }
48 url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain))
49 if err != nil {
50 return nil, err
51 }
52
53 signedClient := &SignedClient{
54 Secret: secret,
55 client: client,
56 Url: url,
57 }
58
59 return signedClient, nil
60}
61
62func (s *SignedClient) newRequest(method, endpoint string, body []byte) (*http.Request, error) {
63 return http.NewRequest(method, s.Url.JoinPath(endpoint).String(), bytes.NewReader(body))
64}
65
66func (s *SignedClient) Init(did string) (*http.Response, error) {
67 const (
68 Method = "POST"
69 Endpoint = "/init"
70 )
71
72 body, _ := json.Marshal(map[string]any{
73 "did": did,
74 })
75
76 req, err := s.newRequest(Method, Endpoint, body)
77 if err != nil {
78 return nil, err
79 }
80
81 return s.client.Do(req)
82}
83
84func (s *SignedClient) NewRepo(did, repoName, defaultBranch string) (*http.Response, error) {
85 const (
86 Method = "PUT"
87 Endpoint = "/repo/new"
88 )
89
90 body, _ := json.Marshal(map[string]any{
91 "did": did,
92 "name": repoName,
93 "default_branch": defaultBranch,
94 })
95
96 req, err := s.newRequest(Method, Endpoint, body)
97 if err != nil {
98 return nil, err
99 }
100
101 return s.client.Do(req)
102}
103
104func (s *SignedClient) RemoveRepo(did, repoName string) (*http.Response, error) {
105 const (
106 Method = "DELETE"
107 Endpoint = "/repo"
108 )
109
110 body, _ := json.Marshal(map[string]any{
111 "did": did,
112 "name": repoName,
113 })
114
115 req, err := s.newRequest(Method, Endpoint, body)
116 if err != nil {
117 return nil, err
118 }
119
120 return s.client.Do(req)
121}
122
123func (s *SignedClient) AddMember(did string) (*http.Response, error) {
124 const (
125 Method = "PUT"
126 Endpoint = "/member/add"
127 )
128
129 body, _ := json.Marshal(map[string]any{
130 "did": did,
131 })
132
133 req, err := s.newRequest(Method, Endpoint, body)
134 if err != nil {
135 return nil, err
136 }
137
138 return s.client.Do(req)
139}
140
141func (s *SignedClient) AddCollaborator(ownerDid, repoName, memberDid string) (*http.Response, error) {
142 const (
143 Method = "POST"
144 )
145 endpoint := fmt.Sprintf("/%s/%s/collaborator/add", ownerDid, repoName)
146
147 body, _ := json.Marshal(map[string]any{
148 "did": memberDid,
149 })
150
151 req, err := s.newRequest(Method, endpoint, body)
152 if err != nil {
153 return nil, err
154 }
155
156 return s.client.Do(req)
157}
158
159func (s *SignedClient) Merge(patch []byte, ownerDid, targetRepo, branch string) (*http.Response, error) {
160 const (
161 Method = "POST"
162 )
163 endpoint := fmt.Sprintf("/%s/%s/merge", ownerDid, targetRepo)
164
165 body, _ := json.Marshal(map[string]interface{}{
166 "patch": string(patch),
167 "branch": branch,
168 })
169
170 req, err := s.newRequest(Method, endpoint, body)
171 if err != nil {
172 return nil, err
173 }
174
175 return s.client.Do(req)
176}
177
178func (s *SignedClient) MergeCheck(patch []byte, ownerDid, targetRepo, branch string) (*http.Response, error) {
179 const (
180 Method = "POST"
181 )
182 endpoint := fmt.Sprintf("/%s/%s/merge/check", ownerDid, targetRepo)
183
184 body, _ := json.Marshal(map[string]interface{}{
185 "patch": string(patch),
186 "branch": branch,
187 })
188
189 req, err := s.newRequest(Method, endpoint, body)
190 if err != nil {
191 return nil, err
192 }
193
194 return s.client.Do(req)
195}
196
197type UnsignedClient struct {
198 Url *url.URL
199 client *http.Client
200}
201
202func NewUnsignedClient(domain string, dev bool) (*UnsignedClient, error) {
203 client := &http.Client{
204 Timeout: 5 * time.Second,
205 }
206
207 scheme := "https"
208 if dev {
209 scheme = "http"
210 }
211 url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain))
212 if err != nil {
213 return nil, err
214 }
215
216 unsignedClient := &UnsignedClient{
217 client: client,
218 Url: url,
219 }
220
221 return unsignedClient, nil
222}
223
224func (us *UnsignedClient) newRequest(method, endpoint string, body []byte) (*http.Request, error) {
225 return http.NewRequest(method, us.Url.JoinPath(endpoint).String(), bytes.NewReader(body))
226}
227
228func (us *UnsignedClient) Index(ownerDid, repoName, ref string) (*http.Response, error) {
229 const (
230 Method = "GET"
231 )
232
233 endpoint := fmt.Sprintf("/%s/%s/tree/%s", ownerDid, repoName, ref)
234 if ref == "" {
235 endpoint = fmt.Sprintf("/%s/%s", ownerDid, repoName)
236 }
237
238 req, err := us.newRequest(Method, endpoint, nil)
239 if err != nil {
240 return nil, err
241 }
242
243 return us.client.Do(req)
244}
245
246func (us *UnsignedClient) Branches(ownerDid, repoName string) (*http.Response, error) {
247 const (
248 Method = "GET"
249 )
250
251 endpoint := fmt.Sprintf("/%s/%s/branches", ownerDid, repoName)
252
253 req, err := us.newRequest(Method, endpoint, nil)
254 if err != nil {
255 return nil, err
256 }
257
258 return us.client.Do(req)
259}