Add search capability to repo and space listing (for child spaces) (#44)

This change is adding search capability to the following apis:
- child repositories
- child spaces
*NOTE* The search space is limited to direct child repos/spaces.
This commit is contained in:
Johannes Batzill 2022-10-24 18:57:55 -07:00 committed by GitHub
parent 5337c46a4f
commit c0258b34ef
28 changed files with 419 additions and 229 deletions

View File

@ -6,6 +6,7 @@ package repo
import (
"context"
"fmt"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/auth"
@ -17,15 +18,25 @@ import (
* ListPaths lists all paths of a repo.
*/
func (c *Controller) ListPaths(ctx context.Context, session *auth.Session,
repoRef string, filter *types.PathFilter) ([]*types.Path, error) {
repoRef string, filter *types.PathFilter) ([]*types.Path, int64, error) {
repo, err := findRepoFromRef(ctx, c.repoStore, repoRef)
if err != nil {
return nil, err
return nil, 0, err
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, false); err != nil {
return nil, err
return nil, 0, err
}
return c.repoStore.ListAllPaths(ctx, repo.ID, filter)
count, err := c.repoStore.CountPaths(ctx, repo.ID, filter)
if err != nil {
return nil, 0, fmt.Errorf("failed to count paths: %w", err)
}
paths, err := c.repoStore.ListPaths(ctx, repo.ID, filter)
if err != nil {
return nil, 0, fmt.Errorf("failed to list paths: %w", err)
}
return paths, count, nil
}

View File

@ -6,6 +6,7 @@ package space
import (
"context"
"fmt"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/auth"
@ -17,15 +18,25 @@ import (
* ListPaths lists all paths of a space.
*/
func (c *Controller) ListPaths(ctx context.Context, session *auth.Session,
spaceRef string, filter *types.PathFilter) ([]*types.Path, error) {
spaceRef string, filter *types.PathFilter) ([]*types.Path, int64, error) {
space, err := findSpaceFromRef(ctx, c.spaceStore, spaceRef)
if err != nil {
return nil, err
return nil, 0, err
}
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView, false); err != nil {
return nil, err
return nil, 0, err
}
return c.spaceStore.ListAllPaths(ctx, space.ID, filter)
count, err := c.spaceStore.CountPaths(ctx, space.ID, filter)
if err != nil {
return nil, 0, fmt.Errorf("failed to count paths: %w", err)
}
paths, err := c.spaceStore.ListPaths(ctx, space.ID, filter)
if err != nil {
return nil, 0, fmt.Errorf("failed to list paths: %w", err)
}
return paths, count, nil
}

View File

@ -18,28 +18,28 @@ import (
* ListRepositories lists the repositories of a space.
*/
func (c *Controller) ListRepositories(ctx context.Context, session *auth.Session,
spaceRef string, filter *types.RepoFilter) (int64, []*types.Repository, error) {
spaceRef string, filter *types.RepoFilter) ([]*types.Repository, int64, error) {
space, err := findSpaceFromRef(ctx, c.spaceStore, spaceRef)
if err != nil {
return 0, nil, err
return nil, 0, err
}
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionRepoView, true); err != nil {
return 0, nil, err
return nil, 0, err
}
count, err := c.repoStore.Count(ctx, space.ID)
count, err := c.repoStore.Count(ctx, space.ID, filter)
if err != nil {
return 0, nil, fmt.Errorf("failed to count child repos: %w", err)
return nil, 0, fmt.Errorf("failed to count child repos: %w", err)
}
repos, err := c.repoStore.List(ctx, space.ID, filter)
if err != nil {
return 0, nil, fmt.Errorf("failed to list child repos: %w", err)
return nil, 0, fmt.Errorf("failed to list child repos: %w", err)
}
/*
* TODO: needs access control? Might want to avoid that (makes paging and performance hard)
*/
return count, repos, nil
return repos, count, nil
}

View File

@ -18,28 +18,28 @@ import (
* ListSpaces lists the child spaces of a space.
*/
func (c *Controller) ListSpaces(ctx context.Context, session *auth.Session,
spaceRef string, filter *types.SpaceFilter) (int64, []*types.Space, error) {
spaceRef string, filter *types.SpaceFilter) ([]*types.Space, int64, error) {
space, err := findSpaceFromRef(ctx, c.spaceStore, spaceRef)
if err != nil {
return 0, nil, err
return nil, 0, err
}
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView, true); err != nil {
return 0, nil, err
return nil, 0, err
}
count, err := c.spaceStore.Count(ctx, space.ID)
count, err := c.spaceStore.Count(ctx, space.ID, filter)
if err != nil {
return 0, nil, fmt.Errorf("failed to count child spaces: %w", err)
return nil, 0, fmt.Errorf("failed to count child spaces: %w", err)
}
spaces, err := c.spaceStore.List(ctx, space.ID, filter)
if err != nil {
return 0, nil, fmt.Errorf("failed to list child spaces: %w", err)
return nil, 0, fmt.Errorf("failed to list child spaces: %w", err)
}
/*
* TODO: needs access control? Might want to avoid that (makes paging and performance hard)
*/
return count, spaces, nil
return spaces, count, nil
}

View File

@ -18,25 +18,25 @@ import (
* List lists all users of the system.
*/
func (c *Controller) List(ctx context.Context, session *auth.Session,
filter *types.UserFilter) (int64, []*types.User, error) {
filter *types.UserFilter) ([]*types.User, int64, error) {
// Ensure principal has required permissions (user is global, no explicit resource)
scope := &types.Scope{}
resource := &types.Resource{
Type: enum.ResourceTypeUser,
}
if err := apiauth.Check(ctx, c.authorizer, session, scope, resource, enum.PermissionUserView); err != nil {
return 0, nil, err
return nil, 0, err
}
count, err := c.userStore.Count(ctx)
if err != nil {
return 0, nil, fmt.Errorf("failed to count users: %w", err)
return nil, 0, fmt.Errorf("failed to count users: %w", err)
}
repos, err := c.userStore.List(ctx, filter)
if err != nil {
return 0, nil, fmt.Errorf("failed to list users: %w", err)
return nil, 0, fmt.Errorf("failed to list users: %w", err)
}
return count, repos, nil
return repos, count, nil
}

View File

@ -31,13 +31,13 @@ func HandleListPaths(repoCtrl *repo.Controller) http.HandlerFunc {
filter.Order = enum.OrderAsc
}
paths, err := repoCtrl.ListPaths(ctx, session, repoRef, filter)
paths, totalCount, err := repoCtrl.ListPaths(ctx, session, repoRef, filter)
if err != nil {
render.TranslatedUserError(w, err)
return
}
// TODO: implement pagination - or should we block that many paths in the first place.
render.Pagination(r, w, filter.Page, filter.Size, int(totalCount))
render.JSON(w, http.StatusOK, paths)
}
}

View File

@ -29,7 +29,7 @@ func HandleListSpaces(spaceCtrl *space.Controller) http.HandlerFunc {
spaceFilter.Order = enum.OrderAsc
}
totalCount, spaces, err := spaceCtrl.ListSpaces(ctx, session, spaceRef, spaceFilter)
spaces, totalCount, err := spaceCtrl.ListSpaces(ctx, session, spaceRef, spaceFilter)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -29,13 +29,13 @@ func HandleListPaths(spaceCtrl *space.Controller) http.HandlerFunc {
filter.Order = enum.OrderAsc
}
paths, err := spaceCtrl.ListPaths(ctx, session, spaceRef, filter)
paths, totalCount, err := spaceCtrl.ListPaths(ctx, session, spaceRef, filter)
if err != nil {
render.TranslatedUserError(w, err)
return
}
// TODO: do we need pagination? we should block that many paths in the first place.
render.Pagination(r, w, filter.Page, filter.Size, int(totalCount))
render.JSON(w, http.StatusOK, paths)
}
}

View File

@ -29,7 +29,7 @@ func HandleListRepos(spaceCtrl *space.Controller) http.HandlerFunc {
filter.Order = enum.OrderAsc
}
totalCount, repos, err := spaceCtrl.ListRepositories(ctx, session, spaceRef, filter)
repos, totalCount, err := spaceCtrl.ListRepositories(ctx, session, spaceRef, filter)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -25,7 +25,7 @@ func HandleList(userCtrl *user.Controller) http.HandlerFunc {
filter.Order = enum.OrderAsc
}
totalCount, list, err := userCtrl.List(ctx, session, filter)
list, totalCount, err := userCtrl.List(ctx, session, filter)
if err != nil {
render.TranslatedUserError(w, err)
return

View File

@ -12,6 +12,7 @@ import (
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/api/usererror"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/swaggest/openapi-go/openapi3"
)
@ -138,6 +139,39 @@ var queryParameterSpacePath = openapi3.ParameterOrRef{
},
}
var queryParameterSortBranch = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamSort,
In: openapi3.ParameterInQuery,
Description: ptr.String("The data by which the branches are sorted."),
Required: ptr.Bool(false),
Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeString),
Default: ptrptr(enum.BranchSortOptionName.String()),
Enum: []interface{}{
ptr.String(enum.BranchSortOptionName.String()),
ptr.String(enum.BranchSortOptionDate.String()),
},
},
},
},
}
var queryParameterQueryBranch = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamQuery,
In: openapi3.ParameterInQuery,
Description: ptr.String("The substring by which the branches are filtered."),
Required: ptr.Bool(false),
Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeString),
},
},
},
}
//nolint:funlen
func repoOperations(reflector *openapi3.Reflector) {
createRepository := openapi3.Operation{}
@ -211,6 +245,7 @@ func repoOperations(reflector *openapi3.Reflector) {
opListPaths := openapi3.Operation{}
opListPaths.WithTags("repository")
opListPaths.WithMapOfAnything(map[string]interface{}{"operationId": "listRepositoryPaths"})
opListPaths.WithParameters(queryParameterPage, queryParameterPerPage)
_ = reflector.SetRequest(&opListPaths, new(repoRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&opListPaths, []types.Path{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opListPaths, new(usererror.Error), http.StatusInternalServerError)

View File

@ -70,36 +70,3 @@ var queryParameterDirection = openapi3.ParameterOrRef{
},
},
}
var queryParameterSortBranch = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamSort,
In: openapi3.ParameterInQuery,
Description: ptr.String("The data by which the branches are sorted."),
Required: ptr.Bool(false),
Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeString),
Default: ptrptr(enum.BranchSortOptionName.String()),
Enum: []interface{}{
ptr.String(enum.BranchSortOptionName.String()),
ptr.String(enum.BranchSortOptionDate.String()),
},
},
},
},
}
var queryParameterQueryBranch = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamQuery,
In: openapi3.ParameterInQuery,
Description: ptr.String("The substring by which the branches are filtered."),
Required: ptr.Bool(false),
Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeString),
},
},
},
}

View File

@ -7,9 +7,12 @@ package openapi
import (
"net/http"
"github.com/gotidy/ptr"
"github.com/harness/gitness/internal/api/controller/space"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/api/usererror"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/swaggest/openapi-go/openapi3"
)
@ -41,6 +44,78 @@ type deletePathRequest struct {
PathID string `json:"pathID" path:"pathID"`
}
var queryParameterSortRepo = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamSort,
In: openapi3.ParameterInQuery,
Description: ptr.String("The data by which the repositories are sorted."),
Required: ptr.Bool(false),
Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeString),
Default: ptrptr(enum.RepoAttrName.String()),
Enum: []interface{}{
ptr.String(enum.RepoAttrName.String()),
ptr.String(enum.RepoAttrPath.String()),
ptr.String(enum.RepoAttrPathName.String()),
ptr.String(enum.RepoAttrCreated.String()),
ptr.String(enum.RepoAttrUpdated.String()),
},
},
},
},
}
var queryParameterQueryRepo = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamQuery,
In: openapi3.ParameterInQuery,
Description: ptr.String("The substring which is used to filter the repositories by their path name."),
Required: ptr.Bool(false),
Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeString),
},
},
},
}
var queryParameterSortSpace = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamSort,
In: openapi3.ParameterInQuery,
Description: ptr.String("The data by which the spaces are sorted."),
Required: ptr.Bool(false),
Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeString),
Default: ptrptr(enum.SpaceAttrName.String()),
Enum: []interface{}{
ptr.String(enum.SpaceAttrName.String()),
ptr.String(enum.SpaceAttrPath.String()),
ptr.String(enum.SpaceAttrPathName.String()),
ptr.String(enum.SpaceAttrCreated.String()),
ptr.String(enum.SpaceAttrUpdated.String()),
},
},
},
},
}
var queryParameterQuerySpace = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamQuery,
In: openapi3.ParameterInQuery,
Description: ptr.String("The substring which is used to filter the spaces by their path name."),
Required: ptr.Bool(false),
Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeString),
},
},
},
}
//nolint:funlen // api spec generation no need for checking func complexity
func spaceOperations(reflector *openapi3.Reflector) {
opCreate := openapi3.Operation{}
@ -103,6 +178,8 @@ func spaceOperations(reflector *openapi3.Reflector) {
opSpaces.WithTags("space")
opSpaces.WithMapOfAnything(map[string]interface{}{"operationId": "listSpaces"})
opSpaces.WithParameters(queryParameterPage, queryParameterPerPage)
opSpaces.WithParameters(queryParameterQuerySpace, queryParameterSortSpace, queryParameterDirection,
queryParameterPage, queryParameterPerPage)
_ = reflector.SetRequest(&opSpaces, new(spaceRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&opSpaces, []types.Space{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opSpaces, new(usererror.Error), http.StatusInternalServerError)
@ -114,7 +191,8 @@ func spaceOperations(reflector *openapi3.Reflector) {
opRepos := openapi3.Operation{}
opRepos.WithTags("space")
opRepos.WithMapOfAnything(map[string]interface{}{"operationId": "listRepos"})
opRepos.WithParameters(queryParameterPage, queryParameterPerPage)
opRepos.WithParameters(queryParameterQueryRepo, queryParameterSortRepo, queryParameterDirection,
queryParameterPage, queryParameterPerPage)
_ = reflector.SetRequest(&opRepos, new(spaceRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&opRepos, []types.Repository{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opRepos, new(usererror.Error), http.StatusInternalServerError)
@ -137,6 +215,7 @@ func spaceOperations(reflector *openapi3.Reflector) {
opListPaths := openapi3.Operation{}
opListPaths.WithTags("space")
opListPaths.WithMapOfAnything(map[string]interface{}{"operationId": "listPaths"})
opListPaths.WithParameters(queryParameterPage, queryParameterPerPage)
_ = reflector.SetRequest(&opListPaths, new(spaceRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&opListPaths, []types.Path{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opListPaths, new(usererror.Error), http.StatusInternalServerError)

View File

@ -198,6 +198,7 @@ func ParseUserFilter(r *http.Request) *types.UserFilter {
// ParseSpaceFilter extracts the space query parameter from the url.
func ParseSpaceFilter(r *http.Request) *types.SpaceFilter {
return &types.SpaceFilter{
Query: ParseQuery(r),
Order: ParseOrder(r),
Page: ParsePage(r),
Sort: ParseSortSpace(r),
@ -208,6 +209,7 @@ func ParseSpaceFilter(r *http.Request) *types.SpaceFilter {
// ParseRepoFilter extracts the repository query parameter from the url.
func ParseRepoFilter(r *http.Request) *types.RepoFilter {
return &types.RepoFilter{
Query: ParseQuery(r),
Order: ParseOrder(r),
Page: ParsePage(r),
Sort: ParseSortRepo(r),

View File

@ -90,7 +90,7 @@ func ListPrimaryChildPathsTx(ctx context.Context, tx *sqlx.Tx, prefix string) ([
return childs, nil
}
// ReplacePathTx replace the path for a target as part of a transaction - keeps the existing as alias if requested.
// ReplacePathTx replaces the path for a target as part of a transaction - keeps the existing as alias if requested.
func ReplacePathTx(ctx context.Context, db *sqlx.DB, tx *sqlx.Tx, path *types.Path, keepAsAlias bool) error {
if path.IsAlias {
return store.ErrPrimaryPathRequired
@ -204,7 +204,7 @@ func FindPathTx(ctx context.Context, tx *sqlx.Tx, targetType enum.PathTargetType
return dst, nil
}
// Deletes a specific path alias (primary can't be deleted, only with delete all).
// DeletePath deletes a specific path alias (primary can't be deleted, only with delete all).
func DeletePath(ctx context.Context, db *sqlx.DB, id int64) error {
tx, err := db.BeginTxx(ctx, nil)
if err != nil {
@ -234,7 +234,7 @@ func DeletePath(ctx context.Context, db *sqlx.DB, id int64) error {
return nil
}
// Deletes all paths for a target as part of a transaction.
// DeleteAllPaths deletes all paths for a target as part of a transaction.
func DeleteAllPaths(ctx context.Context, tx *sqlx.Tx, targetType enum.PathTargetType, targetID int64) error {
// delete all entries for the target
if _, err := tx.ExecContext(ctx, pathDeleteTarget, string(targetType), fmt.Sprint(targetID)); err != nil {
@ -243,58 +243,54 @@ func DeleteAllPaths(ctx context.Context, tx *sqlx.Tx, targetType enum.PathTarget
return nil
}
// Lists all paths for a target.
// CountPaths returns the count of paths for a specified target.
func CountPaths(ctx context.Context, db *sqlx.DB, targetType enum.PathTargetType, targetID int64,
opts *types.PathFilter) (int64, error) {
var count int64
err := db.QueryRowContext(ctx, pathCount, string(targetType), fmt.Sprint(targetID)).Scan(&count)
if err != nil {
return 0, processSQLErrorf(err, "Failed executing count query")
}
return count, nil
}
// ListPaths lists all paths for a target.
func ListPaths(ctx context.Context, db *sqlx.DB, targetType enum.PathTargetType, targetID int64,
opts *types.PathFilter) ([]*types.Path, error) {
dst := []*types.Path{}
// if the principal does not provide any customer filter
// or sorting we use the default select statement.
if opts.Sort == enum.PathAttrNone {
err := db.SelectContext(ctx, &dst, pathSelect, string(targetType), fmt.Sprint(targetID), limit(opts.Size),
offset(opts.Page, opts.Size))
if err != nil {
return nil, processSQLErrorf(err, "Default select query failed")
}
return dst, nil
}
// else we construct the sql statement.
stmt := builder.Select("*").From("paths").Where("path_targetType = $1 AND path_targetId = $2",
string(targetType), fmt.Sprint(targetID))
stmt := builder.
Select("*").
From("paths").
Where("path_targetType = ? AND path_targetId = ?", string(targetType), fmt.Sprint(targetID))
stmt = stmt.Limit(uint64(limit(opts.Size)))
stmt = stmt.Offset(uint64(offset(opts.Page, opts.Size)))
switch opts.Sort {
case enum.PathAttrCreated:
case enum.PathAttrPath, enum.PathAttrNone:
// NOTE: string concatenation is safe because the
// order attribute is an enum and is not user-defined,
// and is therefore not subject to injection attacks.
stmt = stmt.OrderBy("path_value COLLATE NOCASE " + opts.Order.String())
case enum.PathAttrCreated:
stmt = stmt.OrderBy("path_created " + opts.Order.String())
case enum.PathAttrUpdated:
stmt = stmt.OrderBy("path_updated " + opts.Order.String())
case enum.PathAttrID:
stmt = stmt.OrderBy("path_id " + opts.Order.String())
case enum.PathAttrPath:
stmt = stmt.OrderBy("path_value" + opts.Order.String())
case enum.PathAttrNone:
// no sorting required
}
sql, _, err := stmt.ToSql()
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert query to sql")
}
if err = db.SelectContext(ctx, &dst, sql); err != nil {
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, processSQLErrorf(err, "Customer select query failed")
}
return dst, nil
}
// CountPathsTx Count paths for a target as part of a transaction.
// CountPathsTx counts paths for a target as part of a transaction.
func CountPathsTx(ctx context.Context, tx *sqlx.Tx, targetType enum.PathTargetType, targetID int64) (int64, error) {
var count int64
err := tx.QueryRowContext(ctx, pathCount, string(targetType), fmt.Sprint(targetID)).Scan(&count)
@ -316,11 +312,6 @@ path_id
,path_updated
FROM paths
`
const pathSelect = pathBase + `
WHERE path_targetType = $1 AND path_targetId = $2
ORDER BY path_isAlias DESC, path_value ASC
LIMIT $3 OFFSET $4
`
// there's only one entry with a given target & targetId for isAlias -- false.
const pathSelectPrimaryForTarget = pathBase + `

View File

@ -208,9 +208,23 @@ func (s *RepoStore) Delete(ctx context.Context, id int64) error {
}
// Count of repos in a space.
func (s *RepoStore) Count(ctx context.Context, spaceID int64) (int64, error) {
func (s *RepoStore) Count(ctx context.Context, spaceID int64, opts *types.RepoFilter) (int64, error) {
stmt := builder.
Select("count(*)").
From("repositories").
Where("repo_spaceId = ?", spaceID)
if opts.Query != "" {
stmt = stmt.Where("repo_pathName LIKE ?", fmt.Sprintf("%%%s%%", opts.Query))
}
sql, args, err := stmt.ToSql()
if err != nil {
return 0, errors.Wrap(err, "Failed to convert query to sql")
}
var count int64
err := s.db.QueryRow(repoCount, spaceID).Scan(&count)
err = s.db.QueryRowContext(ctx, sql, args...).Scan(&count)
if err != nil {
return 0, processSQLErrorf(err, "Failed executing count query")
}
@ -218,27 +232,21 @@ func (s *RepoStore) Count(ctx context.Context, spaceID int64) (int64, error) {
}
// List returns a list of repos in a space.
// TODO: speed up list - for some reason is 200ms for 1 repo as well as 1000
func (s *RepoStore) List(ctx context.Context, spaceID int64, opts *types.RepoFilter) ([]*types.Repository, error) {
dst := []*types.Repository{}
// if the principal does not provide any customer filter
// or sorting we use the default select statement.
if opts.Sort == enum.RepoAttrNone {
err := s.db.SelectContext(ctx, &dst, repoSelect, spaceID, limit(opts.Size), offset(opts.Page, opts.Size))
if err != nil {
return nil, processSQLErrorf(err, "Failed executing default list query")
}
return dst, nil
}
// else we construct the sql statement.
// construct the sql statement.
stmt := builder.
Select("repositories.*,path_value AS repo_path").
From("repositories").
InnerJoin("paths ON repositories.repo_id=paths.path_targetId AND paths.path_targetType='repo' " +
InnerJoin("paths ON repositories.repo_id=paths.path_targetId AND paths.path_targetType='repo' "+
"AND paths.path_isAlias=0").
Where("repo_spaceId = " + fmt.Sprint(spaceID))
Where("repo_spaceId = ?", fmt.Sprint(spaceID))
if opts.Query != "" {
stmt = stmt.Where("repo_pathName LIKE ?", fmt.Sprintf("%%%s%%", opts.Query))
}
stmt = stmt.Limit(uint64(limit(opts.Size)))
stmt = stmt.Offset(uint64(offset(opts.Page, opts.Size)))
@ -247,33 +255,36 @@ func (s *RepoStore) List(ctx context.Context, spaceID int64, opts *types.RepoFil
// NOTE: string concatenation is safe because the
// order attribute is an enum and is not user-defined,
// and is therefore not subject to injection attacks.
stmt = stmt.OrderBy("repo_name " + opts.Order.String())
stmt = stmt.OrderBy("repo_name COLLATE NOCASE " + opts.Order.String())
case enum.RepoAttrCreated:
stmt = stmt.OrderBy("repo_created " + opts.Order.String())
case enum.RepoAttrUpdated:
stmt = stmt.OrderBy("repo_updated " + opts.Order.String())
case enum.RepoAttrID:
stmt = stmt.OrderBy("repo_id " + opts.Order.String())
case enum.RepoAttrPathName:
stmt = stmt.OrderBy("repo_pathName " + opts.Order.String())
stmt = stmt.OrderBy("repo_pathName COLLATE NOCASE " + opts.Order.String())
case enum.RepoAttrPath:
stmt = stmt.OrderBy("repo_path " + opts.Order.String())
stmt = stmt.OrderBy("repo_path COLLATE NOCASE " + opts.Order.String())
}
sql, _, err := stmt.ToSql()
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert query to sql")
}
if err = s.db.SelectContext(ctx, &dst, sql); err != nil {
if err = s.db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, processSQLErrorf(err, "Failed executing custom list query")
}
return dst, nil
}
// ListAllPaths returns a list of all paths of a repo.
func (s *RepoStore) ListAllPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error) {
// CountPaths returns a count of all paths of a repo.
func (s *RepoStore) CountPaths(ctx context.Context, id int64, opts *types.PathFilter) (int64, error) {
return CountPaths(ctx, s.db, enum.PathTargetTypeRepo, id, opts)
}
// ListPaths returns a list of all paths of a repo.
func (s *RepoStore) ListPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error) {
return ListPaths(ctx, s.db, enum.PathTargetTypeRepo, id, opts)
}
@ -326,18 +337,6 @@ INNER JOIN paths
ON repositories.repo_id=paths.path_targetId AND paths.path_targetType='repo' AND paths.path_isAlias=0
`
const repoSelect = repoSelectBaseWithJoin + `
WHERE repo_spaceId = $1
ORDER BY repo_pathName ASC
LIMIT $2 OFFSET $3
`
const repoCount = `
SELECT count(*)
FROM repositories
WHERE repo_spaceId = $1
`
const repoSelectByID = repoSelectBaseWithJoin + `
WHERE repo_id = $1
`

View File

@ -70,10 +70,10 @@ func (s *RepoStoreSync) Delete(ctx context.Context, id int64) error {
}
// Count of repos in a space.
func (s *RepoStoreSync) Count(ctx context.Context, spaceID int64) (int64, error) {
func (s *RepoStoreSync) Count(ctx context.Context, spaceID int64, opts *types.RepoFilter) (int64, error) {
mutex.RLock()
defer mutex.RUnlock()
return s.base.Count(ctx, spaceID)
return s.base.Count(ctx, spaceID, opts)
}
// List returns a list of repos in a space.
@ -83,9 +83,14 @@ func (s *RepoStoreSync) List(ctx context.Context, spaceID int64, opts *types.Rep
return s.base.List(ctx, spaceID, opts)
}
// ListAllPaths returns a list of all paths of a repo.
func (s *RepoStoreSync) ListAllPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error) {
return s.base.ListAllPaths(ctx, id, opts)
// CountPaths returns a count of all paths of a repo.
func (s *RepoStoreSync) CountPaths(ctx context.Context, id int64, opts *types.PathFilter) (int64, error) {
return s.base.CountPaths(ctx, id, opts)
}
// ListPaths returns a list of all paths of a repo.
func (s *RepoStoreSync) ListPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error) {
return s.base.ListPaths(ctx, id, opts)
}
// CreatePath an alias for a repository.

View File

@ -241,9 +241,23 @@ func (s *SpaceStore) Delete(ctx context.Context, id int64) error {
}
// Count the child spaces of a space.
func (s *SpaceStore) Count(ctx context.Context, id int64) (int64, error) {
func (s *SpaceStore) Count(ctx context.Context, id int64, opts *types.SpaceFilter) (int64, error) {
stmt := builder.
Select("count(*)").
From("spaces").
Where("space_parentId = ?", id)
if opts.Query != "" {
stmt = stmt.Where("space_pathName LIKE ?", fmt.Sprintf("%%%s%%", opts.Query))
}
sql, args, err := stmt.ToSql()
if err != nil {
return 0, errors.Wrap(err, "Failed to convert query to sql")
}
var count int64
err := s.db.QueryRowContext(ctx, spaceCount, id).Scan(&count)
err = s.db.QueryRowContext(ctx, sql, args...).Scan(&count)
if err != nil {
return 0, processSQLErrorf(err, "Failed executing count query")
}
@ -251,61 +265,56 @@ func (s *SpaceStore) Count(ctx context.Context, id int64) (int64, error) {
}
// List returns a list of spaces under the parent space.
// TODO: speed up list - for some reason is 200ms for 1 space as well as 1000
func (s *SpaceStore) List(ctx context.Context, id int64, opts *types.SpaceFilter) ([]*types.Space, error) {
dst := []*types.Space{}
// if the principal does not provide any customer filter
// or sorting we use the default select statement.
if opts.Sort == enum.SpaceAttrNone {
err := s.db.SelectContext(ctx, &dst, spaceSelect, id, limit(opts.Size), offset(opts.Page, opts.Size))
if err != nil {
return nil, processSQLErrorf(err, "Failed executing default list query")
}
return dst, nil
}
// else we construct the sql statement.
stmt := builder.
Select("spaces.*,path_value AS space_path").
From("spaces").
InnerJoin("paths ON spaces.space_id=paths.path_targetId AND paths.path_targetType='space' AND paths.path_isAlias=0").
Where("space_parentId = " + fmt.Sprint(id))
Where("space_parentId = ?", fmt.Sprint(id))
stmt = stmt.Limit(uint64(limit(opts.Size)))
stmt = stmt.Offset(uint64(offset(opts.Page, opts.Size)))
if opts.Query != "" {
stmt = stmt.Where("space_pathName LIKE ?", fmt.Sprintf("%%%s%%", opts.Query))
}
switch opts.Sort {
case enum.SpaceAttrName, enum.SpaceAttrNone:
// NOTE: string concatenation is safe because the
// order attribute is an enum and is not user-defined,
// and is therefore not subject to injection attacks.
stmt = stmt.OrderBy("space_name " + opts.Order.String())
stmt = stmt.OrderBy("space_name COLLATE NOCASE " + opts.Order.String())
case enum.SpaceAttrCreated:
stmt = stmt.OrderBy("space_created " + opts.Order.String())
case enum.SpaceAttrUpdated:
stmt = stmt.OrderBy("space_updated " + opts.Order.String())
case enum.SpaceAttrID:
stmt = stmt.OrderBy("space_id " + opts.Order.String())
case enum.SpaceAttrPathName:
stmt = stmt.OrderBy("space_pathName " + opts.Order.String())
stmt = stmt.OrderBy("space_pathName COLLATE NOCASE " + opts.Order.String())
case enum.SpaceAttrPath:
stmt = stmt.OrderBy("space_path " + opts.Order.String())
stmt = stmt.OrderBy("space_path COLLATE NOCASE " + opts.Order.String())
}
sql, _, err := stmt.ToSql()
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert query to sql")
}
if err = s.db.SelectContext(ctx, &dst, sql); err != nil {
if err = s.db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, processSQLErrorf(err, "Failed executing custom list query")
}
return dst, nil
}
// ListAllPaths returns a list of all paths of a space.
func (s *SpaceStore) ListAllPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error) {
// CountPaths returns a count of all paths of a space.
func (s *SpaceStore) CountPaths(ctx context.Context, id int64, opts *types.PathFilter) (int64, error) {
return CountPaths(ctx, s.db, enum.PathTargetTypeSpace, id, opts)
}
// ListPaths returns a list of all paths of a space.
func (s *SpaceStore) ListPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error) {
return ListPaths(ctx, s.db, enum.PathTargetTypeSpace, id, opts)
}
@ -351,18 +360,6 @@ INNER JOIN paths
ON spaces.space_id=paths.path_targetId AND paths.path_targetType='space' AND paths.path_isAlias=0
`
const spaceSelect = spaceSelectBaseWithJoin + `
WHERE space_parentId = $1
ORDER BY space_pathName ASC
LIMIT $2 OFFSET $3
`
const spaceCount = `
SELECT count(*)
FROM spaces
WHERE space_parentId = $1
`
const spaceSelectByID = spaceSelectBaseWithJoin + `
WHERE space_id = $1
`

View File

@ -70,10 +70,10 @@ func (s *SpaceStoreSync) Delete(ctx context.Context, id int64) error {
}
// Count the child spaces of a space.
func (s *SpaceStoreSync) Count(ctx context.Context, id int64) (int64, error) {
func (s *SpaceStoreSync) Count(ctx context.Context, id int64, opts *types.SpaceFilter) (int64, error) {
mutex.RLock()
defer mutex.RUnlock()
return s.base.Count(ctx, id)
return s.base.Count(ctx, id, opts)
}
// List returns a list of spaces under the parent space.
@ -83,9 +83,14 @@ func (s *SpaceStoreSync) List(ctx context.Context, id int64, opts *types.SpaceFi
return s.base.List(ctx, id, opts)
}
// ListAllPaths returns a list of all paths of a space.
func (s *SpaceStoreSync) ListAllPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error) {
return s.base.ListAllPaths(ctx, id, opts)
// CountPaths returns a count of all paths of a space.
func (s *SpaceStoreSync) CountPaths(ctx context.Context, id int64, opts *types.PathFilter) (int64, error) {
return s.base.CountPaths(ctx, id, opts)
}
// ListPaths returns a list of all paths of a space.
func (s *SpaceStoreSync) ListPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error) {
return s.base.ListPaths(ctx, id, opts)
}
// CreatePath a path for a space.

View File

@ -110,13 +110,16 @@ type (
Delete(ctx context.Context, id int64) error
// Count the child spaces of a space.
Count(ctx context.Context, id int64) (int64, error)
Count(ctx context.Context, id int64, opts *types.SpaceFilter) (int64, error)
// List returns a list of child spaces in a space.
List(ctx context.Context, id int64, opts *types.SpaceFilter) ([]*types.Space, error)
// ListAllPaths returns a list of all paths of a space.
ListAllPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error)
// CountPaths returns a count of all paths of a space.
CountPaths(ctx context.Context, id int64, opts *types.PathFilter) (int64, error)
// ListPaths returns a list of all paths of a space.
ListPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error)
// CreatePath create an alias for a space
CreatePath(ctx context.Context, spaceID int64, params *types.PathParams) (*types.Path, error)
@ -147,13 +150,16 @@ type (
Delete(ctx context.Context, id int64) error
// Count of repos in a space.
Count(ctx context.Context, spaceID int64) (int64, error)
Count(ctx context.Context, spaceID int64, opts *types.RepoFilter) (int64, error)
// List returns a list of repos in a space.
List(ctx context.Context, spaceID int64, opts *types.RepoFilter) ([]*types.Repository, error)
// ListAllPaths returns a list of all alias paths of a repo.
ListAllPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error)
// CountPaths returns a count of all paths of a repo.
CountPaths(ctx context.Context, id int64, opts *types.PathFilter) (int64, error)
// ListPaths returns a list of all paths of a repo.
ListPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error)
// CreatePath an alias for a repo
CreatePath(ctx context.Context, repoID int64, params *types.PathParams) (*types.Path, error)

View File

@ -213,18 +213,33 @@ func (m *MockSpaceStore) EXPECT() *MockSpaceStoreMockRecorder {
}
// Count mocks base method.
func (m *MockSpaceStore) Count(arg0 context.Context, arg1 int64) (int64, error) {
func (m *MockSpaceStore) Count(arg0 context.Context, arg1 int64, arg2 *types.SpaceFilter) (int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Count", arg0, arg1)
ret := m.ctrl.Call(m, "Count", arg0, arg1, arg2)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Count indicates an expected call of Count.
func (mr *MockSpaceStoreMockRecorder) Count(arg0, arg1 interface{}) *gomock.Call {
func (mr *MockSpaceStoreMockRecorder) Count(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Count", reflect.TypeOf((*MockSpaceStore)(nil).Count), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Count", reflect.TypeOf((*MockSpaceStore)(nil).Count), arg0, arg1, arg2)
}
// CountPaths mocks base method.
func (m *MockSpaceStore) CountPaths(arg0 context.Context, arg1 int64, arg2 *types.PathFilter) (int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CountPaths", arg0, arg1, arg2)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CountPaths indicates an expected call of CountPaths.
func (mr *MockSpaceStoreMockRecorder) CountPaths(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountPaths", reflect.TypeOf((*MockSpaceStore)(nil).CountPaths), arg0, arg1, arg2)
}
// Create mocks base method.
@ -329,19 +344,19 @@ func (mr *MockSpaceStoreMockRecorder) List(arg0, arg1, arg2 interface{}) *gomock
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockSpaceStore)(nil).List), arg0, arg1, arg2)
}
// ListAllPaths mocks base method.
func (m *MockSpaceStore) ListAllPaths(arg0 context.Context, arg1 int64, arg2 *types.PathFilter) ([]*types.Path, error) {
// ListPaths mocks base method.
func (m *MockSpaceStore) ListPaths(arg0 context.Context, arg1 int64, arg2 *types.PathFilter) ([]*types.Path, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListAllPaths", arg0, arg1, arg2)
ret := m.ctrl.Call(m, "ListPaths", arg0, arg1, arg2)
ret0, _ := ret[0].([]*types.Path)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListAllPaths indicates an expected call of ListAllPaths.
func (mr *MockSpaceStoreMockRecorder) ListAllPaths(arg0, arg1, arg2 interface{}) *gomock.Call {
// ListPaths indicates an expected call of ListPaths.
func (mr *MockSpaceStoreMockRecorder) ListPaths(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAllPaths", reflect.TypeOf((*MockSpaceStore)(nil).ListAllPaths), arg0, arg1, arg2)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListPaths", reflect.TypeOf((*MockSpaceStore)(nil).ListPaths), arg0, arg1, arg2)
}
// Move mocks base method.
@ -397,18 +412,33 @@ func (m *MockRepoStore) EXPECT() *MockRepoStoreMockRecorder {
}
// Count mocks base method.
func (m *MockRepoStore) Count(arg0 context.Context, arg1 int64) (int64, error) {
func (m *MockRepoStore) Count(arg0 context.Context, arg1 int64, arg2 *types.RepoFilter) (int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Count", arg0, arg1)
ret := m.ctrl.Call(m, "Count", arg0, arg1, arg2)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Count indicates an expected call of Count.
func (mr *MockRepoStoreMockRecorder) Count(arg0, arg1 interface{}) *gomock.Call {
func (mr *MockRepoStoreMockRecorder) Count(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Count", reflect.TypeOf((*MockRepoStore)(nil).Count), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Count", reflect.TypeOf((*MockRepoStore)(nil).Count), arg0, arg1, arg2)
}
// CountPaths mocks base method.
func (m *MockRepoStore) CountPaths(arg0 context.Context, arg1 int64, arg2 *types.PathFilter) (int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CountPaths", arg0, arg1, arg2)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CountPaths indicates an expected call of CountPaths.
func (mr *MockRepoStoreMockRecorder) CountPaths(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountPaths", reflect.TypeOf((*MockRepoStore)(nil).CountPaths), arg0, arg1, arg2)
}
// Create mocks base method.
@ -513,19 +543,19 @@ func (mr *MockRepoStoreMockRecorder) List(arg0, arg1, arg2 interface{}) *gomock.
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockRepoStore)(nil).List), arg0, arg1, arg2)
}
// ListAllPaths mocks base method.
func (m *MockRepoStore) ListAllPaths(arg0 context.Context, arg1 int64, arg2 *types.PathFilter) ([]*types.Path, error) {
// ListPaths mocks base method.
func (m *MockRepoStore) ListPaths(arg0 context.Context, arg1 int64, arg2 *types.PathFilter) ([]*types.Path, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListAllPaths", arg0, arg1, arg2)
ret := m.ctrl.Call(m, "ListPaths", arg0, arg1, arg2)
ret0, _ := ret[0].([]*types.Path)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListAllPaths indicates an expected call of ListAllPaths.
func (mr *MockRepoStoreMockRecorder) ListAllPaths(arg0, arg1, arg2 interface{}) *gomock.Call {
// ListPaths indicates an expected call of ListPaths.
func (mr *MockRepoStoreMockRecorder) ListPaths(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAllPaths", reflect.TypeOf((*MockRepoStore)(nil).ListAllPaths), arg0, arg1, arg2)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListPaths", reflect.TypeOf((*MockRepoStore)(nil).ListPaths), arg0, arg1, arg2)
}
// Move mocks base method.

View File

@ -38,6 +38,6 @@ func (o BranchSortOption) String() string {
case BranchSortOptionDefault:
return defaultString
default:
return ""
return undefined
}
}

View File

@ -32,7 +32,6 @@ type PathAttr int
// Order enumeration.
const (
PathAttrNone PathAttr = iota
PathAttrID
PathAttrPath
PathAttrCreated
PathAttrUpdated
@ -42,8 +41,6 @@ const (
// and returns the equivalent enumeration.
func ParsePathAttr(s string) PathAttr {
switch strings.ToLower(s) {
case id:
return PathAttrID
case path:
return PathAttrPath
case created, createdAt:
@ -54,3 +51,19 @@ func ParsePathAttr(s string) PathAttr {
return PathAttrNone
}
}
// String returns the string representation of the attribute.
func (a PathAttr) String() string {
switch a {
case PathAttrPath:
return path
case PathAttrCreated:
return created
case PathAttrUpdated:
return updated
case PathAttrNone:
return ""
default:
return undefined
}
}

View File

@ -4,7 +4,9 @@
package enum
import "strings"
import (
"strings"
)
// Defines repo attributes that can be used for sorting and filtering.
type RepoAttr int
@ -12,7 +14,6 @@ type RepoAttr int
// Order enumeration.
const (
RepoAttrNone RepoAttr = iota
RepoAttrID
RepoAttrPathName
RepoAttrPath
RepoAttrName
@ -24,8 +25,6 @@ const (
// and returns the equivalent enumeration.
func ParseRepoAtrr(s string) RepoAttr {
switch strings.ToLower(s) {
case id:
return RepoAttrID
case name:
return RepoAttrName
case path:
@ -40,3 +39,23 @@ func ParseRepoAtrr(s string) RepoAttr {
return RepoAttrNone
}
}
// String returns the string representation of the attribute.
func (a RepoAttr) String() string {
switch a {
case RepoAttrPathName:
return pathName
case RepoAttrPath:
return path
case RepoAttrName:
return name
case RepoAttrCreated:
return created
case RepoAttrUpdated:
return updated
case RepoAttrNone:
return ""
default:
return undefined
}
}

View File

@ -15,4 +15,5 @@ const (
updatedAt = "updated_at"
date = "date"
defaultString = "default"
undefined = "undefined"
)

View File

@ -12,7 +12,6 @@ type SpaceAttr int
// Order enumeration.
const (
SpaceAttrNone SpaceAttr = iota
SpaceAttrID
SpaceAttrPathName
SpaceAttrPath
SpaceAttrName
@ -24,19 +23,37 @@ const (
// and returns the equivalent enumeration.
func ParseSpaceAttr(s string) SpaceAttr {
switch strings.ToLower(s) {
case "id":
return SpaceAttrID
case "pathname", "path_name":
return SpaceAttrPathName
case "path":
return SpaceAttrPath
case "name":
case name:
return SpaceAttrName
case "created", "created_at":
case path:
return SpaceAttrPath
case pathName:
return SpaceAttrPathName
case created, createdAt:
return SpaceAttrCreated
case "updated", "updated_at":
case updated, updatedAt:
return SpaceAttrUpdated
default:
return SpaceAttrNone
}
}
// String returns the string representation of the attribute.
func (a SpaceAttr) String() string {
switch a {
case SpaceAttrPathName:
return pathName
case SpaceAttrPath:
return path
case SpaceAttrName:
return name
case SpaceAttrCreated:
return created
case SpaceAttrUpdated:
return updated
case SpaceAttrNone:
return ""
default:
return undefined
}
}

View File

@ -38,6 +38,7 @@ type Repository struct {
type RepoFilter struct {
Page int `json:"page"`
Size int `json:"size"`
Query string `json:"query"`
Sort enum.RepoAttr `json:"sort"`
Order enum.Order `json:"direction"`
}

View File

@ -38,6 +38,7 @@ type Space struct {
type SpaceFilter struct {
Page int `json:"page"`
Size int `json:"size"`
Query string `json:"query"`
Sort enum.SpaceAttr `json:"sort"`
Order enum.Order `json:"direction"`
}