Merge branch 'mg/memberships/spaces_list_filter' of _OKE5H2PQKOUfzFFDuD4FA/default/CODE/gitness (#368)

This commit is contained in:
Marko Gacesa 2023-08-28 16:27:25 +00:00 committed by Harness
commit e8c1ac6ba3
14 changed files with 276 additions and 74 deletions

View File

@ -58,12 +58,12 @@ import (
// Injectors from wire.go: // Injectors from wire.go:
func initSystem(ctx context.Context, config *types.Config) (*server.System, error) { func initSystem(ctx context.Context, config *types.Config) (*server.System, error) {
principalUID := check.ProvidePrincipalUIDCheck()
databaseConfig := server.ProvideDatabaseConfig(config) databaseConfig := server.ProvideDatabaseConfig(config)
db, err := database.ProvideDatabase(ctx, databaseConfig) db, err := database.ProvideDatabase(ctx, databaseConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
principalUID := check.ProvidePrincipalUIDCheck()
pathTransformation := store.ProvidePathTransformation() pathTransformation := store.ProvidePathTransformation()
pathStore := database.ProvidePathStore(db, pathTransformation) pathStore := database.ProvidePathStore(db, pathTransformation)
pathCache := cache.ProvidePathCache(pathStore, pathTransformation) pathCache := cache.ProvidePathCache(pathStore, pathTransformation)
@ -76,7 +76,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
principalUIDTransformation := store.ProvidePrincipalUIDTransformation() principalUIDTransformation := store.ProvidePrincipalUIDTransformation()
principalStore := database.ProvidePrincipalStore(db, principalUIDTransformation) principalStore := database.ProvidePrincipalStore(db, principalUIDTransformation)
tokenStore := database.ProvideTokenStore(db) tokenStore := database.ProvideTokenStore(db)
controller := user.NewController(principalUID, authorizer, principalStore, tokenStore, membershipStore) controller := user.ProvideController(db, principalUID, authorizer, principalStore, tokenStore, membershipStore)
serviceController := service.NewController(principalUID, authorizer, principalStore) serviceController := service.NewController(principalUID, authorizer, principalStore)
bootstrapBootstrap := bootstrap.ProvideBootstrap(config, controller, serviceController) bootstrapBootstrap := bootstrap.ProvideBootstrap(config, controller, serviceController)
authenticator := authn.ProvideAuthenticator(principalStore, tokenStore) authenticator := authn.ProvideAuthenticator(principalStore, tokenStore)

View File

@ -19,7 +19,7 @@ import (
func (c *Controller) MembershipList(ctx context.Context, func (c *Controller) MembershipList(ctx context.Context,
session *auth.Session, session *auth.Session,
spaceRef string, spaceRef string,
opts types.MembershipFilter, filter types.MembershipUserFilter,
) ([]types.MembershipUser, int64, error) { ) ([]types.MembershipUser, int64, error) {
space, err := c.spaceStore.FindByRef(ctx, spaceRef) space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil { if err != nil {
@ -34,17 +34,17 @@ func (c *Controller) MembershipList(ctx context.Context,
var membershipsCount int64 var membershipsCount int64
err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) error { err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) error {
memberships, err = c.membershipStore.ListUsers(ctx, space.ID, opts) memberships, err = c.membershipStore.ListUsers(ctx, space.ID, filter)
if err != nil { if err != nil {
return fmt.Errorf("failed to list memberships for space: %w", err) return fmt.Errorf("failed to list memberships for space: %w", err)
} }
if opts.Page == 1 && len(memberships) < opts.Size { if filter.Page == 1 && len(memberships) < filter.Size {
membershipsCount = int64(len(memberships)) membershipsCount = int64(len(memberships))
return nil return nil
} }
membershipsCount, err = c.membershipStore.CountUsers(ctx, space.ID, opts) membershipsCount, err = c.membershipStore.CountUsers(ctx, space.ID, filter)
if err != nil { if err != nil {
return fmt.Errorf("failed to count memberships for space: %w", err) return fmt.Errorf("failed to count memberships for space: %w", err)
} }

View File

@ -13,10 +13,12 @@ import (
"github.com/harness/gitness/types/check" "github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum" "github.com/harness/gitness/types/enum"
"github.com/jmoiron/sqlx"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
type Controller struct { type Controller struct {
db *sqlx.DB
principalUIDCheck check.PrincipalUID principalUIDCheck check.PrincipalUID
authorizer authz.Authorizer authorizer authz.Authorizer
principalStore store.PrincipalStore principalStore store.PrincipalStore
@ -25,6 +27,7 @@ type Controller struct {
} }
func NewController( func NewController(
db *sqlx.DB,
principalUIDCheck check.PrincipalUID, principalUIDCheck check.PrincipalUID,
authorizer authz.Authorizer, authorizer authz.Authorizer,
principalStore store.PrincipalStore, principalStore store.PrincipalStore,
@ -32,6 +35,7 @@ func NewController(
membershipStore store.MembershipStore, membershipStore store.MembershipStore,
) *Controller { ) *Controller {
return &Controller{ return &Controller{
db: db,
principalUIDCheck: principalUIDCheck, principalUIDCheck: principalUIDCheck,
authorizer: authorizer, authorizer: authorizer,
principalStore: principalStore, principalStore: principalStore,

View File

@ -10,6 +10,7 @@ import (
apiauth "github.com/harness/gitness/internal/api/auth" apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/auth" "github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum" "github.com/harness/gitness/types/enum"
) )
@ -18,21 +19,42 @@ import (
func (c *Controller) MembershipSpaces(ctx context.Context, func (c *Controller) MembershipSpaces(ctx context.Context,
session *auth.Session, session *auth.Session,
userUID string, userUID string,
) ([]types.MembershipSpace, error) { filter types.MembershipSpaceFilter,
) ([]types.MembershipSpace, int64, error) {
user, err := findUserFromUID(ctx, c.principalStore, userUID) user, err := findUserFromUID(ctx, c.principalStore, userUID)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to find user by UID: %w", err) return nil, 0, fmt.Errorf("failed to find user by UID: %w", err)
} }
// Ensure principal has required permissions. // Ensure principal has required permissions.
if err = apiauth.CheckUser(ctx, c.authorizer, session, user, enum.PermissionUserView); err != nil { if err = apiauth.CheckUser(ctx, c.authorizer, session, user, enum.PermissionUserView); err != nil {
return nil, err return nil, 0, err
} }
membershipSpaces, err := c.membershipStore.ListSpaces(ctx, user.ID) var membershipSpaces []types.MembershipSpace
var membershipsCount int64
err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) error {
membershipSpaces, err = c.membershipStore.ListSpaces(ctx, user.ID, filter)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to list membership spaces for user: %w", err) return fmt.Errorf("failed to list membership spaces for user: %w", err)
} }
return membershipSpaces, nil if filter.Page == 1 && len(membershipSpaces) < filter.Size {
membershipsCount = int64(len(membershipSpaces))
return nil
}
membershipsCount, err = c.membershipStore.CountSpaces(ctx, user.ID, filter)
if err != nil {
return fmt.Errorf("failed to count memberships for user: %w", err)
}
return nil
}, dbtx.TxDefaultReadOnly)
if err != nil {
return nil, 0, err
}
return membershipSpaces, membershipsCount, nil
} }

View File

@ -10,14 +10,16 @@ import (
"github.com/harness/gitness/types/check" "github.com/harness/gitness/types/check"
"github.com/google/wire" "github.com/google/wire"
"github.com/jmoiron/sqlx"
) )
// WireSet provides a wire set for this package. // WireSet provides a wire set for this package.
var WireSet = wire.NewSet( var WireSet = wire.NewSet(
NewController, ProvideController,
) )
func ProvideController( func ProvideController(
db *sqlx.DB,
principalUIDCheck check.PrincipalUID, principalUIDCheck check.PrincipalUID,
authorizer authz.Authorizer, authorizer authz.Authorizer,
principalStore store.PrincipalStore, principalStore store.PrincipalStore,
@ -25,6 +27,7 @@ func ProvideController(
membershipStore store.MembershipStore, membershipStore store.MembershipStore,
) *Controller { ) *Controller {
return NewController( return NewController(
db,
principalUIDCheck, principalUIDCheck,
authorizer, authorizer,
principalStore, principalStore,

View File

@ -24,7 +24,7 @@ func HandleMembershipList(spaceCtrl *space.Controller) http.HandlerFunc {
return return
} }
filter := request.ParseMembershipFilter(r) filter := request.ParseMembershipUserFilter(r)
memberships, membershipsCount, err := spaceCtrl.MembershipList(ctx, session, spaceRef, filter) memberships, membershipsCount, err := spaceCtrl.MembershipList(ctx, session, spaceRef, filter)
if err != nil { if err != nil {

View File

@ -18,12 +18,15 @@ func HandleMembershipSpaces(userCtrl *user.Controller) http.HandlerFunc {
session, _ := request.AuthSessionFrom(ctx) session, _ := request.AuthSessionFrom(ctx)
userUID := session.Principal.UID userUID := session.Principal.UID
membershipSpaces, err := userCtrl.MembershipSpaces(ctx, session, userUID) filter := request.ParseMembershipSpaceFilter(r)
membershipSpaces, membershipSpaceCount, err := userCtrl.MembershipSpaces(ctx, session, userUID, filter)
if err != nil { if err != nil {
render.TranslatedUserError(w, err) render.TranslatedUserError(w, err)
return return
} }
render.Pagination(r, w, filter.Page, filter.Size, int(membershipSpaceCount))
render.JSON(w, http.StatusOK, membershipSpaces) render.JSON(w, http.StatusOK, membershipSpaces)
} }
} }

View File

@ -115,7 +115,7 @@ var queryParameterQuerySpace = openapi3.ParameterOrRef{
}, },
} }
var queryParameterSpaceMembers = openapi3.ParameterOrRef{ var queryParameterMembershipUsers = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{ Parameter: &openapi3.Parameter{
Name: request.QueryParamQuery, Name: request.QueryParamQuery,
In: openapi3.ParameterInQuery, In: openapi3.ParameterInQuery,
@ -129,7 +129,7 @@ var queryParameterSpaceMembers = openapi3.ParameterOrRef{
}, },
} }
var queryParameterSortSpaceMembers = openapi3.ParameterOrRef{ var queryParameterSortMembershipUsers = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{ Parameter: &openapi3.Parameter{
Name: request.QueryParamSort, Name: request.QueryParamSort,
In: openapi3.ParameterInQuery, In: openapi3.ParameterInQuery,
@ -138,8 +138,8 @@ var queryParameterSortSpaceMembers = openapi3.ParameterOrRef{
Schema: &openapi3.SchemaOrRef{ Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{ Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeString), Type: ptrSchemaType(openapi3.SchemaTypeString),
Default: ptrptr(enum.MembershipSortName), Default: ptrptr(enum.MembershipUserSortName),
Enum: enum.MembershipSort("").Enum(), Enum: enum.MembershipUserSort("").Enum(),
}, },
}, },
}, },
@ -370,8 +370,8 @@ func spaceOperations(reflector *openapi3.Reflector) {
opMembershipList.WithTags("space") opMembershipList.WithTags("space")
opMembershipList.WithMapOfAnything(map[string]interface{}{"operationId": "membershipList"}) opMembershipList.WithMapOfAnything(map[string]interface{}{"operationId": "membershipList"})
opMembershipList.WithParameters( opMembershipList.WithParameters(
queryParameterSpaceMembers, queryParameterMembershipUsers,
queryParameterOrder, queryParameterSortSpaceMembers, queryParameterOrder, queryParameterSortMembershipUsers,
queryParameterPage, queryParameterLimit) queryParameterPage, queryParameterLimit)
_ = reflector.SetRequest(&opMembershipList, &struct { _ = reflector.SetRequest(&opMembershipList, &struct {
spaceRequest spaceRequest

View File

@ -8,9 +8,12 @@ import (
"net/http" "net/http"
"github.com/harness/gitness/internal/api/controller/user" "github.com/harness/gitness/internal/api/controller/user"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/internal/api/usererror" "github.com/harness/gitness/internal/api/usererror"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/gotidy/ptr"
"github.com/swaggest/openapi-go/openapi3" "github.com/swaggest/openapi-go/openapi3"
) )
@ -18,6 +21,36 @@ type createTokenRequest struct {
user.CreateTokenInput user.CreateTokenInput
} }
var queryParameterMembershipSpaces = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamQuery,
In: openapi3.ParameterInQuery,
Description: ptr.String("The substring by which the spaces the users is a member of are filtered."),
Required: ptr.Bool(false),
Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeString),
},
},
},
}
var queryParameterSortMembershipSpaces = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamSort,
In: openapi3.ParameterInQuery,
Description: ptr.String("The field by which the spaces the user is a member of are sorted."),
Required: ptr.Bool(false),
Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeString),
Default: ptrptr(enum.MembershipSpaceSortUID),
Enum: enum.MembershipSpaceSort("").Enum(),
},
},
},
}
// helper function that constructs the openapi specification // helper function that constructs the openapi specification
// for user account resources. // for user account resources.
func buildUser(reflector *openapi3.Reflector) { func buildUser(reflector *openapi3.Reflector) {
@ -48,6 +81,10 @@ func buildUser(reflector *openapi3.Reflector) {
opMemberSpaces := openapi3.Operation{} opMemberSpaces := openapi3.Operation{}
opMemberSpaces.WithTags("user") opMemberSpaces.WithTags("user")
opMemberSpaces.WithMapOfAnything(map[string]interface{}{"operationId": "membershipSpaces"}) opMemberSpaces.WithMapOfAnything(map[string]interface{}{"operationId": "membershipSpaces"})
opMemberSpaces.WithParameters(
queryParameterMembershipSpaces,
queryParameterOrder, queryParameterSortMembershipSpaces,
queryParameterPage, queryParameterLimit)
_ = reflector.SetRequest(&opMemberSpaces, struct{}{}, http.MethodGet) _ = reflector.SetRequest(&opMemberSpaces, struct{}{}, http.MethodGet)
_ = reflector.SetJSONResponse(&opMemberSpaces, new([]types.MembershipSpace), http.StatusOK) _ = reflector.SetJSONResponse(&opMemberSpaces, new([]types.MembershipSpace), http.StatusOK)
_ = reflector.SetJSONResponse(&opMemberSpaces, new(usererror.Error), http.StatusInternalServerError) _ = reflector.SetJSONResponse(&opMemberSpaces, new(usererror.Error), http.StatusInternalServerError)

View File

@ -11,20 +11,34 @@ import (
"github.com/harness/gitness/types/enum" "github.com/harness/gitness/types/enum"
) )
// ParseMembershipSort extracts the membership sort parameter from the url. // ParseMembershipUserSort extracts the membership sort parameter from the url.
func ParseMembershipSort(r *http.Request) enum.MembershipSort { func ParseMembershipUserSort(r *http.Request) enum.MembershipUserSort {
return enum.ParseMembershipSort( return enum.ParseMembershipUserSort(
r.URL.Query().Get(QueryParamSort), r.URL.Query().Get(QueryParamSort),
) )
} }
// ParseMembershipFilter extracts the membership filter from the url. // ParseMembershipUserFilter extracts the membership filter from the url.
func ParseMembershipFilter(r *http.Request) types.MembershipFilter { func ParseMembershipUserFilter(r *http.Request) types.MembershipUserFilter {
return types.MembershipFilter{ return types.MembershipUserFilter{
Page: ParsePage(r), ListQueryFilter: ParseListQueryFilterFromRequest(r),
Size: ParseLimit(r), Sort: ParseMembershipUserSort(r),
Query: ParseQuery(r), Order: ParseOrder(r),
Sort: ParseMembershipSort(r), }
}
// ParseMembershipSpaceSort extracts the membership space sort parameter from the url.
func ParseMembershipSpaceSort(r *http.Request) enum.MembershipSpaceSort {
return enum.ParseMembershipSpaceSort(
r.URL.Query().Get(QueryParamSort),
)
}
// ParseMembershipSpaceFilter extracts the membership space filter from the url.
func ParseMembershipSpaceFilter(r *http.Request) types.MembershipSpaceFilter {
return types.MembershipSpaceFilter{
ListQueryFilter: ParseListQueryFilterFromRequest(r),
Sort: ParseMembershipSpaceSort(r),
Order: ParseOrder(r), Order: ParseOrder(r),
} }
} }

View File

@ -230,9 +230,10 @@ type (
Create(ctx context.Context, membership *types.Membership) error Create(ctx context.Context, membership *types.Membership) error
Update(ctx context.Context, membership *types.Membership) error Update(ctx context.Context, membership *types.Membership) error
Delete(ctx context.Context, key types.MembershipKey) error Delete(ctx context.Context, key types.MembershipKey) error
CountUsers(ctx context.Context, spaceID int64, filter types.MembershipFilter) (int64, error) CountUsers(ctx context.Context, spaceID int64, filter types.MembershipUserFilter) (int64, error)
ListUsers(ctx context.Context, spaceID int64, filter types.MembershipFilter) ([]types.MembershipUser, error) ListUsers(ctx context.Context, spaceID int64, filter types.MembershipUserFilter) ([]types.MembershipUser, error)
ListSpaces(ctx context.Context, userID int64) ([]types.MembershipSpace, error) CountSpaces(ctx context.Context, userID int64, filter types.MembershipSpaceFilter) (int64, error)
ListSpaces(ctx context.Context, userID int64, filter types.MembershipSpaceFilter) ([]types.MembershipSpace, error)
} }
// TokenStore defines the token data storage. // TokenStore defines the token data storage.

View File

@ -183,7 +183,7 @@ func (s *MembershipStore) Delete(ctx context.Context, key types.MembershipKey) e
// CountUsers returns a number of users memberships that matches the provided filter. // CountUsers returns a number of users memberships that matches the provided filter.
func (s *MembershipStore) CountUsers(ctx context.Context, func (s *MembershipStore) CountUsers(ctx context.Context,
spaceID int64, spaceID int64,
filter types.MembershipFilter, filter types.MembershipUserFilter,
) (int64, error) { ) (int64, error) {
stmt := database.Builder. stmt := database.Builder.
Select("count(*)"). Select("count(*)").
@ -191,11 +191,11 @@ func (s *MembershipStore) CountUsers(ctx context.Context,
InnerJoin("principals ON membership_principal_id = principal_id"). InnerJoin("principals ON membership_principal_id = principal_id").
Where("membership_space_id = ?", spaceID) Where("membership_space_id = ?", spaceID)
stmt = prepareMembershipListUsersStmt(stmt, filter) stmt = applyMembershipUserFilter(stmt, filter)
sql, args, err := stmt.ToSql() sql, args, err := stmt.ToSql()
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to convert membership count query to sql: %w", err) return 0, fmt.Errorf("failed to convert membership users count query to sql: %w", err)
} }
db := dbtx.GetAccessor(ctx, s.db) db := dbtx.GetAccessor(ctx, s.db)
@ -203,7 +203,7 @@ func (s *MembershipStore) CountUsers(ctx context.Context,
var count int64 var count int64
err = db.QueryRowContext(ctx, sql, args...).Scan(&count) err = db.QueryRowContext(ctx, sql, args...).Scan(&count)
if err != nil { if err != nil {
return 0, database.ProcessSQLErrorf(err, "Failed executing membership count query") return 0, database.ProcessSQLErrorf(err, "Failed executing membership users count query")
} }
return count, nil return count, nil
@ -212,7 +212,7 @@ func (s *MembershipStore) CountUsers(ctx context.Context,
// ListUsers returns a list of memberships for a space or a user. // ListUsers returns a list of memberships for a space or a user.
func (s *MembershipStore) ListUsers(ctx context.Context, func (s *MembershipStore) ListUsers(ctx context.Context,
spaceID int64, spaceID int64,
filter types.MembershipFilter, filter types.MembershipUserFilter,
) ([]types.MembershipUser, error) { ) ([]types.MembershipUser, error) {
const columns = membershipColumns + "," + principalInfoCommonColumns const columns = membershipColumns + "," + principalInfoCommonColumns
stmt := database.Builder. stmt := database.Builder.
@ -221,7 +221,7 @@ func (s *MembershipStore) ListUsers(ctx context.Context,
InnerJoin("principals ON membership_principal_id = principal_id"). InnerJoin("principals ON membership_principal_id = principal_id").
Where("membership_space_id = ?", spaceID) Where("membership_space_id = ?", spaceID)
stmt = prepareMembershipListUsersStmt(stmt, filter) stmt = applyMembershipUserFilter(stmt, filter)
stmt = stmt.Limit(database.Limit(filter.Size)) stmt = stmt.Limit(database.Limit(filter.Size))
stmt = stmt.Offset(database.Offset(filter.Page, filter.Size)) stmt = stmt.Offset(database.Offset(filter.Page, filter.Size))
@ -231,9 +231,9 @@ func (s *MembershipStore) ListUsers(ctx context.Context,
} }
switch filter.Sort { switch filter.Sort {
case enum.MembershipSortName: case enum.MembershipUserSortName:
stmt = stmt.OrderBy("principal_display_name " + order.String()) stmt = stmt.OrderBy("principal_display_name " + order.String())
case enum.MembershipSortCreated: case enum.MembershipUserSortCreated:
stmt = stmt.OrderBy("membership_created " + order.String()) stmt = stmt.OrderBy("membership_created " + order.String())
} }
@ -258,9 +258,9 @@ func (s *MembershipStore) ListUsers(ctx context.Context,
return result, nil return result, nil
} }
func prepareMembershipListUsersStmt( func applyMembershipUserFilter(
stmt squirrel.SelectBuilder, stmt squirrel.SelectBuilder,
opts types.MembershipFilter, opts types.MembershipUserFilter,
) squirrel.SelectBuilder { ) squirrel.SelectBuilder {
if opts.Query != "" { if opts.Query != "" {
searchTerm := "%%" + strings.ToLower(opts.Query) + "%%" searchTerm := "%%" + strings.ToLower(opts.Query) + "%%"
@ -270,9 +270,38 @@ func prepareMembershipListUsersStmt(
return stmt return stmt
} }
func (s *MembershipStore) CountSpaces(ctx context.Context,
userID int64,
filter types.MembershipSpaceFilter,
) (int64, error) {
stmt := database.Builder.
Select("count(*)").
From("memberships").
InnerJoin("spaces ON spaces.space_id = membership_space_id").
Where("membership_principal_id = ?", userID)
stmt = applyMembershipSpaceFilter(stmt, filter)
sql, args, err := stmt.ToSql()
if err != nil {
return 0, fmt.Errorf("failed to convert membership spaces count query to sql: %w", err)
}
db := dbtx.GetAccessor(ctx, s.db)
var count int64
err = db.QueryRowContext(ctx, sql, args...).Scan(&count)
if err != nil {
return 0, database.ProcessSQLErrorf(err, "Failed executing membership spaces count query")
}
return count, nil
}
// ListSpaces returns a list of spaces in which the provided user is a member. // ListSpaces returns a list of spaces in which the provided user is a member.
func (s *MembershipStore) ListSpaces(ctx context.Context, func (s *MembershipStore) ListSpaces(ctx context.Context,
userID int64, userID int64,
filter types.MembershipSpaceFilter,
) ([]types.MembershipSpace, error) { ) ([]types.MembershipSpace, error) {
const columns = membershipColumns + "," + spaceColumnsForJoin const columns = membershipColumns + "," + spaceColumnsForJoin
stmt := database.Builder. stmt := database.Builder.
@ -280,8 +309,25 @@ func (s *MembershipStore) ListSpaces(ctx context.Context,
From("memberships"). From("memberships").
InnerJoin("spaces ON spaces.space_id = membership_space_id"). InnerJoin("spaces ON spaces.space_id = membership_space_id").
InnerJoin(`paths ON spaces.space_id=paths.path_space_id AND paths.path_is_primary=true`). InnerJoin(`paths ON spaces.space_id=paths.path_space_id AND paths.path_is_primary=true`).
Where("membership_principal_id = ?", userID). Where("membership_principal_id = ?", userID)
OrderBy("space_path asc")
stmt = applyMembershipSpaceFilter(stmt, filter)
stmt = stmt.Limit(database.Limit(filter.Size))
stmt = stmt.Offset(database.Offset(filter.Page, filter.Size))
order := filter.Order
if order == enum.OrderDefault {
order = enum.OrderAsc
}
switch filter.Sort {
case enum.MembershipSpaceSortUID:
stmt = stmt.OrderBy("space_uid " + order.String())
case enum.MembershipSpaceSortPath:
stmt = stmt.OrderBy("space_path " + order.String())
case enum.MembershipSpaceSortCreated:
stmt = stmt.OrderBy("membership_created " + order.String())
}
sql, args, err := stmt.ToSql() sql, args, err := stmt.ToSql()
if err != nil { if err != nil {
@ -303,6 +349,18 @@ func (s *MembershipStore) ListSpaces(ctx context.Context,
return result, nil return result, nil
} }
func applyMembershipSpaceFilter(
stmt squirrel.SelectBuilder,
opts types.MembershipSpaceFilter,
) squirrel.SelectBuilder {
if opts.Query != "" {
searchTerm := "%%" + strings.ToLower(opts.Query) + "%%"
stmt = stmt.Where("LOWER(space_uid) LIKE ?", searchTerm)
}
return stmt
}
func mapToMembership(m *membership) types.Membership { func mapToMembership(m *membership) types.Membership {
return types.Membership{ return types.Membership{
MembershipKey: types.MembershipKey{ MembershipKey: types.MembershipKey{

View File

@ -8,45 +8,100 @@ import (
"strings" "strings"
) )
// MembershipSort represents membership sort order. // MembershipUserSort represents membership user sort order.
type MembershipSort string type MembershipUserSort string
// Order enumeration. // MembershipUserSort enumeration.
const ( const (
MembershipSortName = name MembershipUserSortName MembershipUserSort = name
MembershipSortCreated = created MembershipUserSortCreated MembershipUserSort = created
) )
var membershipSorts = sortEnum([]MembershipSort{ var membershipUserSorts = sortEnum([]MembershipUserSort{
MembershipSortName, MembershipUserSortName,
MembershipSortCreated, MembershipUserSortCreated,
}) })
func (MembershipSort) Enum() []interface{} { return toInterfaceSlice(membershipSorts) } func (MembershipUserSort) Enum() []interface{} { return toInterfaceSlice(membershipUserSorts) }
func (s MembershipSort) Sanitize() (MembershipSort, bool) { return Sanitize(s, GetAllMembershipSorts) } func (s MembershipUserSort) Sanitize() (MembershipUserSort, bool) {
func GetAllMembershipSorts() ([]MembershipSort, MembershipSort) { return Sanitize(s, GetAllMembershipUserSorts)
return membershipSorts, MembershipSortName }
func GetAllMembershipUserSorts() ([]MembershipUserSort, MembershipUserSort) {
return membershipUserSorts, MembershipUserSortName
} }
// ParseMembershipSort parses the membership sort attribute string // ParseMembershipUserSort parses the membership user sort attribute string
// and returns the equivalent enumeration. // and returns the equivalent enumeration.
func ParseMembershipSort(s string) MembershipSort { func ParseMembershipUserSort(s string) MembershipUserSort {
switch strings.ToLower(s) { switch strings.ToLower(s) {
case name: case name:
return MembershipSortName return MembershipUserSortName
case created, createdAt: case created, createdAt:
return MembershipSortCreated return MembershipUserSortCreated
default: default:
return MembershipSortName return MembershipUserSortName
} }
} }
// String returns the string representation of the attribute. // String returns the string representation of the attribute.
func (s MembershipSort) String() string { func (s MembershipUserSort) String() string {
switch s { switch s {
case MembershipSortName: case MembershipUserSortName:
return name return name
case MembershipSortCreated: case MembershipUserSortCreated:
return created
default:
return undefined
}
}
// MembershipSpaceSort represents membership space sort order.
type MembershipSpaceSort string
// MembershipSpaceSort enumeration.
const (
MembershipSpaceSortUID MembershipSpaceSort = uid
MembershipSpaceSortPath MembershipSpaceSort = path
MembershipSpaceSortCreated MembershipSpaceSort = created
)
var membershipSpaceSorts = sortEnum([]MembershipSpaceSort{
MembershipSpaceSortUID,
MembershipSpaceSortPath,
MembershipSpaceSortCreated,
})
func (MembershipSpaceSort) Enum() []interface{} { return toInterfaceSlice(membershipSpaceSorts) }
func (s MembershipSpaceSort) Sanitize() (MembershipSpaceSort, bool) {
return Sanitize(s, GetAllMembershipSpaceSorts)
}
func GetAllMembershipSpaceSorts() ([]MembershipSpaceSort, MembershipSpaceSort) {
return membershipSpaceSorts, MembershipSpaceSortPath
}
// ParseMembershipSpaceSort parses the membership space sort attribute string
// and returns the equivalent enumeration.
func ParseMembershipSpaceSort(s string) MembershipSpaceSort {
switch strings.ToLower(s) {
case name:
return MembershipSpaceSortUID
case path:
return MembershipSpaceSortPath
case created, createdAt:
return MembershipSpaceSortCreated
default:
return MembershipSpaceSortUID
}
}
// String returns the string representation of the attribute.
func (s MembershipSpaceSort) String() string {
switch s {
case MembershipSpaceSortUID:
return uid
case MembershipSpaceSortPath:
return path
case MembershipSpaceSortCreated:
return created return created
default: default:
return undefined return undefined

View File

@ -32,6 +32,13 @@ type MembershipUser struct {
AddedBy PrincipalInfo `json:"added_by"` AddedBy PrincipalInfo `json:"added_by"`
} }
// MembershipUserFilter holds membership user query parameters.
type MembershipUserFilter struct {
ListQueryFilter
Sort enum.MembershipUserSort `json:"sort"`
Order enum.Order `json:"order"`
}
// MembershipSpace adds space info to the Membership data. // MembershipSpace adds space info to the Membership data.
type MembershipSpace struct { type MembershipSpace struct {
Membership Membership
@ -39,11 +46,9 @@ type MembershipSpace struct {
AddedBy PrincipalInfo `json:"added_by"` AddedBy PrincipalInfo `json:"added_by"`
} }
// MembershipFilter holds membership query parameters. // MembershipSpaceFilter holds membership space query parameters.
type MembershipFilter struct { type MembershipSpaceFilter struct {
Page int `json:"page"` ListQueryFilter
Size int `json:"size"` Sort enum.MembershipSpaceSort `json:"sort"`
Query string `json:"query"`
Sort enum.MembershipSort `json:"sort"`
Order enum.Order `json:"order"` Order enum.Order `json:"order"`
} }