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}