Implemented search for everything (#17)

This commit is contained in:
konrad 2018-11-09 17:33:06 +00:00 committed by Gitea
parent ad74d24ee2
commit d3de658882
18 changed files with 49 additions and 32 deletions

View file

@ -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] 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] 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) * [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. * [x] General search endpoints
-> Search methode in den Handler einbauen und dann die Endpoints entsprechend anpassen integrieren kann (so nach dem Motto einfach nen Searchstring anhängen)
* [ ] Wir brauchen noch ne gute idee, wie man die listen kriegt, auf die man nur so Zugriff hat (ohne namespace) * [ ] 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 * 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 * [ ] Validation der ankommenden structs, am besten mit https://github.com/go-validator/validator oder mit dem Ding von echo

View file

@ -4,7 +4,7 @@ Content-Type: application/json
{ {
"username": "user", "username": "user",
"password": "1234" "password": "12345"
} }
> {% client.global.set("auth_token", response.body.token); %} > {% client.global.set("auth_token", response.body.token); %}

View file

@ -14,7 +14,7 @@ The interface is defined as followed:
type CRUDable interface { type CRUDable interface {
Create(*User) error Create(*User) error
ReadOne() error ReadOne() error
ReadAll(*User, int) (interface{}, error) ReadAll(string, *User, int) (interface{}, error)
Update() error Update() error
Delete() 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 ### 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")`). 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 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) 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 ## Rights
This interface defines methods to check for rights on structs. They accept a `User` as parameter and usually return a `bool`. This interface defines methods to check for rights on structs. They accept a `User` as parameter and usually return a `bool`.

View file

@ -4,7 +4,7 @@ package models
type CRUDable interface { type CRUDable interface {
Create(*User) error Create(*User) error
ReadOne() error ReadOne() error
ReadAll(*User, int) (interface{}, error) ReadAll(string, *User, int) (interface{}, error)
Update() error Update() error
Delete() error Delete() error
} }

View file

@ -27,8 +27,8 @@ func GetListsByNamespaceID(nID int64) (lists []*List, err error) {
} }
// ReadAll gets all lists a user has access to // ReadAll gets all lists a user has access to
func (l *List) ReadAll(u *User, page int) (interface{}, error) { func (l *List) ReadAll(search string, u *User, page int) (interface{}, error) {
lists, err := getRawListsForUser(u, page) lists, err := getRawListsForUser(search, u, page)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -80,7 +80,7 @@ func (l *List) GetSimpleByID() (err error) {
} }
// Gets the lists only, without any tasks or so // 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) fullUser, err := GetUserByID(u.ID)
if err != nil { if err != nil {
return lists, err return lists, err
@ -105,6 +105,7 @@ func getRawListsForUser(u *User, page int) (lists []*List, err error) {
Or("un.user_id = ?", fullUser.ID). Or("un.user_id = ?", fullUser.ID).
GroupBy("l.id"). GroupBy("l.id").
Limit(getLimitFromPageIndex(page)). Limit(getLimitFromPageIndex(page)).
Where("l.title LIKE ?", "%"+search+"%").
Find(&lists) Find(&lists)
return lists, err return lists, err
@ -161,14 +162,14 @@ type ListTasksDummy struct {
} }
// ReadAll gets all tasks for a user // ReadAll gets all tasks for a user
func (lt *ListTasksDummy) ReadAll(u *User, page int) (interface{}, error) { func (lt *ListTasksDummy) ReadAll(search string, u *User, page int) (interface{}, error) {
return GetTasksByUser(u, page) return GetTasksByUser(search, u, page)
} }
//GetTasksByUser returns all tasks for a user //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 // Get all lists
lists, err := getRawListsForUser(u, page) lists, err := getRawListsForUser(search, u, page)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -20,14 +20,14 @@ func TestList_ReadAll(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
lists2 := List{} lists2 := List{}
lists3, err := lists2.ReadAll(&u, 1) lists3, err := lists2.ReadAll("", &u, 1)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, reflect.TypeOf(lists3).Kind(), reflect.Slice) assert.Equal(t, reflect.TypeOf(lists3).Kind(), reflect.Slice)
s := reflect.ValueOf(lists3) s := reflect.ValueOf(lists3)
assert.Equal(t, s.Len(), 1) assert.Equal(t, s.Len(), 1)
// Try getting lists for a nonexistant user // 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.Error(t, err)
assert.True(t, IsErrUserDoesNotExist(err)) assert.True(t, IsErrUserDoesNotExist(err))
} }

View file

@ -1,7 +1,7 @@
package models package models
// ReadAll gets all users who have access to a list // 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 // Check if the user has access to the list
l := &List{ID: ul.ListID} l := &List{ID: ul.ListID}
if err := l.GetSimpleByID(); err != nil { 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"). Join("INNER", "users_list", "user_id = users.id").
Where("users_list.list_id = ?", ul.ListID). Where("users_list.list_id = ?", ul.ListID).
Limit(getLimitFromPageIndex(page)). Limit(getLimitFromPageIndex(page)).
Where("users.username LIKE ?", "%"+search+"%").
Find(&all) Find(&all)
return all, err return all, err

View file

@ -53,7 +53,7 @@ func (n *Namespace) ReadOne() (err error) {
} }
// ReadAll gets all namespaces a user has access to // 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 { type namespaceWithLists struct {
Namespace `xorm:"extends"` Namespace `xorm:"extends"`
@ -72,6 +72,7 @@ func (n *Namespace) ReadAll(doer *User, page int) (interface{}, error) {
Or("users_namespace.user_id = ?", doer.ID). Or("users_namespace.user_id = ?", doer.ID).
GroupBy("namespaces.id"). GroupBy("namespaces.id").
Limit(getLimitFromPageIndex(page)). Limit(getLimitFromPageIndex(page)).
Where("namespaces.name LIKE ?", "%"+search+"%").
Find(&all) Find(&all)
if err != nil { if err != nil {

View file

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

View file

@ -1,7 +1,7 @@
package models package models
// ReadAll gets all users who have access to a namespace // 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 // Check if the user has access to the namespace
l, err := GetNamespaceByID(un.NamespaceID) l, err := GetNamespaceByID(un.NamespaceID)
if err != nil { 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"). Join("INNER", "users_namespace", "user_id = users.id").
Where("users_namespace.namespace_id = ?", un.NamespaceID). Where("users_namespace.namespace_id = ?", un.NamespaceID).
Limit(getLimitFromPageIndex(page)). Limit(getLimitFromPageIndex(page)).
Where("users.username LIKE ?", "%"+search+"%").
Find(&all) Find(&all)
return all, err return all, err

View file

@ -1,7 +1,7 @@
package models package models
// ReadAll implements the method to read all teams of a list // 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 // Check if the user can read the namespace
l := &List{ID: tl.ListID} l := &List{ID: tl.ListID}
if err := l.GetSimpleByID(); err != nil { 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"). Join("INNER", "team_list", "team_id = teams.id").
Where("team_list.list_id = ?", tl.ListID). Where("team_list.list_id = ?", tl.ListID).
Limit(getLimitFromPageIndex(page)). Limit(getLimitFromPageIndex(page)).
Where("teams.name LIKE ?", "%"+search+"%").
Find(&all) Find(&all)
return all, err return all, err

View file

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

View file

@ -1,7 +1,7 @@
package models package models
// ReadAll implements the method to read all teams of a namespace // 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 // Check if the user can read the namespace
n, err := GetNamespaceByID(tn.NamespaceID) n, err := GetNamespaceByID(tn.NamespaceID)
if err != nil { 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"). Join("INNER", "team_namespaces", "team_id = teams.id").
Where("team_namespaces.namespace_id = ?", tn.NamespaceID). Where("team_namespaces.namespace_id = ?", tn.NamespaceID).
Limit(getLimitFromPageIndex(page)). Limit(getLimitFromPageIndex(page)).
Where("teams.name LIKE ?", "%"+search+"%").
Find(&all) Find(&all)
return all, err return all, err

View file

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

View file

@ -84,13 +84,14 @@ func (t *Team) ReadOne() (err error) {
} }
// ReadAll gets all teams the user is part of // 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{} all := []*Team{}
err = x.Select("teams.*"). err = x.Select("teams.*").
Table("teams"). Table("teams").
Join("INNER", "team_members", "team_members.team_id = teams.id"). Join("INNER", "team_members", "team_members.team_id = teams.id").
Where("team_members.user_id = ?", user.ID). Where("team_members.user_id = ?", user.ID).
Limit(getLimitFromPageIndex(page)). Limit(getLimitFromPageIndex(page)).
Where("teams.name LIKE ?", "%"+search+"%").
Find(&all) Find(&all)
return all, err return all, err

View file

@ -32,7 +32,7 @@ func TestTeam_Create(t *testing.T) {
assert.True(t, dummyteam.CanRead(&doer)) assert.True(t, dummyteam.CanRead(&doer))
// Get all teams the user is part of // 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.NoError(t, err)
assert.Equal(t, reflect.TypeOf(ts).Kind(), reflect.Slice) assert.Equal(t, reflect.TypeOf(ts).Kind(), reflect.Slice)
s := reflect.ValueOf(ts) s := reflect.ValueOf(ts)

View file

@ -39,7 +39,7 @@ func Caldav(c echo.Context) error {
} }
// Get all tasks for that user // Get all tasks for that user
tasks, err := models.GetTasksByUser(&u, -1) tasks, err := models.GetTasksByUser("", &u, -1)
if err != nil { if err != nil {
return crud.HandleHTTPError(err) return crud.HandleHTTPError(err)
} }

View file

@ -37,7 +37,10 @@ func (c *WebHandler) ReadAllWeb(ctx echo.Context) error {
return echo.NewHTTPError(http.StatusBadRequest, "Bad page requested.") return echo.NewHTTPError(http.StatusBadRequest, "Bad page requested.")
} }
lists, err := currentStruct.ReadAll(&currentUser, pageNumber) // Search
search := ctx.QueryParam("s")
lists, err := currentStruct.ReadAll(search, &currentUser, pageNumber)
if err != nil { if err != nil {
return HandleHTTPError(err) return HandleHTTPError(err)
} }