forked from tangled.org/core
Monorepo for Tangled — https://tangled.org
1package knotclient 2 3import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "log" 9 "net/http" 10 "net/url" 11 "strconv" 12 "time" 13 14 "tangled.sh/tangled.sh/core/types" 15) 16 17type UnsignedClient struct { 18 Url *url.URL 19 client *http.Client 20} 21 22func NewUnsignedClient(domain string, dev bool) (*UnsignedClient, error) { 23 client := &http.Client{ 24 Timeout: 5 * time.Second, 25 } 26 27 scheme := "https" 28 if dev { 29 scheme = "http" 30 } 31 url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain)) 32 if err != nil { 33 return nil, err 34 } 35 36 unsignedClient := &UnsignedClient{ 37 client: client, 38 Url: url, 39 } 40 41 return unsignedClient, nil 42} 43 44func (us *UnsignedClient) newRequest(method, endpoint string, query url.Values, body []byte) (*http.Request, error) { 45 reqUrl := us.Url.JoinPath(endpoint) 46 47 // add query parameters 48 if query != nil { 49 reqUrl.RawQuery = query.Encode() 50 } 51 52 return http.NewRequest(method, reqUrl.String(), bytes.NewReader(body)) 53} 54 55func do[T any](us *UnsignedClient, req *http.Request) (*T, error) { 56 resp, err := us.client.Do(req) 57 if err != nil { 58 return nil, err 59 } 60 defer resp.Body.Close() 61 62 body, err := io.ReadAll(resp.Body) 63 if err != nil { 64 log.Printf("Error reading response body: %v", err) 65 return nil, err 66 } 67 68 var result T 69 err = json.Unmarshal(body, &result) 70 if err != nil { 71 log.Printf("Error unmarshalling response body: %v", err) 72 return nil, err 73 } 74 75 return &result, nil 76} 77 78func (us *UnsignedClient) Index(ownerDid, repoName, ref string) (*types.RepoIndexResponse, error) { 79 const ( 80 Method = "GET" 81 ) 82 83 endpoint := fmt.Sprintf("/%s/%s/tree/%s", ownerDid, repoName, ref) 84 if ref == "" { 85 endpoint = fmt.Sprintf("/%s/%s", ownerDid, repoName) 86 } 87 88 req, err := us.newRequest(Method, endpoint, nil, nil) 89 if err != nil { 90 return nil, err 91 } 92 93 return do[types.RepoIndexResponse](us, req) 94} 95 96func (us *UnsignedClient) Log(ownerDid, repoName, ref string, page int) (*types.RepoLogResponse, error) { 97 const ( 98 Method = "GET" 99 ) 100 101 endpoint := fmt.Sprintf("/%s/%s/log/%s", ownerDid, repoName, url.PathEscape(ref)) 102 103 query := url.Values{} 104 query.Add("page", strconv.Itoa(page)) 105 query.Add("per_page", strconv.Itoa(60)) 106 107 req, err := us.newRequest(Method, endpoint, query, nil) 108 if err != nil { 109 return nil, err 110 } 111 112 return do[types.RepoLogResponse](us, req) 113} 114 115func (us *UnsignedClient) Branches(ownerDid, repoName string) (*types.RepoBranchesResponse, error) { 116 const ( 117 Method = "GET" 118 ) 119 120 endpoint := fmt.Sprintf("/%s/%s/branches", ownerDid, repoName) 121 122 req, err := us.newRequest(Method, endpoint, nil, nil) 123 if err != nil { 124 return nil, err 125 } 126 127 return do[types.RepoBranchesResponse](us, req) 128} 129 130func (us *UnsignedClient) Tags(ownerDid, repoName string) (*types.RepoTagsResponse, error) { 131 const ( 132 Method = "GET" 133 ) 134 135 endpoint := fmt.Sprintf("/%s/%s/tags", ownerDid, repoName) 136 137 req, err := us.newRequest(Method, endpoint, nil, nil) 138 if err != nil { 139 return nil, err 140 } 141 142 return do[types.RepoTagsResponse](us, req) 143} 144 145func (us *UnsignedClient) Branch(ownerDid, repoName, branch string) (*types.RepoBranchResponse, error) { 146 const ( 147 Method = "GET" 148 ) 149 150 endpoint := fmt.Sprintf("/%s/%s/branches/%s", ownerDid, repoName, url.PathEscape(branch)) 151 152 req, err := us.newRequest(Method, endpoint, nil, nil) 153 if err != nil { 154 return nil, err 155 } 156 157 return do[types.RepoBranchResponse](us, req) 158} 159 160func (us *UnsignedClient) DefaultBranch(ownerDid, repoName string) (*types.RepoDefaultBranchResponse, error) { 161 const ( 162 Method = "GET" 163 ) 164 165 endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName) 166 167 req, err := us.newRequest(Method, endpoint, nil, nil) 168 if err != nil { 169 return nil, err 170 } 171 172 resp, err := us.client.Do(req) 173 if err != nil { 174 return nil, err 175 } 176 defer resp.Body.Close() 177 178 var defaultBranch types.RepoDefaultBranchResponse 179 if err := json.NewDecoder(resp.Body).Decode(&defaultBranch); err != nil { 180 return nil, err 181 } 182 183 return &defaultBranch, nil 184} 185 186func (us *UnsignedClient) Capabilities() (*types.Capabilities, error) { 187 const ( 188 Method = "GET" 189 Endpoint = "/capabilities" 190 ) 191 192 req, err := us.newRequest(Method, Endpoint, nil, nil) 193 if err != nil { 194 return nil, err 195 } 196 197 resp, err := us.client.Do(req) 198 if err != nil { 199 return nil, err 200 } 201 defer resp.Body.Close() 202 203 var capabilities types.Capabilities 204 if err := json.NewDecoder(resp.Body).Decode(&capabilities); err != nil { 205 return nil, err 206 } 207 208 return &capabilities, nil 209} 210 211func (us *UnsignedClient) Compare(ownerDid, repoName, rev1, rev2 string) (*types.RepoFormatPatchResponse, error) { 212 const ( 213 Method = "GET" 214 ) 215 216 endpoint := fmt.Sprintf("/%s/%s/compare/%s/%s", ownerDid, repoName, url.PathEscape(rev1), url.PathEscape(rev2)) 217 218 req, err := us.newRequest(Method, endpoint, nil, nil) 219 if err != nil { 220 return nil, fmt.Errorf("Failed to create request.") 221 } 222 223 compareResp, err := us.client.Do(req) 224 if err != nil { 225 return nil, fmt.Errorf("Failed to create request.") 226 } 227 defer compareResp.Body.Close() 228 229 switch compareResp.StatusCode { 230 case 404: 231 case 400: 232 return nil, fmt.Errorf("Branch comparisons not supported on this knot.") 233 } 234 235 respBody, err := io.ReadAll(compareResp.Body) 236 if err != nil { 237 log.Println("failed to compare across branches") 238 return nil, fmt.Errorf("Failed to compare branches.") 239 } 240 defer compareResp.Body.Close() 241 242 var formatPatchResponse types.RepoFormatPatchResponse 243 err = json.Unmarshal(respBody, &formatPatchResponse) 244 if err != nil { 245 log.Println("failed to unmarshal format-patch response", err) 246 return nil, fmt.Errorf("failed to compare branches.") 247 } 248 249 return &formatPatchResponse, nil 250}