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
246type UnsignedClient struct {
247 Url *url.URL
248 client *http.Client
249}
250
251func NewUnsignedClient(domain string, dev bool) (*UnsignedClient, error) {
252 client := &http.Client{
253 Timeout: 5 * time.Second,
254 }
255
256 scheme := "https"
257 if dev {
258 scheme = "http"
259 }
260 url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain))
261 if err != nil {
262 return nil, err
263 }
264
265 unsignedClient := &UnsignedClient{
266 client: client,
267 Url: url,
268 }
269
270 return unsignedClient, nil
271}
272
273func (us *UnsignedClient) newRequest(method, endpoint string, body []byte) (*http.Request, error) {
274 return http.NewRequest(method, us.Url.JoinPath(endpoint).String(), bytes.NewReader(body))
275}
276
277func (us *UnsignedClient) Index(ownerDid, repoName, ref string) (*http.Response, error) {
278 const (
279 Method = "GET"
280 )
281
282 endpoint := fmt.Sprintf("/%s/%s/tree/%s", ownerDid, repoName, ref)
283 if ref == "" {
284 endpoint = fmt.Sprintf("/%s/%s", ownerDid, repoName)
285 }
286
287 req, err := us.newRequest(Method, endpoint, nil)
288 if err != nil {
289 return nil, err
290 }
291
292 return us.client.Do(req)
293}
294
295func (us *UnsignedClient) Branches(ownerDid, repoName string) (*http.Response, error) {
296 const (
297 Method = "GET"
298 )
299
300 endpoint := fmt.Sprintf("/%s/%s/branches", ownerDid, repoName)
301
302 req, err := us.newRequest(Method, endpoint, nil)
303 if err != nil {
304 return nil, err
305 }
306
307 return us.client.Do(req)
308}
309
310func (us *UnsignedClient) Branch(ownerDid, repoName, branch string) (*http.Response, error) {
311 const (
312 Method = "GET"
313 )
314
315 endpoint := fmt.Sprintf("/%s/%s/branches/%s", ownerDid, repoName, branch)
316
317 req, err := us.newRequest(Method, endpoint, nil)
318 if err != nil {
319 return nil, err
320 }
321
322 return us.client.Do(req)
323}
324
325func (us *UnsignedClient) DefaultBranch(ownerDid, repoName string) (*http.Response, error) {
326 const (
327 Method = "GET"
328 )
329
330 endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName)
331
332 req, err := us.newRequest(Method, endpoint, nil)
333 if err != nil {
334 return nil, err
335 }
336
337 return us.client.Do(req)
338}
339
340func (us *UnsignedClient) Capabilities(ownerDid, repoName string) (*http.Response, error) {
341 const (
342 Method = "GET"
343 Endpoint = "/capabilities"
344 )
345
346 req, err := us.newRequest(Method, Endpoint, nil)
347 if err != nil {
348 return nil, err
349 }
350
351 return us.client.Do(req)
352}
353
354func (us *UnsignedClient) Compare(ownerDid, repoName, rev1, rev2 string) (*http.Response, error) {
355 const (
356 Method = "GET"
357 )
358
359 endpoint := fmt.Sprintf("/%s/%s/compare/%s/%s", ownerDid, repoName, rev1, rev2)
360
361 req, err := us.newRequest(Method, endpoint, nil)
362 if err != nil {
363 return nil, err
364 }
365
366 return us.client.Do(req)
367}