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) RemoveRepo(did, repoName string) (*http.Response, error) {
107 const (
108 Method = "DELETE"
109 Endpoint = "/repo"
110 )
111
112 body, _ := json.Marshal(map[string]any{
113 "did": did,
114 "name": repoName,
115 })
116
117 req, err := s.newRequest(Method, Endpoint, body)
118 if err != nil {
119 return nil, err
120 }
121
122 return s.client.Do(req)
123}
124
125func (s *SignedClient) AddMember(did string) (*http.Response, error) {
126 const (
127 Method = "PUT"
128 Endpoint = "/member/add"
129 )
130
131 body, _ := json.Marshal(map[string]any{
132 "did": did,
133 })
134
135 req, err := s.newRequest(Method, Endpoint, body)
136 if err != nil {
137 return nil, err
138 }
139
140 return s.client.Do(req)
141}
142
143func (s *SignedClient) SetDefaultBranch(ownerDid, repoName, branch string) (*http.Response, error) {
144 const (
145 Method = "PUT"
146 )
147 endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName)
148
149 body, _ := json.Marshal(map[string]any{
150 "branch": branch,
151 })
152
153 req, err := s.newRequest(Method, endpoint, body)
154 if err != nil {
155 return nil, err
156 }
157
158 return s.client.Do(req)
159}
160
161func (s *SignedClient) AddCollaborator(ownerDid, repoName, memberDid string) (*http.Response, error) {
162 const (
163 Method = "POST"
164 )
165 endpoint := fmt.Sprintf("/%s/%s/collaborator/add", ownerDid, repoName)
166
167 body, _ := json.Marshal(map[string]any{
168 "did": memberDid,
169 })
170
171 req, err := s.newRequest(Method, endpoint, body)
172 if err != nil {
173 return nil, err
174 }
175
176 return s.client.Do(req)
177}
178
179func (s *SignedClient) Merge(
180 patch []byte,
181 ownerDid, targetRepo, branch, commitMessage, commitBody, authorName, authorEmail string,
182) (*http.Response, error) {
183 const (
184 Method = "POST"
185 )
186 endpoint := fmt.Sprintf("/%s/%s/merge", ownerDid, targetRepo)
187
188 mr := types.MergeRequest{
189 Branch: branch,
190 CommitMessage: commitMessage,
191 CommitBody: commitBody,
192 AuthorName: authorName,
193 AuthorEmail: authorEmail,
194 Patch: string(patch),
195 }
196
197 body, _ := json.Marshal(mr)
198
199 req, err := s.newRequest(Method, endpoint, body)
200 if err != nil {
201 return nil, err
202 }
203
204 return s.client.Do(req)
205}
206
207func (s *SignedClient) MergeCheck(patch []byte, ownerDid, targetRepo, branch string) (*http.Response, error) {
208 const (
209 Method = "POST"
210 )
211 endpoint := fmt.Sprintf("/%s/%s/merge/check", ownerDid, targetRepo)
212
213 body, _ := json.Marshal(map[string]any{
214 "patch": string(patch),
215 "branch": branch,
216 })
217
218 req, err := s.newRequest(Method, endpoint, body)
219 if err != nil {
220 return nil, err
221 }
222
223 return s.client.Do(req)
224}
225
226type UnsignedClient struct {
227 Url *url.URL
228 client *http.Client
229}
230
231func NewUnsignedClient(domain string, dev bool) (*UnsignedClient, error) {
232 client := &http.Client{
233 Timeout: 5 * time.Second,
234 }
235
236 scheme := "https"
237 if dev {
238 scheme = "http"
239 }
240 url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain))
241 if err != nil {
242 return nil, err
243 }
244
245 unsignedClient := &UnsignedClient{
246 client: client,
247 Url: url,
248 }
249
250 return unsignedClient, nil
251}
252
253func (us *UnsignedClient) newRequest(method, endpoint string, body []byte) (*http.Request, error) {
254 return http.NewRequest(method, us.Url.JoinPath(endpoint).String(), bytes.NewReader(body))
255}
256
257func (us *UnsignedClient) Index(ownerDid, repoName, ref string) (*http.Response, error) {
258 const (
259 Method = "GET"
260 )
261
262 endpoint := fmt.Sprintf("/%s/%s/tree/%s", ownerDid, repoName, ref)
263 if ref == "" {
264 endpoint = fmt.Sprintf("/%s/%s", ownerDid, repoName)
265 }
266
267 req, err := us.newRequest(Method, endpoint, nil)
268 if err != nil {
269 return nil, err
270 }
271
272 return us.client.Do(req)
273}
274
275func (us *UnsignedClient) Branches(ownerDid, repoName string) (*http.Response, error) {
276 const (
277 Method = "GET"
278 )
279
280 endpoint := fmt.Sprintf("/%s/%s/branches", ownerDid, repoName)
281
282 req, err := us.newRequest(Method, endpoint, nil)
283 if err != nil {
284 return nil, err
285 }
286
287 return us.client.Do(req)
288}
289
290func (us *UnsignedClient) Branch(ownerDid, repoName, branch string) (*http.Response, error) {
291 const (
292 Method = "GET"
293 )
294
295 endpoint := fmt.Sprintf("/%s/%s/branches/%s", ownerDid, repoName, branch)
296
297 req, err := us.newRequest(Method, endpoint, nil)
298 if err != nil {
299 return nil, err
300 }
301
302 return us.client.Do(req)
303}
304
305func (us *UnsignedClient) DefaultBranch(ownerDid, repoName string) (*http.Response, error) {
306 const (
307 Method = "GET"
308 )
309
310 endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName)
311
312 req, err := us.newRequest(Method, endpoint, nil)
313 if err != nil {
314 return nil, err
315 }
316
317 return us.client.Do(req)
318}
319
320func (us *UnsignedClient) Capabilities(ownerDid, repoName string) (*http.Response, error) {
321 const (
322 Method = "GET"
323 Endpoint = "/capabilities"
324 )
325
326 req, err := us.newRequest(Method, Endpoint, nil)
327 if err != nil {
328 return nil, err
329 }
330
331 return us.client.Do(req)
332}
333
334func (us *UnsignedClient) Compare(ownerDid, repoName, rev1, rev2 string) (*http.Response, error) {
335 const (
336 Method = "GET"
337 )
338
339 endpoint := fmt.Sprintf("/%s/%s/compare/%s/%s", ownerDid, repoName, rev1, rev2)
340
341 req, err := us.newRequest(Method, endpoint, nil)
342 if err != nil {
343 return nil, err
344 }
345
346 return us.client.Do(req)
347}