forked from
tangled.org/core
Monorepo for Tangled — https://tangled.org
1package knotclient
2
3import (
4 "bytes"
5 "crypto/hmac"
6 "crypto/sha256"
7 "encoding/hex"
8 "encoding/json"
9 "fmt"
10 "io"
11 "log"
12 "net/http"
13 "net/url"
14 "time"
15
16 "tangled.sh/tangled.sh/core/types"
17)
18
19type SignerTransport struct {
20 Secret string
21}
22
23func (s SignerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
24 timestamp := time.Now().Format(time.RFC3339)
25 mac := hmac.New(sha256.New, []byte(s.Secret))
26 message := req.Method + req.URL.Path + timestamp
27 mac.Write([]byte(message))
28 signature := hex.EncodeToString(mac.Sum(nil))
29 req.Header.Set("X-Signature", signature)
30 req.Header.Set("X-Timestamp", timestamp)
31 return http.DefaultTransport.RoundTrip(req)
32}
33
34type SignedClient struct {
35 Secret string
36 Url *url.URL
37 client *http.Client
38}
39
40func NewSignedClient(domain, secret string, dev bool) (*SignedClient, error) {
41 client := &http.Client{
42 Timeout: 5 * time.Second,
43 Transport: SignerTransport{
44 Secret: secret,
45 },
46 }
47
48 scheme := "https"
49 if dev {
50 scheme = "http"
51 }
52 url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain))
53 if err != nil {
54 return nil, err
55 }
56
57 signedClient := &SignedClient{
58 Secret: secret,
59 client: client,
60 Url: url,
61 }
62
63 return signedClient, nil
64}
65
66func (s *SignedClient) newRequest(method, endpoint string, body []byte) (*http.Request, error) {
67 return http.NewRequest(method, s.Url.JoinPath(endpoint).String(), bytes.NewReader(body))
68}
69
70func (s *SignedClient) Init(did string) (*http.Response, error) {
71 const (
72 Method = "POST"
73 Endpoint = "/init"
74 )
75
76 body, _ := json.Marshal(map[string]any{
77 "did": did,
78 })
79
80 req, err := s.newRequest(Method, Endpoint, body)
81 if err != nil {
82 return nil, err
83 }
84
85 return s.client.Do(req)
86}
87
88func (s *SignedClient) NewRepo(did, repoName, defaultBranch string) (*http.Response, error) {
89 const (
90 Method = "PUT"
91 Endpoint = "/repo/new"
92 )
93
94 body, _ := json.Marshal(map[string]any{
95 "did": did,
96 "name": repoName,
97 "default_branch": defaultBranch,
98 })
99
100 req, err := s.newRequest(Method, Endpoint, body)
101 if err != nil {
102 return nil, err
103 }
104
105 return s.client.Do(req)
106}
107
108func (s *SignedClient) RepoLanguages(ownerDid, repoName, ref string) (*types.RepoLanguageResponse, error) {
109 const (
110 Method = "GET"
111 )
112 endpoint := fmt.Sprintf("/%s/%s/languages/%s", ownerDid, repoName, url.PathEscape(ref))
113
114 req, err := s.newRequest(Method, endpoint, nil)
115 if err != nil {
116 return nil, err
117 }
118
119 resp, err := s.client.Do(req)
120 if err != nil {
121 return nil, err
122 }
123
124 var result types.RepoLanguageResponse
125 if resp.StatusCode != http.StatusOK {
126 log.Println("failed to calculate languages", resp.Status)
127 return &types.RepoLanguageResponse{}, nil
128 }
129
130 body, err := io.ReadAll(resp.Body)
131 if err != nil {
132 return nil, err
133 }
134
135 err = json.Unmarshal(body, &result)
136 if err != nil {
137 return nil, err
138 }
139
140 return &result, nil
141}
142
143func (s *SignedClient) RepoForkAheadBehind(ownerDid, source, name, branch, hiddenRef string) (*http.Response, error) {
144 const (
145 Method = "GET"
146 )
147 endpoint := fmt.Sprintf("/repo/fork/sync/%s", url.PathEscape(branch))
148
149 body, _ := json.Marshal(map[string]any{
150 "did": ownerDid,
151 "source": source,
152 "name": name,
153 "hiddenref": hiddenRef,
154 })
155
156 req, err := s.newRequest(Method, endpoint, body)
157 if err != nil {
158 return nil, err
159 }
160
161 return s.client.Do(req)
162}
163
164func (s *SignedClient) SyncRepoFork(ownerDid, source, name, branch string) (*http.Response, error) {
165 const (
166 Method = "POST"
167 )
168 endpoint := fmt.Sprintf("/repo/fork/sync/%s", url.PathEscape(branch))
169
170 body, _ := json.Marshal(map[string]any{
171 "did": ownerDid,
172 "source": source,
173 "name": name,
174 })
175
176 req, err := s.newRequest(Method, endpoint, body)
177 if err != nil {
178 return nil, err
179 }
180
181 return s.client.Do(req)
182}
183
184func (s *SignedClient) ForkRepo(ownerDid, source, name string) (*http.Response, error) {
185 const (
186 Method = "POST"
187 Endpoint = "/repo/fork"
188 )
189
190 body, _ := json.Marshal(map[string]any{
191 "did": ownerDid,
192 "source": source,
193 "name": name,
194 })
195
196 req, err := s.newRequest(Method, Endpoint, body)
197 if err != nil {
198 return nil, err
199 }
200
201 return s.client.Do(req)
202}
203
204func (s *SignedClient) RemoveRepo(did, repoName string) (*http.Response, error) {
205 const (
206 Method = "DELETE"
207 Endpoint = "/repo"
208 )
209
210 body, _ := json.Marshal(map[string]any{
211 "did": did,
212 "name": repoName,
213 })
214
215 req, err := s.newRequest(Method, Endpoint, body)
216 if err != nil {
217 return nil, err
218 }
219
220 return s.client.Do(req)
221}
222
223func (s *SignedClient) AddMember(did string) (*http.Response, error) {
224 const (
225 Method = "PUT"
226 Endpoint = "/member/add"
227 )
228
229 body, _ := json.Marshal(map[string]any{
230 "did": did,
231 })
232
233 req, err := s.newRequest(Method, Endpoint, body)
234 if err != nil {
235 return nil, err
236 }
237
238 return s.client.Do(req)
239}
240
241func (s *SignedClient) SetDefaultBranch(ownerDid, repoName, branch string) (*http.Response, error) {
242 const (
243 Method = "PUT"
244 )
245 endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName)
246
247 body, _ := json.Marshal(map[string]any{
248 "branch": branch,
249 })
250
251 req, err := s.newRequest(Method, endpoint, body)
252 if err != nil {
253 return nil, err
254 }
255
256 return s.client.Do(req)
257}
258
259func (s *SignedClient) AddCollaborator(ownerDid, repoName, memberDid string) (*http.Response, error) {
260 const (
261 Method = "POST"
262 )
263 endpoint := fmt.Sprintf("/%s/%s/collaborator/add", ownerDid, repoName)
264
265 body, _ := json.Marshal(map[string]any{
266 "did": memberDid,
267 })
268
269 req, err := s.newRequest(Method, endpoint, body)
270 if err != nil {
271 return nil, err
272 }
273
274 return s.client.Do(req)
275}
276
277func (s *SignedClient) Merge(
278 patch []byte,
279 ownerDid, targetRepo, branch, commitMessage, commitBody, authorName, authorEmail string,
280) (*http.Response, error) {
281 const (
282 Method = "POST"
283 )
284 endpoint := fmt.Sprintf("/%s/%s/merge", ownerDid, targetRepo)
285
286 mr := types.MergeRequest{
287 Branch: branch,
288 CommitMessage: commitMessage,
289 CommitBody: commitBody,
290 AuthorName: authorName,
291 AuthorEmail: authorEmail,
292 Patch: string(patch),
293 }
294
295 body, _ := json.Marshal(mr)
296
297 req, err := s.newRequest(Method, endpoint, body)
298 if err != nil {
299 return nil, err
300 }
301
302 return s.client.Do(req)
303}
304
305func (s *SignedClient) MergeCheck(patch []byte, ownerDid, targetRepo, branch string) (*http.Response, error) {
306 const (
307 Method = "POST"
308 )
309 endpoint := fmt.Sprintf("/%s/%s/merge/check", ownerDid, targetRepo)
310
311 body, _ := json.Marshal(map[string]any{
312 "patch": string(patch),
313 "branch": branch,
314 })
315
316 req, err := s.newRequest(Method, endpoint, body)
317 if err != nil {
318 return nil, err
319 }
320
321 return s.client.Do(req)
322}
323
324func (s *SignedClient) NewHiddenRef(ownerDid, targetRepo, forkBranch, remoteBranch string) (*http.Response, error) {
325 const (
326 Method = "POST"
327 )
328 endpoint := fmt.Sprintf("/%s/%s/hidden-ref/%s/%s", ownerDid, targetRepo, url.PathEscape(forkBranch), url.PathEscape(remoteBranch))
329
330 req, err := s.newRequest(Method, endpoint, nil)
331 if err != nil {
332 return nil, err
333 }
334
335 return s.client.Do(req)
336}