Skip to content

Commit c5831f4

Browse files
Gateway: added api to get user projects
1 parent 58710d4 commit c5831f4

File tree

5 files changed

+243
-0
lines changed

5 files changed

+243
-0
lines changed

internal/services/gateway/action/user.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"encoding/json"
2020
"fmt"
2121
"regexp"
22+
"sort"
2223
"strings"
2324
"time"
2425

@@ -949,3 +950,86 @@ func (h *ActionHandler) UserCreateRun(ctx context.Context, req *UserCreateRunReq
949950

950951
return h.CreateRuns(ctx, creq)
951952
}
953+
954+
type GetUserProjectsRequest struct {
955+
UserRef string
956+
Limit int
957+
Page int
958+
}
959+
960+
func ProjectsPaginate(page int, limit int, data []*csapitypes.Project) []*csapitypes.Project {
961+
start := page * limit
962+
963+
if start > len(data) {
964+
return nil
965+
}
966+
967+
end := start + limit
968+
if end > len(data) {
969+
end = len(data)
970+
}
971+
972+
return data[start:end]
973+
}
974+
975+
func (h *ActionHandler) GetUserProjects(ctx context.Context, req *GetUserProjectsRequest) ([]*csapitypes.Project, error) {
976+
if !common.IsUserLogged(ctx) {
977+
return nil, errors.Errorf("user not logged in")
978+
}
979+
user, _, err := h.configstoreClient.GetUser(ctx, req.UserRef)
980+
if err != nil {
981+
return nil, util.NewAPIError(util.KindFromRemoteError(err), errors.Wrapf(err, "failed to get user %q", req.UserRef))
982+
}
983+
984+
projects := make([]*csapitypes.Project, 0)
985+
986+
prj, err := h.GetProjectgroupProjects(ctx, "user"+"/"+user.Name)
987+
if err != nil {
988+
errors.Errorf("failed to get projects for user %q", user.Name)
989+
}
990+
projects = append(projects, prj...)
991+
992+
userOrgs, err := h.GetUserOrgs(ctx, req.UserRef)
993+
if err != nil {
994+
return nil, util.NewAPIError(util.KindFromRemoteError(err), errors.Wrapf(err, "failed to get organizations for user %q", req.UserRef))
995+
}
996+
for _, org := range userOrgs {
997+
prj, err := h.GetProjectgroupProjects(ctx, "org"+"/"+org.Organization.Name)
998+
if err != nil {
999+
errors.Errorf("failed to get projects for org %q", org.Organization.Name)
1000+
}
1001+
projects = append(projects, prj...)
1002+
}
1003+
1004+
sort.SliceStable(projects, func(i, j int) bool {
1005+
return strings.Compare(strings.ToLower(projects[i].Path), strings.ToLower(projects[j].Path)) < 0
1006+
})
1007+
1008+
projects = ProjectsPaginate(req.Page, req.Limit, projects)
1009+
1010+
return projects, nil
1011+
}
1012+
1013+
func (h *ActionHandler) GetProjectgroupProjects(ctx context.Context, projectgroup string) ([]*csapitypes.Project, error) {
1014+
projects := make([]*csapitypes.Project, 0)
1015+
1016+
prj, err := h.GetProjectGroupProjects(ctx, projectgroup)
1017+
if err != nil {
1018+
return nil, util.NewAPIError(util.KindFromRemoteError(err), errors.Wrapf(err, "failed to get projects for group %q", projectgroup))
1019+
}
1020+
projects = append(projects, prj...)
1021+
1022+
subgroups, err := h.GetProjectGroupSubgroups(ctx, projectgroup)
1023+
if err != nil {
1024+
return nil, util.NewAPIError(util.KindFromRemoteError(err), errors.Wrapf(err, "failed to get subgroups for group %q", projectgroup))
1025+
}
1026+
for _, s := range subgroups {
1027+
prj, err := h.GetProjectgroupProjects(ctx, s.ID)
1028+
if err != nil {
1029+
return nil, util.NewAPIError(util.KindFromRemoteError(err), errors.Wrapf(err, "failed to get projects for group %q", s.ID))
1030+
}
1031+
projects = append(projects, prj...)
1032+
}
1033+
1034+
return projects, nil
1035+
}

internal/services/gateway/api/user.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ import (
3333
"github.com/rs/zerolog"
3434
)
3535

36+
const (
37+
DefaultProjectsLimit = 25
38+
MaxProjectsLimit = 40
39+
)
40+
3641
type CreateUserHandler struct {
3742
log zerolog.Logger
3843
ah *action.ActionHandler
@@ -634,3 +639,76 @@ func createUserOrgsResponse(o *csapitypes.UserOrgsResponse) *gwapitypes.UserOrgs
634639

635640
return userOrgs
636641
}
642+
643+
type UserProjectsHandler struct {
644+
log zerolog.Logger
645+
ah *action.ActionHandler
646+
}
647+
648+
func NewUserProjectsHandler(log zerolog.Logger, ah *action.ActionHandler) *UserProjectsHandler {
649+
return &UserProjectsHandler{log: log, ah: ah}
650+
}
651+
652+
func (h *UserProjectsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
653+
ctx := r.Context()
654+
655+
userID := common.CurrentUserID(ctx)
656+
if userID == "" {
657+
util.HTTPError(w, util.NewAPIError(util.ErrBadRequest, errors.Errorf("user not authenticated")))
658+
return
659+
}
660+
661+
q := r.URL.Query()
662+
663+
limitS := q.Get("limit")
664+
limit := DefaultProjectsLimit
665+
if limitS != "" {
666+
var err error
667+
limit, err = strconv.Atoi(limitS)
668+
if err != nil {
669+
util.HTTPError(w, util.NewAPIError(util.ErrBadRequest, errors.Wrapf(err, "cannot parse limit")))
670+
return
671+
}
672+
}
673+
if limit < 0 {
674+
util.HTTPError(w, util.NewAPIError(util.ErrBadRequest, errors.Errorf("limit must be greater or equal than 0")))
675+
return
676+
}
677+
if limit > MaxProjectsLimit {
678+
limit = MaxRunsLimit
679+
}
680+
681+
pageS := q.Get("page")
682+
page := 0
683+
if pageS != "" {
684+
var err error
685+
page, err = strconv.Atoi(pageS)
686+
if err != nil {
687+
util.HTTPError(w, util.NewAPIError(util.ErrBadRequest, errors.Wrapf(err, "cannot parse page")))
688+
return
689+
}
690+
}
691+
if page < 0 {
692+
util.HTTPError(w, util.NewAPIError(util.ErrBadRequest, errors.Errorf("page must be greater or equal than 0")))
693+
return
694+
}
695+
696+
userProjects, err := h.ah.GetUserProjects(ctx, &action.GetUserProjectsRequest{
697+
UserRef: userID,
698+
Limit: limit,
699+
Page: page,
700+
})
701+
if util.HTTPError(w, err) {
702+
h.log.Err(err).Send()
703+
return
704+
}
705+
706+
res := make([]*gwapitypes.ProjectResponse, len(userProjects))
707+
for i, userProject := range userProjects {
708+
res[i] = createProjectResponse(userProject)
709+
}
710+
711+
if err := util.HTTPResponse(w, http.StatusOK, res); err != nil {
712+
h.log.Err(err).Send()
713+
}
714+
}

internal/services/gateway/gateway.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ func (g *Gateway) Run(ctx context.Context) error {
181181
deleteUserHandler := api.NewDeleteUserHandler(g.log, g.ah)
182182
userCreateRunHandler := api.NewUserCreateRunHandler(g.log, g.ah)
183183
userOrgsHandler := api.NewUserOrgsHandler(g.log, g.ah)
184+
userProjectsHandler := api.NewUserProjectsHandler(g.log, g.ah)
184185

185186
createUserLAHandler := api.NewCreateUserLAHandler(g.log, g.ah)
186187
deleteUserLAHandler := api.NewDeleteUserLAHandler(g.log, g.ah)
@@ -289,6 +290,7 @@ func (g *Gateway) Run(ctx context.Context) error {
289290
apirouter.Handle("/users/{userref}", authForcedHandler(deleteUserHandler)).Methods("DELETE")
290291
apirouter.Handle("/user/createrun", authForcedHandler(userCreateRunHandler)).Methods("POST")
291292
apirouter.Handle("/user/orgs", authForcedHandler(userOrgsHandler)).Methods("GET")
293+
apirouter.Handle("/user/projects", authForcedHandler(userProjectsHandler)).Methods("GET")
292294

293295
apirouter.Handle("/users/{userref}/runs", authForcedHandler(userRunsHandler)).Methods("GET")
294296
apirouter.Handle("/users/{userref}/runs/{runnumber}", authOptionalHandler(userRunHandler)).Methods("GET")

services/gateway/client/client.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,3 +637,18 @@ func (c *Client) GetUserOrgs(ctx context.Context) ([]*gwapitypes.UserOrgsRespons
637637
resp, err := c.getParsedResponse(ctx, "GET", "/user/orgs", nil, jsonContent, nil, &userOrgs)
638638
return userOrgs, resp, errors.WithStack(err)
639639
}
640+
641+
func (c *Client) GetUserProjects(ctx context.Context, page int, limit int) ([]*gwapitypes.ProjectResponse, *http.Response, error) {
642+
userProjects := []*gwapitypes.ProjectResponse{}
643+
644+
q := url.Values{}
645+
if page > 0 {
646+
q.Add("page", strconv.Itoa(page))
647+
}
648+
if limit > 0 {
649+
q.Add("limit", strconv.Itoa(limit))
650+
}
651+
652+
resp, err := c.getParsedResponse(ctx, "GET", "/user/projects", q, jsonContent, nil, &userProjects)
653+
return userProjects, resp, errors.WithStack(err)
654+
}

tests/setup_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1963,3 +1963,67 @@ func TestUserOrgs(t *testing.T) {
19631963
t.Fatalf("user orgs mismatch (-want +got):\n%s", diff)
19641964
}
19651965
}
1966+
1967+
func TestUserProjects(t *testing.T) {
1968+
dir := t.TempDir()
1969+
1970+
ctx, cancel := context.WithCancel(context.Background())
1971+
defer cancel()
1972+
1973+
tetcd, tgitea, c := setup(ctx, t, dir, true)
1974+
defer shutdownGitea(tgitea)
1975+
defer shutdownEtcd(tetcd)
1976+
1977+
giteaToken, token := createLinkedAccount(ctx, t, tgitea, c)
1978+
1979+
giteaAPIURL := fmt.Sprintf("http://%s:%s", tgitea.HTTPListenAddress, tgitea.HTTPPort)
1980+
giteaClient := gitea.NewClient(giteaAPIURL, giteaToken)
1981+
1982+
giteaRepo, err := giteaClient.CreateRepo(gitea.CreateRepoOption{
1983+
Name: "repo01",
1984+
Private: false,
1985+
})
1986+
if err != nil {
1987+
t.Fatalf("unexpected err: %v", err)
1988+
}
1989+
t.Logf("created gitea repo: %s", giteaRepo.Name)
1990+
1991+
gwClient := gwclient.NewClient(c.Gateway.APIExposedURL, token)
1992+
1993+
project01, _, err := gwClient.CreateProject(ctx, &gwapitypes.CreateProjectRequest{
1994+
Name: "Project01",
1995+
ParentRef: path.Join("user", agolaUser01),
1996+
RemoteSourceName: "gitea",
1997+
RepoPath: path.Join(giteaUser01, "repo01"),
1998+
Visibility: gwapitypes.VisibilityPublic,
1999+
})
2000+
if err != nil {
2001+
t.Fatalf("unexpected err: %v", err)
2002+
}
2003+
2004+
_, _, err = gwClient.CreateOrg(ctx, &gwapitypes.CreateOrgRequest{Name: "org02", Visibility: gwapitypes.VisibilityPublic})
2005+
if err != nil {
2006+
t.Fatalf("unexpected err: %v", err)
2007+
}
2008+
project02, _, err := gwClient.CreateProject(ctx, &gwapitypes.CreateProjectRequest{
2009+
Name: "Project02",
2010+
ParentRef: path.Join("org", "org02"),
2011+
RemoteSourceName: "gitea",
2012+
RepoPath: path.Join(giteaUser01, "repo01"),
2013+
Visibility: gwapitypes.VisibilityPublic,
2014+
})
2015+
if err != nil {
2016+
t.Fatalf("unexpected err: %v", err)
2017+
}
2018+
2019+
userProjects, _, err := gwClient.GetUserProjects(ctx, 0, 10)
2020+
if err != nil {
2021+
t.Fatalf("unexpected err: %v", err)
2022+
}
2023+
2024+
expectedProjects := []*gwapitypes.ProjectResponse{project02, project01}
2025+
2026+
if diff := cmp.Diff(expectedProjects, userProjects); diff != "" {
2027+
t.Fatalf("user projects mismatch (-want +got):\n%s", diff)
2028+
}
2029+
}

0 commit comments

Comments
 (0)