package deliberi
import (
"context"
"fmt"
"net/http"
"strings"
"time"
indigoxrpc "github.com/bluesky-social/indigo/xrpc"
"github.com/samber/lo"
"tangled.org/core/api/org_tangled"
)
type bobbinLookup interface {
RepoOwner(ctx context.Context, repoDid string) (ownerDid, name string, err error)
TicketAuthor(ctx context.Context, ticketUri string) (string, error)
}
type inviteLister interface {
ListCollaboratorInvitesBy(ctx context.Context, subjectDid string) (InviteAnswer, error)
}
type InviteOffer struct {
Uri string `json:"uri"`
KnotDid string `json:"knot"`
RepoDid string `json:"repo"`
AddedBy string `json:"addedBy"`
CreatedAt time.Time `json:"createdAt"`
}
type InviteAnswer struct {
Offers []InviteOffer `json:"items"`
Pending []string `json:"pending"`
Truncated bool `json:"truncated"`
}
type bobbinClient struct {
xc *indigoxrpc.Client
}
func newBobbinClient(apiUrl string) *bobbinClient {
return &bobbinClient{
xc: &indigoxrpc.Client{
Host: strings.TrimRight(apiUrl, "/"),
Client: &http.Client{Timeout: 10 * time.Second},
},
}
}
// the record can't name the opener once someone else edits it, so ask bobbin
func (c *bobbinClient) TicketAuthor(ctx context.Context, ticketUri string) (string, error) {
out, err := org_tangled.TrackGetTicketView(ctx, c.xc, ticketUri)
if err != nil {
return "", fmt.Errorf("calling %s: %w", org_tangled.TrackGetTicketViewNSID, err)
}
if out.Author == nil {
return "", fmt.Errorf("ticket view for %s has no author", ticketUri)
}
return out.Author.Did, nil
}
// bobbin serves getRepoByRepoDid from the repo's org.tangled.repo.manifest
type repoByRepoDid struct {
Value repoManifest `json:"value"`
}
func (c *bobbinClient) RepoOwner(ctx context.Context, repoDid string) (string, string, error) {
var out repoByRepoDid
params := map[string]any{"repoDid": repoDid}
if err := c.xc.Do(ctx, indigoxrpc.Query, "", org_tangled.RepoGetRepoByRepoDidNSID, params, nil, &out); err != nil {
return "", "", fmt.Errorf("calling %s: %w", org_tangled.RepoGetRepoByRepoDidNSID, err)
}
decl := out.Value.Declaration
if decl == nil || decl.Owner == "" {
return "", "", fmt.Errorf("manifest for %s has no declared owner", repoDid)
}
return decl.Owner, decl.Name, nil
}
func (c *bobbinClient) ListCollaboratorInvitesBy(ctx context.Context, subjectDid string) (InviteAnswer, error) {
var out InviteAnswer
params := map[string]any{"subject": subjectDid}
if err := c.xc.Do(ctx, indigoxrpc.Query, "", org_tangled.RepoListCollaboratorInvitesByNSID, params, nil, &out); err != nil {
return InviteAnswer{}, fmt.Errorf("calling %s: %w", org_tangled.RepoListCollaboratorInvitesByNSID, err)
}
if blank, found := lo.Find(out.Offers, func(offer InviteOffer) bool {
return offer.Uri == "" || offer.KnotDid == "" || offer.RepoDid == ""
}); found {
missing := "record"
if blank.Uri != "" {
missing = lo.Ternary(blank.KnotDid == "", "knot", "repo")
}
return InviteAnswer{}, fmt.Errorf(
"%s served offer missing its %s, so we couldn't tell it apart from a withdrawal",
org_tangled.RepoListCollaboratorInvitesByNSID,
missing,
)
}
return out, nil
}