satellite/console: project members refactoring (#2752)
This commit is contained in:
parent
260f648275
commit
0decd1419b
@ -415,7 +415,7 @@ func TestGrapqhlMutation(t *testing.T) {
|
||||
|
||||
t.Run("Add project members mutation", func(t *testing.T) {
|
||||
query := fmt.Sprintf(
|
||||
"mutation {addProjectMembers(projectID:\"%s\",email:[\"%s\",\"%s\"]){id,name,members(limit:50,offset:0){joinedAt}}}",
|
||||
"mutation {addProjectMembers(projectID:\"%s\",email:[\"%s\",\"%s\"]){id,name,members(cursor: { limit: 50, search: \"\", page: 1, order: 1, orderDirection: 2 }){projectMembers{joinedAt}}}}",
|
||||
project.ID.String(),
|
||||
user1.Email,
|
||||
user2.Email,
|
||||
@ -426,14 +426,17 @@ func TestGrapqhlMutation(t *testing.T) {
|
||||
data := result.(map[string]interface{})
|
||||
proj := data[consoleql.AddProjectMembersMutation].(map[string]interface{})
|
||||
|
||||
members := proj[consoleql.FieldMembers].(map[string]interface{})
|
||||
projectMembers := members[consoleql.FieldProjectMembers].([]interface{})
|
||||
|
||||
assert.Equal(t, project.ID.String(), proj[consoleql.FieldID])
|
||||
assert.Equal(t, project.Name, proj[consoleql.FieldName])
|
||||
assert.Equal(t, 3, len(proj[consoleql.FieldMembers].([]interface{})))
|
||||
assert.Equal(t, 3, len(projectMembers))
|
||||
})
|
||||
|
||||
t.Run("Delete project members mutation", func(t *testing.T) {
|
||||
query := fmt.Sprintf(
|
||||
"mutation {deleteProjectMembers(projectID:\"%s\",email:[\"%s\",\"%s\"]){id,name,members(limit:50,offset:0){user{id}}}}",
|
||||
"mutation {deleteProjectMembers(projectID:\"%s\",email:[\"%s\",\"%s\"]){id,name,members(cursor: { limit: 50, search: \"\", page: 1, order: 1, orderDirection: 2 }){projectMembers{user{id}}}}}",
|
||||
project.ID.String(),
|
||||
user1.Email,
|
||||
user2.Email,
|
||||
@ -444,8 +447,9 @@ func TestGrapqhlMutation(t *testing.T) {
|
||||
data := result.(map[string]interface{})
|
||||
proj := data[consoleql.DeleteProjectMembersMutation].(map[string]interface{})
|
||||
|
||||
members := proj[consoleql.FieldMembers].([]interface{})
|
||||
rootMember := members[0].(map[string]interface{})[consoleql.UserType].(map[string]interface{})
|
||||
members := proj[consoleql.FieldMembers].(map[string]interface{})
|
||||
projectMembers := members[consoleql.FieldProjectMembers].([]interface{})
|
||||
rootMember := projectMembers[0].(map[string]interface{})[consoleql.UserType].(map[string]interface{})
|
||||
|
||||
assert.Equal(t, project.ID.String(), proj[consoleql.FieldID])
|
||||
assert.Equal(t, project.Name, proj[consoleql.FieldName])
|
||||
|
@ -27,6 +27,10 @@ const (
|
||||
BucketUsagePageType = "bucketUsagePage"
|
||||
// PaymentMethodType is a field name for payment method
|
||||
PaymentMethodType = "paymentMethod"
|
||||
// ProjectMembersPageType is a field name for project members page
|
||||
ProjectMembersPageType = "projectMembersPage"
|
||||
// ProjectMembersCursorInputType is a graphql type name for project members
|
||||
ProjectMembersCursorInputType = "projectMembersCursor"
|
||||
// FieldName is a field name for "name"
|
||||
FieldName = "name"
|
||||
// FieldBucketName is a field name for "bucket name"
|
||||
@ -55,6 +59,8 @@ const (
|
||||
FieldCurrentPage = "currentPage"
|
||||
// FieldTotalCount is a field name for bucket usage count total
|
||||
FieldTotalCount = "totalCount"
|
||||
// FieldProjectMembers is a field name for project members
|
||||
FieldProjectMembers = "projectMembers"
|
||||
// FieldCardBrand is a field name for credit card brand
|
||||
FieldCardBrand = "brand"
|
||||
// FieldCardLastFour is a field name for credit card last four digits
|
||||
@ -75,6 +81,8 @@ const (
|
||||
SearchArg = "search"
|
||||
// OrderArg is argument name for order
|
||||
OrderArg = "order"
|
||||
// OrderDirectionArg is argument name for order direction
|
||||
OrderDirectionArg = "orderDirection"
|
||||
// SinceArg marks start of the period
|
||||
SinceArg = "since"
|
||||
// BeforeArg marks end of the period
|
||||
@ -99,48 +107,28 @@ func graphqlProject(service *console.Service, types *TypeCreator) *graphql.Objec
|
||||
Type: graphql.DateTime,
|
||||
},
|
||||
FieldMembers: &graphql.Field{
|
||||
Type: graphql.NewList(types.projectMember),
|
||||
Type: types.projectMemberPage,
|
||||
Args: graphql.FieldConfigArgument{
|
||||
OffsetArg: &graphql.ArgumentConfig{
|
||||
Type: graphql.NewNonNull(graphql.Int),
|
||||
},
|
||||
LimitArg: &graphql.ArgumentConfig{
|
||||
Type: graphql.NewNonNull(graphql.Int),
|
||||
},
|
||||
SearchArg: &graphql.ArgumentConfig{
|
||||
Type: graphql.String,
|
||||
},
|
||||
OrderArg: &graphql.ArgumentConfig{
|
||||
Type: graphql.Int,
|
||||
CursorArg: &graphql.ArgumentConfig{
|
||||
Type: graphql.NewNonNull(types.projectMembersCursor),
|
||||
},
|
||||
},
|
||||
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
||||
project, _ := p.Source.(*console.Project)
|
||||
|
||||
offs, _ := p.Args[OffsetArg].(int)
|
||||
lim, _ := p.Args[LimitArg].(int)
|
||||
search, _ := p.Args[SearchArg].(string)
|
||||
order, _ := p.Args[OrderArg].(int)
|
||||
|
||||
pagination := console.Pagination{
|
||||
Limit: lim,
|
||||
Offset: int64(offs),
|
||||
Search: search,
|
||||
Order: console.ProjectMemberOrder(order),
|
||||
}
|
||||
|
||||
members, err := service.GetProjectMembers(p.Context, project.ID, pagination)
|
||||
_, err := console.GetAuth(p.Context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = console.GetAuth(p.Context)
|
||||
cursor := cursorArgsToProjectMembersCursor(p.Args[CursorArg].(map[string]interface{}))
|
||||
page, err := service.GetProjectMembers(p.Context, project.ID, cursor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var users []projectMember
|
||||
for _, member := range members {
|
||||
for _, member := range page.ProjectMembers {
|
||||
user, err := service.GetUser(p.Context, member.MemberID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -152,7 +140,18 @@ func graphqlProject(service *console.Service, types *TypeCreator) *graphql.Objec
|
||||
})
|
||||
}
|
||||
|
||||
return users, nil
|
||||
projectMembersPage := projectMembersPage{
|
||||
ProjectMembers: users,
|
||||
TotalCount: page.TotalCount,
|
||||
Offset: page.Offset,
|
||||
Limit: page.Limit,
|
||||
Order: int(page.Order),
|
||||
OrderDirection: int(page.OrderDirection),
|
||||
Search: page.Search,
|
||||
CurrentPage: page.CurrentPage,
|
||||
PageCount: page.PageCount,
|
||||
}
|
||||
return projectMembersPage, nil
|
||||
},
|
||||
},
|
||||
FieldAPIKeys: &graphql.Field{
|
||||
@ -421,3 +420,20 @@ func fromMapBucketUsageCursor(args map[string]interface{}) (cursor console.Bucke
|
||||
cursor.Search, _ = args[SearchArg].(string)
|
||||
return
|
||||
}
|
||||
|
||||
func cursorArgsToProjectMembersCursor(args map[string]interface{}) console.ProjectMembersCursor {
|
||||
limit, _ := args[LimitArg].(int)
|
||||
page, _ := args[PageArg].(int)
|
||||
order, _ := args[OrderArg].(int)
|
||||
orderDirection, _ := args[OrderDirectionArg].(int)
|
||||
|
||||
var cursor console.ProjectMembersCursor
|
||||
|
||||
cursor.Limit = uint(limit)
|
||||
cursor.Page = uint(page)
|
||||
cursor.Order = console.ProjectMemberOrder(order)
|
||||
cursor.OrderDirection = console.ProjectMemberOrderDirection(orderDirection)
|
||||
cursor.Search, _ = args[SearchArg].(string)
|
||||
|
||||
return cursor
|
||||
}
|
||||
|
@ -38,8 +38,80 @@ func graphqlProjectMember(service *console.Service, types *TypeCreator) *graphql
|
||||
})
|
||||
}
|
||||
|
||||
func graphqlProjectMembersCursor() *graphql.InputObject {
|
||||
return graphql.NewInputObject(graphql.InputObjectConfig{
|
||||
Name: ProjectMembersCursorInputType,
|
||||
Fields: graphql.InputObjectConfigFieldMap{
|
||||
SearchArg: &graphql.InputObjectFieldConfig{
|
||||
Type: graphql.NewNonNull(graphql.String),
|
||||
},
|
||||
LimitArg: &graphql.InputObjectFieldConfig{
|
||||
Type: graphql.NewNonNull(graphql.Int),
|
||||
},
|
||||
PageArg: &graphql.InputObjectFieldConfig{
|
||||
Type: graphql.NewNonNull(graphql.Int),
|
||||
},
|
||||
OrderArg: &graphql.InputObjectFieldConfig{
|
||||
Type: graphql.NewNonNull(graphql.Int),
|
||||
},
|
||||
OrderDirectionArg: &graphql.InputObjectFieldConfig{
|
||||
Type: graphql.NewNonNull(graphql.Int),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func graphqlProjectMembersPage(types *TypeCreator) *graphql.Object {
|
||||
return graphql.NewObject(graphql.ObjectConfig{
|
||||
Name: ProjectMembersPageType,
|
||||
Fields: graphql.Fields{
|
||||
FieldProjectMembers: &graphql.Field{
|
||||
Type: graphql.NewList(types.projectMember),
|
||||
},
|
||||
SearchArg: &graphql.Field{
|
||||
Type: graphql.String,
|
||||
},
|
||||
LimitArg: &graphql.Field{
|
||||
Type: graphql.Int,
|
||||
},
|
||||
OrderArg: &graphql.Field{
|
||||
Type: graphql.Int,
|
||||
},
|
||||
OrderDirectionArg: &graphql.Field{
|
||||
Type: graphql.Int,
|
||||
},
|
||||
OffsetArg: &graphql.Field{
|
||||
Type: graphql.Int,
|
||||
},
|
||||
FieldPageCount: &graphql.Field{
|
||||
Type: graphql.Int,
|
||||
},
|
||||
FieldCurrentPage: &graphql.Field{
|
||||
Type: graphql.Int,
|
||||
},
|
||||
FieldTotalCount: &graphql.Field{
|
||||
Type: graphql.Int,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// projectMember encapsulates User and joinedAt
|
||||
type projectMember struct {
|
||||
User *console.User
|
||||
JoinedAt time.Time
|
||||
}
|
||||
|
||||
type projectMembersPage struct {
|
||||
ProjectMembers []projectMember
|
||||
|
||||
Search string
|
||||
Limit uint
|
||||
Order int
|
||||
OrderDirection int
|
||||
Offset uint64
|
||||
|
||||
PageCount uint
|
||||
CurrentPage uint
|
||||
TotalCount uint64
|
||||
}
|
||||
|
@ -239,17 +239,22 @@ func TestGraphqlQuery(t *testing.T) {
|
||||
|
||||
t.Run("Project query team members", func(t *testing.T) {
|
||||
query := fmt.Sprintf(
|
||||
"query {project(id:\"%s\"){members(offset:0, limit:50){user{id,fullName,shortName,email,createdAt}}}}",
|
||||
"query {project(id: \"%s\") {members( cursor: { limit: %d, search: \"%s\", page: %d, order: %d, orderDirection: %d } ) { projectMembers{ user { id, fullName, shortName, email, createdAt }, joinedAt }, search, limit, order, offset, pageCount, currentPage, totalCount } } }",
|
||||
createdProject.ID.String(),
|
||||
)
|
||||
5,
|
||||
"",
|
||||
1,
|
||||
1,
|
||||
2)
|
||||
|
||||
result := testQuery(t, query)
|
||||
|
||||
data := result.(map[string]interface{})
|
||||
project := data[consoleql.ProjectQuery].(map[string]interface{})
|
||||
members := project[consoleql.FieldMembers].([]interface{})
|
||||
members := project[consoleql.FieldMembers].(map[string]interface{})
|
||||
projectMembers := members[consoleql.FieldProjectMembers].([]interface{})
|
||||
|
||||
assert.Equal(t, 3, len(members))
|
||||
assert.Equal(t, 3, len(projectMembers))
|
||||
|
||||
testUser := func(t *testing.T, actual map[string]interface{}, expected *console.User) {
|
||||
assert.Equal(t, expected.Email, actual[consoleql.FieldEmail])
|
||||
@ -265,7 +270,7 @@ func TestGraphqlQuery(t *testing.T) {
|
||||
|
||||
var foundRoot, foundU1, foundU2 bool
|
||||
|
||||
for _, entry := range members {
|
||||
for _, entry := range projectMembers {
|
||||
member := entry.(map[string]interface{})
|
||||
user := member[consoleql.UserType].(map[string]interface{})
|
||||
|
||||
|
@ -18,21 +18,23 @@ type TypeCreator struct {
|
||||
|
||||
token *graphql.Object
|
||||
|
||||
user *graphql.Object
|
||||
reward *graphql.Object
|
||||
creditUsage *graphql.Object
|
||||
project *graphql.Object
|
||||
projectUsage *graphql.Object
|
||||
bucketUsage *graphql.Object
|
||||
bucketUsagePage *graphql.Object
|
||||
paymentMethod *graphql.Object
|
||||
projectMember *graphql.Object
|
||||
apiKeyInfo *graphql.Object
|
||||
createAPIKey *graphql.Object
|
||||
user *graphql.Object
|
||||
reward *graphql.Object
|
||||
creditUsage *graphql.Object
|
||||
project *graphql.Object
|
||||
projectUsage *graphql.Object
|
||||
bucketUsage *graphql.Object
|
||||
bucketUsagePage *graphql.Object
|
||||
paymentMethod *graphql.Object
|
||||
projectMember *graphql.Object
|
||||
projectMemberPage *graphql.Object
|
||||
apiKeyInfo *graphql.Object
|
||||
createAPIKey *graphql.Object
|
||||
|
||||
userInput *graphql.InputObject
|
||||
projectInput *graphql.InputObject
|
||||
bucketUsageCursor *graphql.InputObject
|
||||
userInput *graphql.InputObject
|
||||
projectInput *graphql.InputObject
|
||||
bucketUsageCursor *graphql.InputObject
|
||||
projectMembersCursor *graphql.InputObject
|
||||
}
|
||||
|
||||
// Create create types and check for error
|
||||
@ -53,6 +55,11 @@ func (c *TypeCreator) Create(log *zap.Logger, service *console.Service, mailServ
|
||||
return err
|
||||
}
|
||||
|
||||
c.projectMembersCursor = graphqlProjectMembersCursor()
|
||||
if err := c.projectMembersCursor.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// entities
|
||||
c.user = graphqlUser()
|
||||
if err := c.user.Error(); err != nil {
|
||||
@ -104,6 +111,11 @@ func (c *TypeCreator) Create(log *zap.Logger, service *console.Service, mailServ
|
||||
return err
|
||||
}
|
||||
|
||||
c.projectMemberPage = graphqlProjectMembersPage(c)
|
||||
if err := c.projectMemberPage.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.project = graphqlProject(service, c)
|
||||
if err := c.project.Error(); err != nil {
|
||||
return err
|
||||
|
@ -14,8 +14,8 @@ import (
|
||||
type ProjectMembers interface {
|
||||
// GetByMemberID is a method for querying project members from the database by memberID.
|
||||
GetByMemberID(ctx context.Context, memberID uuid.UUID) ([]ProjectMember, error)
|
||||
// GetByProjectID is a method for querying project members from the database by projectID, offset and limit.
|
||||
GetByProjectID(ctx context.Context, projectID uuid.UUID, pagination Pagination) ([]ProjectMember, error)
|
||||
// GetPagedByProjectID is a method for querying project members from the database by projectID and cursor
|
||||
GetPagedByProjectID(ctx context.Context, projectID uuid.UUID, cursor ProjectMembersCursor) (*ProjectMembersPage, error)
|
||||
// Insert is a method for inserting project member into the database.
|
||||
Insert(ctx context.Context, memberID, projectID uuid.UUID) (*ProjectMember, error)
|
||||
// Delete is a method for deleting project member by memberID and projectID from the database.
|
||||
@ -32,12 +32,28 @@ type ProjectMember struct {
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// Pagination defines pagination, filtering and sorting rules
|
||||
type Pagination struct {
|
||||
Limit int
|
||||
Offset int64
|
||||
Search string
|
||||
Order ProjectMemberOrder
|
||||
// ProjectMembersCursor holds info for project members cursor pagination
|
||||
type ProjectMembersCursor struct {
|
||||
Search string
|
||||
Limit uint
|
||||
Page uint
|
||||
Order ProjectMemberOrder
|
||||
OrderDirection ProjectMemberOrderDirection
|
||||
}
|
||||
|
||||
// ProjectMembersPage represent project members page result
|
||||
type ProjectMembersPage struct {
|
||||
ProjectMembers []ProjectMember
|
||||
|
||||
Search string
|
||||
Limit uint
|
||||
Order ProjectMemberOrder
|
||||
OrderDirection ProjectMemberOrderDirection
|
||||
Offset uint64
|
||||
|
||||
PageCount uint
|
||||
CurrentPage uint
|
||||
TotalCount uint64
|
||||
}
|
||||
|
||||
// ProjectMemberOrder is used for querying project members in specified order
|
||||
@ -51,3 +67,13 @@ const (
|
||||
// Created indicates that we should order by created date
|
||||
Created ProjectMemberOrder = 3
|
||||
)
|
||||
|
||||
// ProjectMemberOrderDirection is used for querying project members in specific order direction
|
||||
type ProjectMemberOrderDirection uint8
|
||||
|
||||
const (
|
||||
// Ascending indicates that we should order ascending
|
||||
Ascending ProjectMemberOrderDirection = 1
|
||||
// Descending indicates that we should order descending
|
||||
Descending ProjectMemberOrderDirection = 2
|
||||
)
|
||||
|
@ -84,40 +84,45 @@ func TestProjectMembersRepository(t *testing.T) {
|
||||
|
||||
t.Run("Get paged", func(t *testing.T) {
|
||||
// sql injection test. F.E '%SomeText%' = > ''%SomeText%' OR 'x' != '%'' will be true
|
||||
members, err := projectMembers.GetByProjectID(ctx, createdProjects[0].ID, console.Pagination{Limit: 6, Offset: 0, Search: "son%' OR 'x' != '", Order: 2})
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, members)
|
||||
assert.Equal(t, 0, len(members))
|
||||
|
||||
members, err = projectMembers.GetByProjectID(ctx, createdProjects[0].ID, console.Pagination{Limit: 3, Offset: 0, Search: "", Order: 1})
|
||||
members, err := projectMembers.GetPagedByProjectID(ctx, createdProjects[0].ID, console.ProjectMembersCursor{Limit: 6, Search: "son%' OR 'x' != '", Order: 2, Page: 1})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, members)
|
||||
assert.Equal(t, 3, len(members))
|
||||
assert.Equal(t, uint64(0), members.TotalCount)
|
||||
assert.Equal(t, uint(0), members.CurrentPage)
|
||||
assert.Equal(t, uint(0), members.PageCount)
|
||||
assert.Equal(t, 0, len(members.ProjectMembers))
|
||||
|
||||
members, err = projectMembers.GetByProjectID(ctx, createdProjects[0].ID, console.Pagination{Limit: 2, Offset: 0, Search: "iam", Order: 2}) // TODO: fix case sensitity issues and change back to "Liam"
|
||||
members, err = projectMembers.GetPagedByProjectID(ctx, createdProjects[0].ID, console.ProjectMembersCursor{Limit: 3, Search: "", Order: 1, Page: 1})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, members)
|
||||
assert.Equal(t, 2, len(members))
|
||||
assert.Equal(t, uint64(5), members.TotalCount)
|
||||
assert.Equal(t, uint(1), members.CurrentPage)
|
||||
assert.Equal(t, uint(2), members.PageCount)
|
||||
assert.Equal(t, 3, len(members.ProjectMembers))
|
||||
|
||||
members, err = projectMembers.GetByProjectID(ctx, createdProjects[0].ID, console.Pagination{Limit: 2, Offset: 0, Search: "iam", Order: 1}) // TODO: fix case sensitity issues and change back to "Liam"
|
||||
members, err = projectMembers.GetPagedByProjectID(ctx, createdProjects[0].ID, console.ProjectMembersCursor{Limit: 2, Search: "iam", Order: 2, Page: 1}) // TODO: fix case sensitity issues and change back to "Liam"
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, members)
|
||||
assert.Equal(t, 2, len(members))
|
||||
assert.Equal(t, uint64(2), members.TotalCount)
|
||||
assert.Equal(t, 2, len(members.ProjectMembers))
|
||||
|
||||
members, err = projectMembers.GetByProjectID(ctx, createdProjects[0].ID, console.Pagination{Limit: 6, Offset: 0, Search: "son", Order: 123})
|
||||
members, err = projectMembers.GetPagedByProjectID(ctx, createdProjects[0].ID, console.ProjectMembersCursor{Limit: 2, Search: "iam", Order: 1, Page: 1}) // TODO: fix case sensitity issues and change back to "Liam"
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, members)
|
||||
assert.Equal(t, 5, len(members))
|
||||
assert.Equal(t, uint64(2), members.TotalCount)
|
||||
assert.Equal(t, 2, len(members.ProjectMembers))
|
||||
|
||||
members, err = projectMembers.GetByProjectID(ctx, createdProjects[0].ID, console.Pagination{Limit: 6, Offset: 3, Search: "son", Order: 2})
|
||||
members, err = projectMembers.GetPagedByProjectID(ctx, createdProjects[0].ID, console.ProjectMembersCursor{Limit: 6, Search: "son", Order: 123, Page: 1})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, members)
|
||||
assert.Equal(t, 2, len(members))
|
||||
assert.Equal(t, uint64(5), members.TotalCount)
|
||||
assert.Equal(t, 5, len(members.ProjectMembers))
|
||||
|
||||
members, err = projectMembers.GetByProjectID(ctx, createdProjects[0].ID, console.Pagination{Limit: -123, Offset: -14, Search: "son", Order: 2})
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, members)
|
||||
assert.Equal(t, 0, len(members))
|
||||
members, err = projectMembers.GetPagedByProjectID(ctx, createdProjects[0].ID, console.ProjectMembersCursor{Limit: 6, Search: "son", Order: 2, Page: 1})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, members)
|
||||
assert.Equal(t, uint64(5), members.TotalCount)
|
||||
assert.Equal(t, 5, len(members.ProjectMembers))
|
||||
})
|
||||
|
||||
t.Run("Get member by memberID success", func(t *testing.T) {
|
||||
@ -140,15 +145,15 @@ func TestProjectMembersRepository(t *testing.T) {
|
||||
err := projectMembers.Delete(ctx, createdUsers[0].ID, createdProjects[0].ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
projMembers, err := projectMembers.GetByProjectID(ctx, createdProjects[0].ID, console.Pagination{
|
||||
projMembers, err := projectMembers.GetPagedByProjectID(ctx, createdProjects[0].ID, console.ProjectMembersCursor{
|
||||
Order: 1,
|
||||
Search: "",
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
Page: 1,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, projectMembers)
|
||||
assert.Equal(t, len(projMembers), 4)
|
||||
assert.Equal(t, len(projMembers.ProjectMembers), 4)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -895,7 +895,7 @@ func (s *Service) DeleteProjectMembers(ctx context.Context, projectID uuid.UUID,
|
||||
}
|
||||
|
||||
// GetProjectMembers returns ProjectMembers for given Project
|
||||
func (s *Service) GetProjectMembers(ctx context.Context, projectID uuid.UUID, pagination Pagination) (pm []ProjectMember, err error) {
|
||||
func (s *Service) GetProjectMembers(ctx context.Context, projectID uuid.UUID, cursor ProjectMembersCursor) (pmp *ProjectMembersPage, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
auth, err := GetAuth(ctx)
|
||||
if err != nil {
|
||||
@ -907,11 +907,11 @@ func (s *Service) GetProjectMembers(ctx context.Context, projectID uuid.UUID, pa
|
||||
return nil, ErrUnauthorized.Wrap(err)
|
||||
}
|
||||
|
||||
if pagination.Limit > maxLimit {
|
||||
pagination.Limit = maxLimit
|
||||
if cursor.Limit > maxLimit {
|
||||
cursor.Limit = maxLimit
|
||||
}
|
||||
|
||||
pm, err = s.store.ProjectMembers().GetByProjectID(ctx, projectID, pagination)
|
||||
pmp, err = s.store.ProjectMembers().GetPagedByProjectID(ctx, projectID, cursor)
|
||||
if err != nil {
|
||||
return nil, errs.New(internalErrMsg)
|
||||
}
|
||||
|
@ -328,11 +328,11 @@ func (m *lockedProjectMembers) GetByMemberID(ctx context.Context, memberID uuid.
|
||||
return m.db.GetByMemberID(ctx, memberID)
|
||||
}
|
||||
|
||||
// GetByProjectID is a method for querying project members from the database by projectID, offset and limit.
|
||||
func (m *lockedProjectMembers) GetByProjectID(ctx context.Context, projectID uuid.UUID, pagination console.Pagination) ([]console.ProjectMember, error) {
|
||||
// GetPagedByProjectID is a method for querying project members from the database by projectID and cursor
|
||||
func (m *lockedProjectMembers) GetPagedByProjectID(ctx context.Context, projectID uuid.UUID, cursor console.ProjectMembersCursor) (*console.ProjectMembersPage, error) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
return m.db.GetByProjectID(ctx, projectID, pagination)
|
||||
return m.db.GetPagedByProjectID(ctx, projectID, cursor)
|
||||
}
|
||||
|
||||
// Insert is a method for inserting project member into the database.
|
||||
|
@ -32,30 +32,75 @@ func (pm *projectMembers) GetByMemberID(ctx context.Context, memberID uuid.UUID)
|
||||
}
|
||||
|
||||
// GetByProjectID is a method for querying project members from the database by projectID, offset and limit.
|
||||
func (pm *projectMembers) GetByProjectID(ctx context.Context, projectID uuid.UUID, pagination console.Pagination) (_ []console.ProjectMember, err error) {
|
||||
func (pm *projectMembers) GetPagedByProjectID(ctx context.Context, projectID uuid.UUID, cursor console.ProjectMembersCursor) (_ *console.ProjectMembersPage, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
if pagination.Limit < 0 || pagination.Offset < 0 {
|
||||
return nil, errs.New("invalid pagination argument")
|
||||
|
||||
search := "%" + strings.Replace(cursor.Search, " ", "%", -1) + "%"
|
||||
|
||||
if cursor.Limit > 50 {
|
||||
cursor.Limit = 50
|
||||
}
|
||||
|
||||
var projectMembers []console.ProjectMember
|
||||
searchSubQuery := "%" + strings.Replace(pagination.Search, " ", "%", -1) + "%"
|
||||
//`+getOrder(pagination.Order)+`
|
||||
if cursor.Page == 0 {
|
||||
return nil, errs.New("page cannot be 0")
|
||||
}
|
||||
|
||||
page := &console.ProjectMembersPage{
|
||||
Search: cursor.Search,
|
||||
Limit: cursor.Limit,
|
||||
Offset: uint64((cursor.Page - 1) * cursor.Limit),
|
||||
Order: cursor.Order,
|
||||
OrderDirection: cursor.OrderDirection,
|
||||
}
|
||||
|
||||
countQuery := pm.db.Rebind(`
|
||||
SELECT COUNT(*)
|
||||
FROM project_members pm
|
||||
INNER JOIN users u ON pm.member_id = u.id
|
||||
WHERE pm.project_id = ?
|
||||
AND ( u.email LIKE ? OR
|
||||
u.full_name LIKE ? OR
|
||||
u.short_name LIKE ?
|
||||
)`)
|
||||
|
||||
countRow := pm.db.QueryRowContext(ctx,
|
||||
countQuery,
|
||||
projectID[:],
|
||||
search,
|
||||
search,
|
||||
search)
|
||||
|
||||
err = countRow.Scan(&page.TotalCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if page.TotalCount == 0 {
|
||||
return page, nil
|
||||
}
|
||||
if page.Offset > page.TotalCount-1 {
|
||||
return nil, errs.New("page is out of range")
|
||||
}
|
||||
// TODO: LIKE is case-sensitive postgres, however this should be case-insensitive and possibly allow typos
|
||||
reboundQuery := pm.db.Rebind(`
|
||||
SELECT pm.*
|
||||
FROM project_members pm
|
||||
INNER JOIN users u ON pm.member_id = u.id
|
||||
WHERE pm.project_id = ?
|
||||
AND ( u.email LIKE ? OR
|
||||
u.full_name LIKE ? OR
|
||||
u.short_name LIKE ? )
|
||||
ORDER BY ` + sanitizedOrderColumnName(pagination.Order) + ` ASC
|
||||
LIMIT ? OFFSET ?
|
||||
`)
|
||||
WHERE pm.project_id = ?
|
||||
AND ( u.email LIKE ? OR
|
||||
u.full_name LIKE ? OR
|
||||
u.short_name LIKE ? )
|
||||
ORDER BY ` + sanitizedOrderColumnName(cursor.Order) + `
|
||||
` + sanitizeOrderDirectionName(page.OrderDirection) + `
|
||||
LIMIT ? OFFSET ?`)
|
||||
|
||||
rows, err := pm.db.Query(reboundQuery, projectID[:], searchSubQuery, searchSubQuery, searchSubQuery, pagination.Limit, pagination.Offset)
|
||||
rows, err := pm.db.QueryContext(ctx,
|
||||
reboundQuery,
|
||||
projectID[:],
|
||||
search,
|
||||
search,
|
||||
search,
|
||||
page.Limit,
|
||||
page.Offset)
|
||||
|
||||
defer func() {
|
||||
err = errs.Combine(err, rows.Close())
|
||||
@ -65,6 +110,7 @@ func (pm *projectMembers) GetByProjectID(ctx context.Context, projectID uuid.UUI
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var projectMembers []console.ProjectMember
|
||||
for rows.Next() {
|
||||
pm := console.ProjectMember{}
|
||||
var memberIDBytes, projectIDBytes []uint8
|
||||
@ -91,7 +137,17 @@ func (pm *projectMembers) GetByProjectID(ctx context.Context, projectID uuid.UUI
|
||||
projectMembers = append(projectMembers, pm)
|
||||
}
|
||||
|
||||
return projectMembers, err
|
||||
page.ProjectMembers = projectMembers
|
||||
page.Order = cursor.Order
|
||||
|
||||
page.PageCount = uint(page.TotalCount / uint64(cursor.Limit))
|
||||
if page.TotalCount%uint64(cursor.Limit) != 0 {
|
||||
page.PageCount++
|
||||
}
|
||||
|
||||
page.CurrentPage = cursor.Page
|
||||
|
||||
return page, err
|
||||
}
|
||||
|
||||
// Insert is a method for inserting project member into the database.
|
||||
@ -155,6 +211,14 @@ func sanitizedOrderColumnName(pmo console.ProjectMemberOrder) string {
|
||||
}
|
||||
}
|
||||
|
||||
func sanitizeOrderDirectionName(pmo console.ProjectMemberOrderDirection) string {
|
||||
if pmo == 2 {
|
||||
return "DESC"
|
||||
}
|
||||
|
||||
return "ASC"
|
||||
}
|
||||
|
||||
// projectMembersFromDbxSlice is used for creating []ProjectMember entities from autogenerated []*dbx.ProjectMember struct
|
||||
func projectMembersFromDbxSlice(ctx context.Context, projectMembersDbx []*dbx.ProjectMember) (_ []console.ProjectMember, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
Loading…
Reference in New Issue
Block a user