forked from tangled.org/core
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 "io" 11 "log" 12 "net/http" 13 "net/url" 14 "strconv" 15 "time" 16 17 "tangled.sh/tangled.sh/core/types" 18) 19 20type SignerTransport struct { 21 Secret string 22} 23 24func (s SignerTransport) RoundTrip(req *http.Request) (*http.Response, error) { 25 timestamp := time.Now().Format(time.RFC3339) 26 mac := hmac.New(sha256.New, []byte(s.Secret)) 27 message := req.Method + req.URL.Path + timestamp 28 mac.Write([]byte(message)) 29 signature := hex.EncodeToString(mac.Sum(nil)) 30 req.Header.Set("X-Signature", signature) 31 req.Header.Set("X-Timestamp", timestamp) 32 return http.DefaultTransport.RoundTrip(req) 33} 34 35type SignedClient struct { 36 Secret string 37 Url *url.URL 38 client *http.Client 39} 40 41func NewSignedClient(domain, secret string, dev bool) (*SignedClient, error) { 42 client := &http.Client{ 43 Timeout: 5 * time.Second, 44 Transport: SignerTransport{ 45 Secret: secret, 46 }, 47 } 48 49 scheme := "https" 50 if dev { 51 scheme = "http" 52 } 53 url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain)) 54 if err != nil { 55 return nil, err 56 } 57 58 signedClient := &SignedClient{ 59 Secret: secret, 60 client: client, 61 Url: url, 62 } 63 64 return signedClient, nil 65} 66 67func (s *SignedClient) newRequest(method, endpoint string, body []byte) (*http.Request, error) { 68 return http.NewRequest(method, s.Url.JoinPath(endpoint).String(), bytes.NewReader(body)) 69} 70 71func (s *SignedClient) Init(did string) (*http.Response, error) { 72 const ( 73 Method = "POST" 74 Endpoint = "/init" 75 ) 76 77 body, _ := json.Marshal(map[string]any{ 78 "did": did, 79 }) 80 81 req, err := s.newRequest(Method, Endpoint, body) 82 if err != nil { 83 return nil, err 84 } 85 86 return s.client.Do(req) 87} 88 89func (s *SignedClient) NewRepo(did, repoName, defaultBranch string) (*http.Response, error) { 90 const ( 91 Method = "PUT" 92 Endpoint = "/repo/new" 93 ) 94 95 body, _ := json.Marshal(map[string]any{ 96 "did": did, 97 "name": repoName, 98 "default_branch": defaultBranch, 99 }) 100 101 req, err := s.newRequest(Method, Endpoint, body) 102 if err != nil { 103 return nil, err 104 } 105 106 return s.client.Do(req) 107} 108 109func (s *SignedClient) RepoForkAheadBehind(ownerDid, source, name, branch, hiddenRef string) (*http.Response, error) { 110 const ( 111 Method = "GET" 112 ) 113 endpoint := fmt.Sprintf("/repo/fork/sync/%s", url.PathEscape(branch)) 114 115 body, _ := json.Marshal(map[string]any{ 116 "did": ownerDid, 117 "source": source, 118 "name": name, 119 "hiddenref": hiddenRef, 120 }) 121 122 req, err := s.newRequest(Method, endpoint, body) 123 if err != nil { 124 return nil, err 125 } 126 127 return s.client.Do(req) 128} 129 130func (s *SignedClient) SyncRepoFork(ownerDid, source, name, branch string) (*http.Response, error) { 131 const ( 132 Method = "POST" 133 ) 134 endpoint := fmt.Sprintf("/repo/fork/sync/%s", url.PathEscape(branch)) 135 136 body, _ := json.Marshal(map[string]any{ 137 "did": ownerDid, 138 "source": source, 139 "name": name, 140 }) 141 142 req, err := s.newRequest(Method, endpoint, body) 143 if err != nil { 144 return nil, err 145 } 146 147 return s.client.Do(req) 148} 149 150func (s *SignedClient) ForkRepo(ownerDid, source, name string) (*http.Response, error) { 151 const ( 152 Method = "POST" 153 Endpoint = "/repo/fork" 154 ) 155 156 body, _ := json.Marshal(map[string]any{ 157 "did": ownerDid, 158 "source": source, 159 "name": name, 160 }) 161 162 req, err := s.newRequest(Method, Endpoint, body) 163 if err != nil { 164 return nil, err 165 } 166 167 return s.client.Do(req) 168} 169 170func (s *SignedClient) RemoveRepo(did, repoName string) (*http.Response, error) { 171 const ( 172 Method = "DELETE" 173 Endpoint = "/repo" 174 ) 175 176 body, _ := json.Marshal(map[string]any{ 177 "did": did, 178 "name": repoName, 179 }) 180 181 req, err := s.newRequest(Method, Endpoint, body) 182 if err != nil { 183 return nil, err 184 } 185 186 return s.client.Do(req) 187} 188 189func (s *SignedClient) AddMember(did string) (*http.Response, error) { 190 const ( 191 Method = "PUT" 192 Endpoint = "/member/add" 193 ) 194 195 body, _ := json.Marshal(map[string]any{ 196 "did": did, 197 }) 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) SetDefaultBranch(ownerDid, repoName, branch string) (*http.Response, error) { 208 const ( 209 Method = "PUT" 210 ) 211 endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName) 212 213 body, _ := json.Marshal(map[string]any{ 214 "branch": branch, 215 }) 216 217 req, err := s.newRequest(Method, endpoint, body) 218 if err != nil { 219 return nil, err 220 } 221 222 return s.client.Do(req) 223} 224 225func (s *SignedClient) AddCollaborator(ownerDid, repoName, memberDid string) (*http.Response, error) { 226 const ( 227 Method = "POST" 228 ) 229 endpoint := fmt.Sprintf("/%s/%s/collaborator/add", ownerDid, repoName) 230 231 body, _ := json.Marshal(map[string]any{ 232 "did": memberDid, 233 }) 234 235 req, err := s.newRequest(Method, endpoint, body) 236 if err != nil { 237 return nil, err 238 } 239 240 return s.client.Do(req) 241} 242 243func (s *SignedClient) Merge( 244 patch []byte, 245 ownerDid, targetRepo, branch, commitMessage, commitBody, authorName, authorEmail string, 246) (*http.Response, error) { 247 const ( 248 Method = "POST" 249 ) 250 endpoint := fmt.Sprintf("/%s/%s/merge", ownerDid, targetRepo) 251 252 mr := types.MergeRequest{ 253 Branch: branch, 254 CommitMessage: commitMessage, 255 CommitBody: commitBody, 256 AuthorName: authorName, 257 AuthorEmail: authorEmail, 258 Patch: string(patch), 259 } 260 261 body, _ := json.Marshal(mr) 262 263 req, err := s.newRequest(Method, endpoint, body) 264 if err != nil { 265 return nil, err 266 } 267 268 return s.client.Do(req) 269} 270 271func (s *SignedClient) MergeCheck(patch []byte, ownerDid, targetRepo, branch string) (*http.Response, error) { 272 const ( 273 Method = "POST" 274 ) 275 endpoint := fmt.Sprintf("/%s/%s/merge/check", ownerDid, targetRepo) 276 277 body, _ := json.Marshal(map[string]any{ 278 "patch": string(patch), 279 "branch": branch, 280 }) 281 282 req, err := s.newRequest(Method, endpoint, body) 283 if err != nil { 284 return nil, err 285 } 286 287 return s.client.Do(req) 288} 289 290func (s *SignedClient) NewHiddenRef(ownerDid, targetRepo, forkBranch, remoteBranch string) (*http.Response, error) { 291 const ( 292 Method = "POST" 293 ) 294 endpoint := fmt.Sprintf("/%s/%s/hidden-ref/%s/%s", ownerDid, targetRepo, url.PathEscape(forkBranch), url.PathEscape(remoteBranch)) 295 296 req, err := s.newRequest(Method, endpoint, nil) 297 if err != nil { 298 return nil, err 299 } 300 301 return s.client.Do(req) 302} 303 304type UnsignedClient struct { 305 Url *url.URL 306 client *http.Client 307} 308 309func NewUnsignedClient(domain string, dev bool) (*UnsignedClient, error) { 310 client := &http.Client{ 311 Timeout: 5 * time.Second, 312 } 313 314 scheme := "https" 315 if dev { 316 scheme = "http" 317 } 318 url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain)) 319 if err != nil { 320 return nil, err 321 } 322 323 unsignedClient := &UnsignedClient{ 324 client: client, 325 Url: url, 326 } 327 328 return unsignedClient, nil 329} 330 331func (us *UnsignedClient) newRequest(method, endpoint string, query url.Values, body []byte) (*http.Request, error) { 332 reqUrl := us.Url.JoinPath(endpoint) 333 334 // add query parameters 335 if query != nil { 336 reqUrl.RawQuery = query.Encode() 337 } 338 339 return http.NewRequest(method, reqUrl.String(), bytes.NewReader(body)) 340} 341 342func (us *UnsignedClient) Index(ownerDid, repoName, ref string) (*http.Response, error) { 343 const ( 344 Method = "GET" 345 ) 346 347 endpoint := fmt.Sprintf("/%s/%s/tree/%s", ownerDid, repoName, ref) 348 if ref == "" { 349 endpoint = fmt.Sprintf("/%s/%s", ownerDid, repoName) 350 } 351 352 req, err := us.newRequest(Method, endpoint, nil, nil) 353 if err != nil { 354 return nil, err 355 } 356 357 return us.client.Do(req) 358} 359 360func (us *UnsignedClient) Log(ownerDid, repoName, ref string, page int) (*http.Response, error) { 361 const ( 362 Method = "GET" 363 ) 364 365 endpoint := fmt.Sprintf("/%s/%s/log/%s", ownerDid, repoName, url.PathEscape(ref)) 366 367 query := url.Values{} 368 query.Add("page", strconv.Itoa(page)) 369 query.Add("per_page", strconv.Itoa(60)) 370 371 req, err := us.newRequest(Method, endpoint, query, nil) 372 if err != nil { 373 return nil, err 374 } 375 376 return us.client.Do(req) 377} 378 379func (us *UnsignedClient) Branches(ownerDid, repoName string) (*http.Response, error) { 380 const ( 381 Method = "GET" 382 ) 383 384 endpoint := fmt.Sprintf("/%s/%s/branches", ownerDid, repoName) 385 386 req, err := us.newRequest(Method, endpoint, nil, nil) 387 if err != nil { 388 return nil, err 389 } 390 391 return us.client.Do(req) 392} 393 394func (us *UnsignedClient) Tags(ownerDid, repoName string) (*types.RepoTagsResponse, error) { 395 const ( 396 Method = "GET" 397 ) 398 399 endpoint := fmt.Sprintf("/%s/%s/tags", ownerDid, repoName) 400 401 req, err := us.newRequest(Method, endpoint, nil, nil) 402 if err != nil { 403 return nil, err 404 } 405 406 resp, err := us.client.Do(req) 407 if err != nil { 408 return nil, err 409 } 410 411 body, err := io.ReadAll(resp.Body) 412 if err != nil { 413 return nil, err 414 } 415 416 var result types.RepoTagsResponse 417 err = json.Unmarshal(body, &result) 418 if err != nil { 419 return nil, err 420 } 421 422 return &result, nil 423} 424 425func (us *UnsignedClient) Branch(ownerDid, repoName, branch string) (*http.Response, error) { 426 const ( 427 Method = "GET" 428 ) 429 430 endpoint := fmt.Sprintf("/%s/%s/branches/%s", ownerDid, repoName, url.PathEscape(branch)) 431 432 req, err := us.newRequest(Method, endpoint, nil, nil) 433 if err != nil { 434 return nil, err 435 } 436 437 return us.client.Do(req) 438} 439 440func (us *UnsignedClient) DefaultBranch(ownerDid, repoName string) (*types.RepoDefaultBranchResponse, error) { 441 const ( 442 Method = "GET" 443 ) 444 445 endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName) 446 447 req, err := us.newRequest(Method, endpoint, nil, nil) 448 if err != nil { 449 return nil, err 450 } 451 452 resp, err := us.client.Do(req) 453 if err != nil { 454 return nil, err 455 } 456 defer resp.Body.Close() 457 458 var defaultBranch types.RepoDefaultBranchResponse 459 if err := json.NewDecoder(resp.Body).Decode(&defaultBranch); err != nil { 460 return nil, err 461 } 462 463 return &defaultBranch, nil 464} 465 466func (us *UnsignedClient) Capabilities() (*types.Capabilities, error) { 467 const ( 468 Method = "GET" 469 Endpoint = "/capabilities" 470 ) 471 472 req, err := us.newRequest(Method, Endpoint, nil, nil) 473 if err != nil { 474 return nil, err 475 } 476 477 resp, err := us.client.Do(req) 478 if err != nil { 479 return nil, err 480 } 481 defer resp.Body.Close() 482 483 var capabilities types.Capabilities 484 if err := json.NewDecoder(resp.Body).Decode(&capabilities); err != nil { 485 return nil, err 486 } 487 488 return &capabilities, nil 489} 490 491func (us *UnsignedClient) Compare(ownerDid, repoName, rev1, rev2 string) (*types.RepoFormatPatchResponse, error) { 492 const ( 493 Method = "GET" 494 ) 495 496 endpoint := fmt.Sprintf("/%s/%s/compare/%s/%s", ownerDid, repoName, url.PathEscape(rev1), url.PathEscape(rev2)) 497 498 req, err := us.newRequest(Method, endpoint, nil, nil) 499 if err != nil { 500 return nil, fmt.Errorf("Failed to create request.") 501 } 502 503 compareResp, err := us.client.Do(req) 504 if err != nil { 505 return nil, fmt.Errorf("Failed to create request.") 506 } 507 defer compareResp.Body.Close() 508 509 switch compareResp.StatusCode { 510 case 404: 511 case 400: 512 return nil, fmt.Errorf("Branch comparisons not supported on this knot.") 513 } 514 515 respBody, err := io.ReadAll(compareResp.Body) 516 if err != nil { 517 log.Println("failed to compare across branches") 518 return nil, fmt.Errorf("Failed to compare branches.") 519 } 520 defer compareResp.Body.Close() 521 522 var formatPatchResponse types.RepoFormatPatchResponse 523 err = json.Unmarshal(respBody, &formatPatchResponse) 524 if err != nil { 525 log.Println("failed to unmarshal format-patch response", err) 526 return nil, fmt.Errorf("failed to compare branches.") 527 } 528 529 return &formatPatchResponse, nil 530}