feat: [CDE-146]: API to list all gitspaces for a user. (#2209)

* feat: [CDE-146]: Addressing review comments-
restructuring method.
* feat: [CDE-146]: API to list all gitspaces.
* feat: [CDE-146]: API to list all gitspaces.
* feat: [CDE-146]: API to list all gitspaces for a user.
unified-ui
Dhruv Dhruv 2024-07-15 09:43:50 +00:00 committed by Harness
parent b4f35ddbbe
commit afd24f911b
6 changed files with 248 additions and 2 deletions

View File

@ -0,0 +1,168 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gitspace
import (
"context"
"errors"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
func (c *Controller) ListAllGitspaces( // nolint:gocognit
ctx context.Context,
session *auth.Session,
) ([]*types.GitspaceConfig, error) {
var result []*types.GitspaceConfig
err := c.tx.WithTx(ctx, func(ctx context.Context) (err error) {
allGitspaceConfigs, err := c.gitspaceConfigStore.ListAll(ctx, session.Principal.UID)
if err != nil {
return fmt.Errorf("failed to list gitspace configs: %w", err)
}
var spacesMap = make(map[int64]string)
for idx := 0; idx < len(allGitspaceConfigs); idx++ {
if spacesMap[allGitspaceConfigs[idx].SpaceID] == "" {
space, findSpaceErr := c.spaceStore.Find(ctx, allGitspaceConfigs[idx].SpaceID)
if findSpaceErr != nil {
return fmt.Errorf(
"failed to find space for ID %d: %w", allGitspaceConfigs[idx].SpaceID, findSpaceErr)
}
spacesMap[allGitspaceConfigs[idx].SpaceID] = space.Path
}
allGitspaceConfigs[idx].SpacePath = spacesMap[allGitspaceConfigs[idx].SpaceID]
}
authorizedSpaceIDs, err := c.getAuthorizedSpaces(ctx, session, spacesMap)
if err != nil {
return err
}
finalGitspaceConfigs, err := c.filterAndPopulateInstanceDetails(ctx, allGitspaceConfigs, authorizedSpaceIDs)
if err != nil {
return err
}
result = finalGitspaceConfigs
return nil
}, dbtx.TxDefaultReadOnly)
if err != nil {
return nil, err
}
return result, nil
}
func (c *Controller) filterAndPopulateInstanceDetails(
ctx context.Context,
allGitspaceConfigs []*types.GitspaceConfig,
authorizedSpaceIDs map[int64]bool,
) ([]*types.GitspaceConfig, error) {
authorizedGitspaceConfigs := c.getAuthorizedGitspaceConfigs(allGitspaceConfigs, authorizedSpaceIDs)
gitspaceInstancesMap, err := c.getLatestInstanceMap(ctx, authorizedGitspaceConfigs)
if err != nil {
return nil, err
}
var result []*types.GitspaceConfig
for _, gitspaceConfig := range authorizedGitspaceConfigs {
instance := gitspaceInstancesMap[gitspaceConfig.ID]
gitspaceConfig.GitspaceInstance = instance
if instance != nil {
gitspaceStateType, stateErr := enum.GetGitspaceStateFromInstance(instance.State)
if stateErr != nil {
return nil, stateErr
}
gitspaceConfig.State = gitspaceStateType
instance.SpacePath = gitspaceConfig.SpacePath
} else {
gitspaceConfig.State = enum.GitspaceStateUninitialized
}
result = append(result, gitspaceConfig)
}
return result, nil
}
func (c *Controller) getAuthorizedGitspaceConfigs(
allGitspaceConfigs []*types.GitspaceConfig,
authorizedSpaceIDs map[int64]bool,
) []*types.GitspaceConfig {
var authorizedGitspaceConfigs = make([]*types.GitspaceConfig, 0)
for idx := 0; idx < len(allGitspaceConfigs); idx++ {
if authorizedSpaceIDs[allGitspaceConfigs[idx].SpaceID] {
authorizedGitspaceConfigs = append(authorizedGitspaceConfigs, allGitspaceConfigs[idx])
}
}
return authorizedGitspaceConfigs
}
func (c *Controller) getAuthorizedSpaces(
ctx context.Context,
session *auth.Session,
spacesMap map[int64]string,
) (map[int64]bool, error) {
var authorizedSpaceIDs = make(map[int64]bool, 0)
for spaceID, spacePath := range spacesMap {
authErr := apiauth.CheckGitspace(ctx, c.authorizer, session, spacePath, "", enum.PermissionGitspaceView)
if authErr != nil && !errors.Is(authErr, apiauth.ErrNotAuthorized) {
return nil, fmt.Errorf("failed to check gitspace auth for space ID %d: %w", spaceID, authErr)
}
authorizedSpaceIDs[spaceID] = true
}
return authorizedSpaceIDs, nil
}
func (c *Controller) getLatestInstanceMap(
ctx context.Context,
authorizedGitspaceConfigs []*types.GitspaceConfig,
) (map[int64]*types.GitspaceInstance, error) {
var authorizedConfigIDs = make([]int64, 0)
for _, config := range authorizedGitspaceConfigs {
authorizedConfigIDs = append(authorizedConfigIDs, config.ID)
}
var gitspaceInstances, err = c.gitspaceInstanceStore.FindAllLatestByGitspaceConfigID(ctx, authorizedConfigIDs)
if err != nil {
return nil, err
}
var gitspaceInstancesMap = make(map[int64]*types.GitspaceInstance)
for _, gitspaceEntry := range gitspaceInstances {
gitspaceInstancesMap[gitspaceEntry.GitSpaceConfigID] = gitspaceEntry
}
return gitspaceInstancesMap, nil
}

View File

@ -0,0 +1,36 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gitspace
import (
"net/http"
"github.com/harness/gitness/app/api/controller/gitspace"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
func HandleListAllGitspaces(gitspaceCtrl *gitspace.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
gitspaces, err := gitspaceCtrl.ListAllGitspaces(ctx, session)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, gitspaces)
}
}

View File

@ -58,6 +58,9 @@ type gitspaceEventsListRequest struct {
paginationRequest
}
type gitspacesListAllRequest struct {
}
func gitspaceOperations(reflector *openapi3.Reflector) {
opCreate := openapi3.Operation{}
opCreate.WithTags("gitspaces")
@ -155,4 +158,15 @@ func gitspaceOperations(reflector *openapi3.Reflector) {
_ = reflector.SetJSONResponse(&opRepoLookup, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opRepoLookup, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodPost, "/gitspaces/lookup-repo", opCreate)
opListAll := openapi3.Operation{}
opListAll.WithTags("gitspaces")
opListAll.WithSummary("List all gitspaces")
opListAll.WithMapOfAnything(map[string]interface{}{"operationId": "listAllGitspaces"})
_ = reflector.SetRequest(&opListAll, new(gitspacesListAllRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&opListAll, new([]*types.GitspaceConfig), http.StatusOK)
_ = reflector.SetJSONResponse(&opListAll, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opListAll, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opListAll, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodGet, "/gitspaces", opListAll)
}

View File

@ -711,6 +711,7 @@ func setupGitspaces(r chi.Router, gitspacesCtrl *gitspace.Controller) {
r.Route("/gitspaces", func(r chi.Router) {
r.Post("/lookup-repo", handlergitspace.HandleLookupRepo(gitspacesCtrl))
r.Post("/", handlergitspace.HandleCreateConfig(gitspacesCtrl))
r.Get("/", handlergitspace.HandleListAllGitspaces(gitspacesCtrl))
r.Route(fmt.Sprintf("/{%s}", request.PathParamGitspaceIdentifier), func(r chi.Router) {
r.Get("/", handlergitspace.HandleFind(gitspacesCtrl))
r.Post("/actions", handlergitspace.HandleAction(gitspacesCtrl))

View File

@ -579,6 +579,9 @@ type (
// Count the number of gitspace configs in a space matching the given filter.
Count(ctx context.Context, filter *types.GitspaceFilter) (int64, error)
// ListAll lists all the gitspace configs present for a user in the given spaces in the datastore.
ListAll(ctx context.Context, userUID string) ([]*types.GitspaceConfig, error)
}
GitspaceInstanceStore interface {

View File

@ -251,6 +251,28 @@ func (s gitspaceConfigStore) List(ctx context.Context, filter *types.GitspaceFil
return s.mapToGitspaceConfigs(ctx, dst)
}
func (s gitspaceConfigStore) ListAll(
ctx context.Context,
userUID string,
) ([]*types.GitspaceConfig, error) {
stmt := database.Builder.
Select(gitspaceConfigSelectColumns).
From(gitspaceConfigsTable).
Where(squirrel.Eq{"gconf_is_deleted": false}).
Where(squirrel.Eq{"gconf_user_uid": userUID})
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert squirrel builder to sql")
}
db := dbtx.GetAccessor(ctx, s.db)
var dst []*gitspaceConfig
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed executing custom list query")
}
return s.mapToGitspaceConfigs(ctx, dst)
}
func (s *gitspaceConfigStore) mapToGitspaceConfig(
_ context.Context,
in *gitspaceConfig,
@ -276,8 +298,10 @@ func (s *gitspaceConfigStore) mapToGitspaceConfig(
return res, nil
}
func (s *gitspaceConfigStore) mapToGitspaceConfigs(ctx context.Context,
configs []*gitspaceConfig) ([]*types.GitspaceConfig, error) {
func (s *gitspaceConfigStore) mapToGitspaceConfigs(
ctx context.Context,
configs []*gitspaceConfig,
) ([]*types.GitspaceConfig, error) {
var err error
res := make([]*types.GitspaceConfig, len(configs))
for i := range configs {