Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gateway: add api user/projects #316

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions internal/services/gateway/action/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"encoding/json"
"fmt"
"regexp"
"sort"
"strings"
"time"

Expand Down Expand Up @@ -1027,3 +1028,86 @@ func (h *ActionHandler) GetUserGitSource(ctx context.Context, remoteSourceRef, u

return gitSource, rs, la, nil
}

type GetUserProjectsRequest struct {
UserRef string
Limit int
Page int
}

func ProjectsPaginate(page int, limit int, data []*csapitypes.Project) []*csapitypes.Project {
start := page * limit

if start > len(data) {
return nil
}

end := start + limit
if end > len(data) {
end = len(data)
}

return data[start:end]
}

func (h *ActionHandler) GetUserProjects(ctx context.Context, req *GetUserProjectsRequest) ([]*csapitypes.Project, error) {
if !common.IsUserLogged(ctx) {
return nil, errors.Errorf("user not logged in")
}
user, _, err := h.configstoreClient.GetUser(ctx, req.UserRef)
if err != nil {
return nil, util.NewAPIError(util.KindFromRemoteError(err), errors.Wrapf(err, "failed to get user %q", req.UserRef))
}

projects := make([]*csapitypes.Project, 0)

prj, err := h.GetProjectgroupProjects(ctx, "user"+"/"+user.Name)
if err != nil {
return nil, errors.Wrapf(err, "failed to get projects for user %q", user.Name)
}
projects = append(projects, prj...)

userOrgs, err := h.GetUserOrgs(ctx, req.UserRef)
if err != nil {
return nil, util.NewAPIError(util.KindFromRemoteError(err), errors.Wrapf(err, "failed to get organizations for user %q", req.UserRef))
}
for _, org := range userOrgs {
prj, err := h.GetProjectgroupProjects(ctx, "org"+"/"+org.Organization.Name)
if err != nil {
return nil, errors.Wrapf(err, "failed to get projects for org %q", org.Organization.Name)
}
projects = append(projects, prj...)
}

sort.SliceStable(projects, func(i, j int) bool {
return strings.Compare(strings.ToLower(projects[i].Path), strings.ToLower(projects[j].Path)) < 0
})

projects = ProjectsPaginate(req.Page, req.Limit, projects)

return projects, nil
}

func (h *ActionHandler) GetProjectgroupProjects(ctx context.Context, projectgroup string) ([]*csapitypes.Project, error) {
projects := make([]*csapitypes.Project, 0)

prj, err := h.GetProjectGroupProjects(ctx, projectgroup)
if err != nil {
return nil, util.NewAPIError(util.KindFromRemoteError(err), errors.Wrapf(err, "failed to get projects for group %q", projectgroup))
}
projects = append(projects, prj...)

subgroups, err := h.GetProjectGroupSubgroups(ctx, projectgroup)
if err != nil {
return nil, util.NewAPIError(util.KindFromRemoteError(err), errors.Wrapf(err, "failed to get subgroups for group %q", projectgroup))
}
for _, s := range subgroups {
prj, err := h.GetProjectgroupProjects(ctx, s.ID)
if err != nil {
return nil, util.NewAPIError(util.KindFromRemoteError(err), errors.Wrapf(err, "failed to get projects for group %q", s.ID))
}
projects = append(projects, prj...)
}

return projects, nil
}
78 changes: 78 additions & 0 deletions internal/services/gateway/api/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ import (
"github.com/rs/zerolog"
)

const (
DefaultProjectsLimit = 25
MaxProjectsLimit = 40
)

type CreateUserHandler struct {
log zerolog.Logger
ah *action.ActionHandler
Expand Down Expand Up @@ -643,3 +648,76 @@ func createUserOrgsResponse(o *csapitypes.UserOrgsResponse) *gwapitypes.UserOrgs

return userOrgs
}

type UserProjectsHandler struct {
log zerolog.Logger
ah *action.ActionHandler
}

func NewUserProjectsHandler(log zerolog.Logger, ah *action.ActionHandler) *UserProjectsHandler {
return &UserProjectsHandler{log: log, ah: ah}
}

func (h *UserProjectsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

userID := common.CurrentUserID(ctx)
if userID == "" {
util.HTTPError(w, util.NewAPIError(util.ErrBadRequest, errors.Errorf("user not authenticated")))
return
}

q := r.URL.Query()

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

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

userProjects, err := h.ah.GetUserProjects(ctx, &action.GetUserProjectsRequest{
UserRef: userID,
Limit: limit,
Page: page,
})
if util.HTTPError(w, err) {
h.log.Err(err).Send()
return
}

res := make([]*gwapitypes.ProjectResponse, len(userProjects))
for i, userProject := range userProjects {
res[i] = createProjectResponse(userProject)
}

if err := util.HTTPResponse(w, http.StatusOK, res); err != nil {
h.log.Err(err).Send()
}
}
2 changes: 2 additions & 0 deletions internal/services/gateway/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ func (g *Gateway) Run(ctx context.Context) error {
deleteUserHandler := api.NewDeleteUserHandler(g.log, g.ah)
userCreateRunHandler := api.NewUserCreateRunHandler(g.log, g.ah)
userOrgsHandler := api.NewUserOrgsHandler(g.log, g.ah)
userProjectsHandler := api.NewUserProjectsHandler(g.log, g.ah)

createUserLAHandler := api.NewCreateUserLAHandler(g.log, g.ah)
deleteUserLAHandler := api.NewDeleteUserLAHandler(g.log, g.ah)
Expand Down Expand Up @@ -291,6 +292,7 @@ func (g *Gateway) Run(ctx context.Context) error {
apirouter.Handle("/users/{userref}", authForcedHandler(deleteUserHandler)).Methods("DELETE")
apirouter.Handle("/user/createrun", authForcedHandler(userCreateRunHandler)).Methods("POST")
apirouter.Handle("/user/orgs", authForcedHandler(userOrgsHandler)).Methods("GET")
apirouter.Handle("/user/projects", authForcedHandler(userProjectsHandler)).Methods("GET")

apirouter.Handle("/users/{userref}/runs", authForcedHandler(userRunsHandler)).Methods("GET")
apirouter.Handle("/users/{userref}/runs/{runnumber}", authOptionalHandler(userRunHandler)).Methods("GET")
Expand Down
15 changes: 15 additions & 0 deletions services/gateway/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -643,3 +643,18 @@ func (c *Client) RefreshRemoteRepo(ctx context.Context, projectRef string) (*gwa
resp, err := c.getParsedResponse(ctx, "POST", path.Join("/projects", url.PathEscape(projectRef), "/refreshremoterepo"), nil, jsonContent, nil, project)
return project, resp, err
}

func (c *Client) GetUserProjects(ctx context.Context, page int, limit int) ([]*gwapitypes.ProjectResponse, *http.Response, error) {
userProjects := []*gwapitypes.ProjectResponse{}

q := url.Values{}
if page > 0 {
q.Add("page", strconv.Itoa(page))
}
if limit > 0 {
q.Add("limit", strconv.Itoa(limit))
}

resp, err := c.getParsedResponse(ctx, "GET", "/user/projects", q, jsonContent, nil, &userProjects)
return userProjects, resp, errors.WithStack(err)
}
63 changes: 63 additions & 0 deletions tests/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2252,3 +2252,66 @@ func TestRefreshRemoteRepositoryInfo(t *testing.T) {
t.Fatalf("projects mismatch (-expected +got):\n%s", diff)
}
}

func TestUserProjects(t *testing.T) {
dir := t.TempDir()

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

tgitea, c := setup(ctx, t, dir, true)
defer shutdownGitea(tgitea)

giteaToken, token := createLinkedAccount(ctx, t, tgitea, c)

giteaAPIURL := fmt.Sprintf("http://%s:%s", tgitea.HTTPListenAddress, tgitea.HTTPPort)
giteaClient := gitea.NewClient(giteaAPIURL, giteaToken)

giteaRepo, err := giteaClient.CreateRepo(gitea.CreateRepoOption{
Name: "repo01",
Private: false,
})
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
t.Logf("created gitea repo: %s", giteaRepo.Name)

gwClient := gwclient.NewClient(c.Gateway.APIExposedURL, token)

project01, _, err := gwClient.CreateProject(ctx, &gwapitypes.CreateProjectRequest{
Name: "Project01",
ParentRef: path.Join("user", agolaUser01),
RemoteSourceName: "gitea",
RepoPath: path.Join(giteaUser01, "repo01"),
Visibility: gwapitypes.VisibilityPublic,
})
if err != nil {
t.Fatalf("unexpected err: %v", err)
}

_, _, err = gwClient.CreateOrg(ctx, &gwapitypes.CreateOrgRequest{Name: "org02", Visibility: gwapitypes.VisibilityPublic})
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
project02, _, err := gwClient.CreateProject(ctx, &gwapitypes.CreateProjectRequest{
Name: "Project02",
ParentRef: path.Join("org", "org02"),
RemoteSourceName: "gitea",
RepoPath: path.Join(giteaUser01, "repo01"),
Visibility: gwapitypes.VisibilityPublic,
})
if err != nil {
t.Fatalf("unexpected err: %v", err)
}

userProjects, _, err := gwClient.GetUserProjects(ctx, 0, 10)
if err != nil {
t.Fatalf("unexpected err: %v", err)
}

expectedProjects := []*gwapitypes.ProjectResponse{project02, project01}

if diff := cmp.Diff(expectedProjects, userProjects); diff != "" {
t.Fatalf("user projects mismatch (-want +got):\n%s", diff)
}
}