Something went wrong. Try again.
An ATProto Powered Reputation Network
Something went wrong. Try again.
4.3 kB · 127 lines
Go
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128package appview
import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "strings" "time"
"github.com/bluesky-social/indigo/atproto/atclient"
"provenance/internal/domain" "provenance/internal/reputation" "provenance/internal/storage" "provenance/internal/trust" "provenance/internal/upstream")
const MaxClientBody = 4 << 20
type Client struct { base string key string http *http.Client}
func NewClient(base, key string) (*Client, error) { u, err := url.Parse(base) if err != nil || u.Host == "" || u.User != nil || u.Path != "" && u.Path != "/" || u.RawQuery != "" || u.Fragment != "" { return nil, errors.New("invalid appview URL") } if u.Scheme != "https" && !(u.Scheme == "http" && (u.Hostname() == "127.0.0.1" || u.Hostname() == "::1" || u.Hostname() == "localhost")) { return nil, errors.New("appview URL requires HTTPS or loopback HTTP") } return &Client{strings.TrimRight(base, "/"), key, &http.Client{Timeout: 2 * time.Minute, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}}, nil}
func (c *Client) call(ctx context.Context, method, path, did string, body, out any) error { var data []byte var err error if body != nil { data, err = json.Marshal(body) if err != nil { return err } } req, err := http.NewRequestWithContext(ctx, method, c.base+path, bytes.NewReader(data)) if err != nil { return err } req.Header.Set("Authorization", "Bearer "+c.key) req.Header.Set(IdentityHeader, did) req.Header.Set("Content-Type", "application/json") req.Header.Set("User-Agent", upstream.UserAgent) resp, err := c.http.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return &atclient.APIError{StatusCode: resp.StatusCode} } if out == nil { _, err = io.Copy(io.Discard, io.LimitReader(resp.Body, MaxClientBody)) return err } data, err = io.ReadAll(io.LimitReader(resp.Body, MaxClientBody+1)) if err != nil { return err } if len(data) > MaxClientBody { return errors.New("appview response is too large") } return json.Unmarshal(data, out)}
func (c *Client) Join(ctx context.Context, did string) error { return c.call(ctx, http.MethodPost, "/v1/participants", did, nil, nil)}func (c *Client) Sync(ctx context.Context, did string) error { return c.call(ctx, http.MethodPost, "/v1/sync", did, nil, nil)}func (c *Client) Config(ctx context.Context, did string) (storage.Participant, error) { var out storage.Participant err := c.call(ctx, http.MethodGet, "/v1/me", did, nil, &out) return out, err}func (c *Client) SetThreshold(ctx context.Context, did string, value float64) error { return c.call(ctx, http.MethodPut, "/v1/me/threshold", did, map[string]float64{"threshold": value}, nil)}func (c *Client) Tags(ctx context.Context, prefix string) ([]string, error) { var out []string err := c.call(ctx, http.MethodGet, "/v1/tags?"+url.Values{"prefix": {prefix}}.Encode(), "", nil, &out) return out, err}func (c *Client) Vouches(ctx context.Context, did string, offset int) ([]domain.Vouch, error) { var out []domain.Vouch err := c.call(ctx, http.MethodGet, "/v1/vouches?"+url.Values{"author": {did}, "offset": {fmt.Sprint(offset)}}.Encode(), "", nil, &out) return out, err}func (c *Client) Evaluate(ctx context.Context, did, target, tag string, mode domain.Mode, path string, options ...trust.Accounts) (Evaluation, error) { params := url.Values{"target": {target}, "tag": {tag}, "mode": {string(mode)}, "include_path": {path}} for _, accounts := range options { for name, values := range accounts { if _, exists := params[name]; exists { return Evaluation{}, errors.New("algorithm option conflicts with query parameter") } params[name] = values } } var out Evaluation err := c.call(ctx, http.MethodGet, "/v1/me/trust?"+params.Encode(), did, nil, &out) return out, err}
// Aggregate uses the authenticated account and its private author-trust threshold.func (c *Client) Aggregate(ctx context.Context, did, target, tag string, mode domain.Mode) (reputation.Result, error) { params := url.Values{"target": {target}, "tag": {tag}, "mode": {string(mode)}} var out reputation.Result err := c.call(ctx, http.MethodGet, "/v1/me/reputation?"+params.Encode(), did, nil, &out) return out, err}