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}