1package knotclient
2
3import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "io"
8 "net/http"
9 "net/url"
10 "path"
11 "strconv"
12
13 "tangled.sh/tangled.sh/core/types"
14)
15
16type UnsignedClient struct {
17 Domain string
18 Dev bool
19 client *http.Client
20}
21
22func (u *UnsignedClient) scheme() string {
23 if u.Dev {
24 return "http"
25 }
26
27 return "https"
28}
29
30func (u *UnsignedClient) url() (*url.URL, error) {
31 return url.Parse(fmt.Sprintf("%s://%s", u.scheme(), u.Domain))
32}
33
34type KnotRequest interface {
35 Method() string
36 Path() string
37 Query() url.Values
38 Body() []byte
39}
40
41func do[T any](u *UnsignedClient, method, path string, query url.Values, body []byte) (*T, error) {
42 // Create a copy of the base URL to avoid modifying the original
43 base, err := u.url()
44 if err != nil {
45 return nil, fmt.Errorf("failed to parse URL: %w", err)
46 }
47
48 // add path
49 base = base.JoinPath(path)
50
51 // add query
52 if query != nil {
53 base.RawQuery = query.Encode()
54 }
55
56 // Create the request
57 req, err := http.NewRequest(method, base.String(), bytes.NewReader(body))
58 if err != nil {
59 return nil, err
60 }
61
62 resp, err := u.client.Do(req)
63 if err != nil {
64 return nil, err
65 }
66 defer resp.Body.Close()
67
68 body, err = io.ReadAll(resp.Body)
69 if err != nil {
70 return nil, err
71 }
72
73 var result T
74 err = json.Unmarshal(body, &result)
75 if err != nil {
76 return nil, err
77 }
78
79 return &result, nil
80}
81
82func (u *UnsignedClient) Index(ownerDid, repoName, ref string) (*types.RepoIndexResponse, error) {
83 method := http.MethodGet
84 endpoint := path.Join(ownerDid, repoName, "tree", ref)
85 if ref == "" {
86 endpoint = path.Join(ownerDid, repoName)
87 }
88 return do[types.RepoIndexResponse](u, method, endpoint, nil, nil)
89}
90
91func (u *UnsignedClient) Log(ownerDid, repoName, ref string, page int) (*types.RepoLogResponse, error) {
92 method := http.MethodGet
93 endpoint := fmt.Sprintf("/%s/%s/log/%s", ownerDid, repoName, url.PathEscape(ref))
94
95 query := url.Values{}
96 query.Add("page", strconv.Itoa(page))
97 query.Add("per_page", strconv.Itoa(60))
98
99 return do[types.RepoLogResponse](u, method, endpoint, nil, nil)
100}
101
102func (u *UnsignedClient) Branches(ownerDid, repoName string) (*types.RepoBranchesResponse, error) {
103 method := http.MethodGet
104 endpoint := fmt.Sprintf("/%s/%s/branches", ownerDid, repoName)
105
106 return do[types.RepoBranchesResponse](u, method, endpoint, nil, nil)
107}
108
109func (u *UnsignedClient) Tags(ownerDid, repoName string) (*types.RepoTagsResponse, error) {
110 method := http.MethodGet
111 endpoint := fmt.Sprintf("/%s/%s/tags", ownerDid, repoName)
112
113 return do[types.RepoTagsResponse](u, method, endpoint, nil, nil)
114}
115
116func (u *UnsignedClient) Branch(ownerDid, repoName, branch string) (*types.RepoBranchResponse, error) {
117 method := http.MethodGet
118 endpoint := fmt.Sprintf("/%s/%s/branches/%s", ownerDid, repoName, url.PathEscape(branch))
119
120 return do[types.RepoBranchResponse](u, method, endpoint, nil, nil)
121}
122
123func (u *UnsignedClient) DefaultBranch(ownerDid, repoName string) (*types.RepoDefaultBranchResponse, error) {
124 method := http.MethodGet
125 endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName)
126
127 return do[types.RepoDefaultBranchResponse](u, method, endpoint, nil, nil)
128}
129
130func (u *UnsignedClient) Capabilities() (*types.Capabilities, error) {
131 method := http.MethodGet
132 endpoint := "capabilities"
133
134 return do[types.Capabilities](u, method, endpoint, nil, nil)
135}
136
137func (u *UnsignedClient) Compare(ownerDid, repoName, rev1, rev2 string) (*types.RepoFormatPatchResponse, error) {
138 method := http.MethodGet
139 endpoint := fmt.Sprintf("/%s/%s/compare/%s/%s", ownerDid, repoName, url.PathEscape(rev1), url.PathEscape(rev2))
140
141 return do[types.RepoFormatPatchResponse](u, method, endpoint, nil, nil)
142}