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 "tangled.sh/tangled.sh/core/types"
15)
16
17type SignerTransport struct {
18 Secret string
19}
20
21func (s SignerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
22 timestamp := time.Now().Format(time.RFC3339)
23 mac := hmac.New(sha256.New, []byte(s.Secret))
24 message := req.Method + req.URL.Path + timestamp
25 mac.Write([]byte(message))
26 signature := hex.EncodeToString(mac.Sum(nil))
27 req.Header.Set("X-Signature", signature)
28 req.Header.Set("X-Timestamp", timestamp)
29 return http.DefaultTransport.RoundTrip(req)
30}
31
32type SignedClient struct {
33 Secret string
34 Url *url.URL
35 client *http.Client
36}
37
38func NewSignedClient(domain, secret string, dev bool) (*SignedClient, error) {
39 client := &http.Client{
40 Timeout: 5 * time.Second,
41 Transport: SignerTransport{
42 Secret: secret,
43 },
44 }
45
46 scheme := "https"
47 if dev {
48 scheme = "http"
49 }
50 url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain))
51 if err != nil {
52 return nil, err
53 }
54
55 signedClient := &SignedClient{
56 Secret: secret,
57 client: client,
58 Url: url,
59 }
60
61 return signedClient, nil
62}
63
64func (s *SignedClient) newRequest(method, endpoint string, body []byte) (*http.Request, error) {
65 return http.NewRequest(method, s.Url.JoinPath(endpoint).String(), bytes.NewReader(body))
66}
67
68func (s *SignedClient) Init(did string) (*http.Response, error) {
69 const (
70 Method = "POST"
71 Endpoint = "/init"
72 )
73
74 body, _ := json.Marshal(map[string]any{
75 "did": did,
76 })
77
78 req, err := s.newRequest(Method, Endpoint, body)
79 if err != nil {
80 return nil, err
81 }
82
83 return s.client.Do(req)
84}
85
86func (s *SignedClient) NewRepo(did, repoName, defaultBranch string) (*http.Response, error) {
87 const (
88 Method = "PUT"
89 Endpoint = "/repo/new"
90 )
91
92 body, _ := json.Marshal(map[string]any{
93 "did": did,
94 "name": repoName,
95 "default_branch": defaultBranch,
96 })
97
98 req, err := s.newRequest(Method, Endpoint, body)
99 if err != nil {
100 return nil, err
101 }
102
103 return s.client.Do(req)
104}
105
106func (s *SignedClient) ForkRepo(ownerDid, source, name string) (*http.Response, error) {
107 const (
108 Method = "POST"
109 Endpoint = "/repo/fork"
110 )
111
112 body, _ := json.Marshal(map[string]any{
113 "did": ownerDid,
114 "source": source,
115 "name": name,
116 })
117
118 req, err := s.newRequest(Method, Endpoint, body)
119 if err != nil {
120 return nil, err
121 }
122
123 return s.client.Do(req)
124}
125
126func (s *SignedClient) RemoveRepo(did, repoName string) (*http.Response, error) {
127 const (
128 Method = "DELETE"
129 Endpoint = "/repo"
130 )
131
132 body, _ := json.Marshal(map[string]any{
133 "did": did,
134 "name": repoName,
135 })
136
137 req, err := s.newRequest(Method, Endpoint, body)
138 if err != nil {
139 return nil, err
140 }
141
142 return s.client.Do(req)
143}
144
145func (s *SignedClient) AddMember(did string) (*http.Response, error) {
146 const (
147 Method = "PUT"
148 Endpoint = "/member/add"
149 )
150
151 body, _ := json.Marshal(map[string]any{
152 "did": did,
153 })
154
155 req, err := s.newRequest(Method, Endpoint, body)
156 if err != nil {
157 return nil, err
158 }
159
160 return s.client.Do(req)
161}
162
163func (s *SignedClient) SetDefaultBranch(ownerDid, repoName, branch string) (*http.Response, error) {
164 const (
165 Method = "PUT"
166 )
167 endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName)
168
169 body, _ := json.Marshal(map[string]any{
170 "branch": branch,
171 })
172
173 req, err := s.newRequest(Method, endpoint, body)
174 if err != nil {
175 return nil, err
176 }
177
178 return s.client.Do(req)
179}
180
181func (s *SignedClient) AddCollaborator(ownerDid, repoName, memberDid string) (*http.Response, error) {
182 const (
183 Method = "POST"
184 )
185 endpoint := fmt.Sprintf("/%s/%s/collaborator/add", ownerDid, repoName)
186
187 body, _ := json.Marshal(map[string]any{
188 "did": memberDid,
189 })
190
191 req, err := s.newRequest(Method, endpoint, body)
192 if err != nil {
193 return nil, err
194 }
195
196 return s.client.Do(req)
197}
198
199func (s *SignedClient) Merge(
200 patch []byte,
201 ownerDid, targetRepo, branch, commitMessage, commitBody, authorName, authorEmail string,
202) (*http.Response, error) {
203 const (
204 Method = "POST"
205 )
206 endpoint := fmt.Sprintf("/%s/%s/merge", ownerDid, targetRepo)
207
208 mr := types.MergeRequest{
209 Branch: branch,
210 CommitMessage: commitMessage,
211 CommitBody: commitBody,
212 AuthorName: authorName,
213 AuthorEmail: authorEmail,
214 Patch: string(patch),
215 }
216
217 body, _ := json.Marshal(mr)
218
219 req, err := s.newRequest(Method, endpoint, body)
220 if err != nil {
221 return nil, err
222 }
223
224 return s.client.Do(req)
225}
226
227func (s *SignedClient) MergeCheck(patch []byte, ownerDid, targetRepo, branch string) (*http.Response, error) {
228 const (
229 Method = "POST"
230 )
231 endpoint := fmt.Sprintf("/%s/%s/merge/check", ownerDid, targetRepo)
232
233 body, _ := json.Marshal(map[string]any{
234 "patch": string(patch),
235 "branch": branch,
236 })
237
238 req, err := s.newRequest(Method, endpoint, body)
239 if err != nil {
240 return nil, err
241 }
242
243 return s.client.Do(req)
244}
245
246func (s *SignedClient) NewHiddenRef(ownerDid, targetRepo, forkBranch, remoteBranch string) (*http.Response, error) {
247 const (
248 Method = "POST"
249 )
250 endpoint := fmt.Sprintf("/%s/%s/hidden-ref/%s/%s", ownerDid, targetRepo, forkBranch, remoteBranch)
251
252 req, err := s.newRequest(Method, endpoint, nil)
253 if err != nil {
254 return nil, err
255 }
256
257 return s.client.Do(req)
258}
259
260type UnsignedClient struct {
261 Url *url.URL
262 client *http.Client
263}
264
265func NewUnsignedClient(domain string, dev bool) (*UnsignedClient, error) {
266 client := &http.Client{
267 Timeout: 5 * time.Second,
268 }
269
270 scheme := "https"
271 if dev {
272 scheme = "http"
273 }
274 url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain))
275 if err != nil {
276 return nil, err
277 }
278
279 unsignedClient := &UnsignedClient{
280 client: client,
281 Url: url,
282 }
283
284 return unsignedClient, nil
285}
286
287func (us *UnsignedClient) newRequest(method, endpoint string, body []byte) (*http.Request, error) {
288 return http.NewRequest(method, us.Url.JoinPath(endpoint).String(), bytes.NewReader(body))
289}
290
291func (us *UnsignedClient) Index(ownerDid, repoName, ref string) (*http.Response, error) {
292 const (
293 Method = "GET"
294 )
295
296 endpoint := fmt.Sprintf("/%s/%s/tree/%s", ownerDid, repoName, ref)
297 if ref == "" {
298 endpoint = fmt.Sprintf("/%s/%s", ownerDid, repoName)
299 }
300
301 req, err := us.newRequest(Method, endpoint, nil)
302 if err != nil {
303 return nil, err
304 }
305
306 return us.client.Do(req)
307}
308
309func (us *UnsignedClient) Branches(ownerDid, repoName string) (*http.Response, error) {
310 const (
311 Method = "GET"
312 )
313
314 endpoint := fmt.Sprintf("/%s/%s/branches", ownerDid, repoName)
315
316 req, err := us.newRequest(Method, endpoint, nil)
317 if err != nil {
318 return nil, err
319 }
320
321 return us.client.Do(req)
322}
323
324func (us *UnsignedClient) Branch(ownerDid, repoName, branch string) (*http.Response, error) {
325 const (
326 Method = "GET"
327 )
328
329 endpoint := fmt.Sprintf("/%s/%s/branches/%s", ownerDid, repoName, branch)
330
331 req, err := us.newRequest(Method, endpoint, nil)
332 if err != nil {
333 return nil, err
334 }
335
336 return us.client.Do(req)
337}
338
339func (us *UnsignedClient) DefaultBranch(ownerDid, repoName string) (*types.RepoDefaultBranchResponse, error) {
340 const (
341 Method = "GET"
342 )
343
344 endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName)
345
346 req, err := us.newRequest(Method, endpoint, nil)
347 if err != nil {
348 return nil, err
349 }
350
351 resp, err := us.client.Do(req)
352 if err != nil {
353 return nil, err
354 }
355 defer resp.Body.Close()
356
357 var branchResponse types.RepoDefaultBranchResponse
358 if err := json.NewDecoder(resp.Body).Decode(&branchResponse); err != nil {
359 return nil, err
360 }
361
362 return &branchResponse, nil
363}
364
365func (us *UnsignedClient) Capabilities() (*types.Capabilities, error) {
366 const (
367 Method = "GET"
368 Endpoint = "/capabilities"
369 )
370
371 req, err := us.newRequest(Method, Endpoint, nil)
372 if err != nil {
373 return nil, err
374 }
375
376 resp, err := us.client.Do(req)
377 if err != nil {
378 return nil, err
379 }
380 defer resp.Body.Close()
381
382 var capabilities types.Capabilities
383 if err := json.NewDecoder(resp.Body).Decode(&capabilities); err != nil {
384 return nil, err
385 }
386
387 return &capabilities, nil
388}
389
390func (us *UnsignedClient) Compare(ownerDid, repoName, rev1, rev2 string) (*http.Response, error) {
391 const (
392 Method = "GET"
393 )
394
395 endpoint := fmt.Sprintf("/%s/%s/compare/%s/%s", ownerDid, repoName, url.PathEscape(rev1), url.PathEscape(rev2))
396
397 req, err := us.newRequest(Method, endpoint, nil)
398 if err != nil {
399 return nil, err
400 }
401
402 return us.client.Do(req)
403}