forked from tangled.org/core
this repo has no description
1package state 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) ForkRepo(ownerDid, source, name string) (*http.Response, error) { 109 const ( 110 Method = "POST" 111 Endpoint = "/repo/fork" 112 ) 113 114 body, _ := json.Marshal(map[string]any{ 115 "did": ownerDid, 116 "source": source, 117 "name": name, 118 }) 119 120 req, err := s.newRequest(Method, Endpoint, body) 121 if err != nil { 122 return nil, err 123 } 124 125 return s.client.Do(req) 126} 127 128func (s *SignedClient) RemoveRepo(did, repoName string) (*http.Response, error) { 129 const ( 130 Method = "DELETE" 131 Endpoint = "/repo" 132 ) 133 134 body, _ := json.Marshal(map[string]any{ 135 "did": did, 136 "name": repoName, 137 }) 138 139 req, err := s.newRequest(Method, Endpoint, body) 140 if err != nil { 141 return nil, err 142 } 143 144 return s.client.Do(req) 145} 146 147func (s *SignedClient) AddMember(did string) (*http.Response, error) { 148 const ( 149 Method = "PUT" 150 Endpoint = "/member/add" 151 ) 152 153 body, _ := json.Marshal(map[string]any{ 154 "did": did, 155 }) 156 157 req, err := s.newRequest(Method, Endpoint, body) 158 if err != nil { 159 return nil, err 160 } 161 162 return s.client.Do(req) 163} 164 165func (s *SignedClient) SetDefaultBranch(ownerDid, repoName, branch string) (*http.Response, error) { 166 const ( 167 Method = "PUT" 168 ) 169 endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName) 170 171 body, _ := json.Marshal(map[string]any{ 172 "branch": branch, 173 }) 174 175 req, err := s.newRequest(Method, endpoint, body) 176 if err != nil { 177 return nil, err 178 } 179 180 return s.client.Do(req) 181} 182 183func (s *SignedClient) AddCollaborator(ownerDid, repoName, memberDid string) (*http.Response, error) { 184 const ( 185 Method = "POST" 186 ) 187 endpoint := fmt.Sprintf("/%s/%s/collaborator/add", ownerDid, repoName) 188 189 body, _ := json.Marshal(map[string]any{ 190 "did": memberDid, 191 }) 192 193 req, err := s.newRequest(Method, endpoint, body) 194 if err != nil { 195 return nil, err 196 } 197 198 return s.client.Do(req) 199} 200 201func (s *SignedClient) Merge( 202 patch []byte, 203 ownerDid, targetRepo, branch, commitMessage, commitBody, authorName, authorEmail string, 204) (*http.Response, error) { 205 const ( 206 Method = "POST" 207 ) 208 endpoint := fmt.Sprintf("/%s/%s/merge", ownerDid, targetRepo) 209 210 mr := types.MergeRequest{ 211 Branch: branch, 212 CommitMessage: commitMessage, 213 CommitBody: commitBody, 214 AuthorName: authorName, 215 AuthorEmail: authorEmail, 216 Patch: string(patch), 217 } 218 219 body, _ := json.Marshal(mr) 220 221 req, err := s.newRequest(Method, endpoint, body) 222 if err != nil { 223 return nil, err 224 } 225 226 return s.client.Do(req) 227} 228 229func (s *SignedClient) MergeCheck(patch []byte, ownerDid, targetRepo, branch string) (*http.Response, error) { 230 const ( 231 Method = "POST" 232 ) 233 endpoint := fmt.Sprintf("/%s/%s/merge/check", ownerDid, targetRepo) 234 235 body, _ := json.Marshal(map[string]any{ 236 "patch": string(patch), 237 "branch": branch, 238 }) 239 240 req, err := s.newRequest(Method, endpoint, body) 241 if err != nil { 242 return nil, err 243 } 244 245 return s.client.Do(req) 246} 247 248func (s *SignedClient) NewHiddenRef(ownerDid, targetRepo, forkBranch, remoteBranch string) (*http.Response, error) { 249 const ( 250 Method = "POST" 251 ) 252 endpoint := fmt.Sprintf("/%s/%s/hidden-ref/%s/%s", ownerDid, targetRepo, forkBranch, remoteBranch) 253 254 req, err := s.newRequest(Method, endpoint, nil) 255 if err != nil { 256 return nil, err 257 } 258 259 return s.client.Do(req) 260} 261 262type UnsignedClient struct { 263 Url *url.URL 264 client *http.Client 265} 266 267func NewUnsignedClient(domain string, dev bool) (*UnsignedClient, error) { 268 client := &http.Client{ 269 Timeout: 5 * time.Second, 270 } 271 272 scheme := "https" 273 if dev { 274 scheme = "http" 275 } 276 url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain)) 277 if err != nil { 278 return nil, err 279 } 280 281 unsignedClient := &UnsignedClient{ 282 client: client, 283 Url: url, 284 } 285 286 return unsignedClient, nil 287} 288 289func (us *UnsignedClient) newRequest(method, endpoint string, body []byte) (*http.Request, error) { 290 return http.NewRequest(method, us.Url.JoinPath(endpoint).String(), bytes.NewReader(body)) 291} 292 293func (us *UnsignedClient) Index(ownerDid, repoName, ref string) (*http.Response, error) { 294 const ( 295 Method = "GET" 296 ) 297 298 endpoint := fmt.Sprintf("/%s/%s/tree/%s", ownerDid, repoName, ref) 299 if ref == "" { 300 endpoint = fmt.Sprintf("/%s/%s", ownerDid, repoName) 301 } 302 303 req, err := us.newRequest(Method, endpoint, nil) 304 if err != nil { 305 return nil, err 306 } 307 308 return us.client.Do(req) 309} 310 311func (us *UnsignedClient) Branches(ownerDid, repoName string) (*http.Response, error) { 312 const ( 313 Method = "GET" 314 ) 315 316 endpoint := fmt.Sprintf("/%s/%s/branches", ownerDid, repoName) 317 318 req, err := us.newRequest(Method, endpoint, nil) 319 if err != nil { 320 return nil, err 321 } 322 323 return us.client.Do(req) 324} 325 326func (us *UnsignedClient) Branch(ownerDid, repoName, branch string) (*http.Response, error) { 327 const ( 328 Method = "GET" 329 ) 330 331 endpoint := fmt.Sprintf("/%s/%s/branches/%s", ownerDid, repoName, branch) 332 333 req, err := us.newRequest(Method, endpoint, nil) 334 if err != nil { 335 return nil, err 336 } 337 338 return us.client.Do(req) 339} 340 341func (us *UnsignedClient) DefaultBranch(ownerDid, repoName string) (*http.Response, error) { 342 const ( 343 Method = "GET" 344 ) 345 346 endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName) 347 348 req, err := us.newRequest(Method, endpoint, nil) 349 if err != nil { 350 return nil, err 351 } 352 353 return us.client.Do(req) 354} 355 356func (us *UnsignedClient) Capabilities() (*types.Capabilities, error) { 357 const ( 358 Method = "GET" 359 Endpoint = "/capabilities" 360 ) 361 362 req, err := us.newRequest(Method, Endpoint, nil) 363 if err != nil { 364 return nil, err 365 } 366 367 resp, err := us.client.Do(req) 368 if err != nil { 369 return nil, err 370 } 371 defer resp.Body.Close() 372 373 var capabilities types.Capabilities 374 if err := json.NewDecoder(resp.Body).Decode(&capabilities); err != nil { 375 return nil, err 376 } 377 378 return &capabilities, nil 379} 380 381func (us *UnsignedClient) Compare(ownerDid, repoName, rev1, rev2 string) (*types.RepoFormatPatchResponse, error) { 382 const ( 383 Method = "GET" 384 ) 385 386 endpoint := fmt.Sprintf("/%s/%s/compare/%s/%s", ownerDid, repoName, url.PathEscape(rev1), url.PathEscape(rev2)) 387 388 req, err := us.newRequest(Method, endpoint, nil) 389 if err != nil { 390 return nil, fmt.Errorf("Failed to create request.") 391 } 392 393 compareResp, err := us.client.Do(req) 394 if err != nil { 395 return nil, fmt.Errorf("Failed to create request.") 396 } 397 defer compareResp.Body.Close() 398 399 switch compareResp.StatusCode { 400 case 404: 401 case 400: 402 return nil, fmt.Errorf("Branch comparisons not supported on this knot.") 403 } 404 405 respBody, err := io.ReadAll(compareResp.Body) 406 if err != nil { 407 log.Println("failed to compare across branches") 408 return nil, fmt.Errorf("Failed to compare branches.") 409 } 410 defer compareResp.Body.Close() 411 412 var formatPatchResponse types.RepoFormatPatchResponse 413 err = json.Unmarshal(respBody, &formatPatchResponse) 414 if err != nil { 415 log.Println("failed to unmarshal format-patch response", err) 416 return nil, fmt.Errorf("failed to compare branches.") 417 } 418 419 return &formatPatchResponse, nil 420}