Ensure case insensitive search on postgres (#927)
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/927 Co-authored-by: konrad <konrad@kola-entertainments.de> Co-committed-by: konrad <konrad@kola-entertainments.de>
This commit is contained in:
parent
9c2a59582a
commit
4c5f457313
23 changed files with 308 additions and 108 deletions
35
pkg/db/helpers.go
Normal file
35
pkg/db/helpers.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// Vikunja is a to-do list application to facilitate your life.
|
||||||
|
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public Licensee for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public Licensee
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"xorm.io/builder"
|
||||||
|
"xorm.io/xorm/schemas"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ILIKE returns an ILIKE query on postgres and a LIKE query on all other platforms.
|
||||||
|
// Postgres' is case sensitive by default.
|
||||||
|
// To work around this, we're using ILIKE as opposed to normal LIKE statements.
|
||||||
|
// ILIKE is preferred over LOWER(text) LIKE for performance reasons.
|
||||||
|
// See https://stackoverflow.com/q/7005302/10924593
|
||||||
|
func ILIKE(column, search string) builder.Cond {
|
||||||
|
if Type() == schemas.POSTGRES {
|
||||||
|
return builder.Expr(column+" ILIKE ?", "%"+search+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &builder.Like{column, "%" + search + "%"}
|
||||||
|
}
|
|
@ -21,9 +21,12 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.vikunja.io/api/pkg/db"
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/log"
|
"code.vikunja.io/api/pkg/log"
|
||||||
"code.vikunja.io/api/pkg/user"
|
"code.vikunja.io/api/pkg/user"
|
||||||
"code.vikunja.io/web"
|
"code.vikunja.io/web"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
@ -200,7 +203,7 @@ func getLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*lab
|
||||||
if len(ids) > 0 {
|
if len(ids) > 0 {
|
||||||
cond = builder.And(cond, builder.In("labels.id", ids))
|
cond = builder.And(cond, builder.In("labels.id", ids))
|
||||||
} else {
|
} else {
|
||||||
cond = builder.And(cond, &builder.Like{"labels.title", "%" + opts.Search + "%"})
|
cond = builder.And(cond, db.ILIKE("labels.title", opts.Search))
|
||||||
}
|
}
|
||||||
|
|
||||||
limit, start := getLimitFromPageIndex(opts.Page, opts.PerPage)
|
limit, start := getLimitFromPageIndex(opts.Page, opts.PerPage)
|
||||||
|
|
|
@ -30,6 +30,24 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLabelTask_ReadAll(t *testing.T) {
|
func TestLabelTask_ReadAll(t *testing.T) {
|
||||||
|
label := Label{
|
||||||
|
ID: 4,
|
||||||
|
Title: "Label #4 - visible via other task",
|
||||||
|
Created: testCreatedTime,
|
||||||
|
Updated: testUpdatedTime,
|
||||||
|
CreatedByID: 2,
|
||||||
|
CreatedBy: &user.User{
|
||||||
|
ID: 2,
|
||||||
|
Username: "user2",
|
||||||
|
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||||
|
Issuer: "local",
|
||||||
|
EmailRemindersEnabled: true,
|
||||||
|
OverdueTasksRemindersEnabled: true,
|
||||||
|
Created: testCreatedTime,
|
||||||
|
Updated: testUpdatedTime,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
ID int64
|
ID int64
|
||||||
TaskID int64
|
TaskID int64
|
||||||
|
@ -62,23 +80,7 @@ func TestLabelTask_ReadAll(t *testing.T) {
|
||||||
wantLabels: []*labelWithTaskID{
|
wantLabels: []*labelWithTaskID{
|
||||||
{
|
{
|
||||||
TaskID: 1,
|
TaskID: 1,
|
||||||
Label: Label{
|
Label: label,
|
||||||
ID: 4,
|
|
||||||
Title: "Label #4 - visible via other task",
|
|
||||||
Created: testCreatedTime,
|
|
||||||
Updated: testUpdatedTime,
|
|
||||||
CreatedByID: 2,
|
|
||||||
CreatedBy: &user.User{
|
|
||||||
ID: 2,
|
|
||||||
Username: "user2",
|
|
||||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
|
||||||
Issuer: "local",
|
|
||||||
EmailRemindersEnabled: true,
|
|
||||||
OverdueTasksRemindersEnabled: true,
|
|
||||||
Created: testCreatedTime,
|
|
||||||
Updated: testUpdatedTime,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -104,6 +106,22 @@ func TestLabelTask_ReadAll(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
errType: IsErrTaskDoesNotExist,
|
errType: IsErrTaskDoesNotExist,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "search",
|
||||||
|
fields: fields{
|
||||||
|
TaskID: 1,
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
a: &user.User{ID: 1},
|
||||||
|
search: "VISIBLE",
|
||||||
|
},
|
||||||
|
wantLabels: []*labelWithTaskID{
|
||||||
|
{
|
||||||
|
TaskID: 1,
|
||||||
|
Label: label,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
|
@ -20,12 +20,15 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"code.vikunja.io/api/pkg/db"
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/user"
|
"code.vikunja.io/api/pkg/user"
|
||||||
"code.vikunja.io/api/pkg/utils"
|
"code.vikunja.io/api/pkg/utils"
|
||||||
|
|
||||||
"code.vikunja.io/web"
|
"code.vikunja.io/web"
|
||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"xorm.io/builder"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -206,7 +209,14 @@ func (share *LinkSharing) ReadAll(s *xorm.Session, a web.Auth, search string, pa
|
||||||
|
|
||||||
var shares []*LinkSharing
|
var shares []*LinkSharing
|
||||||
query := s.
|
query := s.
|
||||||
Where("list_id = ? AND hash LIKE ?", share.ListID, "%"+search+"%")
|
Where(builder.And(
|
||||||
|
builder.Eq{"list_id": share.ListID},
|
||||||
|
builder.Or(
|
||||||
|
db.ILIKE("hash", search),
|
||||||
|
db.ILIKE("name", search),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
|
||||||
if limit > 0 {
|
if limit > 0 {
|
||||||
query = query.Limit(limit, start)
|
query = query.Limit(limit, start)
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,21 @@ func TestLinkSharing_ReadAll(t *testing.T) {
|
||||||
assert.Empty(t, sharing.Password)
|
assert.Empty(t, sharing.Password)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
t.Run("search", func(t *testing.T) {
|
||||||
|
db.LoadAndAssertFixtures(t)
|
||||||
|
s := db.NewSession()
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
share := &LinkSharing{
|
||||||
|
ListID: 1,
|
||||||
|
}
|
||||||
|
all, _, _, err := share.ReadAll(s, doer, "wITHPASS", 1, -1)
|
||||||
|
shares := all.([]*LinkSharing)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, shares, 1)
|
||||||
|
assert.Equal(t, int64(4), shares[0].ID)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLinkSharing_ReadOne(t *testing.T) {
|
func TestLinkSharing_ReadOne(t *testing.T) {
|
||||||
|
|
|
@ -21,6 +21,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.vikunja.io/api/pkg/db"
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/config"
|
"code.vikunja.io/api/pkg/config"
|
||||||
"code.vikunja.io/api/pkg/events"
|
"code.vikunja.io/api/pkg/events"
|
||||||
"code.vikunja.io/api/pkg/files"
|
"code.vikunja.io/api/pkg/files"
|
||||||
|
@ -403,10 +405,9 @@ func getRawListsForUser(s *xorm.Session, opts *listOptions) (lists []*List, resu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filterCond = db.ILIKE("l.title", opts.search)
|
||||||
if len(ids) > 0 {
|
if len(ids) > 0 {
|
||||||
filterCond = builder.In("l.id", ids)
|
filterCond = builder.In("l.id", ids)
|
||||||
} else {
|
|
||||||
filterCond = &builder.Like{"l.title", "%" + opts.search + "%"}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets all Lists where the user is either owner or in a team which has access to the list
|
// Gets all Lists where the user is either owner or in a team which has access to the list
|
||||||
|
|
|
@ -19,6 +19,8 @@ package models
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.vikunja.io/api/pkg/db"
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/events"
|
"code.vikunja.io/api/pkg/events"
|
||||||
|
|
||||||
"code.vikunja.io/web"
|
"code.vikunja.io/web"
|
||||||
|
@ -198,7 +200,7 @@ func (tl *TeamList) ReadAll(s *xorm.Session, a web.Auth, search string, page int
|
||||||
Table("teams").
|
Table("teams").
|
||||||
Join("INNER", "team_lists", "team_id = teams.id").
|
Join("INNER", "team_lists", "team_id = teams.id").
|
||||||
Where("team_lists.list_id = ?", tl.ListID).
|
Where("team_lists.list_id = ?", tl.ListID).
|
||||||
Where("teams.name LIKE ?", "%"+search+"%")
|
Where(db.ILIKE("teams.name", search))
|
||||||
if limit > 0 {
|
if limit > 0 {
|
||||||
query = query.Limit(limit, start)
|
query = query.Limit(limit, start)
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,20 @@ func TestTeamList_ReadAll(t *testing.T) {
|
||||||
assert.True(t, IsErrNeedToHaveListReadAccess(err))
|
assert.True(t, IsErrNeedToHaveListReadAccess(err))
|
||||||
_ = s.Close()
|
_ = s.Close()
|
||||||
})
|
})
|
||||||
|
t.Run("search", func(t *testing.T) {
|
||||||
|
tl := TeamList{
|
||||||
|
ListID: 19,
|
||||||
|
}
|
||||||
|
db.LoadAndAssertFixtures(t)
|
||||||
|
s := db.NewSession()
|
||||||
|
teams, _, _, err := tl.ReadAll(s, u, "TEAM9", 1, 50)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, reflect.TypeOf(teams).Kind(), reflect.Slice)
|
||||||
|
ts := teams.([]*TeamWithRight)
|
||||||
|
assert.Len(t, ts, 1)
|
||||||
|
assert.Equal(t, int64(9), ts[0].ID)
|
||||||
|
_ = s.Close()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTeamList_Create(t *testing.T) {
|
func TestTeamList_Create(t *testing.T) {
|
||||||
|
|
|
@ -217,6 +217,19 @@ func TestList_ReadAll(t *testing.T) {
|
||||||
assert.True(t, user.IsErrUserDoesNotExist(err))
|
assert.True(t, user.IsErrUserDoesNotExist(err))
|
||||||
_ = s.Close()
|
_ = s.Close()
|
||||||
})
|
})
|
||||||
|
t.Run("search", func(t *testing.T) {
|
||||||
|
db.LoadAndAssertFixtures(t)
|
||||||
|
s := db.NewSession()
|
||||||
|
u := &user.User{ID: 1}
|
||||||
|
list := List{}
|
||||||
|
lists3, _, _, err := list.ReadAll(s, u, "TEST10", 1, 50)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
ls := lists3.([]*List)
|
||||||
|
assert.Equal(t, 1, len(ls))
|
||||||
|
assert.Equal(t, int64(10), ls[0].ID)
|
||||||
|
_ = s.Close()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestList_ReadOne(t *testing.T) {
|
func TestList_ReadOne(t *testing.T) {
|
||||||
|
|
|
@ -19,6 +19,8 @@ package models
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.vikunja.io/api/pkg/db"
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/events"
|
"code.vikunja.io/api/pkg/events"
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/user"
|
"code.vikunja.io/api/pkg/user"
|
||||||
|
@ -204,7 +206,7 @@ func (lu *ListUser) ReadAll(s *xorm.Session, a web.Auth, search string, page int
|
||||||
query := s.
|
query := s.
|
||||||
Join("INNER", "users_lists", "user_id = users.id").
|
Join("INNER", "users_lists", "user_id = users.id").
|
||||||
Where("users_lists.list_id = ?", lu.ListID).
|
Where("users_lists.list_id = ?", lu.ListID).
|
||||||
Where("users.username LIKE ?", "%"+search+"%")
|
Where(db.ILIKE("users.username", search))
|
||||||
if limit > 0 {
|
if limit > 0 {
|
||||||
query = query.Limit(limit, start)
|
query = query.Limit(limit, start)
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,6 +143,33 @@ func TestListUser_Create(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListUser_ReadAll(t *testing.T) {
|
func TestListUser_ReadAll(t *testing.T) {
|
||||||
|
user1Read := &UserWithRight{
|
||||||
|
User: user.User{
|
||||||
|
ID: 1,
|
||||||
|
Username: "user1",
|
||||||
|
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||||
|
Issuer: "local",
|
||||||
|
EmailRemindersEnabled: true,
|
||||||
|
OverdueTasksRemindersEnabled: true,
|
||||||
|
Created: testCreatedTime,
|
||||||
|
Updated: testUpdatedTime,
|
||||||
|
},
|
||||||
|
Right: RightRead,
|
||||||
|
}
|
||||||
|
user2Read := &UserWithRight{
|
||||||
|
User: user.User{
|
||||||
|
ID: 2,
|
||||||
|
Username: "user2",
|
||||||
|
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||||
|
Issuer: "local",
|
||||||
|
EmailRemindersEnabled: true,
|
||||||
|
OverdueTasksRemindersEnabled: true,
|
||||||
|
Created: testCreatedTime,
|
||||||
|
Updated: testUpdatedTime,
|
||||||
|
},
|
||||||
|
Right: RightRead,
|
||||||
|
}
|
||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
ID int64
|
ID int64
|
||||||
UserID int64
|
UserID int64
|
||||||
|
@ -175,32 +202,8 @@ func TestListUser_ReadAll(t *testing.T) {
|
||||||
a: &user.User{ID: 3},
|
a: &user.User{ID: 3},
|
||||||
},
|
},
|
||||||
want: []*UserWithRight{
|
want: []*UserWithRight{
|
||||||
{
|
user1Read,
|
||||||
User: user.User{
|
user2Read,
|
||||||
ID: 1,
|
|
||||||
Username: "user1",
|
|
||||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
|
||||||
Issuer: "local",
|
|
||||||
EmailRemindersEnabled: true,
|
|
||||||
OverdueTasksRemindersEnabled: true,
|
|
||||||
Created: testCreatedTime,
|
|
||||||
Updated: testUpdatedTime,
|
|
||||||
},
|
|
||||||
Right: RightRead,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
User: user.User{
|
|
||||||
ID: 2,
|
|
||||||
Username: "user2",
|
|
||||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
|
||||||
Issuer: "local",
|
|
||||||
EmailRemindersEnabled: true,
|
|
||||||
OverdueTasksRemindersEnabled: true,
|
|
||||||
Created: testCreatedTime,
|
|
||||||
Updated: testUpdatedTime,
|
|
||||||
},
|
|
||||||
Right: RightRead,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -214,6 +217,19 @@ func TestListUser_ReadAll(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
errType: IsErrNeedToHaveListReadAccess,
|
errType: IsErrNeedToHaveListReadAccess,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Search",
|
||||||
|
fields: fields{
|
||||||
|
ListID: 3,
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
a: &user.User{ID: 3},
|
||||||
|
search: "USER2",
|
||||||
|
},
|
||||||
|
want: []*UserWithRight{
|
||||||
|
user2Read,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
|
@ -22,10 +22,12 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/events"
|
"code.vikunja.io/api/pkg/db"
|
||||||
|
|
||||||
|
"code.vikunja.io/api/pkg/events"
|
||||||
"code.vikunja.io/api/pkg/log"
|
"code.vikunja.io/api/pkg/log"
|
||||||
"code.vikunja.io/api/pkg/user"
|
"code.vikunja.io/api/pkg/user"
|
||||||
|
|
||||||
"code.vikunja.io/web"
|
"code.vikunja.io/web"
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
|
@ -200,7 +202,7 @@ func makeNamespaceSlice(namespaces map[int64]*NamespaceWithLists, userMap map[in
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNamespaceFilterCond(search string) (filterCond builder.Cond) {
|
func getNamespaceFilterCond(search string) (filterCond builder.Cond) {
|
||||||
filterCond = &builder.Like{"namespaces.title", "%" + search + "%"}
|
filterCond = db.ILIKE("namespaces.title", search)
|
||||||
|
|
||||||
if search == "" {
|
if search == "" {
|
||||||
return
|
return
|
||||||
|
|
|
@ -19,9 +19,11 @@ package models
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/events"
|
"code.vikunja.io/api/pkg/db"
|
||||||
|
|
||||||
|
"code.vikunja.io/api/pkg/events"
|
||||||
"code.vikunja.io/web"
|
"code.vikunja.io/web"
|
||||||
|
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -178,14 +180,12 @@ func (tn *TeamNamespace) ReadAll(s *xorm.Session, a web.Auth, search string, pag
|
||||||
|
|
||||||
// Get the teams
|
// Get the teams
|
||||||
all := []*TeamWithRight{}
|
all := []*TeamWithRight{}
|
||||||
|
|
||||||
limit, start := getLimitFromPageIndex(page, perPage)
|
limit, start := getLimitFromPageIndex(page, perPage)
|
||||||
|
|
||||||
query := s.
|
query := s.
|
||||||
Table("teams").
|
Table("teams").
|
||||||
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).
|
||||||
Where("teams.name LIKE ?", "%"+search+"%")
|
Where(db.ILIKE("teams.name", search))
|
||||||
if limit > 0 {
|
if limit > 0 {
|
||||||
query = query.Limit(limit, start)
|
query = query.Limit(limit, start)
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,21 @@ func TestTeamNamespace_ReadAll(t *testing.T) {
|
||||||
assert.True(t, IsErrNeedToHaveNamespaceReadAccess(err))
|
assert.True(t, IsErrNeedToHaveNamespaceReadAccess(err))
|
||||||
_ = s.Close()
|
_ = s.Close()
|
||||||
})
|
})
|
||||||
|
t.Run("search", func(t *testing.T) {
|
||||||
|
tn := TeamNamespace{
|
||||||
|
NamespaceID: 3,
|
||||||
|
}
|
||||||
|
db.LoadAndAssertFixtures(t)
|
||||||
|
s := db.NewSession()
|
||||||
|
teams, _, _, err := tn.ReadAll(s, u, "READ_only_on_list6", 1, 50)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, reflect.TypeOf(teams).Kind(), reflect.Slice)
|
||||||
|
ts := teams.([]*TeamWithRight)
|
||||||
|
assert.Len(t, ts, 1)
|
||||||
|
assert.Equal(t, int64(2), ts[0].ID)
|
||||||
|
|
||||||
|
_ = s.Close()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTeamNamespace_Create(t *testing.T) {
|
func TestTeamNamespace_Create(t *testing.T) {
|
||||||
|
|
|
@ -356,4 +356,17 @@ func TestNamespace_ReadAll(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Nil(t, nn)
|
assert.Nil(t, nn)
|
||||||
})
|
})
|
||||||
|
t.Run("search", func(t *testing.T) {
|
||||||
|
db.LoadAndAssertFixtures(t)
|
||||||
|
s := db.NewSession()
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
n := &Namespace{}
|
||||||
|
nn, _, _, err := n.ReadAll(s, user6, "NamespACE7", 1, -1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
namespaces := nn.([]*NamespaceWithLists)
|
||||||
|
assert.NotNil(t, namespaces)
|
||||||
|
assert.Len(t, namespaces, 2)
|
||||||
|
assert.Equal(t, int64(7), namespaces[1].ID)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,12 @@ package models
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/events"
|
"code.vikunja.io/api/pkg/db"
|
||||||
|
|
||||||
|
"code.vikunja.io/api/pkg/events"
|
||||||
user2 "code.vikunja.io/api/pkg/user"
|
user2 "code.vikunja.io/api/pkg/user"
|
||||||
"code.vikunja.io/web"
|
"code.vikunja.io/web"
|
||||||
|
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -185,13 +187,11 @@ func (nu *NamespaceUser) ReadAll(s *xorm.Session, a web.Auth, search string, pag
|
||||||
|
|
||||||
// Get all users
|
// Get all users
|
||||||
all := []*UserWithRight{}
|
all := []*UserWithRight{}
|
||||||
|
|
||||||
limit, start := getLimitFromPageIndex(page, perPage)
|
limit, start := getLimitFromPageIndex(page, perPage)
|
||||||
|
|
||||||
query := s.
|
query := s.
|
||||||
Join("INNER", "users_namespaces", "user_id = users.id").
|
Join("INNER", "users_namespaces", "user_id = users.id").
|
||||||
Where("users_namespaces.namespace_id = ?", nu.NamespaceID).
|
Where("users_namespaces.namespace_id = ?", nu.NamespaceID).
|
||||||
Where("users.username LIKE ?", "%"+search+"%")
|
Where(db.ILIKE("users.username", search))
|
||||||
if limit > 0 {
|
if limit > 0 {
|
||||||
query = query.Limit(limit, start)
|
query = query.Limit(limit, start)
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,6 +142,33 @@ func TestNamespaceUser_Create(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNamespaceUser_ReadAll(t *testing.T) {
|
func TestNamespaceUser_ReadAll(t *testing.T) {
|
||||||
|
user1 := &UserWithRight{
|
||||||
|
User: user.User{
|
||||||
|
ID: 1,
|
||||||
|
Username: "user1",
|
||||||
|
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||||
|
Issuer: "local",
|
||||||
|
EmailRemindersEnabled: true,
|
||||||
|
OverdueTasksRemindersEnabled: true,
|
||||||
|
Created: testCreatedTime,
|
||||||
|
Updated: testUpdatedTime,
|
||||||
|
},
|
||||||
|
Right: RightRead,
|
||||||
|
}
|
||||||
|
user2 := &UserWithRight{
|
||||||
|
User: user.User{
|
||||||
|
ID: 2,
|
||||||
|
Username: "user2",
|
||||||
|
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||||
|
Issuer: "local",
|
||||||
|
EmailRemindersEnabled: true,
|
||||||
|
OverdueTasksRemindersEnabled: true,
|
||||||
|
Created: testCreatedTime,
|
||||||
|
Updated: testUpdatedTime,
|
||||||
|
},
|
||||||
|
Right: RightRead,
|
||||||
|
}
|
||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
ID int64
|
ID int64
|
||||||
UserID int64
|
UserID int64
|
||||||
|
@ -174,32 +201,8 @@ func TestNamespaceUser_ReadAll(t *testing.T) {
|
||||||
a: &user.User{ID: 3},
|
a: &user.User{ID: 3},
|
||||||
},
|
},
|
||||||
want: []*UserWithRight{
|
want: []*UserWithRight{
|
||||||
{
|
user1,
|
||||||
User: user.User{
|
user2,
|
||||||
ID: 1,
|
|
||||||
Username: "user1",
|
|
||||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
|
||||||
Issuer: "local",
|
|
||||||
EmailRemindersEnabled: true,
|
|
||||||
OverdueTasksRemindersEnabled: true,
|
|
||||||
Created: testCreatedTime,
|
|
||||||
Updated: testUpdatedTime,
|
|
||||||
},
|
|
||||||
Right: RightRead,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
User: user.User{
|
|
||||||
ID: 2,
|
|
||||||
Username: "user2",
|
|
||||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
|
||||||
Issuer: "local",
|
|
||||||
EmailRemindersEnabled: true,
|
|
||||||
OverdueTasksRemindersEnabled: true,
|
|
||||||
Created: testCreatedTime,
|
|
||||||
Updated: testUpdatedTime,
|
|
||||||
},
|
|
||||||
Right: RightRead,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -213,6 +216,19 @@ func TestNamespaceUser_ReadAll(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
errType: IsErrNeedToHaveNamespaceReadAccess,
|
errType: IsErrNeedToHaveNamespaceReadAccess,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Search",
|
||||||
|
fields: fields{
|
||||||
|
NamespaceID: 3,
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
a: &user.User{ID: 3},
|
||||||
|
search: "usER2",
|
||||||
|
},
|
||||||
|
want: []*UserWithRight{
|
||||||
|
user2,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
|
@ -19,6 +19,10 @@ package models
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.vikunja.io/api/pkg/db"
|
||||||
|
|
||||||
|
"xorm.io/builder"
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/events"
|
"code.vikunja.io/api/pkg/events"
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/user"
|
"code.vikunja.io/api/pkg/user"
|
||||||
|
@ -267,12 +271,14 @@ func (la *TaskAssginee) ReadAll(s *xorm.Session, a web.Auth, search string, page
|
||||||
return nil, 0, 0, ErrGenericForbidden{}
|
return nil, 0, 0, ErrGenericForbidden{}
|
||||||
}
|
}
|
||||||
limit, start := getLimitFromPageIndex(page, perPage)
|
limit, start := getLimitFromPageIndex(page, perPage)
|
||||||
|
|
||||||
var taskAssignees []*user.User
|
var taskAssignees []*user.User
|
||||||
query := s.Table("task_assignees").
|
query := s.Table("task_assignees").
|
||||||
Select("users.*").
|
Select("users.*").
|
||||||
Join("INNER", "users", "task_assignees.user_id = users.id").
|
Join("INNER", "users", "task_assignees.user_id = users.id").
|
||||||
Where("task_id = ? AND users.username LIKE ?", la.TaskID, "%"+search+"%")
|
Where(builder.And(
|
||||||
|
builder.Eq{"task_id": la.TaskID},
|
||||||
|
db.ILIKE("users.username", search),
|
||||||
|
))
|
||||||
if limit > 0 {
|
if limit > 0 {
|
||||||
query = query.Limit(limit, start)
|
query = query.Limit(limit, start)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,14 @@ package models
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.vikunja.io/api/pkg/db"
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/events"
|
"code.vikunja.io/api/pkg/events"
|
||||||
|
|
||||||
"xorm.io/xorm"
|
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/user"
|
"code.vikunja.io/api/pkg/user"
|
||||||
"code.vikunja.io/web"
|
"code.vikunja.io/web"
|
||||||
|
|
||||||
|
"xorm.io/builder"
|
||||||
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TaskComment represents a task comment
|
// TaskComment represents a task comment
|
||||||
|
@ -214,10 +216,12 @@ func (tc *TaskComment) ReadAll(s *xorm.Session, auth web.Auth, search string, pa
|
||||||
}
|
}
|
||||||
|
|
||||||
limit, start := getLimitFromPageIndex(page, perPage)
|
limit, start := getLimitFromPageIndex(page, perPage)
|
||||||
|
|
||||||
comments := []*TaskComment{}
|
comments := []*TaskComment{}
|
||||||
query := s.
|
query := s.
|
||||||
Where("task_id = ? AND comment like ?", tc.TaskID, "%"+search+"%").
|
Where(builder.And(
|
||||||
|
builder.Eq{"task_id": tc.TaskID},
|
||||||
|
db.ILIKE("comment", search),
|
||||||
|
)).
|
||||||
Join("LEFT", "users", "users.id = task_comments.author_id")
|
Join("LEFT", "users", "users.id = task_comments.author_id")
|
||||||
if limit > 0 {
|
if limit > 0 {
|
||||||
query = query.Limit(limit, start)
|
query = query.Limit(limit, start)
|
||||||
|
|
|
@ -233,4 +233,16 @@ func TestTaskComment_ReadAll(t *testing.T) {
|
||||||
}
|
}
|
||||||
assert.True(t, foundComment)
|
assert.True(t, foundComment)
|
||||||
})
|
})
|
||||||
|
t.Run("normal", func(t *testing.T) {
|
||||||
|
db.LoadAndAssertFixtures(t)
|
||||||
|
s := db.NewSession()
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
tc := &TaskComment{TaskID: 35}
|
||||||
|
u := &user.User{ID: 1}
|
||||||
|
result, _, _, err := tc.ReadAll(s, u, "COMMENT 15", 0, -1)
|
||||||
|
resultComment := result.([]*TaskComment)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(15), resultComment[0].ID)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -369,16 +369,7 @@ func getRawTasksForLists(s *xorm.Session, lists []*List, a web.Auth, opts *taskO
|
||||||
var where builder.Cond
|
var where builder.Cond
|
||||||
|
|
||||||
if opts.search != "" {
|
if opts.search != "" {
|
||||||
// Postgres' is case sensitive by default.
|
where = db.ILIKE("title", opts.search)
|
||||||
// To work around this, we're using ILIKE as opposed to normal LIKE statements.
|
|
||||||
// ILIKE is preferred over LOWER(text) LIKE for performance reasons.
|
|
||||||
// See https://stackoverflow.com/q/7005302/10924593
|
|
||||||
// Seems okay to use that now, we may need to find a better solution overall in the future.
|
|
||||||
if config.DatabaseType.GetString() == "postgres" {
|
|
||||||
where = builder.Expr("title ILIKE ?", "%"+opts.search+"%")
|
|
||||||
} else {
|
|
||||||
where = &builder.Like{"title", "%" + opts.search + "%"}
|
|
||||||
}
|
|
||||||
|
|
||||||
searchIndex := getTaskIndexFromSearchString(opts.search)
|
searchIndex := getTaskIndexFromSearchString(opts.search)
|
||||||
if searchIndex > 0 {
|
if searchIndex > 0 {
|
||||||
|
|
|
@ -19,13 +19,14 @@ package models
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.vikunja.io/api/pkg/db"
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/events"
|
"code.vikunja.io/api/pkg/events"
|
||||||
|
|
||||||
"xorm.io/xorm"
|
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/user"
|
"code.vikunja.io/api/pkg/user"
|
||||||
"code.vikunja.io/web"
|
"code.vikunja.io/web"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Team holds a team object
|
// Team holds a team object
|
||||||
|
@ -210,13 +211,12 @@ func (t *Team) ReadAll(s *xorm.Session, a web.Auth, search string, page int, per
|
||||||
}
|
}
|
||||||
|
|
||||||
limit, start := getLimitFromPageIndex(page, perPage)
|
limit, start := getLimitFromPageIndex(page, perPage)
|
||||||
|
|
||||||
all := []*Team{}
|
all := []*Team{}
|
||||||
query := s.Select("teams.*").
|
query := s.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 = ?", a.GetID()).
|
Where("team_members.user_id = ?", a.GetID()).
|
||||||
Where("teams.name LIKE ?", "%"+search+"%")
|
Where(db.ILIKE("teams.name", search))
|
||||||
if limit > 0 {
|
if limit > 0 {
|
||||||
query = query.Limit(limit, start)
|
query = query.Limit(limit, start)
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,18 @@ func TestTeam_ReadAll(t *testing.T) {
|
||||||
ts := reflect.ValueOf(teams)
|
ts := reflect.ValueOf(teams)
|
||||||
assert.Equal(t, 8, ts.Len())
|
assert.Equal(t, 8, ts.Len())
|
||||||
})
|
})
|
||||||
|
t.Run("search", func(t *testing.T) {
|
||||||
|
s := db.NewSession()
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
team := &Team{}
|
||||||
|
teams, _, _, err := team.ReadAll(s, doer, "READ_only_on_list6", 1, 50)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, reflect.TypeOf(teams).Kind(), reflect.Slice)
|
||||||
|
ts := teams.([]*Team)
|
||||||
|
assert.Len(t, ts, 1)
|
||||||
|
assert.Equal(t, int64(2), ts[0].ID)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTeam_Update(t *testing.T) {
|
func TestTeam_Update(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue