Implemented search for everything (#17)
This commit is contained in:
parent
ad74d24ee2
commit
d3de658882
18 changed files with 49 additions and 32 deletions
|
@ -217,8 +217,7 @@ Teams sind global, d.h. Ein Team kann mehrere Namespaces verwalten.
|
|||
* [x] Ne extra funktion für list exists machen, damit die nicht immer über GetListByID gehen, um sql-abfragen zu sparen
|
||||
* [x] Rausfinden warum xorm teilweise beim einfügen IDs mit einfügen will -> Das schlägt dann wegen duplicate fehl
|
||||
* [x] Bei den Structs "AfterLoad" raus, das verbraucht bei Gruppenabfragen zu viele SQL-Abfragen -> Die sollen einfach die entsprechenden Read()-Methoden verwenden (Krassestes bsp. ist GET /namespaces mit so ca 50 Abfragen)
|
||||
* [ ] Search endpoints /users?s=name und /teams?s=name, erstmal nur mit Namen suchen, später einstellbar auch mit email.
|
||||
-> Search methode in den Handler einbauen und dann die Endpoints entsprechend anpassen integrieren kann (so nach dem Motto einfach nen Searchstring anhängen)
|
||||
* [x] General search endpoints
|
||||
* [ ] 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
|
||||
|
|
|
@ -4,7 +4,7 @@ Content-Type: application/json
|
|||
|
||||
{
|
||||
"username": "user",
|
||||
"password": "1234"
|
||||
"password": "12345"
|
||||
}
|
||||
|
||||
> {% client.global.set("auth_token", response.body.token); %}
|
||||
|
|
|
@ -14,7 +14,7 @@ The interface is defined as followed:
|
|||
type CRUDable interface {
|
||||
Create(*User) error
|
||||
ReadOne() error
|
||||
ReadAll(*User, int) (interface{}, error)
|
||||
ReadAll(string, *User, int) (interface{}, error)
|
||||
Update() error
|
||||
Delete() error
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ make an array of a set type (If you know a way to do this, don't hesitate to dro
|
|||
|
||||
### Pagination
|
||||
|
||||
When using the `ReadAll`-method, the second parameter contains the requested page. Your function should return only the number of results
|
||||
When using the `ReadAll`-method, the third parameter contains the requested page. Your function should return only the number of results
|
||||
corresponding to that page. The number of items per page is definied in the config as `service.pagecount` (Get it with `viper.GetInt("service.pagecount")`).
|
||||
|
||||
These can be calculated in combination with a helper function, `getLimitFromPageIndex(pageIndex)` which returns
|
||||
|
@ -49,6 +49,14 @@ lists := []List{}
|
|||
err := x.Limit(getLimitFromPageIndex(pageIndex)).Find(&lists)
|
||||
```
|
||||
|
||||
### Search
|
||||
|
||||
When using the `ReadAll`-method, the first parameter is a search term which should be used to search items of your struct. You define the critera.
|
||||
|
||||
Users can then pass the `?s=something` parameter to the url to search.
|
||||
|
||||
As the logic for "give me everything" and "give me everything where the name contains 'something'" is mostly the same, we made the decision to design the function like this, in order to keep the places with mostly the same logic as few as possible. Also just adding `?s=query` to the url one already knows and uses is a lot more convenient.
|
||||
|
||||
## Rights
|
||||
|
||||
This interface defines methods to check for rights on structs. They accept a `User` as parameter and usually return a `bool`.
|
||||
|
|
|
@ -4,7 +4,7 @@ package models
|
|||
type CRUDable interface {
|
||||
Create(*User) error
|
||||
ReadOne() error
|
||||
ReadAll(*User, int) (interface{}, error)
|
||||
ReadAll(string, *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, page int) (interface{}, error) {
|
||||
lists, err := getRawListsForUser(u, page)
|
||||
func (l *List) ReadAll(search string, u *User, page int) (interface{}, error) {
|
||||
lists, err := getRawListsForUser(search, 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, page int) (lists []*List, err error) {
|
||||
func getRawListsForUser(search string, u *User, page int) (lists []*List, err error) {
|
||||
fullUser, err := GetUserByID(u.ID)
|
||||
if err != nil {
|
||||
return lists, err
|
||||
|
@ -105,6 +105,7 @@ func getRawListsForUser(u *User, page int) (lists []*List, err error) {
|
|||
Or("un.user_id = ?", fullUser.ID).
|
||||
GroupBy("l.id").
|
||||
Limit(getLimitFromPageIndex(page)).
|
||||
Where("l.title LIKE ?", "%"+search+"%").
|
||||
Find(&lists)
|
||||
|
||||
return lists, err
|
||||
|
@ -161,14 +162,14 @@ type ListTasksDummy struct {
|
|||
}
|
||||
|
||||
// ReadAll gets all tasks for a user
|
||||
func (lt *ListTasksDummy) ReadAll(u *User, page int) (interface{}, error) {
|
||||
return GetTasksByUser(u, page)
|
||||
func (lt *ListTasksDummy) ReadAll(search string, u *User, page int) (interface{}, error) {
|
||||
return GetTasksByUser(search, u, page)
|
||||
}
|
||||
|
||||
//GetTasksByUser returns all tasks for a user
|
||||
func GetTasksByUser(u *User, page int) (tasks []*ListTask, err error) {
|
||||
func GetTasksByUser(search string, u *User, page int) (tasks []*ListTask, err error) {
|
||||
// Get all lists
|
||||
lists, err := getRawListsForUser(u, page)
|
||||
lists, err := getRawListsForUser(search, 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, 1)
|
||||
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}, 1)
|
||||
_, 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, page int) (interface{}, error) {
|
||||
func (ul *ListUser) ReadAll(search string, 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 {
|
||||
|
@ -17,6 +17,7 @@ func (ul *ListUser) ReadAll(u *User, page int) (interface{}, error) {
|
|||
Join("INNER", "users_list", "user_id = users.id").
|
||||
Where("users_list.list_id = ?", ul.ListID).
|
||||
Limit(getLimitFromPageIndex(page)).
|
||||
Where("users.username LIKE ?", "%"+search+"%").
|
||||
Find(&all)
|
||||
|
||||
return all, err
|
||||
|
|
|
@ -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, page int) (interface{}, error) {
|
||||
func (n *Namespace) ReadAll(search string, doer *User, page int) (interface{}, error) {
|
||||
|
||||
type namespaceWithLists struct {
|
||||
Namespace `xorm:"extends"`
|
||||
|
@ -72,6 +72,7 @@ func (n *Namespace) ReadAll(doer *User, page int) (interface{}, error) {
|
|||
Or("users_namespace.user_id = ?", doer.ID).
|
||||
GroupBy("namespaces.id").
|
||||
Limit(getLimitFromPageIndex(page)).
|
||||
Where("namespaces.name LIKE ?", "%"+search+"%").
|
||||
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, 1)
|
||||
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, page int) (interface{}, error) {
|
||||
func (un *NamespaceUser) ReadAll(search string, u *User, page int) (interface{}, error) {
|
||||
// Check if the user has access to the namespace
|
||||
l, err := GetNamespaceByID(un.NamespaceID)
|
||||
if err != nil {
|
||||
|
@ -17,6 +17,7 @@ func (un *NamespaceUser) ReadAll(u *User, page int) (interface{}, error) {
|
|||
Join("INNER", "users_namespace", "user_id = users.id").
|
||||
Where("users_namespace.namespace_id = ?", un.NamespaceID).
|
||||
Limit(getLimitFromPageIndex(page)).
|
||||
Where("users.username LIKE ?", "%"+search+"%").
|
||||
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, page int) (interface{}, error) {
|
||||
func (tl *TeamList) ReadAll(search string, 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 {
|
||||
|
@ -18,6 +18,7 @@ func (tl *TeamList) ReadAll(u *User, page int) (interface{}, error) {
|
|||
Join("INNER", "team_list", "team_id = teams.id").
|
||||
Where("team_list.list_id = ?", tl.ListID).
|
||||
Limit(getLimitFromPageIndex(page)).
|
||||
Where("teams.name LIKE ?", "%"+search+"%").
|
||||
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, 1)
|
||||
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, 1)
|
||||
_, 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, 1)
|
||||
_, 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, 1)
|
||||
_, 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, page int) (interface{}, error) {
|
||||
func (tn *TeamNamespace) ReadAll(search string, user *User, page int) (interface{}, error) {
|
||||
// Check if the user can read the namespace
|
||||
n, err := GetNamespaceByID(tn.NamespaceID)
|
||||
if err != nil {
|
||||
|
@ -18,6 +18,7 @@ func (tn *TeamNamespace) ReadAll(user *User, page int) (interface{}, error) {
|
|||
Join("INNER", "team_namespaces", "team_id = teams.id").
|
||||
Where("team_namespaces.namespace_id = ?", tn.NamespaceID).
|
||||
Limit(getLimitFromPageIndex(page)).
|
||||
Where("teams.name LIKE ?", "%"+search+"%").
|
||||
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, 1)
|
||||
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, 1)
|
||||
_, 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, 1)
|
||||
_, err = tn.ReadAll("", nouser, 1)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrNeedToHaveNamespaceReadAccess(err))
|
||||
|
||||
|
|
|
@ -84,13 +84,14 @@ func (t *Team) ReadOne() (err error) {
|
|||
}
|
||||
|
||||
// ReadAll gets all teams the user is part of
|
||||
func (t *Team) ReadAll(user *User, page int) (teams interface{}, err error) {
|
||||
func (t *Team) ReadAll(search string, 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)).
|
||||
Where("teams.name LIKE ?", "%"+search+"%").
|
||||
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, 1)
|
||||
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, -1)
|
||||
tasks, err := models.GetTasksByUser("", &u, -1)
|
||||
if err != nil {
|
||||
return crud.HandleHTTPError(err)
|
||||
}
|
||||
|
|
|
@ -37,7 +37,10 @@ func (c *WebHandler) ReadAllWeb(ctx echo.Context) error {
|
|||
return echo.NewHTTPError(http.StatusBadRequest, "Bad page requested.")
|
||||
}
|
||||
|
||||
lists, err := currentStruct.ReadAll(¤tUser, pageNumber)
|
||||
// Search
|
||||
search := ctx.QueryParam("s")
|
||||
|
||||
lists, err := currentStruct.ReadAll(search, ¤tUser, pageNumber)
|
||||
if err != nil {
|
||||
return HandleHTTPError(err)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue