diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.ts b/client/src/app/+admin/follows/followers-list/followers-list.component.ts
index ca993dcd3..4a25b7ff3 100644
--- a/client/src/app/+admin/follows/followers-list/followers-list.component.ts
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.ts
@@ -28,7 +28,7 @@ export class FollowersListComponent extends RestTable implements OnInit {
}
ngOnInit () {
- this.loadSort()
+ this.initialize()
}
protected loadData () {
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.ts b/client/src/app/+admin/follows/following-list/following-list.component.ts
index dd57884c6..70235a48d 100644
--- a/client/src/app/+admin/follows/following-list/following-list.component.ts
+++ b/client/src/app/+admin/follows/following-list/following-list.component.ts
@@ -29,7 +29,7 @@ export class FollowingListComponent extends RestTable implements OnInit {
}
ngOnInit () {
- this.loadSort()
+ this.initialize()
}
async removeFollowing (follow: ActorFollow) {
diff --git a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts
index 866ba1b23..44778ab56 100644
--- a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts
+++ b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts
@@ -34,7 +34,7 @@ export class JobsListComponent extends RestTable implements OnInit {
ngOnInit () {
this.loadJobState()
- this.loadSort()
+ this.initialize()
}
onJobStateChanged () {
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts
index 681db7434..9837af586 100644
--- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts
+++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts
@@ -57,7 +57,7 @@ export class VideoAbuseListComponent extends RestTable implements OnInit {
}
ngOnInit () {
- this.loadSort()
+ this.initialize()
}
openModerationCommentModal (videoAbuse: VideoAbuse) {
diff --git a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts
index bb051d00f..e491edaca 100644
--- a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts
+++ b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts
@@ -39,7 +39,7 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
}
ngOnInit () {
- this.loadSort()
+ this.initialize()
}
getVideoUrl (videoBlacklist: VideoBlacklist) {
diff --git a/client/src/app/+admin/users/user-list/user-list.component.html b/client/src/app/+admin/users/user-list/user-list.component.html
index 9d1f2e34a..ae8921802 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.html
+++ b/client/src/app/+admin/users/user-list/user-list.component.html
@@ -25,6 +25,7 @@
diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts
index f3e7e0ead..33384dc35 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.ts
+++ b/client/src/app/+admin/users/user-list/user-list.component.ts
@@ -35,7 +35,7 @@ export class UserListComponent extends RestTable implements OnInit {
}
ngOnInit () {
- this.loadSort()
+ this.initialize()
this.bulkUserActions = [
{
@@ -58,7 +58,7 @@ export class UserListComponent extends RestTable implements OnInit {
protected loadData () {
this.selectedUsers = []
- this.userService.getUsers(this.pagination, this.sort)
+ this.userService.getUsers(this.pagination, this.sort, this.search)
.subscribe(
resultList => {
this.users = resultList.data
diff --git a/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.ts b/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.ts
index 13517b9f4..520278671 100644
--- a/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.ts
+++ b/client/src/app/+my-account/my-account-ownership/my-account-ownership.component.ts
@@ -31,7 +31,7 @@ export class MyAccountOwnershipComponent extends RestTable implements OnInit {
}
ngOnInit () {
- this.loadSort()
+ this.initialize()
}
protected loadData () {
diff --git a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts
index d9fb20446..5b920c98d 100644
--- a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts
+++ b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts
@@ -27,7 +27,7 @@ export class MyAccountVideoImportsComponent extends RestTable implements OnInit
}
ngOnInit () {
- this.loadSort()
+ this.initialize()
}
isVideoImportSuccess (videoImport: VideoImport) {
diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.html b/client/src/app/shared/moderation/user-moderation-dropdown.component.html
index 2c477ab23..01db7cd4a 100644
--- a/client/src/app/shared/moderation/user-moderation-dropdown.component.html
+++ b/client/src/app/shared/moderation/user-moderation-dropdown.component.html
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts
index 174e9f024..105c99d8b 100644
--- a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts
+++ b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts
@@ -17,6 +17,7 @@ export class UserModerationDropdownComponent implements OnInit {
@Input() user: User
@Input() buttonSize: 'normal' | 'small' = 'normal'
+ @Input() placement = 'left'
@Output() userChanged = new EventEmitter()
@Output() userDeleted = new EventEmitter()
diff --git a/client/src/app/shared/rest/rest-table.ts b/client/src/app/shared/rest/rest-table.ts
index fe1a91d2d..26748f245 100644
--- a/client/src/app/shared/rest/rest-table.ts
+++ b/client/src/app/shared/rest/rest-table.ts
@@ -1,8 +1,9 @@
import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
import { LazyLoadEvent } from 'primeng/components/common/lazyloadevent'
import { SortMeta } from 'primeng/components/common/sortmeta'
-
import { RestPagination } from './rest-pagination'
+import { Subject } from 'rxjs'
+import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
export abstract class RestTable {
@@ -11,10 +12,17 @@ export abstract class RestTable {
abstract sort: SortMeta
abstract pagination: RestPagination
+ protected search: string
+ private searchStream: Subject
private sortLocalStorageKey = 'rest-table-sort-' + this.constructor.name
protected abstract loadData (): void
+ initialize () {
+ this.loadSort()
+ this.initSearch()
+ }
+
loadSort () {
const result = peertubeLocalStorage.getItem(this.sortLocalStorageKey)
@@ -46,4 +54,21 @@ export abstract class RestTable {
peertubeLocalStorage.setItem(this.sortLocalStorageKey, JSON.stringify(this.sort))
}
+ initSearch () {
+ this.searchStream = new Subject()
+
+ this.searchStream
+ .pipe(
+ debounceTime(400),
+ distinctUntilChanged()
+ )
+ .subscribe(search => {
+ this.search = search
+ this.loadData()
+ })
+ }
+
+ onSearch (search: string) {
+ this.searchStream.next(search)
+ }
}
diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts
index 0eb3870b0..27a81f0a2 100644
--- a/client/src/app/shared/users/user.service.ts
+++ b/client/src/app/shared/users/user.service.ts
@@ -158,10 +158,12 @@ export class UserService {
.pipe(catchError(err => this.restExtractor.handleError(err)))
}
- getUsers (pagination: RestPagination, sort: SortMeta): Observable> {
+ getUsers (pagination: RestPagination, sort: SortMeta, search?: string): Observable> {
let params = new HttpParams()
params = this.restService.addRestGetParams(params, pagination, sort)
+ if (search) params = params.append('search', search)
+
return this.authHttp.get>(UserService.BASE_USERS_URL, { params })
.pipe(
map(res => this.restExtractor.convertResultListDateToHuman(res)),
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index 0b0081520..4f8137c03 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -238,7 +238,7 @@ async function autocompleteUsers (req: express.Request, res: express.Response, n
}
async function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) {
- const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort)
+ const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.search)
return res.json(getFormattedObjects(resultList.data, resultList.total))
}
diff --git a/server/models/account/user.ts b/server/models/account/user.ts
index e56b0bf40..39654cfcf 100644
--- a/server/models/account/user.ts
+++ b/server/models/account/user.ts
@@ -181,7 +181,25 @@ export class UserModel extends Model {
return this.count()
}
- static listForApi (start: number, count: number, sort: string) {
+ static listForApi (start: number, count: number, sort: string, search?: string) {
+ let where = undefined
+ if (search) {
+ where = {
+ [Sequelize.Op.or]: [
+ {
+ email: {
+ [Sequelize.Op.iLike]: '%' + search + '%'
+ }
+ },
+ {
+ username: {
+ [ Sequelize.Op.iLike ]: '%' + search + '%'
+ }
+ }
+ ]
+ }
+ }
+
const query = {
attributes: {
include: [
@@ -204,7 +222,8 @@ export class UserModel extends Model {
},
offset: start,
limit: count,
- order: getSort(sort)
+ order: getSort(sort),
+ where
}
return UserModel.findAndCountAll(query)
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts
index 8b9c6b455..513bca8a0 100644
--- a/server/tests/api/users/users.ts
+++ b/server/tests/api/users/users.ts
@@ -180,7 +180,7 @@ describe('Test users', function () {
it('Should be able to upload a video again')
it('Should be able to create a new user', async function () {
- await createUser(server.url, accessToken, user.username,user.password, 2 * 1024 * 1024)
+ await createUser(server.url, accessToken, user.username, user.password, 2 * 1024 * 1024)
})
it('Should be able to login with this user', async function () {
@@ -322,6 +322,40 @@ describe('Test users', function () {
expect(users[ 1 ].nsfwPolicy).to.equal('display')
})
+ it('Should search user by username', async function () {
+ const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'oot')
+ const users = res.body.data as User[]
+
+ expect(res.body.total).to.equal(1)
+ expect(users.length).to.equal(1)
+
+ expect(users[ 0 ].username).to.equal('root')
+ })
+
+ it('Should search user by email', async function () {
+ {
+ const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'r_1@exam')
+ const users = res.body.data as User[]
+
+ expect(res.body.total).to.equal(1)
+ expect(users.length).to.equal(1)
+
+ expect(users[ 0 ].username).to.equal('user_1')
+ expect(users[ 0 ].email).to.equal('user_1@example.com')
+ }
+
+ {
+ const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'example')
+ const users = res.body.data as User[]
+
+ expect(res.body.total).to.equal(2)
+ expect(users.length).to.equal(2)
+
+ expect(users[ 0 ].username).to.equal('root')
+ expect(users[ 1 ].username).to.equal('user_1')
+ }
+ })
+
it('Should update my password', async function () {
await updateMyUser({
url: server.url,
diff --git a/server/tests/utils/users/users.ts b/server/tests/utils/users/users.ts
index 41d8ce265..d77233d62 100644
--- a/server/tests/utils/users/users.ts
+++ b/server/tests/utils/users/users.ts
@@ -112,7 +112,7 @@ function getUsersList (url: string, accessToken: string) {
.expect('Content-Type', /json/)
}
-function getUsersListPaginationAndSort (url: string, accessToken: string, start: number, count: number, sort: string) {
+function getUsersListPaginationAndSort (url: string, accessToken: string, start: number, count: number, sort: string, search?: string) {
const path = '/api/v1/users'
return request(url)
@@ -120,6 +120,7 @@ function getUsersListPaginationAndSort (url: string, accessToken: string, start:
.query({ start })
.query({ count })
.query({ sort })
+ .query({ search })
.set('Accept', 'application/json')
.set('Authorization', 'Bearer ' + accessToken)
.expect(200)