diff --git a/knotserver/xrpc/list.go b/knotserver/xrpc/list.go new file mode 100644 index 00000000..fad19068 --- /dev/null +++ b/knotserver/xrpc/list.go @@ -0,0 +1,49 @@ +package xrpc + +import ( + "fmt" + "net/http" + "strconv" + + "tangled.org/core/knotserver/db" +) + +func parseListParams(r *http.Request) (db.ListPage, error) { + q := r.URL.Query() + p := db.ListPage{Limit: db.ListDefaultLimit, Desc: true} + + if s := q.Get("limit"); s != "" { + n, err := strconv.Atoi(s) + if err != nil { + return p, fmt.Errorf("limit must be an integer") + } + p.Limit = min(max(n, 1), db.ListMaxLimit) + } + + if s := q.Get("cursor"); s != "" { + n, err := strconv.Atoi(s) + if err != nil { + return p, fmt.Errorf("cursor must be an integer") + } + p.Cursor = &n + } + + switch q.Get("order") { + case "", "desc": + p.Desc = true + case "asc": + p.Desc = false + default: + return p, fmt.Errorf("order must be 'asc' or 'desc'") + } + + return p, nil +} + +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 +} diff --git a/knotserver/xrpc/list_collaborators.go b/knotserver/xrpc/list_collaborators.go new file mode 100644 index 00000000..d70bd1fd --- /dev/null +++ b/knotserver/xrpc/list_collaborators.go @@ -0,0 +1,55 @@ +package xrpc + +import ( + "net/http" + "strconv" + + "github.com/bluesky-social/indigo/atproto/syntax" + "tangled.org/core/api/tangled" + "tangled.org/core/knotserver/db" + xrpcerr "tangled.org/core/xrpc/errors" +) + +func (x *Xrpc) ListCollaborators(w http.ResponseWriter, r *http.Request) { + subject := r.URL.Query().Get("subject") + repoDid, err := syntax.ParseDID(subject) + if err != nil { + writeError(w, xrpcerr.InvalidRepoError(subject), http.StatusBadRequest) + return + } + + p, err := parseListParams(r) + if err != nil { + writeError(w, xrpcerr.NewXrpcError( + xrpcerr.WithTag("InvalidRequest"), + xrpcerr.WithMessage(err.Error()), + ), http.StatusBadRequest) + return + } + + collaborators, next, err := db.ListCollaborators(x.Db, repoDid, p) + if err != nil { + x.Logger.Error("failed to list collaborators", "repoDid", repoDid, "error", err) + writeError(w, xrpcerr.NewXrpcError( + xrpcerr.WithTag("InternalServerError"), + xrpcerr.WithMessage("failed to list collaborators"), + ), http.StatusInternalServerError) + return + } + + response := tangled.RepoListCollaborators_Output{ + Items: mapSlice(collaborators, func(c db.Collaborator) *tangled.RepoListCollaborators_ListItem { + return &tangled.RepoListCollaborators_ListItem{ + Subject: c.Subject.String(), + AddedBy: c.AddedBy.String(), + CreatedAt: c.Created, + } + }), + } + if next != nil { + cur := strconv.Itoa(*next) + response.Cursor = &cur + } + + x.writeJson(w, response) +} diff --git a/knotserver/xrpc/list_members.go b/knotserver/xrpc/list_members.go new file mode 100644 index 00000000..71b48502 --- /dev/null +++ b/knotserver/xrpc/list_members.go @@ -0,0 +1,47 @@ +package xrpc + +import ( + "net/http" + "strconv" + + "tangled.org/core/api/tangled" + "tangled.org/core/knotserver/db" + xrpcerr "tangled.org/core/xrpc/errors" +) + +func (x *Xrpc) ListMembers(w http.ResponseWriter, r *http.Request) { + p, err := parseListParams(r) + if err != nil { + writeError(w, xrpcerr.NewXrpcError( + xrpcerr.WithTag("InvalidRequest"), + xrpcerr.WithMessage(err.Error()), + ), http.StatusBadRequest) + return + } + + members, next, err := db.ListKnotMembers(x.Db, p) + if err != nil { + x.Logger.Error("failed to list knot members", "error", err) + writeError(w, xrpcerr.NewXrpcError( + xrpcerr.WithTag("InternalServerError"), + xrpcerr.WithMessage("failed to list knot members"), + ), http.StatusInternalServerError) + return + } + + response := tangled.KnotListMembers_Output{ + Items: mapSlice(members, func(m db.KnotMember) *tangled.KnotListMembers_ListItem { + return &tangled.KnotListMembers_ListItem{ + Subject: m.Subject.String(), + AddedBy: m.Did.String(), + CreatedAt: m.Created, + } + }), + } + if next != nil { + cur := strconv.Itoa(*next) + response.Cursor = &cur + } + + x.writeJson(w, response) +} diff --git a/knotserver/xrpc/xrpc.go b/knotserver/xrpc/xrpc.go index 72a109d1..b8c5bcc0 100644 --- a/knotserver/xrpc/xrpc.go +++ b/knotserver/xrpc/xrpc.go @@ -83,9 +83,11 @@ func (x *Xrpc) Router() http.Handler { r.Get("/"+tangled.RepoBranchNSID, x.RepoBranch) r.Get("/"+tangled.RepoArchiveNSID, x.RepoArchive) r.Get("/"+tangled.RepoLanguagesNSID, x.RepoLanguages) + r.Get("/"+tangled.RepoListCollaboratorsNSID, x.ListCollaborators) // knot query endpoints (no auth required) r.Get("/"+tangled.KnotListKeysNSID, x.ListKeys) + r.Get("/"+tangled.KnotListMembersNSID, x.ListMembers) r.Get("/"+tangled.KnotVersionNSID, x.Version) // service query endpoints (no auth required)