this repo has no description
1package knotclient
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) RepoLanguages(ownerDid, repoName, ref string) (*types.RepoLanguageResponse, error) {
107 const (
108 Method = "GET"
109 )
110 endpoint := fmt.Sprintf("/%s/%s/languages/%s", ownerDid, repoName, url.PathEscape(ref))
111
112 req, err := s.newRequest(Method, endpoint, nil)
113 if err != nil {
114 return nil, err
115 }
116
117 resp, err := s.client.Do(req)
118 if err != nil {
119 return nil, err
120 }
121
122 var result types.RepoLanguageResponse
123 if resp.StatusCode != http.StatusOK {
124 log.Println("failed to calculate languages", resp.Status)
125 return &types.RepoLanguageResponse{}, nil
126 }
127
128 body, err := io.ReadAll(resp.Body)
129 if err != nil {
130 return nil, err
131 }
132
133 err = json.Unmarshal(body, &result)
134 if err != nil {
135 return nil, err
136 }
137
138 return &result, nil
139}
140
141func (s *SignedClient) RepoForkAheadBehind(ownerDid, source, name, branch, hiddenRef string) (*http.Response, error) {
142 const (
143 Method = "GET"
144 )
145 endpoint := fmt.Sprintf("/repo/fork/sync/%s", url.PathEscape(branch))
146
147 body, _ := json.Marshal(map[string]any{
148 "did": ownerDid,
149 "source": source,
150 "name": name,
151 "hiddenref": hiddenRef,
152 })
153
154 req, err := s.newRequest(Method, endpoint, body)
155 if err != nil {
156 return nil, err
157 }
158
159 return s.client.Do(req)
160}
161
162func (s *SignedClient) SyncRepoFork(ownerDid, source, name, branch string) (*http.Response, error) {
163 const (
164 Method = "POST"
165 )
166 endpoint := fmt.Sprintf("/repo/fork/sync/%s", url.PathEscape(branch))
167
168 body, _ := json.Marshal(map[string]any{
169 "did": ownerDid,
170 "source": source,
171 "name": name,
172 })
173
174 req, err := s.newRequest(Method, endpoint, body)
175 if err != nil {
176 return nil, err
177 }
178
179 return s.client.Do(req)
180}
181
182func (s *SignedClient) ForkRepo(ownerDid, source, name string) (*http.Response, error) {
183 const (
184 Method = "POST"
185 Endpoint = "/repo/fork"
186 )
187
188 body, _ := json.Marshal(map[string]any{
189 "did": ownerDid,
190 "source": source,
191 "name": name,
192 })
193
194 req, err := s.newRequest(Method, Endpoint, body)
195 if err != nil {
196 return nil, err
197 }
198
199 return s.client.Do(req)
200}
201
202func (s *SignedClient) RemoveRepo(did, repoName string) (*http.Response, error) {
203 const (
204 Method = "DELETE"
205 Endpoint = "/repo"
206 )
207
208 body, _ := json.Marshal(map[string]any{
209 "did": did,
210 "name": repoName,
211 })
212
213 req, err := s.newRequest(Method, Endpoint, body)
214 if err != nil {
215 return nil, err
216 }
217
218 return s.client.Do(req)
219}
220
221func (s *SignedClient) AddMember(did string) (*http.Response, error) {
222 const (
223 Method = "PUT"
224 Endpoint = "/member/add"
225 )
226
227 body, _ := json.Marshal(map[string]any{
228 "did": did,
229 })
230
231 req, err := s.newRequest(Method, Endpoint, body)
232 if err != nil {
233 return nil, err
234 }
235
236 return s.client.Do(req)
237}
238
239func (s *SignedClient) SetDefaultBranch(ownerDid, repoName, branch string) (*http.Response, error) {
240 const (
241 Method = "PUT"
242 )
243 endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName)
244
245 body, _ := json.Marshal(map[string]any{
246 "branch": branch,
247 })
248
249 req, err := s.newRequest(Method, endpoint, body)
250 if err != nil {
251 return nil, err
252 }
253
254 return s.client.Do(req)
255}
256
257func (s *SignedClient) AddCollaborator(ownerDid, repoName, memberDid string) (*http.Response, error) {
258 const (
259 Method = "POST"
260 )
261 endpoint := fmt.Sprintf("/%s/%s/collaborator/add", ownerDid, repoName)
262
263 body, _ := json.Marshal(map[string]any{
264 "did": memberDid,
265 })
266
267 req, err := s.newRequest(Method, endpoint, body)
268 if err != nil {
269 return nil, err
270 }
271
272 return s.client.Do(req)
273}
274
275func (s *SignedClient) Merge(
276 patch []byte,
277 ownerDid, targetRepo, branch, commitMessage, commitBody, authorName, authorEmail string,
278) (*http.Response, error) {
279 const (
280 Method = "POST"
281 )
282 endpoint := fmt.Sprintf("/%s/%s/merge", ownerDid, targetRepo)
283
284 mr := types.MergeRequest{
285 Branch: branch,
286 CommitMessage: commitMessage,
287 CommitBody: commitBody,
288 AuthorName: authorName,
289 AuthorEmail: authorEmail,
290 Patch: string(patch),
291 }
292
293 body, _ := json.Marshal(mr)
294
295 req, err := s.newRequest(Method, endpoint, body)
296 if err != nil {
297 return nil, err
298 }
299
300 return s.client.Do(req)
301}
302
303func (s *SignedClient) MergeCheck(patch []byte, ownerDid, targetRepo, branch string) (*http.Response, error) {
304 const (
305 Method = "POST"
306 )
307 endpoint := fmt.Sprintf("/%s/%s/merge/check", ownerDid, targetRepo)
308
309 body, _ := json.Marshal(map[string]any{
310 "patch": string(patch),
311 "branch": branch,
312 })
313
314 req, err := s.newRequest(Method, endpoint, body)
315 if err != nil {
316 return nil, err
317 }
318
319 return s.client.Do(req)
320}
321
322func (s *SignedClient) NewHiddenRef(ownerDid, targetRepo, forkBranch, remoteBranch string) (*http.Response, error) {
323 const (
324 Method = "POST"
325 )
326 endpoint := fmt.Sprintf("/%s/%s/hidden-ref/%s/%s", ownerDid, targetRepo, url.PathEscape(forkBranch), url.PathEscape(remoteBranch))
327
328 req, err := s.newRequest(Method, endpoint, nil)
329 if err != nil {
330 return nil, err
331 }
332
333 return s.client.Do(req)
334}