satellite/console: Add ability to list projects by owner ID
Listing projects by owner ID also includes the number of members in each project. Change-Id: I53a09674b60c199ef378943851bb0f164e92e4e2
This commit is contained in:
parent
424d2787eb
commit
c24f84914c
@ -48,7 +48,7 @@ func TestAPI(t *testing.T) {
|
||||
t.Run("GetProject", func(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
expected := fmt.Sprintf(
|
||||
`{"id":"%s","name":"%s","description":"%s","partnerId":"%s","ownerId":"%s","rateLimit":null,"maxBuckets":null,"createdAt":"%s"}`,
|
||||
`{"id":"%s","name":"%s","description":"%s","partnerId":"%s","ownerId":"%s","rateLimit":null,"maxBuckets":null,"createdAt":"%s","memberCount":0}`,
|
||||
project.ID.String(),
|
||||
project.Name,
|
||||
project.Description,
|
||||
|
@ -33,6 +33,8 @@ type Projects interface {
|
||||
Update(ctx context.Context, project *Project) error
|
||||
// List returns paginated projects, created before provided timestamp.
|
||||
List(ctx context.Context, offset int64, limit int, before time.Time) (ProjectsPage, error)
|
||||
// ListByOwnerID is a method for querying all projects from the database by ownerID. It also includes the number of members for each project.
|
||||
ListByOwnerID(ctx context.Context, userID uuid.UUID, limit int, offset int64) (ProjectsPage, error)
|
||||
|
||||
// UpdateRateLimit is a method for updating projects rate limit.
|
||||
UpdateRateLimit(ctx context.Context, id uuid.UUID, newLimit int) error
|
||||
@ -54,6 +56,7 @@ type Project struct {
|
||||
RateLimit *int `json:"rateLimit"`
|
||||
MaxBuckets *int `json:"maxBuckets"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
MemberCount int `json:"memberCount"`
|
||||
}
|
||||
|
||||
// ProjectInfo holds data needed to create/update Project.
|
||||
|
@ -6,6 +6,8 @@ package console_test
|
||||
import (
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -15,6 +17,7 @@ import (
|
||||
|
||||
"storj.io/common/testcontext"
|
||||
"storj.io/common/testrand"
|
||||
"storj.io/common/uuid"
|
||||
"storj.io/storj/satellite"
|
||||
"storj.io/storj/satellite/console"
|
||||
"storj.io/storj/satellite/satellitedb/satellitedbtest"
|
||||
@ -238,6 +241,130 @@ func TestProjectsList(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestProjectsListByOwner(t *testing.T) {
|
||||
const (
|
||||
limit = 5
|
||||
length = limit*4 - 1 // make length offset from page size so we can test incomplete page at end
|
||||
)
|
||||
|
||||
rateLimit := 100
|
||||
|
||||
satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) {
|
||||
owner1, err := db.Console().Users().Insert(ctx,
|
||||
&console.User{
|
||||
ID: testrand.UUID(),
|
||||
FullName: "Billy H",
|
||||
Email: "billyh@example.com",
|
||||
PasswordHash: []byte("example_password"),
|
||||
Status: 1,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
owner2, err := db.Console().Users().Insert(ctx,
|
||||
&console.User{
|
||||
ID: testrand.UUID(),
|
||||
FullName: "James H",
|
||||
Email: "james@example.com",
|
||||
PasswordHash: []byte("example_password_2"),
|
||||
Status: 1,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
projectsDB := db.Console().Projects()
|
||||
projectMembersDB := db.Console().ProjectMembers()
|
||||
|
||||
// Create projects
|
||||
var owner1Projects []console.Project
|
||||
var owner2Projects []console.Project
|
||||
for i := 0; i < length; i++ {
|
||||
proj1, err := projectsDB.Insert(ctx,
|
||||
&console.Project{
|
||||
Name: "owner1example" + strconv.Itoa(i),
|
||||
Description: "example",
|
||||
OwnerID: owner1.ID,
|
||||
RateLimit: &rateLimit,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
proj2, err := projectsDB.Insert(ctx,
|
||||
&console.Project{
|
||||
Name: "owner2example" + strconv.Itoa(i),
|
||||
Description: "example",
|
||||
OwnerID: owner2.ID,
|
||||
RateLimit: &rateLimit,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// insert 0, 1, or 2 project members
|
||||
numMembers := i % 3
|
||||
switch numMembers {
|
||||
case 1:
|
||||
_, err = projectMembersDB.Insert(ctx, owner1.ID, proj1.ID)
|
||||
require.NoError(t, err)
|
||||
_, err = projectMembersDB.Insert(ctx, owner2.ID, proj2.ID)
|
||||
require.NoError(t, err)
|
||||
case 2:
|
||||
_, err = projectMembersDB.Insert(ctx, owner1.ID, proj1.ID)
|
||||
require.NoError(t, err)
|
||||
_, err = projectMembersDB.Insert(ctx, owner2.ID, proj1.ID)
|
||||
require.NoError(t, err)
|
||||
_, err = projectMembersDB.Insert(ctx, owner1.ID, proj2.ID)
|
||||
require.NoError(t, err)
|
||||
_, err = projectMembersDB.Insert(ctx, owner2.ID, proj2.ID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
proj1.MemberCount = numMembers
|
||||
proj2.MemberCount = numMembers
|
||||
|
||||
owner1Projects = append(owner1Projects, *proj1)
|
||||
owner2Projects = append(owner2Projects, *proj2)
|
||||
}
|
||||
|
||||
// test listing for each
|
||||
var testCases = []struct {
|
||||
id uuid.UUID
|
||||
originalProjects []console.Project
|
||||
}{
|
||||
{id: owner1.ID, originalProjects: owner1Projects},
|
||||
{id: owner2.ID, originalProjects: owner2Projects},
|
||||
}
|
||||
for _, tt := range testCases {
|
||||
projsPage, err := projectsDB.ListByOwnerID(ctx, tt.id, limit, 0)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, projsPage.Projects, limit)
|
||||
|
||||
ownerProjectsDB := projsPage.Projects
|
||||
|
||||
for projsPage.Next {
|
||||
projsPage, err = projectsDB.ListByOwnerID(ctx, tt.id, limit, projsPage.NextOffset)
|
||||
require.NoError(t, err)
|
||||
// number of projects should not exceed page limit
|
||||
require.True(t, len(projsPage.Projects) > 0 && len(projsPage.Projects) <= limit)
|
||||
|
||||
ownerProjectsDB = append(ownerProjectsDB, projsPage.Projects...)
|
||||
}
|
||||
|
||||
require.False(t, projsPage.Next)
|
||||
require.Equal(t, int64(0), projsPage.NextOffset)
|
||||
require.Equal(t, length, len(ownerProjectsDB))
|
||||
// sort originalProjects by Name in alphabetical order
|
||||
originalProjects := tt.originalProjects
|
||||
sort.SliceStable(originalProjects, func(i, j int) bool {
|
||||
return strings.Compare(originalProjects[i].Name, originalProjects[j].Name) < 0
|
||||
})
|
||||
for i, p := range ownerProjectsDB {
|
||||
// expect response projects to be in alphabetical order
|
||||
require.Equal(t, originalProjects[i].Name, p.Name)
|
||||
require.Equal(t, originalProjects[i].MemberCount, p.MemberCount)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetMaxBuckets(t *testing.T) {
|
||||
satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) {
|
||||
maxCount := 100
|
||||
|
@ -5,6 +5,7 @@ package satellitedb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
@ -201,6 +202,57 @@ func (projects *projects) List(ctx context.Context, offset int64, limit int, bef
|
||||
return page, nil
|
||||
}
|
||||
|
||||
// ListByOwnerID is a method for querying all projects from the database by ownerID. It also includes the number of members for each project.
|
||||
func (projects *projects) ListByOwnerID(ctx context.Context, ownerID uuid.UUID, limit int, offset int64) (_ console.ProjectsPage, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
var page console.ProjectsPage
|
||||
|
||||
rows, err := projects.sdb.Query(ctx, projects.sdb.Rebind(`
|
||||
SELECT id, name, description, owner_id, rate_limit, max_buckets, created_at,
|
||||
(SELECT COUNT(*) FROM project_members WHERE project_id = projects.id) AS member_count
|
||||
FROM projects
|
||||
WHERE owner_id = ?
|
||||
ORDER BY name ASC
|
||||
OFFSET ? ROWS
|
||||
LIMIT ?
|
||||
`), ownerID, offset, limit+1) // add 1 to limit to see if there is another page
|
||||
if err != nil {
|
||||
return console.ProjectsPage{}, err
|
||||
}
|
||||
defer func() { err = errs.Combine(err, rows.Close()) }()
|
||||
|
||||
count := 0
|
||||
projectsToSend := make([]console.Project, 0, limit)
|
||||
for rows.Next() {
|
||||
count++
|
||||
if count == limit+1 {
|
||||
// we are done with this page; do not include this project
|
||||
page.Next = true
|
||||
page.NextOffset = offset + int64(limit)
|
||||
break
|
||||
}
|
||||
var rateLimit, maxBuckets sql.NullInt32
|
||||
nextProject := &console.Project{}
|
||||
err = rows.Scan(&nextProject.ID, &nextProject.Name, &nextProject.Description, &nextProject.OwnerID, &rateLimit, &maxBuckets, &nextProject.CreatedAt, &nextProject.MemberCount)
|
||||
if err != nil {
|
||||
return console.ProjectsPage{}, err
|
||||
}
|
||||
if rateLimit.Valid {
|
||||
nextProject.RateLimit = new(int)
|
||||
*nextProject.RateLimit = int(rateLimit.Int32)
|
||||
}
|
||||
if maxBuckets.Valid {
|
||||
nextProject.MaxBuckets = new(int)
|
||||
*nextProject.MaxBuckets = int(maxBuckets.Int32)
|
||||
}
|
||||
projectsToSend = append(projectsToSend, *nextProject)
|
||||
}
|
||||
|
||||
page.Projects = projectsToSend
|
||||
return page, rows.Err()
|
||||
}
|
||||
|
||||
// projectFromDBX is used for creating Project entity from autogenerated dbx.Project struct.
|
||||
func projectFromDBX(ctx context.Context, project *dbx.Project) (_ *console.Project, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
Loading…
Reference in New Issue
Block a user