Added pagination (#16)
This commit is contained in:
parent
0e7e1b7e38
commit
d232836423
22 changed files with 71 additions and 31 deletions
|
@ -222,7 +222,7 @@ Teams sind global, d.h. Ein Team kann mehrere Namespaces verwalten.
|
|||
* [ ] Wir brauchen noch ne gute idee, wie man die listen kriegt, auf die man nur so Zugriff hat (ohne namespace)
|
||||
* Dazu am Besten nen pseudonamespace anlegen (id -1 oder so), der hat das dann alles
|
||||
* [ ] Validation der ankommenden structs, am besten mit https://github.com/go-validator/validator oder mit dem Ding von echo
|
||||
* [ ] Pagination
|
||||
* [x] Pagination
|
||||
* Sollte in der Config definierbar sein, wie viel pro Seite angezeigt werden soll, die CRUD-Methoden übergeben dann ein "gibt mir die Seite sowieso" an die CRUDable-Funktionenen, die müssen das dann Auswerten. Geht leider nicht anders, wenn man erst 2342352 Einträge hohlt und die dann nachträglich auf 200 begrenzt ist das ne massive Ressourcenverschwendung.
|
||||
* [ ] Testing mit locust: https://locust.io/
|
||||
* [ ] Methode einbauen, um mit einem gültigen token ein neues gültiges zu kriegen
|
||||
|
|
|
@ -4,7 +4,7 @@ Content-Type: application/json
|
|||
|
||||
{
|
||||
"username": "user",
|
||||
"password": "12345"
|
||||
"password": "1234"
|
||||
}
|
||||
|
||||
> {% client.global.set("auth_token", response.body.token); %}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Get all lists
|
||||
GET http://localhost:8080/api/v1/lists
|
||||
GET http://localhost:8080/api/v1/lists?page=0
|
||||
Authorization: Bearer {{auth_token}}
|
||||
|
||||
###
|
||||
|
|
|
@ -7,6 +7,8 @@ service:
|
|||
interface: ":3456"
|
||||
# The URL of the frontend, used to send password reset emails.
|
||||
frontendurl: ""
|
||||
# The number of items which gets returned per page
|
||||
pagecount: 50
|
||||
|
||||
database:
|
||||
# Database type to use. Supported types are mysql and sqlite.
|
||||
|
|
|
@ -33,6 +33,8 @@ service:
|
|||
frontendurl: ""
|
||||
# The base path on the file system where the binary and assets are
|
||||
rootpath: <the path of the executable>
|
||||
# The number of items which gets returned per page
|
||||
pagecount: 50
|
||||
|
||||
database:
|
||||
# Database type to use. Supported types are mysql and sqlite.
|
||||
|
|
|
@ -29,6 +29,7 @@ func InitConfig() (err error) {
|
|||
}
|
||||
exPath := filepath.Dir(ex)
|
||||
viper.SetDefault("service.rootpath", exPath)
|
||||
viper.SetDefault("service.pagecount", 50)
|
||||
// Database
|
||||
viper.SetDefault("database.type", "sqlite")
|
||||
viper.SetDefault("database.host", "localhost")
|
||||
|
|
|
@ -4,7 +4,7 @@ package models
|
|||
type CRUDable interface {
|
||||
Create(*User) error
|
||||
ReadOne() error
|
||||
ReadAll(*User) (interface{}, error)
|
||||
ReadAll(*User, int) (interface{}, error)
|
||||
Update() error
|
||||
Delete() error
|
||||
}
|
||||
|
|
|
@ -27,8 +27,8 @@ func GetListsByNamespaceID(nID int64) (lists []*List, err error) {
|
|||
}
|
||||
|
||||
// ReadAll gets all lists a user has access to
|
||||
func (l *List) ReadAll(u *User) (interface{}, error) {
|
||||
lists, err := getRawListsForUser(u)
|
||||
func (l *List) ReadAll(u *User, page int) (interface{}, error) {
|
||||
lists, err := getRawListsForUser(u, page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ func (l *List) GetSimpleByID() (err error) {
|
|||
}
|
||||
|
||||
// Gets the lists only, without any tasks or so
|
||||
func getRawListsForUser(u *User) (lists []*List, err error) {
|
||||
func getRawListsForUser(u *User, page int) (lists []*List, err error) {
|
||||
fullUser, err := GetUserByID(u.ID)
|
||||
if err != nil {
|
||||
return lists, err
|
||||
|
@ -104,6 +104,7 @@ func getRawListsForUser(u *User) (lists []*List, err error) {
|
|||
Or("ul.user_id = ?", fullUser.ID).
|
||||
Or("un.user_id = ?", fullUser.ID).
|
||||
GroupBy("l.id").
|
||||
Limit(getLimitFromPageIndex(page)).
|
||||
Find(&lists)
|
||||
|
||||
return lists, err
|
||||
|
@ -160,14 +161,14 @@ type ListTasksDummy struct {
|
|||
}
|
||||
|
||||
// ReadAll gets all tasks for a user
|
||||
func (lt *ListTasksDummy) ReadAll(u *User) (interface{}, error) {
|
||||
return GetTasksByUser(u)
|
||||
func (lt *ListTasksDummy) ReadAll(u *User, page int) (interface{}, error) {
|
||||
return GetTasksByUser(u, page)
|
||||
}
|
||||
|
||||
//GetTasksByUser returns all tasks for a user
|
||||
func GetTasksByUser(u *User) (tasks []*ListTask, err error) {
|
||||
func GetTasksByUser(u *User, page int) (tasks []*ListTask, err error) {
|
||||
// Get all lists
|
||||
lists, err := getRawListsForUser(u)
|
||||
lists, err := getRawListsForUser(u, page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -20,14 +20,14 @@ func TestList_ReadAll(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
lists2 := List{}
|
||||
lists3, err := lists2.ReadAll(&u)
|
||||
lists3, err := lists2.ReadAll(&u, 1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, reflect.TypeOf(lists3).Kind(), reflect.Slice)
|
||||
s := reflect.ValueOf(lists3)
|
||||
assert.Equal(t, s.Len(), 1)
|
||||
|
||||
// Try getting lists for a nonexistant user
|
||||
_, err = lists2.ReadAll(&User{ID: 984234})
|
||||
_, err = lists2.ReadAll(&User{ID: 984234}, 1)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrUserDoesNotExist(err))
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package models
|
||||
|
||||
// ReadAll gets all users who have access to a list
|
||||
func (ul *ListUser) ReadAll(u *User) (interface{}, error) {
|
||||
func (ul *ListUser) ReadAll(u *User, page int) (interface{}, error) {
|
||||
// Check if the user has access to the list
|
||||
l := &List{ID: ul.ListID}
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
|
@ -16,6 +16,7 @@ func (ul *ListUser) ReadAll(u *User) (interface{}, error) {
|
|||
err := x.
|
||||
Join("INNER", "users_list", "user_id = users.id").
|
||||
Where("users_list.list_id = ?", ul.ListID).
|
||||
Limit(getLimitFromPageIndex(page)).
|
||||
Find(&all)
|
||||
|
||||
return all, err
|
||||
|
|
|
@ -90,3 +90,15 @@ func SetEngine() (err error) {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getLimitFromPageIndex(page int) (limit, start int) {
|
||||
|
||||
// Get everything when page index is -1
|
||||
if page < 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
limit = viper.GetInt("service.pagecount")
|
||||
start = limit * (page - 1)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ func (n *Namespace) ReadOne() (err error) {
|
|||
}
|
||||
|
||||
// ReadAll gets all namespaces a user has access to
|
||||
func (n *Namespace) ReadAll(doer *User) (interface{}, error) {
|
||||
func (n *Namespace) ReadAll(doer *User, page int) (interface{}, error) {
|
||||
|
||||
type namespaceWithLists struct {
|
||||
Namespace `xorm:"extends"`
|
||||
|
@ -71,6 +71,7 @@ func (n *Namespace) ReadAll(doer *User) (interface{}, error) {
|
|||
Or("namespaces.owner_id = ?", doer.ID).
|
||||
Or("users_namespace.user_id = ?", doer.ID).
|
||||
GroupBy("namespaces.id").
|
||||
Limit(getLimitFromPageIndex(page)).
|
||||
Find(&all)
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -85,7 +85,7 @@ func TestNamespace_Create(t *testing.T) {
|
|||
assert.True(t, IsErrNamespaceDoesNotExist(err))
|
||||
|
||||
// Get all namespaces of a user
|
||||
nsps, err := n.ReadAll(&doer)
|
||||
nsps, err := n.ReadAll(&doer, 1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, reflect.TypeOf(nsps).Kind(), reflect.Slice)
|
||||
s := reflect.ValueOf(nsps)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package models
|
||||
|
||||
// ReadAll gets all users who have access to a namespace
|
||||
func (un *NamespaceUser) ReadAll(u *User) (interface{}, error) {
|
||||
func (un *NamespaceUser) ReadAll(u *User, page int) (interface{}, error) {
|
||||
// Check if the user has access to the namespace
|
||||
l, err := GetNamespaceByID(un.NamespaceID)
|
||||
if err != nil {
|
||||
|
@ -16,6 +16,7 @@ func (un *NamespaceUser) ReadAll(u *User) (interface{}, error) {
|
|||
err = x.
|
||||
Join("INNER", "users_namespace", "user_id = users.id").
|
||||
Where("users_namespace.namespace_id = ?", un.NamespaceID).
|
||||
Limit(getLimitFromPageIndex(page)).
|
||||
Find(&all)
|
||||
|
||||
return all, err
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package models
|
||||
|
||||
// ReadAll implements the method to read all teams of a list
|
||||
func (tl *TeamList) ReadAll(u *User) (interface{}, error) {
|
||||
func (tl *TeamList) ReadAll(u *User, page int) (interface{}, error) {
|
||||
// Check if the user can read the namespace
|
||||
l := &List{ID: tl.ListID}
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
|
@ -17,6 +17,7 @@ func (tl *TeamList) ReadAll(u *User) (interface{}, error) {
|
|||
Table("teams").
|
||||
Join("INNER", "team_list", "team_id = teams.id").
|
||||
Where("team_list.list_id = ?", tl.ListID).
|
||||
Limit(getLimitFromPageIndex(page)).
|
||||
Find(&all)
|
||||
|
||||
return all, err
|
||||
|
|
|
@ -50,27 +50,27 @@ func TestTeamList(t *testing.T) {
|
|||
assert.True(t, IsErrListDoesNotExist(err))
|
||||
|
||||
// Test Read all
|
||||
teams, err := tl.ReadAll(&u)
|
||||
teams, err := tl.ReadAll(&u, 1)
|
||||
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)
|
||||
_, err = tl4.ReadAll(&u, 1)
|
||||
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)
|
||||
_, err = tl5.ReadAll(&u, 1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test read all for a list where the user not has access
|
||||
tl6 := tl
|
||||
tl6.ListID = 3
|
||||
_, err = tl6.ReadAll(&u)
|
||||
_, err = tl6.ReadAll(&u, 1)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrNeedToHaveListReadAccess(err))
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package models
|
||||
|
||||
// ReadAll implements the method to read all teams of a namespace
|
||||
func (tn *TeamNamespace) ReadAll(user *User) (interface{}, error) {
|
||||
func (tn *TeamNamespace) ReadAll(user *User, page int) (interface{}, error) {
|
||||
// Check if the user can read the namespace
|
||||
n, err := GetNamespaceByID(tn.NamespaceID)
|
||||
if err != nil {
|
||||
|
@ -17,6 +17,7 @@ func (tn *TeamNamespace) ReadAll(user *User) (interface{}, error) {
|
|||
err = x.Table("teams").
|
||||
Join("INNER", "team_namespaces", "team_id = teams.id").
|
||||
Where("team_namespaces.namespace_id = ?", tn.NamespaceID).
|
||||
Limit(getLimitFromPageIndex(page)).
|
||||
Find(&all)
|
||||
|
||||
return all, err
|
||||
|
|
|
@ -49,20 +49,20 @@ func TestTeamNamespace(t *testing.T) {
|
|||
assert.True(t, IsErrNamespaceDoesNotExist(err))
|
||||
|
||||
// Check readall
|
||||
teams, err := tn.ReadAll(&dummyuser)
|
||||
teams, err := tn.ReadAll(&dummyuser, 1)
|
||||
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)
|
||||
_, err = tn4.ReadAll(&dummyuser, 1)
|
||||
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)
|
||||
_, err = tn.ReadAll(nouser, 1)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrNeedToHaveNamespaceReadAccess(err))
|
||||
|
||||
|
|
|
@ -84,12 +84,13 @@ func (t *Team) ReadOne() (err error) {
|
|||
}
|
||||
|
||||
// ReadAll gets all teams the user is part of
|
||||
func (t *Team) ReadAll(user *User) (teams interface{}, err error) {
|
||||
func (t *Team) ReadAll(user *User, page int) (teams interface{}, err error) {
|
||||
all := []*Team{}
|
||||
err = x.Select("teams.*").
|
||||
Table("teams").
|
||||
Join("INNER", "team_members", "team_members.team_id = teams.id").
|
||||
Where("team_members.user_id = ?", user.ID).
|
||||
Limit(getLimitFromPageIndex(page)).
|
||||
Find(&all)
|
||||
|
||||
return all, err
|
||||
|
|
|
@ -32,7 +32,7 @@ func TestTeam_Create(t *testing.T) {
|
|||
assert.True(t, dummyteam.CanRead(&doer))
|
||||
|
||||
// Get all teams the user is part of
|
||||
ts, err := tm.ReadAll(&doer)
|
||||
ts, err := tm.ReadAll(&doer, 1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, reflect.TypeOf(ts).Kind(), reflect.Slice)
|
||||
s := reflect.ValueOf(ts)
|
||||
|
|
|
@ -39,7 +39,7 @@ func Caldav(c echo.Context) error {
|
|||
}
|
||||
|
||||
// Get all tasks for that user
|
||||
tasks, err := models.GetTasksByUser(&u)
|
||||
tasks, err := models.GetTasksByUser(&u, -1)
|
||||
if err != nil {
|
||||
return crud.HandleHTTPError(err)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package crud
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"github.com/labstack/echo"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ReadAllWeb is the webhandler to get all objects of a type
|
||||
|
@ -21,7 +23,21 @@ func (c *WebHandler) ReadAllWeb(ctx echo.Context) error {
|
|||
return echo.NewHTTPError(http.StatusBadRequest, "No or invalid model provided.")
|
||||
}
|
||||
|
||||
lists, err := currentStruct.ReadAll(¤tUser)
|
||||
// Pagination
|
||||
page := ctx.QueryParam("page")
|
||||
if page == "" {
|
||||
page = "1"
|
||||
}
|
||||
pageNumber, err := strconv.Atoi(page)
|
||||
if err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Bad page requested.")
|
||||
}
|
||||
if pageNumber < 0 {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Bad page requested.")
|
||||
}
|
||||
|
||||
lists, err := currentStruct.ReadAll(¤tUser, pageNumber)
|
||||
if err != nil {
|
||||
return HandleHTTPError(err)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue