Skip to content

Commit

Permalink
*: add cursor fetching to GetOrgMembers api.
Browse files Browse the repository at this point in the history
  • Loading branch information
sgotti committed Sep 25, 2023
1 parent 7c5eb94 commit b2dacaf
Show file tree
Hide file tree
Showing 18 changed files with 604 additions and 70 deletions.
37 changes: 29 additions & 8 deletions cmd/agola/cmd/orgmemberlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/sorintlab/errors"
"github.com/spf13/cobra"

gwapitypes "agola.io/agola/services/gateway/api/types"
gwclient "agola.io/agola/services/gateway/client"
)

Expand Down Expand Up @@ -54,19 +55,39 @@ func init() {
cmdOrgMember.AddCommand(cmdOrgMemberList)
}

func printOrgMembers(orgMembers []*gwapitypes.OrgMemberResponse) error {
for _, orgMember := range orgMembers {
out, err := json.MarshalIndent(orgMember, "", "\t")
if err != nil {
return errors.WithStack(err)
}
os.Stdout.Write(out)
}

return nil
}

func orgMemberList(cmd *cobra.Command, args []string) error {
gwClient := gwclient.NewClient(gatewayURL, token)

orgMembers, _, err := gwClient.GetOrgMembers(context.TODO(), orgMemberListOpts.orgname)
if err != nil {
return errors.Wrapf(err, "failed to get organization member")
}
var cursor string
orgMembers := []*gwapitypes.OrgMemberResponse{}
for {
orgMembersResp, resp, err := gwClient.GetOrgMembers(context.TODO(), orgMemberListOpts.orgname, &gwclient.ListOptions{Cursor: cursor})
if err != nil {
return errors.Wrapf(err, "failed to get organization member")
}

out, err := json.MarshalIndent(orgMembers, "", "\t")
if err != nil {
return errors.WithStack(err)
orgMembers = append(orgMembers, orgMembersResp.Members...)
if err := printOrgMembers(orgMembersResp.Members); err != nil {
return errors.WithStack(err)
}

cursor = resp.Cursor
if cursor == "" {
break
}
}
os.Stdout.Write(out)

return nil
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ require (
github.com/gorilla/mux v1.8.0
github.com/gorilla/securecookie v1.1.1
github.com/hashicorp/go-sockaddr v1.0.2
github.com/huandu/go-sqlbuilder v1.21.0
github.com/huandu/go-sqlbuilder v1.22.0
github.com/huandu/xstrings v1.4.0
github.com/iancoleman/strcase v0.2.0
github.com/lib/pq v1.10.7
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3
github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U=
github.com/huandu/go-sqlbuilder v1.21.0 h1:+NLH8PQg5/WGMXJLIpAXTdoH1pv9Q3BU6w4P7OabBmc=
github.com/huandu/go-sqlbuilder v1.21.0/go.mod h1:nUVmMitjOmn/zacMLXT0d3Yd3RHoO2K+vy906JzqxMI=
github.com/huandu/go-sqlbuilder v1.22.0 h1:69SpvXvhAoeb7y5uERUCB0/Ck09DwQ6ccYovejm1zHA=
github.com/huandu/go-sqlbuilder v1.22.0/go.mod h1:nUVmMitjOmn/zacMLXT0d3Yd3RHoO2K+vy906JzqxMI=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
Expand Down
51 changes: 39 additions & 12 deletions internal/services/configstore/action/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,43 +26,70 @@ import (
"agola.io/agola/services/configstore/types"
)

type OrgMemberResponse struct {
type OrgMember struct {
User *types.User
Role types.MemberRole
}

func orgMemberResponse(orgUser *db.OrgUser) *OrgMemberResponse {
return &OrgMemberResponse{
func orgMemberResponse(orgUser *db.OrgUser) *OrgMember {
return &OrgMember{
User: orgUser.User,
Role: orgUser.Role,
}
}

func (h *ActionHandler) GetOrgMembers(ctx context.Context, orgRef string) ([]*OrgMemberResponse, error) {
var orgUsers []*db.OrgUser
type GetOrgMembersRequest struct {
OrgRef string
StartUserName string

Limit int
SortDirection types.SortDirection
}

type GetOrgMembersResponse struct {
OrgMembers []*OrgMember

HasMore bool
}

func (h *ActionHandler) GetOrgMembers(ctx context.Context, req *GetOrgMembersRequest) (*GetOrgMembersResponse, error) {
limit := req.Limit
if limit > 0 {
limit += 1
}

var dbOrgMembers []*db.OrgUser
err := h.d.Do(ctx, func(tx *sql.Tx) error {
var err error
org, err := h.d.GetOrg(tx, orgRef)
org, err := h.d.GetOrg(tx, req.OrgRef)
if err != nil {
return errors.WithStack(err)
}
if org == nil {
return util.NewAPIError(util.ErrNotExist, errors.Errorf("org %q doesn't exist", orgRef))
return util.NewAPIError(util.ErrNotExist, errors.Errorf("org %q doesn't exist", req.OrgRef))
}

orgUsers, err = h.d.GetOrgUsers(tx, org.ID)
dbOrgMembers, err = h.d.GetOrgMembers(tx, org.ID, req.StartUserName, limit, req.SortDirection)
return errors.WithStack(err)
})
if err != nil {
return nil, errors.WithStack(err)
}

res := make([]*OrgMemberResponse, len(orgUsers))
for i, orgUser := range orgUsers {
res[i] = orgMemberResponse(orgUser)
orgMembers := make([]*OrgMember, len(dbOrgMembers))
for i, orgUser := range dbOrgMembers {
orgMembers[i] = orgMemberResponse(orgUser)
}

hasMore := len(orgMembers) > req.Limit
if hasMore {
orgMembers = orgMembers[0:req.Limit]
}

return res, nil
return &GetOrgMembersResponse{
OrgMembers: orgMembers,
HasMore: hasMore,
}, nil
}

type CreateOrgRequest struct {
Expand Down
48 changes: 45 additions & 3 deletions internal/services/configstore/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package api
import (
"net/http"
"net/url"
"strconv"

"github.com/gorilla/mux"
"github.com/sorintlab/errors"
Expand All @@ -25,9 +26,9 @@ import (
"agola.io/agola/services/configstore/types"
)

type ErrorResponse struct {
Message string `json:"message"`
}
const (
agolaHasMoreHeader = "X-Agola-HasMore"
)

func GetObjectKindRef(r *http.Request) (types.ObjectKind, string, error) {
vars := mux.Vars(r)
Expand All @@ -49,3 +50,44 @@ func GetObjectKindRef(r *http.Request) (types.ObjectKind, string, error) {

return "", "", util.NewAPIError(util.ErrBadRequest, errors.Errorf("cannot get project or projectgroup ref"))
}

type requestOptions struct {
Limit int
SortDirection types.SortDirection
}

func parseRequestOptions(r *http.Request) (*requestOptions, error) {
query := r.URL.Query()

limit := 0
limitS := query.Get("limit")
if limitS != "" {
var err error
limit, err = strconv.Atoi(limitS)
if err != nil {
return nil, util.NewAPIError(util.ErrBadRequest, errors.Wrapf(err, "cannot parse limit"))
}
}
if limit < 0 {
return nil, util.NewAPIError(util.ErrBadRequest, errors.Errorf("limit must be greater or equal than 0"))
}

sortDirection := types.SortDirection(query.Get("sortdirection"))
if sortDirection != "" {
switch sortDirection {
case types.SortDirectionAsc:
case types.SortDirectionDesc:
default:
return nil, util.NewAPIError(util.ErrBadRequest, errors.Errorf("wrong sort direction %q", sortDirection))
}
}

return &requestOptions{
Limit: limit,
SortDirection: sortDirection,
}, nil
}

func addHasMoreHeader(w http.ResponseWriter, hasMore bool) {
w.Header().Add(agolaHasMoreHeader, strconv.FormatBool(hasMore))
}
46 changes: 36 additions & 10 deletions internal/services/configstore/api/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ func (h *RemoveOrgMemberHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
}
}

func orgMemberResponse(orgUser *action.OrgMemberResponse) *csapitypes.OrgMemberResponse {
func orgMemberResponse(orgUser *action.OrgMember) *csapitypes.OrgMemberResponse {
return &csapitypes.OrgMemberResponse{
User: orgUser.User,
Role: orgUser.Role,
Expand All @@ -301,24 +301,50 @@ func NewOrgMembersHandler(log zerolog.Logger, ah *action.ActionHandler) *OrgMemb
}

func (h *OrgMembersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
res, err := h.do(w, r)
if util.HTTPError(w, err) {
h.log.Err(err).Send()
return
}

if err := util.HTTPResponse(w, http.StatusOK, res); err != nil {
h.log.Err(err).Send()
}
}

func (h *OrgMembersHandler) do(w http.ResponseWriter, r *http.Request) ([]*csapitypes.OrgMemberResponse, error) {
ctx := r.Context()
query := r.URL.Query()
vars := mux.Vars(r)
orgRef := vars["orgref"]

orgUsers, err := h.ah.GetOrgMembers(ctx, orgRef)
if util.HTTPError(w, err) {
h.log.Err(err).Send()
return
ropts, err := parseRequestOptions(r)
if err != nil {
return nil, errors.WithStack(err)
}

res := make([]*csapitypes.OrgMemberResponse, len(orgUsers))
for i, orgUser := range orgUsers {
res[i] = orgMemberResponse(orgUser)
startUserName := query.Get("startusername")

areq := &action.GetOrgMembersRequest{
OrgRef: orgRef,
StartUserName: startUserName,

Limit: ropts.Limit,
SortDirection: ropts.SortDirection,
}
ares, err := h.ah.GetOrgMembers(ctx, areq)
if err != nil {
return nil, errors.WithStack(err)
}

if err := util.HTTPResponse(w, http.StatusOK, res); err != nil {
h.log.Err(err).Send()
res := make([]*csapitypes.OrgMemberResponse, len(ares.OrgMembers))
for i, orgMember := range ares.OrgMembers {
res[i] = orgMemberResponse(orgMember)
}

addHasMoreHeader(w, ares.HasMore)

return res, nil
}

type OrgInvitationsHandler struct {
Expand Down
Loading

0 comments on commit b2dacaf

Please sign in to comment.