From 78445dce4b6641f892dceff125618035d186eaf5 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 26 Feb 2025 16:21:45 +0100 Subject: [PATCH] Added Filter by Role to user search (#1278) * Added Filter by Role to user search * Lint fix * Removed debug print * Fixed issue * moved filtering from client to api * updated users mock * linter fix and made it clearer what is happening * Update api/users.go Co-authored-by: Joscha Henningsen <44805696+joschahenningsen@users.noreply.github.com> * Added separate error handling for not being able to parse role ID * Fixed code to pass test * resolved conversations * Update users.go --------- Co-authored-by: johanneskarrer Co-authored-by: Joscha Henningsen <44805696+joschahenningsen@users.noreply.github.com> Co-authored-by: Kordian Bruck --- api/users.go | 28 +++++++++++++++++----- dao/users.go | 7 ++++++ mock_dao/users.go | 15 ++++++++++++ web/template/admin/admin_tabs/users.gohtml | 9 +++++++ web/ts/admin.ts | 10 ++++---- 5 files changed, 59 insertions(+), 10 deletions(-) diff --git a/api/users.go b/api/users.go index fa1b910ac..83dc3b2b5 100644 --- a/api/users.go +++ b/api/users.go @@ -8,6 +8,7 @@ import ( "fmt" "net/http" "regexp" + "strconv" "strings" "time" @@ -127,21 +128,36 @@ func (r usersRoutes) updateUser(c *gin.Context) { } func (r usersRoutes) prepareUserSearch(c *gin.Context) (users []model.User, err error) { - q := c.Query("q") - reg, _ := regexp.Compile("[^a-zA-Z0-9 ]+") - q = reg.ReplaceAllString(q, "") - if len(q) < 3 { + query := c.Query("q") + roleQuery := c.Query("r") + reg := regexp.MustCompile("[^a-zA-Z0-9 ]+") + query = reg.ReplaceAllString(query, "") + // make the search work with empty query but selected role + if len(query) < 3 && (roleQuery == "-1" || roleQuery == "") { _ = c.Error(tools.RequestError{ Status: http.StatusBadRequest, CustomMessage: "query too short (minimum length is 3)", }) return nil, errors.New("query too short (minimum length is 3)") } - users, err = r.UsersDao.SearchUser(q) + if roleQuery == "" || roleQuery == "-1" { + users, err = r.UsersDao.SearchUser(query) + } else { + role, err := strconv.ParseUint(roleQuery, 10, 64) + if err != nil { + _ = c.Error(tools.RequestError{ + Status: http.StatusBadRequest, + CustomMessage: "could not parse role", + Err: err, + }) + return nil, err + } + users, err = r.UsersDao.SearchUserWithRole(query, role) + } if err != nil && err != gorm.ErrRecordNotFound { _ = c.Error(tools.RequestError{ Status: http.StatusInternalServerError, - CustomMessage: "can not search user", + CustomMessage: "cannot search for user's", Err: err, }) return nil, err diff --git a/dao/users.go b/dao/users.go index f4d221b59..59d76f2a8 100644 --- a/dao/users.go +++ b/dao/users.go @@ -19,6 +19,7 @@ type UsersDao interface { CreateUser(ctx context.Context, user *model.User) (err error) DeleteUser(ctx context.Context, uid uint) (err error) SearchUser(query string) (users []model.User, err error) + SearchUserWithRole(query string, role uint64) (users []model.User, err error) IsUserAdmin(ctx context.Context, uid uint) (res bool, err error) GetUserByEmail(ctx context.Context, email string) (user model.User, err error) GetAllAdminsAndLecturers(users *[]model.User) (err error) @@ -70,6 +71,12 @@ func (d usersDao) SearchUser(query string) (users []model.User, err error) { return users, res.Error } +func (d usersDao) SearchUserWithRole(query string, role uint64) (users []model.User, err error) { + q := "%" + query + "%" + res := DB.Where("role = ? AND (UPPER(lrz_id) LIKE UPPER(?) OR UPPER(email) LIKE UPPER(?) OR UPPER(name) LIKE UPPER(?))", role, q, q, q).Limit(10).Preload("Settings").Find(&users) + return users, res.Error +} + func (d usersDao) IsUserAdmin(ctx context.Context, uid uint) (res bool, err error) { var user model.User err = DB.Find(&user, "id = ?", uid).Error diff --git a/mock_dao/users.go b/mock_dao/users.go index 8f8e8a05a..6c21480df 100644 --- a/mock_dao/users.go +++ b/mock_dao/users.go @@ -251,6 +251,21 @@ func (mr *MockUsersDaoMockRecorder) SearchUser(query interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchUser", reflect.TypeOf((*MockUsersDao)(nil).SearchUser), query) } +// SearchUserWithRole mocks base method. +func (m *MockUsersDao) SearchUserWithRole(query string, role uint64) ([]model.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SearchUserWithRole", query, role) + ret0, _ := ret[0].([]model.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SearchUserWithRole indicates an expected call of SearchUserWithRole. +func (mr *MockUsersDaoMockRecorder) SearchUserWithRole(query, role interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchUserWithRole", reflect.TypeOf((*MockUsersDao)(nil).SearchUserWithRole), query, role) +} + // UpdateUser mocks base method. func (m *MockUsersDao) UpdateUser(user model.User) error { m.ctrl.T.Helper() diff --git a/web/template/admin/admin_tabs/users.gohtml b/web/template/admin/admin_tabs/users.gohtml index 7460d2344..68bdf4bbd 100644 --- a/web/template/admin/admin_tabs/users.gohtml +++ b/web/template/admin/admin_tabs/users.gohtml @@ -6,6 +6,15 @@

Search

+
+ + +
diff --git a/web/ts/admin.ts b/web/ts/admin.ts index 7d9d955f0..2f6755e49 100755 --- a/web/ts/admin.ts +++ b/web/ts/admin.ts @@ -15,25 +15,27 @@ export class AdminUserList { showSearchResults: boolean; searchLoading: boolean; searchInput: string; + roles: number; constructor(usersAsJson: object[]) { this.list = usersAsJson; this.rowsPerPage = 10; this.showSearchResults = false; this.currentIndex = 0; + this.searchInput = ""; + this.roles = -1; this.numberOfPages = Math.ceil(this.list.length / this.rowsPerPage); this.updateVisibleRows(); } async search() { - if (this.searchInput.length < 3) { + if (this.searchInput.length < 3 && this.roles == -1) { this.showSearchResults = false; this.updateVisibleRows(); return; - } - if (this.searchInput.length > 2) { + } else { this.searchLoading = true; - fetch("/api/searchUser?q=" + this.searchInput) + fetch("/api/searchUser?q=" + this.searchInput + "&r=" + this.roles) .then((response) => { this.searchLoading = false; if (!response.ok) {