Monorepo for Tangled
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135package knotacl
import ( "context" "fmt" "log/slog" "net/http" "slices" "time"
indigoxrpc "github.com/bluesky-social/indigo/xrpc" "tangled.org/core/api/tangled")
const ( listPageLimit = 1000 maxListPages = 256 requestTimeout = 5 * time.Second listDrainBudget = 30 * time.Second)
type Client struct { dev bool http *http.Client logger *slog.Logger}
func NewClient(dev bool, logger *slog.Logger) *Client { return &Client{dev: dev, http: &http.Client{Timeout: requestTimeout}, logger: logger}}
func (c *Client) xrpcClient(host string) *indigoxrpc.Client { scheme := "https" if c.dev { scheme = "http" } return &indigoxrpc.Client{ Host: fmt.Sprintf("%s://%s", scheme, host), Client: c.http, }}
func (c *Client) GetKnotMembers(ctx context.Context, host string) ([]string, error) { ctx, cancel := context.WithTimeout(ctx, listDrainBudget) defer cancel()
xc := c.xrpcClient(host) subjects, truncated, err := drainList( "", make(map[string]struct{}), func(cursor string) ([]*tangled.KnotListMembers_ListItem, *string, error) { out, err := tangled.KnotListMembers(ctx, xc, cursor, listPageLimit, "", host) if err != nil { return nil, nil, err } return out.Items, out.Cursor, nil }, func(i *tangled.KnotListMembers_ListItem) string { return i.Subject }, ) if err != nil { return nil, err } if truncated { c.logger.Warn("knot member list truncated before draining all pages", "host", host, "limit", maxListPages) } return dedup(subjects), nil}
func (c *Client) GetRepoCollaborators(ctx context.Context, host, repoDid string) ([]string, error) { ctx, cancel := context.WithTimeout(ctx, listDrainBudget) defer cancel()
xc := c.xrpcClient(host) subjects, truncated, err := drainList( "", make(map[string]struct{}), func(cursor string) ([]*tangled.RepoListCollaborators_ListItem, *string, error) { out, err := tangled.RepoListCollaborators(ctx, xc, cursor, listPageLimit, "", repoDid) if err != nil { return nil, nil, err } return out.Items, out.Cursor, nil }, func(i *tangled.RepoListCollaborators_ListItem) string { return i.Subject }, ) if err != nil { return nil, err } if truncated { c.logger.Warn("repo collaborator list truncated before draining all pages", "host", host, "repoDid", repoDid, "limit", maxListPages) } return dedup(subjects), nil}
func drainList[T any]( cursor string, seen map[string]struct{}, page func(cursor string) ([]*T, *string, error), subject func(*T) string,) (subjects []string, truncated bool, err error) { if len(seen) >= maxListPages { return nil, true, nil } if _, repeated := seen[cursor]; repeated { return nil, true, nil } seen[cursor] = struct{}{} items, next, err := page(cursor) if err != nil { return nil, false, err } subjects = mapSlice(items, subject) if len(items) == 0 || next == nil || *next == "" { return subjects, false, nil } rest, truncated, err := drainList(*next, seen, page, subject) if err != nil { return nil, false, err } return append(subjects, rest...), truncated, nil}
func dedup(subjects []string) []string { slices.Sort(subjects) return slices.Compact(subjects)}
func mapSlice[T, U any](items []T, f func(T) U) []U { out := make([]U, len(items)) for i, it := range items { out[i] = f(it) } return out}