forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

wip

Changed files
+401 -473
appview
knotclient
+1
appview/signer.go
···
+
-473
appview/state/signer.go
···
-
package state
-
-
import (
-
"bytes"
-
"crypto/hmac"
-
"crypto/sha256"
-
"encoding/hex"
-
"encoding/json"
-
"fmt"
-
"io"
-
"log"
-
"net/http"
-
"net/url"
-
"strconv"
-
"time"
-
-
"tangled.sh/tangled.sh/core/types"
-
)
-
-
type SignerTransport struct {
-
Secret string
-
}
-
-
func (s SignerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
-
timestamp := time.Now().Format(time.RFC3339)
-
mac := hmac.New(sha256.New, []byte(s.Secret))
-
message := req.Method + req.URL.Path + timestamp
-
mac.Write([]byte(message))
-
signature := hex.EncodeToString(mac.Sum(nil))
-
req.Header.Set("X-Signature", signature)
-
req.Header.Set("X-Timestamp", timestamp)
-
return http.DefaultTransport.RoundTrip(req)
-
}
-
-
type SignedClient struct {
-
Secret string
-
Url *url.URL
-
client *http.Client
-
}
-
-
func NewSignedClient(domain, secret string, dev bool) (*SignedClient, error) {
-
client := &http.Client{
-
Timeout: 5 * time.Second,
-
Transport: SignerTransport{
-
Secret: secret,
-
},
-
}
-
-
scheme := "https"
-
if dev {
-
scheme = "http"
-
}
-
url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain))
-
if err != nil {
-
return nil, err
-
}
-
-
signedClient := &SignedClient{
-
Secret: secret,
-
client: client,
-
Url: url,
-
}
-
-
return signedClient, nil
-
}
-
-
func (s *SignedClient) newRequest(method, endpoint string, body []byte) (*http.Request, error) {
-
return http.NewRequest(method, s.Url.JoinPath(endpoint).String(), bytes.NewReader(body))
-
}
-
-
func (s *SignedClient) Init(did string) (*http.Response, error) {
-
const (
-
Method = "POST"
-
Endpoint = "/init"
-
)
-
-
body, _ := json.Marshal(map[string]any{
-
"did": did,
-
})
-
-
req, err := s.newRequest(Method, Endpoint, body)
-
if err != nil {
-
return nil, err
-
}
-
-
return s.client.Do(req)
-
}
-
-
func (s *SignedClient) NewRepo(did, repoName, defaultBranch string) (*http.Response, error) {
-
const (
-
Method = "PUT"
-
Endpoint = "/repo/new"
-
)
-
-
body, _ := json.Marshal(map[string]any{
-
"did": did,
-
"name": repoName,
-
"default_branch": defaultBranch,
-
})
-
-
req, err := s.newRequest(Method, Endpoint, body)
-
if err != nil {
-
return nil, err
-
}
-
-
return s.client.Do(req)
-
}
-
-
func (s *SignedClient) ForkRepo(ownerDid, source, name string) (*http.Response, error) {
-
const (
-
Method = "POST"
-
Endpoint = "/repo/fork"
-
)
-
-
body, _ := json.Marshal(map[string]any{
-
"did": ownerDid,
-
"source": source,
-
"name": name,
-
})
-
-
req, err := s.newRequest(Method, Endpoint, body)
-
if err != nil {
-
return nil, err
-
}
-
-
return s.client.Do(req)
-
}
-
-
func (s *SignedClient) RemoveRepo(did, repoName string) (*http.Response, error) {
-
const (
-
Method = "DELETE"
-
Endpoint = "/repo"
-
)
-
-
body, _ := json.Marshal(map[string]any{
-
"did": did,
-
"name": repoName,
-
})
-
-
req, err := s.newRequest(Method, Endpoint, body)
-
if err != nil {
-
return nil, err
-
}
-
-
return s.client.Do(req)
-
}
-
-
func (s *SignedClient) AddMember(did string) (*http.Response, error) {
-
const (
-
Method = "PUT"
-
Endpoint = "/member/add"
-
)
-
-
body, _ := json.Marshal(map[string]any{
-
"did": did,
-
})
-
-
req, err := s.newRequest(Method, Endpoint, body)
-
if err != nil {
-
return nil, err
-
}
-
-
return s.client.Do(req)
-
}
-
-
func (s *SignedClient) SetDefaultBranch(ownerDid, repoName, branch string) (*http.Response, error) {
-
const (
-
Method = "PUT"
-
)
-
endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName)
-
-
body, _ := json.Marshal(map[string]any{
-
"branch": branch,
-
})
-
-
req, err := s.newRequest(Method, endpoint, body)
-
if err != nil {
-
return nil, err
-
}
-
-
return s.client.Do(req)
-
}
-
-
func (s *SignedClient) AddCollaborator(ownerDid, repoName, memberDid string) (*http.Response, error) {
-
const (
-
Method = "POST"
-
)
-
endpoint := fmt.Sprintf("/%s/%s/collaborator/add", ownerDid, repoName)
-
-
body, _ := json.Marshal(map[string]any{
-
"did": memberDid,
-
})
-
-
req, err := s.newRequest(Method, endpoint, body)
-
if err != nil {
-
return nil, err
-
}
-
-
return s.client.Do(req)
-
}
-
-
func (s *SignedClient) Merge(
-
patch []byte,
-
ownerDid, targetRepo, branch, commitMessage, commitBody, authorName, authorEmail string,
-
) (*http.Response, error) {
-
const (
-
Method = "POST"
-
)
-
endpoint := fmt.Sprintf("/%s/%s/merge", ownerDid, targetRepo)
-
-
mr := types.MergeRequest{
-
Branch: branch,
-
CommitMessage: commitMessage,
-
CommitBody: commitBody,
-
AuthorName: authorName,
-
AuthorEmail: authorEmail,
-
Patch: string(patch),
-
}
-
-
body, _ := json.Marshal(mr)
-
-
req, err := s.newRequest(Method, endpoint, body)
-
if err != nil {
-
return nil, err
-
}
-
-
return s.client.Do(req)
-
}
-
-
func (s *SignedClient) MergeCheck(patch []byte, ownerDid, targetRepo, branch string) (*http.Response, error) {
-
const (
-
Method = "POST"
-
)
-
endpoint := fmt.Sprintf("/%s/%s/merge/check", ownerDid, targetRepo)
-
-
body, _ := json.Marshal(map[string]any{
-
"patch": string(patch),
-
"branch": branch,
-
})
-
-
req, err := s.newRequest(Method, endpoint, body)
-
if err != nil {
-
return nil, err
-
}
-
-
return s.client.Do(req)
-
}
-
-
func (s *SignedClient) NewHiddenRef(ownerDid, targetRepo, forkBranch, remoteBranch string) (*http.Response, error) {
-
const (
-
Method = "POST"
-
)
-
endpoint := fmt.Sprintf("/%s/%s/hidden-ref/%s/%s", ownerDid, targetRepo, url.PathEscape(forkBranch), url.PathEscape(remoteBranch))
-
-
req, err := s.newRequest(Method, endpoint, nil)
-
if err != nil {
-
return nil, err
-
}
-
-
return s.client.Do(req)
-
}
-
-
type UnsignedClient struct {
-
Url *url.URL
-
client *http.Client
-
}
-
-
func NewUnsignedClient(domain string, dev bool) (*UnsignedClient, error) {
-
client := &http.Client{
-
Timeout: 5 * time.Second,
-
}
-
-
scheme := "https"
-
if dev {
-
scheme = "http"
-
}
-
url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain))
-
if err != nil {
-
return nil, err
-
}
-
-
unsignedClient := &UnsignedClient{
-
client: client,
-
Url: url,
-
}
-
-
return unsignedClient, nil
-
}
-
-
func (us *UnsignedClient) newRequest(method, endpoint string, query url.Values, body []byte) (*http.Request, error) {
-
reqUrl := us.Url.JoinPath(endpoint)
-
-
// add query parameters
-
if query != nil {
-
reqUrl.RawQuery = query.Encode()
-
}
-
-
return http.NewRequest(method, reqUrl.String(), bytes.NewReader(body))
-
}
-
-
func (us *UnsignedClient) Index(ownerDid, repoName, ref string) (*http.Response, error) {
-
const (
-
Method = "GET"
-
)
-
-
endpoint := fmt.Sprintf("/%s/%s/tree/%s", ownerDid, repoName, ref)
-
if ref == "" {
-
endpoint = fmt.Sprintf("/%s/%s", ownerDid, repoName)
-
}
-
-
req, err := us.newRequest(Method, endpoint, nil, nil)
-
if err != nil {
-
return nil, err
-
}
-
-
return us.client.Do(req)
-
}
-
-
func (us *UnsignedClient) Log(ownerDid, repoName, ref string, page int) (*http.Response, error) {
-
const (
-
Method = "GET"
-
)
-
-
endpoint := fmt.Sprintf("/%s/%s/log/%s", ownerDid, repoName, url.PathEscape(ref))
-
-
query := url.Values{}
-
query.Add("page", strconv.Itoa(page))
-
query.Add("per_page", strconv.Itoa(60))
-
-
req, err := us.newRequest(Method, endpoint, query, nil)
-
if err != nil {
-
return nil, err
-
}
-
-
return us.client.Do(req)
-
}
-
-
func (us *UnsignedClient) Branches(ownerDid, repoName string) (*http.Response, error) {
-
const (
-
Method = "GET"
-
)
-
-
endpoint := fmt.Sprintf("/%s/%s/branches", ownerDid, repoName)
-
-
req, err := us.newRequest(Method, endpoint, nil, nil)
-
if err != nil {
-
return nil, err
-
}
-
-
return us.client.Do(req)
-
}
-
-
func (us *UnsignedClient) Tags(ownerDid, repoName string) (*http.Response, error) {
-
const (
-
Method = "GET"
-
)
-
-
endpoint := fmt.Sprintf("/%s/%s/tags", ownerDid, repoName)
-
-
req, err := us.newRequest(Method, endpoint, nil, nil)
-
if err != nil {
-
return nil, err
-
}
-
-
return us.client.Do(req)
-
}
-
-
func (us *UnsignedClient) Branch(ownerDid, repoName, branch string) (*http.Response, error) {
-
const (
-
Method = "GET"
-
)
-
-
endpoint := fmt.Sprintf("/%s/%s/branches/%s", ownerDid, repoName, url.PathEscape(branch))
-
-
req, err := us.newRequest(Method, endpoint, nil, nil)
-
if err != nil {
-
return nil, err
-
}
-
-
return us.client.Do(req)
-
}
-
-
func (us *UnsignedClient) DefaultBranch(ownerDid, repoName string) (*types.RepoDefaultBranchResponse, error) {
-
const (
-
Method = "GET"
-
)
-
-
endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName)
-
-
req, err := us.newRequest(Method, endpoint, nil, nil)
-
if err != nil {
-
return nil, err
-
}
-
-
resp, err := us.client.Do(req)
-
if err != nil {
-
return nil, err
-
}
-
defer resp.Body.Close()
-
-
var defaultBranch types.RepoDefaultBranchResponse
-
if err := json.NewDecoder(resp.Body).Decode(&defaultBranch); err != nil {
-
return nil, err
-
}
-
-
return &defaultBranch, nil
-
}
-
-
func (us *UnsignedClient) Capabilities() (*types.Capabilities, error) {
-
const (
-
Method = "GET"
-
Endpoint = "/capabilities"
-
)
-
-
req, err := us.newRequest(Method, Endpoint, nil, nil)
-
if err != nil {
-
return nil, err
-
}
-
-
resp, err := us.client.Do(req)
-
if err != nil {
-
return nil, err
-
}
-
defer resp.Body.Close()
-
-
var capabilities types.Capabilities
-
if err := json.NewDecoder(resp.Body).Decode(&capabilities); err != nil {
-
return nil, err
-
}
-
-
return &capabilities, nil
-
}
-
-
func (us *UnsignedClient) Compare(ownerDid, repoName, rev1, rev2 string) (*types.RepoFormatPatchResponse, error) {
-
const (
-
Method = "GET"
-
)
-
-
endpoint := fmt.Sprintf("/%s/%s/compare/%s/%s", ownerDid, repoName, url.PathEscape(rev1), url.PathEscape(rev2))
-
-
req, err := us.newRequest(Method, endpoint, nil, nil)
-
if err != nil {
-
return nil, fmt.Errorf("Failed to create request.")
-
}
-
-
compareResp, err := us.client.Do(req)
-
if err != nil {
-
return nil, fmt.Errorf("Failed to create request.")
-
}
-
defer compareResp.Body.Close()
-
-
switch compareResp.StatusCode {
-
case 404:
-
case 400:
-
return nil, fmt.Errorf("Branch comparisons not supported on this knot.")
-
}
-
-
respBody, err := io.ReadAll(compareResp.Body)
-
if err != nil {
-
log.Println("failed to compare across branches")
-
return nil, fmt.Errorf("Failed to compare branches.")
-
}
-
defer compareResp.Body.Close()
-
-
var formatPatchResponse types.RepoFormatPatchResponse
-
err = json.Unmarshal(respBody, &formatPatchResponse)
-
if err != nil {
-
log.Println("failed to unmarshal format-patch response", err)
-
return nil, fmt.Errorf("failed to compare branches.")
-
}
-
-
return &formatPatchResponse, nil
-
}
+258
knotclient/signed.go
···
+
package knotclient
+
+
import (
+
"bytes"
+
"crypto/hmac"
+
"crypto/sha256"
+
"encoding/hex"
+
"encoding/json"
+
"fmt"
+
"net/http"
+
"net/url"
+
"time"
+
+
"tangled.sh/tangled.sh/core/types"
+
)
+
+
type SignerTransport struct {
+
Secret string
+
}
+
+
func (s SignerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+
timestamp := time.Now().Format(time.RFC3339)
+
mac := hmac.New(sha256.New, []byte(s.Secret))
+
message := req.Method + req.URL.Path + timestamp
+
mac.Write([]byte(message))
+
signature := hex.EncodeToString(mac.Sum(nil))
+
req.Header.Set("X-Signature", signature)
+
req.Header.Set("X-Timestamp", timestamp)
+
return http.DefaultTransport.RoundTrip(req)
+
}
+
+
type SignedClient struct {
+
Secret string
+
Url *url.URL
+
client *http.Client
+
}
+
+
func NewSignedClient(domain, secret string, dev bool) (*SignedClient, error) {
+
client := &http.Client{
+
Timeout: 5 * time.Second,
+
Transport: SignerTransport{
+
Secret: secret,
+
},
+
}
+
+
scheme := "https"
+
if dev {
+
scheme = "http"
+
}
+
url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain))
+
if err != nil {
+
return nil, err
+
}
+
+
signedClient := &SignedClient{
+
Secret: secret,
+
client: client,
+
Url: url,
+
}
+
+
return signedClient, nil
+
}
+
+
func (s *SignedClient) newRequest(method, endpoint string, body []byte) (*http.Request, error) {
+
return http.NewRequest(method, s.Url.JoinPath(endpoint).String(), bytes.NewReader(body))
+
}
+
+
func (s *SignedClient) Init(did string) (*http.Response, error) {
+
const (
+
Method = "POST"
+
Endpoint = "/init"
+
)
+
+
body, _ := json.Marshal(map[string]any{
+
"did": did,
+
})
+
+
req, err := s.newRequest(Method, Endpoint, body)
+
if err != nil {
+
return nil, err
+
}
+
+
return s.client.Do(req)
+
}
+
+
func (s *SignedClient) NewRepo(did, repoName, defaultBranch string) (*http.Response, error) {
+
const (
+
Method = "PUT"
+
Endpoint = "/repo/new"
+
)
+
+
body, _ := json.Marshal(map[string]any{
+
"did": did,
+
"name": repoName,
+
"default_branch": defaultBranch,
+
})
+
+
req, err := s.newRequest(Method, Endpoint, body)
+
if err != nil {
+
return nil, err
+
}
+
+
return s.client.Do(req)
+
}
+
+
func (s *SignedClient) ForkRepo(ownerDid, source, name string) (*http.Response, error) {
+
const (
+
Method = "POST"
+
Endpoint = "/repo/fork"
+
)
+
+
body, _ := json.Marshal(map[string]any{
+
"did": ownerDid,
+
"source": source,
+
"name": name,
+
})
+
+
req, err := s.newRequest(Method, Endpoint, body)
+
if err != nil {
+
return nil, err
+
}
+
+
return s.client.Do(req)
+
}
+
+
func (s *SignedClient) RemoveRepo(did, repoName string) (*http.Response, error) {
+
const (
+
Method = "DELETE"
+
Endpoint = "/repo"
+
)
+
+
body, _ := json.Marshal(map[string]any{
+
"did": did,
+
"name": repoName,
+
})
+
+
req, err := s.newRequest(Method, Endpoint, body)
+
if err != nil {
+
return nil, err
+
}
+
+
return s.client.Do(req)
+
}
+
+
func (s *SignedClient) AddMember(did string) (*http.Response, error) {
+
const (
+
Method = "PUT"
+
Endpoint = "/member/add"
+
)
+
+
body, _ := json.Marshal(map[string]any{
+
"did": did,
+
})
+
+
req, err := s.newRequest(Method, Endpoint, body)
+
if err != nil {
+
return nil, err
+
}
+
+
return s.client.Do(req)
+
}
+
+
func (s *SignedClient) SetDefaultBranch(ownerDid, repoName, branch string) (*http.Response, error) {
+
const (
+
Method = "PUT"
+
)
+
endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName)
+
+
body, _ := json.Marshal(map[string]any{
+
"branch": branch,
+
})
+
+
req, err := s.newRequest(Method, endpoint, body)
+
if err != nil {
+
return nil, err
+
}
+
+
return s.client.Do(req)
+
}
+
+
func (s *SignedClient) AddCollaborator(ownerDid, repoName, memberDid string) (*http.Response, error) {
+
const (
+
Method = "POST"
+
)
+
endpoint := fmt.Sprintf("/%s/%s/collaborator/add", ownerDid, repoName)
+
+
body, _ := json.Marshal(map[string]any{
+
"did": memberDid,
+
})
+
+
req, err := s.newRequest(Method, endpoint, body)
+
if err != nil {
+
return nil, err
+
}
+
+
return s.client.Do(req)
+
}
+
+
func (s *SignedClient) Merge(
+
patch []byte,
+
ownerDid, targetRepo, branch, commitMessage, commitBody, authorName, authorEmail string,
+
) (*http.Response, error) {
+
const (
+
Method = "POST"
+
)
+
endpoint := fmt.Sprintf("/%s/%s/merge", ownerDid, targetRepo)
+
+
mr := types.MergeRequest{
+
Branch: branch,
+
CommitMessage: commitMessage,
+
CommitBody: commitBody,
+
AuthorName: authorName,
+
AuthorEmail: authorEmail,
+
Patch: string(patch),
+
}
+
+
body, _ := json.Marshal(mr)
+
+
req, err := s.newRequest(Method, endpoint, body)
+
if err != nil {
+
return nil, err
+
}
+
+
return s.client.Do(req)
+
}
+
+
func (s *SignedClient) MergeCheck(patch []byte, ownerDid, targetRepo, branch string) (*http.Response, error) {
+
const (
+
Method = "POST"
+
)
+
endpoint := fmt.Sprintf("/%s/%s/merge/check", ownerDid, targetRepo)
+
+
body, _ := json.Marshal(map[string]any{
+
"patch": string(patch),
+
"branch": branch,
+
})
+
+
req, err := s.newRequest(Method, endpoint, body)
+
if err != nil {
+
return nil, err
+
}
+
+
return s.client.Do(req)
+
}
+
+
func (s *SignedClient) NewHiddenRef(ownerDid, targetRepo, forkBranch, remoteBranch string) (*http.Response, error) {
+
const (
+
Method = "POST"
+
)
+
endpoint := fmt.Sprintf("/%s/%s/hidden-ref/%s/%s", ownerDid, targetRepo, url.PathEscape(forkBranch), url.PathEscape(remoteBranch))
+
+
req, err := s.newRequest(Method, endpoint, nil)
+
if err != nil {
+
return nil, err
+
}
+
+
return s.client.Do(req)
+
}
+142
knotclient/unsigned.go
···
+
package knotclient
+
+
import (
+
"bytes"
+
"encoding/json"
+
"fmt"
+
"io"
+
"net/http"
+
"net/url"
+
"path"
+
"strconv"
+
+
"tangled.sh/tangled.sh/core/types"
+
)
+
+
type UnsignedClient struct {
+
Domain string
+
Dev bool
+
client *http.Client
+
}
+
+
func (u *UnsignedClient) scheme() string {
+
if u.Dev {
+
return "http"
+
}
+
+
return "https"
+
}
+
+
func (u *UnsignedClient) url() (*url.URL, error) {
+
return url.Parse(fmt.Sprintf("%s://%s", u.scheme(), u.Domain))
+
}
+
+
type KnotRequest interface {
+
Method() string
+
Path() string
+
Query() url.Values
+
Body() []byte
+
}
+
+
func do[T any](u *UnsignedClient, method, path string, query url.Values, body []byte) (*T, error) {
+
// Create a copy of the base URL to avoid modifying the original
+
base, err := u.url()
+
if err != nil {
+
return nil, fmt.Errorf("failed to parse URL: %w", err)
+
}
+
+
// add path
+
base = base.JoinPath(path)
+
+
// add query
+
if query != nil {
+
base.RawQuery = query.Encode()
+
}
+
+
// Create the request
+
req, err := http.NewRequest(method, base.String(), bytes.NewReader(body))
+
if err != nil {
+
return nil, err
+
}
+
+
resp, err := u.client.Do(req)
+
if err != nil {
+
return nil, err
+
}
+
defer resp.Body.Close()
+
+
body, err = io.ReadAll(resp.Body)
+
if err != nil {
+
return nil, err
+
}
+
+
var result T
+
err = json.Unmarshal(body, &result)
+
if err != nil {
+
return nil, err
+
}
+
+
return &result, nil
+
}
+
+
func (u *UnsignedClient) Index(ownerDid, repoName, ref string) (*types.RepoIndexResponse, error) {
+
method := http.MethodGet
+
endpoint := path.Join(ownerDid, repoName, "tree", ref)
+
if ref == "" {
+
endpoint = path.Join(ownerDid, repoName)
+
}
+
return do[types.RepoIndexResponse](u, method, endpoint, nil, nil)
+
}
+
+
func (u *UnsignedClient) Log(ownerDid, repoName, ref string, page int) (*types.RepoLogResponse, error) {
+
method := http.MethodGet
+
endpoint := fmt.Sprintf("/%s/%s/log/%s", ownerDid, repoName, url.PathEscape(ref))
+
+
query := url.Values{}
+
query.Add("page", strconv.Itoa(page))
+
query.Add("per_page", strconv.Itoa(60))
+
+
return do[types.RepoLogResponse](u, method, endpoint, nil, nil)
+
}
+
+
func (u *UnsignedClient) Branches(ownerDid, repoName string) (*types.RepoBranchesResponse, error) {
+
method := http.MethodGet
+
endpoint := fmt.Sprintf("/%s/%s/branches", ownerDid, repoName)
+
+
return do[types.RepoBranchesResponse](u, method, endpoint, nil, nil)
+
}
+
+
func (u *UnsignedClient) Tags(ownerDid, repoName string) (*types.RepoTagsResponse, error) {
+
method := http.MethodGet
+
endpoint := fmt.Sprintf("/%s/%s/tags", ownerDid, repoName)
+
+
return do[types.RepoTagsResponse](u, method, endpoint, nil, nil)
+
}
+
+
func (u *UnsignedClient) Branch(ownerDid, repoName, branch string) (*types.RepoBranchResponse, error) {
+
method := http.MethodGet
+
endpoint := fmt.Sprintf("/%s/%s/branches/%s", ownerDid, repoName, url.PathEscape(branch))
+
+
return do[types.RepoBranchResponse](u, method, endpoint, nil, nil)
+
}
+
+
func (u *UnsignedClient) DefaultBranch(ownerDid, repoName string) (*types.RepoBranchResponse, error) {
+
method := http.MethodGet
+
endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName)
+
+
return do[types.RepoBranchResponse](u, method, endpoint, nil, nil)
+
}
+
+
func (u *UnsignedClient) Capabilities() (*types.Capabilities, error) {
+
method := http.MethodGet
+
endpoint := "capabilities"
+
+
return do[types.Capabilities](u, method, endpoint, nil, nil)
+
}
+
+
func (u *UnsignedClient) Compare(ownerDid, repoName, rev1, rev2 string) (*types.RepoFormatPatchResponse, error) {
+
method := http.MethodGet
+
endpoint := fmt.Sprintf("/%s/%s/compare/%s/%s", ownerDid, repoName, url.PathEscape(rev1), url.PathEscape(rev2))
+
+
return do[types.RepoFormatPatchResponse](u, method, endpoint, nil, nil)
+
}