1
0

Improve pagination (#105)

This commit is contained in:
konrad
2019-10-23 21:11:40 +00:00
parent 89f385a53d
commit 8948a5f219
97 changed files with 2137 additions and 529 deletions

View File

@ -124,15 +124,16 @@ func (l *Label) Delete() (err error) {
// @tags labels
// @Accept json
// @Produce json
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search labels by label text."
// @Security JWTKeyAuth
// @Success 200 {array} models.Label "The labels"
// @Failure 500 {object} models.Message "Internal error"
// @Router /labels [get]
func (l *Label) ReadAll(search string, a web.Auth, page int) (ls interface{}, err error) {
func (l *Label) ReadAll(a web.Auth, search string, page int, perPage int) (ls interface{}, resultCount int, numberOfEntries int64, err error) {
if _, is := a.(*LinkSharing); is {
return nil, ErrGenericForbidden{}
return nil, 0, 0, ErrGenericForbidden{}
}
u := &User{ID: a.GetID()}
@ -140,13 +141,15 @@ func (l *Label) ReadAll(search string, a web.Auth, page int) (ls interface{}, er
// Get all tasks
taskIDs, err := getUserTaskIDs(u)
if err != nil {
return nil, err
return nil, 0, 0, err
}
return getLabelsByTaskIDs(&LabelByTaskIDsOptions{
Search: search,
User: u,
TaskIDs: taskIDs,
Page: page,
PerPage: perPage,
GetUnusedLabels: true,
GroupByLabelIDsOnly: true,
})
@ -198,15 +201,17 @@ func getLabelByIDSimple(labelID int64) (*Label, error) {
func getUserTaskIDs(u *User) (taskIDs []int64, err error) {
// Get all lists
lists, err := getRawListsForUser("", u, -1)
lists, _, _, err := getRawListsForUser("", u, -1, 0)
if err != nil {
return nil, err
}
tasks, err := getRawTasksForLists(lists, &taskOptions{
tasks, _, _, err := getRawTasksForLists(lists, &taskOptions{
startDate: time.Unix(0, 0),
endDate: time.Unix(0, 0),
sortby: SortTasksByUnsorted,
page: -1,
perPage: 0,
})
if err != nil {
return nil, err

View File

@ -101,21 +101,22 @@ func (lt *LabelTask) Create(a web.Auth) (err error) {
// @Accept json
// @Produce json
// @Param task path int true "Task ID"
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search labels by label text."
// @Security JWTKeyAuth
// @Success 200 {array} models.Label "The labels"
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{task}/labels [get]
func (lt *LabelTask) ReadAll(search string, a web.Auth, page int) (labels interface{}, err error) {
func (lt *LabelTask) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
// Check if the user has the right to see the task
task := Task{ID: lt.TaskID}
canRead, err := task.CanRead(a)
if err != nil {
return nil, err
return nil, 0, 0, err
}
if !canRead {
return nil, ErrNoRightToSeeTask{lt.TaskID, a.GetID()}
return nil, 0, 0, ErrNoRightToSeeTask{lt.TaskID, a.GetID()}
}
return getLabelsByTaskIDs(&LabelByTaskIDsOptions{
@ -137,6 +138,7 @@ type LabelByTaskIDsOptions struct {
User *User
Search string
Page int
PerPage int
TaskIDs []int64
GetUnusedLabels bool
GroupByLabelIDsOnly bool
@ -144,7 +146,7 @@ type LabelByTaskIDsOptions struct {
// Helper function to get all labels for a set of tasks
// Used when getting all labels for one task as well when getting all lables
func getLabelsByTaskIDs(opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, err error) {
func getLabelsByTaskIDs(opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, resultCount int, totalEntries int64, err error) {
// Include unused labels. Needed to be able to show a list of all unused labels a user
// has access to.
var uidOrNil interface{}
@ -172,10 +174,10 @@ func getLabelsByTaskIDs(opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, err
Or(builder.In("label_task.task_id", opts.TaskIDs)).
And("labels.title LIKE ?", "%"+opts.Search+"%").
GroupBy(groupBy).
Limit(getLimitFromPageIndex(opts.Page)).
Limit(getLimitFromPageIndex(opts.Page, opts.PerPage)).
Find(&labels)
if err != nil {
return nil, err
return nil, 0, 0, err
}
// Get all created by users
@ -186,7 +188,7 @@ func getLabelsByTaskIDs(opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, err
users := make(map[int64]*User)
err = x.In("id", userids).Find(&users)
if err != nil {
return nil, err
return nil, 0, 0, err
}
// Obfuscate all user emails
@ -199,7 +201,19 @@ func getLabelsByTaskIDs(opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, err
labels[in].CreatedBy = users[l.CreatedByID]
}
return labels, err
// Get the total number of entries
totalEntries, err = x.Table("labels").
Join("LEFT", "label_task", "label_task.label_id = labels.id").
Where(requestOrNil, uidOrNil).
Or(builder.In("label_task.task_id", opts.TaskIDs)).
And("labels.title LIKE ?", "%"+opts.Search+"%").
GroupBy(groupBy).
Count(&Label{})
if err != nil {
return nil, 0, 0, err
}
return labels, len(labels), totalEntries, err
}
// Create or update a bunch of task labels

View File

@ -89,7 +89,7 @@ func TestLabelTask_ReadAll(t *testing.T) {
CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights,
}
gotLabels, err := l.ReadAll(tt.args.search, tt.args.a, tt.args.page)
gotLabels, _, _, err := l.ReadAll(tt.args.a, tt.args.search, tt.args.page, 0)
if (err != nil) != tt.wantErr {
t.Errorf("LabelTask.ReadAll() error = %v, wantErr %v", err, tt.wantErr)
return

View File

@ -117,7 +117,7 @@ func TestLabel_ReadAll(t *testing.T) {
CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights,
}
gotLs, err := l.ReadAll(tt.args.search, tt.args.a, tt.args.page)
gotLs, _, _, err := l.ReadAll(tt.args.a, tt.args.search, tt.args.page, 0)
if (err != nil) != tt.wantErr {
t.Errorf("Label.ReadAll() error = %v, wantErr %v", err, tt.wantErr)
return

View File

@ -136,29 +136,30 @@ func (share *LinkSharing) ReadOne() (err error) {
// @Accept json
// @Produce json
// @Param list path int true "List ID"
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search shares by hash."
// @Security JWTKeyAuth
// @Success 200 {array} models.LinkSharing "The share links"
// @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{list}/shares [get]
func (share *LinkSharing) ReadAll(search string, a web.Auth, page int) (interface{}, error) {
func (share *LinkSharing) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) {
list := &List{ID: share.ListID}
can, err := list.CanRead(a)
if err != nil {
return nil, err
return nil, 0, 0, err
}
if !can {
return nil, ErrGenericForbidden{}
return nil, 0, 0, ErrGenericForbidden{}
}
var shares []*LinkSharing
err = x.
Where("list_id = ? AND hash LIKE ?", share.ListID, "%"+search+"%").
Limit(getLimitFromPageIndex(page)).
Limit(getLimitFromPageIndex(page, perPage)).
Find(&shares)
if err != nil {
return nil, err
return nil, 0, 0, err
}
// Find all users and add them
@ -170,14 +171,22 @@ func (share *LinkSharing) ReadAll(search string, a web.Auth, page int) (interfac
users := make(map[int64]*User)
err = x.In("id", userIDs).Find(&users)
if err != nil {
return nil, err
return nil, 0, 0, err
}
for _, s := range shares {
s.SharedBy = users[s.SharedByID]
}
return shares, err
// Total count
totalItems, err = x.
Where("list_id = ? AND hash LIKE ?", share.ListID, "%"+search+"%").
Count(&LinkSharing{})
if err != nil {
return nil, 0, 0, err
}
return shares, len(shares), totalItems, err
}
// Delete removes a link share

View File

@ -77,35 +77,36 @@ func GetListsByNamespaceID(nID int64, doer *User) (lists []*List, err error) {
// @tags list
// @Accept json
// @Produce json
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search lists by title."
// @Security JWTKeyAuth
// @Success 200 {array} models.List "The lists"
// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list"
// @Failure 500 {object} models.Message "Internal error"
// @Router /lists [get]
func (l *List) ReadAll(search string, a web.Auth, page int) (interface{}, error) {
func (l *List) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) {
// Check if we're dealing with a share auth
shareAuth, ok := a.(*LinkSharing)
if ok {
list := &List{ID: shareAuth.ListID}
err := list.GetSimpleByID()
if err != nil {
return nil, err
return nil, 0, 0, err
}
lists := []*List{list}
err = AddListDetails(lists)
return lists, err
return lists, 0, 0, err
}
lists, err := getRawListsForUser(search, &User{ID: a.GetID()}, page)
lists, resultCount, totalItems, err := getRawListsForUser(search, &User{ID: a.GetID()}, page, perPage)
if err != nil {
return nil, err
return nil, 0, 0, err
}
// Add more list details
err = AddListDetails(lists)
return lists, err
return lists, resultCount, totalItems, err
}
// ReadOne gets one list by its ID
@ -177,10 +178,10 @@ func GetListSimplByTaskID(taskID int64) (l *List, err error) {
}
// Gets the lists only, without any tasks or so
func getRawListsForUser(search string, u *User, page int) (lists []*List, err error) {
func getRawListsForUser(search string, u *User, page int, perPage int) (lists []*List, resultCount int, totalItems int64, err error) {
fullUser, err := GetUserByID(u.ID)
if err != nil {
return lists, err
return nil, 0, 0, err
}
// Gets all Lists where the user is either owner or in a team which has access to the list
@ -201,11 +202,33 @@ func getRawListsForUser(search string, u *User, page int) (lists []*List, err er
Or("ul.user_id = ?", fullUser.ID).
Or("un.user_id = ?", fullUser.ID).
GroupBy("l.id").
Limit(getLimitFromPageIndex(page)).
Limit(getLimitFromPageIndex(page, perPage)).
Where("l.title LIKE ?", "%"+search+"%").
Find(&lists)
if err != nil {
return nil, 0, 0, err
}
return lists, err
totalItems, err = x.
Table("list").
Alias("l").
Join("INNER", []string{"namespaces", "n"}, "l.namespace_id = n.id").
Join("LEFT", []string{"team_namespaces", "tn"}, "tn.namespace_id = n.id").
Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tn.team_id").
Join("LEFT", []string{"team_list", "tl"}, "l.id = tl.list_id").
Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id").
Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id").
Join("LEFT", []string{"users_namespace", "un"}, "un.namespace_id = l.namespace_id").
Where("tm.user_id = ?", fullUser.ID).
Or("tm2.user_id = ?", fullUser.ID).
Or("l.owner_id = ?", fullUser.ID).
Or("ul.user_id = ?", fullUser.ID).
Or("un.user_id = ?", fullUser.ID).
GroupBy("l.id").
Limit(getLimitFromPageIndex(page, perPage)).
Where("l.title LIKE ?", "%"+search+"%").
Count(&List{})
return lists, len(lists), totalItems, err
}
// AddListDetails adds owner user objects and list tasks to all lists in the slice

View File

@ -36,14 +36,15 @@ func TestList_ReadAll(t *testing.T) {
assert.NoError(t, err)
lists2 := List{}
lists3, err := lists2.ReadAll("", u, 1)
lists3, _, _, err := lists2.ReadAll(u, "", 1, 50)
assert.NoError(t, err)
assert.Equal(t, reflect.TypeOf(lists3).Kind(), reflect.Slice)
s := reflect.ValueOf(lists3)
assert.Equal(t, 16, s.Len())
// Try getting lists for a nonexistant user
_, err = lists2.ReadAll("", &User{ID: 984234}, 1)
_, _, _, err = lists2.ReadAll(&User{ID: 984234}, "", 1, 50)
assert.Error(t, err)
assert.True(t, IsErrUserDoesNotExist(err))
}

View File

@ -154,22 +154,23 @@ func (tl *TeamList) Delete() (err error) {
// @Accept json
// @Produce json
// @Param id path int true "List ID"
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search teams by its name."
// @Security JWTKeyAuth
// @Success 200 {array} models.TeamWithRight "The teams with their right."
// @Failure 403 {object} code.vikunja.io/web.HTTPError "No right to see the list."
// @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{id}/teams [get]
func (tl *TeamList) ReadAll(search string, a web.Auth, page int) (interface{}, error) {
func (tl *TeamList) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) {
// Check if the user can read the namespace
l := &List{ID: tl.ListID}
canRead, err := l.CanRead(a)
if err != nil {
return nil, err
return nil, 0, 0, err
}
if !canRead {
return nil, ErrNeedToHaveListReadAccess{ListID: tl.ListID, UserID: a.GetID()}
return nil, 0, 0, ErrNeedToHaveListReadAccess{ListID: tl.ListID, UserID: a.GetID()}
}
// Get the teams
@ -178,11 +179,24 @@ func (tl *TeamList) ReadAll(search string, a web.Auth, page int) (interface{}, e
Table("teams").
Join("INNER", "team_list", "team_id = teams.id").
Where("team_list.list_id = ?", tl.ListID).
Limit(getLimitFromPageIndex(page)).
Limit(getLimitFromPageIndex(page, perPage)).
Where("teams.name LIKE ?", "%"+search+"%").
Find(&all)
if err != nil {
return nil, 0, 0, err
}
return all, err
totalItems, err = x.
Table("teams").
Join("INNER", "team_list", "team_id = teams.id").
Where("team_list.list_id = ?", tl.ListID).
Where("teams.name LIKE ?", "%"+search+"%").
Count(&TeamWithRight{})
if err != nil {
return nil, 0, 0, err
}
return all, len(all), totalItems, err
}
// Update updates a team <-> list relation

View File

@ -69,27 +69,27 @@ func TestTeamList(t *testing.T) {
assert.True(t, IsErrListDoesNotExist(err))
// Test Read all
teams, err := tl.ReadAll("", u, 1)
teams, _, _, err := tl.ReadAll(u, "", 1, 50)
assert.NoError(t, err)
assert.Equal(t, reflect.TypeOf(teams).Kind(), reflect.Slice)
s := reflect.ValueOf(teams)
assert.Equal(t, s.Len(), 1)
// Test Read all for nonexistant list
_, err = tl4.ReadAll("", u, 1)
_, _, _, err = tl4.ReadAll(u, "", 1, 50)
assert.Error(t, err)
assert.True(t, IsErrListDoesNotExist(err))
// Test Read all for a list where the user is owner of the namespace this list belongs to
tl5 := tl
tl5.ListID = 2
_, err = tl5.ReadAll("", u, 1)
_, _, _, err = tl5.ReadAll(u, "", 1, 50)
assert.NoError(t, err)
// Test read all for a list where the user not has access
tl6 := tl
tl6.ListID = 5
_, err = tl6.ReadAll("", u, 1)
_, _, _, err = tl6.ReadAll(u, "", 1, 50)
assert.Error(t, err)
assert.True(t, IsErrNeedToHaveListReadAccess(err))

View File

@ -159,22 +159,23 @@ func (lu *ListUser) Delete() (err error) {
// @Accept json
// @Produce json
// @Param id path int true "List ID"
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search users by its name."
// @Security JWTKeyAuth
// @Success 200 {array} models.UserWithRight "The users with the right they have."
// @Failure 403 {object} code.vikunja.io/web.HTTPError "No right to see the list."
// @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{id}/users [get]
func (lu *ListUser) ReadAll(search string, a web.Auth, page int) (interface{}, error) {
func (lu *ListUser) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
// Check if the user has access to the list
l := &List{ID: lu.ListID}
canRead, err := l.CanRead(a)
if err != nil {
return nil, err
return nil, 0, 0, err
}
if !canRead {
return nil, ErrNeedToHaveListReadAccess{UserID: a.GetID(), ListID: lu.ListID}
return nil, 0, 0, ErrNeedToHaveListReadAccess{UserID: a.GetID(), ListID: lu.ListID}
}
// Get all users
@ -182,16 +183,25 @@ func (lu *ListUser) ReadAll(search string, a web.Auth, page int) (interface{}, e
err = x.
Join("INNER", "users_list", "user_id = users.id").
Where("users_list.list_id = ?", lu.ListID).
Limit(getLimitFromPageIndex(page)).
Limit(getLimitFromPageIndex(page, perPage)).
Where("users.username LIKE ?", "%"+search+"%").
Find(&all)
if err != nil {
return nil, 0, 0, err
}
// Obfuscate all user emails
for _, u := range all {
u.Email = ""
}
return all, err
numberOfTotalItems, err = x.
Join("INNER", "users_list", "user_id = users.id").
Where("users_list.list_id = ?", lu.ListID).
Where("users.username LIKE ?", "%"+search+"%").
Count(&UserWithRight{})
return all, len(all), numberOfTotalItems, err
}
// Update updates a user <-> list relation

View File

@ -203,7 +203,7 @@ func TestListUser_ReadAll(t *testing.T) {
CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights,
}
got, err := ul.ReadAll(tt.args.search, tt.args.a, tt.args.page)
got, _, _, err := ul.ReadAll(tt.args.a, tt.args.search, tt.args.page, 50)
if (err != nil) != tt.wantErr {
t.Errorf("ListUser.ReadAll() error = %v, wantErr %v", err, tt.wantErr)
}

View File

@ -69,14 +69,18 @@ func SetEngine() (err error) {
return nil
}
func getLimitFromPageIndex(page int) (limit, start int) {
func getLimitFromPageIndex(page int, perPage int) (limit, start int) {
// Get everything when page index is -1
if page < 0 {
return 0, 0
}
limit = config.ServicePageCount.GetInt()
limit = config.ServiceMaxItemsPerPage.GetInt()
if perPage > 0 {
limit = perPage
}
start = limit * (page - 1)
return
}

View File

@ -131,20 +131,21 @@ type NamespaceWithLists struct {
// @tags namespace
// @Accept json
// @Produce json
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search namespaces by name."
// @Security JWTKeyAuth
// @Success 200 {array} models.NamespaceWithLists "The Namespaces."
// @Failure 500 {object} models.Message "Internal error"
// @Router /namespaces [get]
func (n *Namespace) ReadAll(search string, a web.Auth, page int) (interface{}, error) {
func (n *Namespace) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
if _, is := a.(*LinkSharing); is {
return nil, ErrGenericForbidden{}
return nil, 0, 0, ErrGenericForbidden{}
}
doer, err := getUserWithError(a)
if err != nil {
return nil, err
return nil, 0, 0, err
}
all := []*NamespaceWithLists{}
@ -167,11 +168,11 @@ func (n *Namespace) ReadAll(search string, a web.Auth, page int) (interface{}, e
Or("namespaces.owner_id = ?", doer.ID).
Or("users_namespace.user_id = ?", doer.ID).
GroupBy("namespaces.id").
Limit(getLimitFromPageIndex(page)).
Limit(getLimitFromPageIndex(page, perPage)).
Where("namespaces.name LIKE ?", "%"+search+"%").
Find(&all)
if err != nil {
return all, err
return all, 0, 0, err
}
// Get all users
@ -187,7 +188,7 @@ func (n *Namespace) ReadAll(search string, a web.Auth, page int) (interface{}, e
Find(&users)
if err != nil {
return all, err
return all, 0, 0, err
}
// Make a list of namespace ids
@ -202,7 +203,7 @@ func (n *Namespace) ReadAll(search string, a web.Auth, page int) (interface{}, e
In("namespace_id", namespaceids).
Find(&lists)
if err != nil {
return all, err
return all, 0, 0, err
}
// Get all lists individually shared with our user (not via a namespace)
@ -218,7 +219,7 @@ func (n *Namespace) ReadAll(search string, a web.Auth, page int) (interface{}, e
GroupBy("l.id").
Find(&individualLists)
if err != nil {
return nil, err
return nil, 0, 0, err
}
// Make the namespace -1 so we now later which one it was
@ -234,9 +235,13 @@ func (n *Namespace) ReadAll(search string, a web.Auth, page int) (interface{}, e
}
// More details for the lists
AddListDetails(lists)
err = AddListDetails(lists)
if err != nil {
return nil, 0, 0, err
}
// Put objects in our namespace list
// TODO: Refactor this to use maps for better efficiency
for i, n := range all {
// Users
@ -255,7 +260,22 @@ func (n *Namespace) ReadAll(search string, a web.Auth, page int) (interface{}, e
}
}
return all, nil
numberOfTotalItems, err = x.
Table("namespaces").
Join("LEFT", "team_namespaces", "namespaces.id = team_namespaces.namespace_id").
Join("LEFT", "team_members", "team_members.team_id = team_namespaces.team_id").
Join("LEFT", "users_namespace", "users_namespace.namespace_id = namespaces.id").
Where("team_members.user_id = ?", doer.ID).
Or("namespaces.owner_id = ?", doer.ID).
Or("users_namespace.user_id = ?", doer.ID).
GroupBy("namespaces.id").
Where("namespaces.name LIKE ?", "%"+search+"%").
Count(&NamespaceWithLists{})
if err != nil {
return all, 0, 0, err
}
return all, len(all), numberOfTotalItems, nil
}
// Create implements the creation method via the interface

View File

@ -139,22 +139,23 @@ func (tn *TeamNamespace) Delete() (err error) {
// @Accept json
// @Produce json
// @Param id path int true "Namespace ID"
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search teams by its name."
// @Security JWTKeyAuth
// @Success 200 {array} models.TeamWithRight "The teams with the right they have."
// @Failure 403 {object} code.vikunja.io/web.HTTPError "No right to see the namespace."
// @Failure 500 {object} models.Message "Internal error"
// @Router /namespaces/{id}/teams [get]
func (tn *TeamNamespace) ReadAll(search string, a web.Auth, page int) (interface{}, error) {
func (tn *TeamNamespace) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
// Check if the user can read the namespace
n := Namespace{ID: tn.NamespaceID}
canRead, err := n.CanRead(a)
if err != nil {
return nil, err
return nil, 0, 0, err
}
if !canRead {
return nil, ErrNeedToHaveNamespaceReadAccess{NamespaceID: tn.NamespaceID, UserID: a.GetID()}
return nil, 0, 0, ErrNeedToHaveNamespaceReadAccess{NamespaceID: tn.NamespaceID, UserID: a.GetID()}
}
// Get the teams
@ -163,11 +164,20 @@ func (tn *TeamNamespace) ReadAll(search string, a web.Auth, page int) (interface
err = x.Table("teams").
Join("INNER", "team_namespaces", "team_id = teams.id").
Where("team_namespaces.namespace_id = ?", tn.NamespaceID).
Limit(getLimitFromPageIndex(page)).
Limit(getLimitFromPageIndex(page, perPage)).
Where("teams.name LIKE ?", "%"+search+"%").
Find(&all)
if err != nil {
return nil, 0, 0, err
}
return all, err
numberOfTotalItems, err = x.Table("teams").
Join("INNER", "team_namespaces", "team_id = teams.id").
Where("team_namespaces.namespace_id = ?", tn.NamespaceID).
Where("teams.name LIKE ?", "%"+search+"%").
Count(&TeamWithRight{})
return all, len(all), numberOfTotalItems, err
}
// Update updates a team <-> namespace relation

View File

@ -68,20 +68,20 @@ func TestTeamNamespace(t *testing.T) {
assert.True(t, IsErrNamespaceDoesNotExist(err))
// Check readall
teams, err := tn.ReadAll("", dummyuser, 1)
teams, _, _, err := tn.ReadAll(dummyuser, "", 1, 50)
assert.NoError(t, err)
assert.Equal(t, reflect.TypeOf(teams).Kind(), reflect.Slice)
s := reflect.ValueOf(teams)
assert.Equal(t, s.Len(), 1)
// Check readall for a nonexistant namespace
_, err = tn4.ReadAll("", dummyuser, 1)
_, _, _, err = tn4.ReadAll(dummyuser, "", 1, 50)
assert.Error(t, err)
assert.True(t, IsErrNamespaceDoesNotExist(err))
// Check with no right to read the namespace
nouser := &User{ID: 393}
_, err = tn.ReadAll("", nouser, 1)
_, _, _, err = tn.ReadAll(nouser, "", 1, 50)
assert.Error(t, err)
assert.True(t, IsErrNeedToHaveNamespaceReadAccess(err))

View File

@ -118,7 +118,7 @@ func TestNamespace_Create(t *testing.T) {
assert.True(t, IsErrNamespaceDoesNotExist(err))
// Get all namespaces of a user
nsps, err := n.ReadAll("", doer, 1)
nsps, _, _, err := n.ReadAll(doer, "", 1, 50)
assert.NoError(t, err)
assert.Equal(t, reflect.TypeOf(nsps).Kind(), reflect.Slice)
s := reflect.ValueOf(nsps)

View File

@ -145,22 +145,23 @@ func (nu *NamespaceUser) Delete() (err error) {
// @Accept json
// @Produce json
// @Param id path int true "Namespace ID"
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search users by its name."
// @Security JWTKeyAuth
// @Success 200 {array} models.UserWithRight "The users with the right they have."
// @Failure 403 {object} code.vikunja.io/web.HTTPError "No right to see the namespace."
// @Failure 500 {object} models.Message "Internal error"
// @Router /namespaces/{id}/users [get]
func (nu *NamespaceUser) ReadAll(search string, a web.Auth, page int) (interface{}, error) {
func (nu *NamespaceUser) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
// Check if the user has access to the namespace
l := Namespace{ID: nu.NamespaceID}
canRead, err := l.CanRead(a)
if err != nil {
return nil, err
return nil, 0, 0, err
}
if !canRead {
return nil, ErrNeedToHaveNamespaceReadAccess{}
return nil, 0, 0, ErrNeedToHaveNamespaceReadAccess{}
}
// Get all users
@ -168,16 +169,25 @@ func (nu *NamespaceUser) ReadAll(search string, a web.Auth, page int) (interface
err = x.
Join("INNER", "users_namespace", "user_id = users.id").
Where("users_namespace.namespace_id = ?", nu.NamespaceID).
Limit(getLimitFromPageIndex(page)).
Limit(getLimitFromPageIndex(page, perPage)).
Where("users.username LIKE ?", "%"+search+"%").
Find(&all)
if err != nil {
return nil, 0, 0, err
}
// Obfuscate all user emails
for _, u := range all {
u.Email = ""
}
return all, err
numberOfTotalItems, err = x.
Join("INNER", "users_namespace", "user_id = users.id").
Where("users_namespace.namespace_id = ?", nu.NamespaceID).
Where("users.username LIKE ?", "%"+search+"%").
Count(&UserWithRight{})
return all, len(all), numberOfTotalItems, err
}
// Update updates a user <-> namespace relation

View File

@ -202,7 +202,7 @@ func TestNamespaceUser_ReadAll(t *testing.T) {
CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights,
}
got, err := un.ReadAll(tt.args.search, tt.args.a, tt.args.page)
got, _, _, err := un.ReadAll(tt.args.a, tt.args.search, tt.args.page, 50)
if (err != nil) != tt.wantErr {
t.Errorf("NamespaceUser.ReadAll() error = %v, wantErr %v", err, tt.wantErr)
return

View File

@ -219,25 +219,26 @@ func (t *Task) addNewAssigneeByID(newAssigneeID int64, list *List) (err error) {
// @tags assignees
// @Accept json
// @Produce json
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search assignees by their username."
// @Param taskID path int true "Task ID"
// @Security JWTKeyAuth
// @Success 200 {array} models.User "The assignees"
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{taskID}/assignees [get]
func (la *TaskAssginee) ReadAll(search string, a web.Auth, page int) (interface{}, error) {
func (la *TaskAssginee) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
task, err := GetListSimplByTaskID(la.TaskID)
if err != nil {
return nil, err
return nil, 0, 0, err
}
can, err := task.CanRead(a)
if err != nil {
return nil, err
return nil, 0, 0, err
}
if !can {
return nil, ErrGenericForbidden{}
return nil, 0, 0, ErrGenericForbidden{}
}
var taskAssignees []*User
@ -245,9 +246,18 @@ func (la *TaskAssginee) ReadAll(search string, a web.Auth, page int) (interface{
Select("users.*").
Join("INNER", "users", "task_assignees.user_id = users.id").
Where("task_id = ? AND users.username LIKE ?", la.TaskID, "%"+search+"%").
Limit(getLimitFromPageIndex(page)).
Limit(getLimitFromPageIndex(page, perPage)).
Find(&taskAssignees)
return taskAssignees, err
if err != nil {
return nil, 0, 0, err
}
numberOfTotalItems, err = x.Table("task_assignees").
Select("users.*").
Join("INNER", "users", "task_assignees.user_id = users.id").
Where("task_id = ? AND users.username LIKE ?", la.TaskID, "%"+search+"%").
Count(&User{})
return taskAssignees, len(taskAssignees), numberOfTotalItems, err
}
// BulkAssignees is a helper struct used to update multiple assignees at once.

View File

@ -100,21 +100,23 @@ func (ta *TaskAttachment) ReadOne() (err error) {
// @Accept json
// @Produce json
// @Param id path int true "Task ID"
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Security JWTKeyAuth
// @Success 200 {array} models.TaskAttachment "All attachments for this task"
// @Failure 403 {object} models.Message "No access to this task."
// @Failure 404 {object} models.Message "The task does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{id}/attachments [get]
func (ta *TaskAttachment) ReadAll(s string, a web.Auth, page int) (interface{}, error) {
func (ta *TaskAttachment) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
attachments := []*TaskAttachment{}
err := x.
Limit(getLimitFromPageIndex(page)).
err = x.
Limit(getLimitFromPageIndex(page, perPage)).
Where("task_id = ?", ta.TaskID).
Find(&attachments)
if err != nil {
return nil, err
return nil, 0, 0, err
}
fileIDs := make([]int64, 0, len(attachments))
@ -127,13 +129,13 @@ func (ta *TaskAttachment) ReadAll(s string, a web.Auth, page int) (interface{},
fs := make(map[int64]*files.File)
err = x.In("id", fileIDs).Find(&fs)
if err != nil {
return nil, err
return nil, 0, 0, err
}
us := make(map[int64]*User)
err = x.In("id", userIDs).Find(&us)
if err != nil {
return nil, err
return nil, 0, 0, err
}
for _, r := range attachments {
@ -146,7 +148,10 @@ func (ta *TaskAttachment) ReadAll(s string, a web.Auth, page int) (interface{},
r.CreatedBy = us[r.CreatedByID]
}
return attachments, err
numberOfTotalItems, err = x.
Where("task_id = ?", ta.TaskID).
Count(&TaskAttachment{})
return attachments, len(attachments), numberOfTotalItems, err
}
// Delete removes an attachment

View File

@ -119,7 +119,7 @@ func TestTaskAttachment_NewAttachment(t *testing.T) {
func TestTaskAttachment_ReadAll(t *testing.T) {
files.InitTestFileFixtures(t)
ta := &TaskAttachment{TaskID: 1}
as, err := ta.ReadAll("", &User{ID: 1}, 0)
as, _, _, err := ta.ReadAll(&User{ID: 1}, "", 0, 50)
attachments, _ := as.([]*TaskAttachment)
assert.NoError(t, err)
assert.Len(t, attachments, 3)

View File

@ -728,7 +728,7 @@ func TestTask_ReadAll(t *testing.T) {
CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights,
}
got, err := lt.ReadAll(tt.args.search, tt.args.a, tt.args.page)
got, _, _, err := lt.ReadAll(tt.args.a, tt.args.search, tt.args.page, 50)
if (err != nil) != tt.wantErr {
t.Errorf("Test %s, Task.ReadAll() error = %v, wantErr %v", tt.name, err, tt.wantErr)
return

View File

@ -123,7 +123,8 @@ const (
// @tags task
// @Accept json
// @Produce json
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search tasks by task text."
// @Param sort query string false "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, duedate, duedatedesc, duedateasc."
// @Param startdate query int false "The start date parameter to filter by. Expects a unix timestamp. If no end date, but a start date is specified, the end date is set to the current time."
@ -132,7 +133,7 @@ const (
// @Success 200 {array} models.Task "The tasks"
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/all [get]
func (t *Task) ReadAll(search string, a web.Auth, page int) (interface{}, error) {
func (t *Task) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) {
var sortby SortBy
switch t.Sorting {
case "priority":
@ -156,6 +157,8 @@ func (t *Task) ReadAll(search string, a web.Auth, page int) (interface{}, error)
sortby: sortby,
startDate: time.Unix(t.StartDateSortUnix, 0),
endDate: time.Unix(t.EndDateSortUnix, 0),
page: page,
perPage: perPage,
}
shareAuth, is := a.(*LinkSharing)
@ -163,15 +166,15 @@ func (t *Task) ReadAll(search string, a web.Auth, page int) (interface{}, error)
list := &List{ID: shareAuth.ListID}
err := list.GetSimpleByID()
if err != nil {
return nil, err
return nil, 0, 0, err
}
return getTasksForLists([]*List{list}, taskopts)
}
// Get all lists for the user
lists, err := getRawListsForUser("", &User{ID: a.GetID()}, page)
lists, _, _, err := getRawListsForUser("", &User{ID: a.GetID()}, -1, 0)
if err != nil {
return nil, err
return nil, 0, 0, err
}
return getTasksForLists(lists, taskopts)
@ -182,9 +185,11 @@ type taskOptions struct {
sortby SortBy
startDate time.Time
endDate time.Time
page int
perPage int
}
func getRawTasksForLists(lists []*List, opts *taskOptions) (taskMap map[int64]*Task, err error) {
func getRawTasksForLists(lists []*List, opts *taskOptions) (taskMap map[int64]*Task, resultCount int, totalItems int64, err error) {
// Get all list IDs and get the tasks
var listIDs []int64
@ -219,42 +224,62 @@ func getRawTasksForLists(lists []*List, opts *taskOptions) (taskMap map[int64]*T
endDateUnix = opts.endDate.Unix()
}
if err := x.In("list_id", listIDs).
err := x.In("list_id", listIDs).
Where("text LIKE ?", "%"+opts.search+"%").
And("((due_date_unix BETWEEN ? AND ?) OR "+
"(start_date_unix BETWEEN ? and ?) OR "+
"(end_date_unix BETWEEN ? and ?))", startDateUnix, endDateUnix, startDateUnix, endDateUnix, startDateUnix, endDateUnix).
OrderBy(orderby).
Find(&taskMap); err != nil {
return nil, err
Limit(getLimitFromPageIndex(opts.page, opts.perPage)).
Find(&taskMap)
if err != nil {
return nil, 0, 0, err
}
totalItems, err = x.In("list_id", listIDs).
Where("text LIKE ?", "%"+opts.search+"%").
And("((due_date_unix BETWEEN ? AND ?) OR "+
"(start_date_unix BETWEEN ? and ?) OR "+
"(end_date_unix BETWEEN ? and ?))", startDateUnix, endDateUnix, startDateUnix, endDateUnix, startDateUnix, endDateUnix).
Count(&Task{})
if err != nil {
return nil, 0, 0, err
}
} else {
if err := x.In("list_id", listIDs).
err := x.In("list_id", listIDs).
Where("text LIKE ?", "%"+opts.search+"%").
OrderBy(orderby).
Find(&taskMap); err != nil {
return nil, err
Limit(getLimitFromPageIndex(opts.page, opts.perPage)).
Find(&taskMap)
if err != nil {
return nil, 0, 0, err
}
totalItems, err = x.In("list_id", listIDs).
Where("text LIKE ?", "%"+opts.search+"%").
Count(&Task{})
if err != nil {
return nil, 0, 0, err
}
}
return
return taskMap, len(taskMap), totalItems, nil
}
func getTasksForLists(lists []*List, opts *taskOptions) (tasks []*Task, err error) {
func getTasksForLists(lists []*List, opts *taskOptions) (tasks []*Task, resultCount int, totalItems int64, err error) {
taskMap, err := getRawTasksForLists(lists, opts)
taskMap, resultCount, totalItems, err := getRawTasksForLists(lists, opts)
if err != nil {
return nil, err
return nil, 0, 0, err
}
tasks, err = addMoreInfoToTasks(taskMap)
if err != nil {
return nil, err
return nil, 0, 0, err
}
// Because the list is sorted by id which we don't want (since we're dealing with maps)
// we have to manually sort the tasks again here.
sortTasks(tasks, opts.sortby)
return tasks, err
return tasks, resultCount, totalItems, err
}
func sortTasks(tasks []*Task, by SortBy) {
@ -339,7 +364,7 @@ func GetTaskByID(listTaskID int64) (listTask Task, err error) {
}
// Get task labels
taskLabels, err := getLabelsByTaskIDs(&LabelByTaskIDsOptions{
taskLabels, _, _, err := getLabelsByTaskIDs(&LabelByTaskIDsOptions{
TaskIDs: []int64{listTaskID},
})
if err != nil {
@ -413,7 +438,7 @@ func addMoreInfoToTasks(taskMap map[int64]*Task) (tasks []*Task, err error) {
}
// Get all labels for all the tasks
labels, err := getLabelsByTaskIDs(&LabelByTaskIDsOptions{
labels, _, _, err := getLabelsByTaskIDs(&LabelByTaskIDsOptions{
TaskIDs: taskIDs,
Page: -1,
})

View File

@ -135,27 +135,37 @@ func (t *Team) ReadOne() (err error) {
// @tags team
// @Accept json
// @Produce json
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search teams by its name."
// @Security JWTKeyAuth
// @Success 200 {array} models.Team "The teams."
// @Failure 500 {object} models.Message "Internal error"
// @Router /teams [get]
func (t *Team) ReadAll(search string, a web.Auth, page int) (interface{}, error) {
func (t *Team) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
if _, is := a.(*LinkSharing); is {
return nil, ErrGenericForbidden{}
return nil, 0, 0, ErrGenericForbidden{}
}
all := []*Team{}
err := x.Select("teams.*").
err = x.Select("teams.*").
Table("teams").
Join("INNER", "team_members", "team_members.team_id = teams.id").
Where("team_members.user_id = ?", a.GetID()).
Limit(getLimitFromPageIndex(page)).
Limit(getLimitFromPageIndex(page, perPage)).
Where("teams.name LIKE ?", "%"+search+"%").
Find(&all)
if err != nil {
return nil, 0, 0, err
}
return all, err
numberOfTotalItems, err = x.
Table("teams").
Join("INNER", "team_members", "team_members.team_id = teams.id").
Where("team_members.user_id = ?", a.GetID()).
Where("teams.name LIKE ?", "%"+search+"%").
Count(&Team{})
return all, len(all), numberOfTotalItems, err
}
// Create is the handler to create a team

View File

@ -55,7 +55,7 @@ func TestTeam_Create(t *testing.T) {
assert.True(t, IsErrTeamDoesNotExist(err))
// Get all teams the user is part of
ts, err := tm.ReadAll("", doer, 1)
ts, _, _, err := tm.ReadAll(doer, "", 1, 50)
assert.NoError(t, err)
assert.Equal(t, reflect.TypeOf(ts).Kind(), reflect.Slice)
s := reflect.ValueOf(ts)