diff options
-rw-r--r-- | client/src/app/+admin/users/user-list/user-list.component.html | 29 | ||||
-rw-r--r-- | client/src/app/+admin/users/user-list/user-list.component.scss | 17 | ||||
-rw-r--r-- | client/src/app/+admin/users/user-list/user-list.component.ts | 50 | ||||
-rw-r--r-- | client/src/app/core/rest/rest.service.ts | 4 | ||||
-rw-r--r-- | client/src/app/core/users/user.service.ts | 25 | ||||
-rw-r--r-- | server/controllers/api/users/index.ts | 10 | ||||
-rw-r--r-- | server/middlewares/validators/users.ts | 16 | ||||
-rw-r--r-- | server/models/account/user.ts | 21 | ||||
-rw-r--r-- | server/tests/api/users/users.ts | 34 | ||||
-rw-r--r-- | shared/extra-utils/users/users.ts | 13 | ||||
-rw-r--r-- | support/doc/api/openapi.yaml | 26 |
11 files changed, 213 insertions, 32 deletions
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 27d4a5787..9580a3c8a 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 | |||
@@ -16,14 +16,27 @@ | |||
16 | </my-action-dropdown> | 16 | </my-action-dropdown> |
17 | </div> | 17 | </div> |
18 | 18 | ||
19 | <div class="ml-auto has-feedback has-clear"> | 19 | <div class="ml-auto"> |
20 | <input | 20 | <div class="input-group has-feedback has-clear"> |
21 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." | 21 | <div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body"> |
22 | (keyup)="onSearch($event)" | 22 | <div class="input-group-text" ngbDropdownToggle> |
23 | > | 23 | <span class="caret" aria-haspopup="menu" role="button"></span> |
24 | <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a> | 24 | </div> |
25 | <span class="sr-only" i18n>Clear filters</span> | 25 | |
26 | <div role="menu" ngbDropdownMenu> | ||
27 | <h6 class="dropdown-header" i18n>Advanced user filters</h6> | ||
28 | <a [routerLink]="[ '/admin/users/list' ]" [queryParams]="{ 'search': 'banned:true' }" class="dropdown-item" i18n>Banned users</a> | ||
29 | </div> | ||
30 | </div> | ||
31 | <input | ||
32 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." | ||
33 | (keyup)="onUserSearch($event)" | ||
34 | > | ||
35 | <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetTableFilter()"></a> | ||
36 | <span class="sr-only" i18n>Clear filters</span> | ||
37 | </div> | ||
26 | </div> | 38 | </div> |
39 | |||
27 | <a class="ml-2 add-button" routerLink="/admin/users/create"> | 40 | <a class="ml-2 add-button" routerLink="/admin/users/create"> |
28 | <my-global-icon iconName="add" aria-hidden="true"></my-global-icon> | 41 | <my-global-icon iconName="add" aria-hidden="true"></my-global-icon> |
29 | <ng-container i18n>Create user</ng-container> | 42 | <ng-container i18n>Create user</ng-container> |
@@ -70,7 +83,7 @@ | |||
70 | alt="Avatar" | 83 | alt="Avatar" |
71 | > | 84 | > |
72 | <div> | 85 | <div> |
73 | <span> | 86 | <span class="user-table-primary-text"> |
74 | <span *ngIf="user.blocked" i18n-title title="The user was banned" class="glyphicon glyphicon-ban-circle"></span> | 87 | <span *ngIf="user.blocked" i18n-title title="The user was banned" class="glyphicon glyphicon-ban-circle"></span> |
75 | {{ user.account.displayName }} | 88 | {{ user.account.displayName }} |
76 | </span> | 89 | </span> |
diff --git a/client/src/app/+admin/users/user-list/user-list.component.scss b/client/src/app/+admin/users/user-list/user-list.component.scss index 697b2c11b..2b84dec75 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.scss +++ b/client/src/app/+admin/users/user-list/user-list.component.scss | |||
@@ -17,6 +17,12 @@ tr.banned > td { | |||
17 | font-weight: $font-semibold; | 17 | font-weight: $font-semibold; |
18 | } | 18 | } |
19 | 19 | ||
20 | .user-table-primary-text .glyphicon { | ||
21 | font-size: 80%; | ||
22 | color: gray; | ||
23 | margin-left: 0.1rem; | ||
24 | } | ||
25 | |||
20 | .caption { | 26 | .caption { |
21 | justify-content: space-between; | 27 | justify-content: space-between; |
22 | 28 | ||
@@ -33,3 +39,14 @@ p-tableCheckbox { | |||
33 | .chip { | 39 | .chip { |
34 | @include chip; | 40 | @include chip; |
35 | } | 41 | } |
42 | |||
43 | .input-group { | ||
44 | @include peertube-input-group(300px); | ||
45 | input { | ||
46 | flex: 1; | ||
47 | } | ||
48 | |||
49 | .dropdown-toggle::after { | ||
50 | margin-left: 0; | ||
51 | } | ||
52 | } | ||
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 8f01c7d51..0b72b07c1 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 | |||
@@ -5,6 +5,7 @@ import { Actor, DropdownAction } from '@app/shared/shared-main' | |||
5 | import { UserBanModalComponent } from '@app/shared/shared-moderation' | 5 | import { UserBanModalComponent } from '@app/shared/shared-moderation' |
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | 6 | import { I18n } from '@ngx-translate/i18n-polyfill' |
7 | import { ServerConfig, User } from '@shared/models' | 7 | import { ServerConfig, User } from '@shared/models' |
8 | import { Params, Router, ActivatedRoute } from '@angular/router' | ||
8 | 9 | ||
9 | @Component({ | 10 | @Component({ |
10 | selector: 'my-user-list', | 11 | selector: 'my-user-list', |
@@ -30,6 +31,8 @@ export class UserListComponent extends RestTable implements OnInit { | |||
30 | private serverService: ServerService, | 31 | private serverService: ServerService, |
31 | private userService: UserService, | 32 | private userService: UserService, |
32 | private auth: AuthService, | 33 | private auth: AuthService, |
34 | private route: ActivatedRoute, | ||
35 | private router: Router, | ||
33 | private i18n: I18n | 36 | private i18n: I18n |
34 | ) { | 37 | ) { |
35 | super() | 38 | super() |
@@ -50,6 +53,14 @@ export class UserListComponent extends RestTable implements OnInit { | |||
50 | 53 | ||
51 | this.initialize() | 54 | this.initialize() |
52 | 55 | ||
56 | this.route.queryParams | ||
57 | .subscribe(params => { | ||
58 | this.search = params.search || '' | ||
59 | |||
60 | this.setTableFilter(this.search) | ||
61 | this.loadData() | ||
62 | }) | ||
63 | |||
53 | this.bulkUserActions = [ | 64 | this.bulkUserActions = [ |
54 | [ | 65 | [ |
55 | { | 66 | { |
@@ -102,6 +113,26 @@ export class UserListComponent extends RestTable implements OnInit { | |||
102 | this.loadData() | 113 | this.loadData() |
103 | } | 114 | } |
104 | 115 | ||
116 | /* Table filter functions */ | ||
117 | onUserSearch (event: Event) { | ||
118 | this.onSearch(event) | ||
119 | this.setQueryParams((event.target as HTMLInputElement).value) | ||
120 | } | ||
121 | |||
122 | setQueryParams (search: string) { | ||
123 | const queryParams: Params = {} | ||
124 | if (search) Object.assign(queryParams, { search }) | ||
125 | |||
126 | this.router.navigate([ '/admin/users/list' ], { queryParams }) | ||
127 | } | ||
128 | |||
129 | resetTableFilter () { | ||
130 | this.setTableFilter('') | ||
131 | this.setQueryParams('') | ||
132 | this.resetSearch() | ||
133 | } | ||
134 | /* END Table filter functions */ | ||
135 | |||
105 | switchToDefaultAvatar ($event: Event) { | 136 | switchToDefaultAvatar ($event: Event) { |
106 | ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL() | 137 | ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL() |
107 | } | 138 | } |
@@ -165,14 +196,17 @@ export class UserListComponent extends RestTable implements OnInit { | |||
165 | protected loadData () { | 196 | protected loadData () { |
166 | this.selectedUsers = [] | 197 | this.selectedUsers = [] |
167 | 198 | ||
168 | this.userService.getUsers(this.pagination, this.sort, this.search) | 199 | this.userService.getUsers({ |
169 | .subscribe( | 200 | pagination: this.pagination, |
170 | resultList => { | 201 | sort: this.sort, |
171 | this.users = resultList.data | 202 | search: this.search |
172 | this.totalRecords = resultList.total | 203 | }).subscribe( |
173 | }, | 204 | resultList => { |
205 | this.users = resultList.data | ||
206 | this.totalRecords = resultList.total | ||
207 | }, | ||
174 | 208 | ||
175 | err => this.notifier.error(err.message) | 209 | err => this.notifier.error(err.message) |
176 | ) | 210 | ) |
177 | } | 211 | } |
178 | } | 212 | } |
diff --git a/client/src/app/core/rest/rest.service.ts b/client/src/app/core/rest/rest.service.ts index c12b6bd41..9e32c6d58 100644 --- a/client/src/app/core/rest/rest.service.ts +++ b/client/src/app/core/rest/rest.service.ts | |||
@@ -9,11 +9,12 @@ interface QueryStringFilterPrefixes { | |||
9 | prefix: string | 9 | prefix: string |
10 | handler?: (v: string) => string | number | 10 | handler?: (v: string) => string | number |
11 | multiple?: boolean | 11 | multiple?: boolean |
12 | isBoolean?: boolean | ||
12 | } | 13 | } |
13 | } | 14 | } |
14 | 15 | ||
15 | type ParseQueryStringFilterResult = { | 16 | type ParseQueryStringFilterResult = { |
16 | [key: string]: string | number | (string | number)[] | 17 | [key: string]: string | number | boolean | (string | number | boolean)[] |
17 | } | 18 | } |
18 | 19 | ||
19 | @Injectable() | 20 | @Injectable() |
@@ -96,6 +97,7 @@ export class RestService { | |||
96 | return t | 97 | return t |
97 | }) | 98 | }) |
98 | .filter(t => !!t || t === 0) | 99 | .filter(t => !!t || t === 0) |
100 | .map(t => prefixObj.isBoolean ? t === 'true' : t) | ||
99 | 101 | ||
100 | if (matchedTokens.length === 0) continue | 102 | if (matchedTokens.length === 0) continue |
101 | 103 | ||
diff --git a/client/src/app/core/users/user.service.ts b/client/src/app/core/users/user.service.ts index ab395b1f9..2c817d45e 100644 --- a/client/src/app/core/users/user.service.ts +++ b/client/src/app/core/users/user.service.ts | |||
@@ -290,11 +290,32 @@ export class UserService { | |||
290 | }) | 290 | }) |
291 | } | 291 | } |
292 | 292 | ||
293 | getUsers (pagination: RestPagination, sort: SortMeta, search?: string): Observable<ResultList<UserServerModel>> { | 293 | getUsers (parameters: { |
294 | pagination: RestPagination | ||
295 | sort: SortMeta | ||
296 | search?: string | ||
297 | }): Observable<ResultList<UserServerModel>> { | ||
298 | const { pagination, sort, search } = parameters | ||
299 | |||
294 | let params = new HttpParams() | 300 | let params = new HttpParams() |
295 | params = this.restService.addRestGetParams(params, pagination, sort) | 301 | params = this.restService.addRestGetParams(params, pagination, sort) |
296 | 302 | ||
297 | if (search) params = params.append('search', search) | 303 | if (search) { |
304 | const filters = this.restService.parseQueryStringFilter(search, { | ||
305 | blocked: { | ||
306 | prefix: 'banned:', | ||
307 | isBoolean: true, | ||
308 | handler: v => { | ||
309 | if (v === 'true') return v | ||
310 | if (v === 'false') return v | ||
311 | |||
312 | return undefined | ||
313 | } | ||
314 | } | ||
315 | }) | ||
316 | |||
317 | params = this.restService.addObjectParams(params, filters) | ||
318 | } | ||
298 | 319 | ||
299 | return this.authHttp.get<ResultList<UserServerModel>>(UserService.BASE_USERS_URL, { params }) | 320 | return this.authHttp.get<ResultList<UserServerModel>>(UserService.BASE_USERS_URL, { params }) |
300 | .pipe( | 321 | .pipe( |
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index c8e9eaeaa..839431afb 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts | |||
@@ -18,6 +18,7 @@ import { | |||
18 | setDefaultPagination, | 18 | setDefaultPagination, |
19 | setDefaultSort, | 19 | setDefaultSort, |
20 | userAutocompleteValidator, | 20 | userAutocompleteValidator, |
21 | usersListValidator, | ||
21 | usersAddValidator, | 22 | usersAddValidator, |
22 | usersGetValidator, | 23 | usersGetValidator, |
23 | usersRegisterValidator, | 24 | usersRegisterValidator, |
@@ -85,6 +86,7 @@ usersRouter.get('/', | |||
85 | usersSortValidator, | 86 | usersSortValidator, |
86 | setDefaultSort, | 87 | setDefaultSort, |
87 | setDefaultPagination, | 88 | setDefaultPagination, |
89 | asyncMiddleware(usersListValidator), | ||
88 | asyncMiddleware(listUsers) | 90 | asyncMiddleware(listUsers) |
89 | ) | 91 | ) |
90 | 92 | ||
@@ -282,7 +284,13 @@ async function autocompleteUsers (req: express.Request, res: express.Response) { | |||
282 | } | 284 | } |
283 | 285 | ||
284 | async function listUsers (req: express.Request, res: express.Response) { | 286 | async function listUsers (req: express.Request, res: express.Response) { |
285 | const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.search) | 287 | const resultList = await UserModel.listForApi({ |
288 | start: req.query.start, | ||
289 | count: req.query.count, | ||
290 | sort: req.query.sort, | ||
291 | search: req.query.search, | ||
292 | blocked: req.query.blocked | ||
293 | }) | ||
286 | 294 | ||
287 | return res.json(getFormattedObjects(resultList.data, resultList.total, { withAdminFlags: true })) | 295 | return res.json(getFormattedObjects(resultList.data, resultList.total, { withAdminFlags: true })) |
288 | } | 296 | } |
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index 4a9ed6830..6860a3bed 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -38,6 +38,21 @@ import { UserRole } from '../../../shared/models/users' | |||
38 | import { MUserDefault } from '@server/types/models' | 38 | import { MUserDefault } from '@server/types/models' |
39 | import { Hooks } from '@server/lib/plugins/hooks' | 39 | import { Hooks } from '@server/lib/plugins/hooks' |
40 | 40 | ||
41 | const usersListValidator = [ | ||
42 | query('blocked') | ||
43 | .optional() | ||
44 | .customSanitizer(toBooleanOrNull) | ||
45 | .isBoolean().withMessage('Should be a valid boolean banned state'), | ||
46 | |||
47 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
48 | logger.debug('Checking usersList parameters', { parameters: req.query }) | ||
49 | |||
50 | if (areValidationErrors(req, res)) return | ||
51 | |||
52 | return next() | ||
53 | } | ||
54 | ] | ||
55 | |||
41 | const usersAddValidator = [ | 56 | const usersAddValidator = [ |
42 | body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), | 57 | body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), |
43 | body('password').custom(isUserPasswordValidOrEmpty).withMessage('Should have a valid password'), | 58 | body('password').custom(isUserPasswordValidOrEmpty).withMessage('Should have a valid password'), |
@@ -444,6 +459,7 @@ const ensureCanManageUser = [ | |||
444 | // --------------------------------------------------------------------------- | 459 | // --------------------------------------------------------------------------- |
445 | 460 | ||
446 | export { | 461 | export { |
462 | usersListValidator, | ||
447 | usersAddValidator, | 463 | usersAddValidator, |
448 | deleteMeValidator, | 464 | deleteMeValidator, |
449 | usersRegisterValidator, | 465 | usersRegisterValidator, |
diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 3bde1e744..de193131a 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts | |||
@@ -412,11 +412,18 @@ export class UserModel extends Model<UserModel> { | |||
412 | return this.count() | 412 | return this.count() |
413 | } | 413 | } |
414 | 414 | ||
415 | static listForApi (start: number, count: number, sort: string, search?: string) { | 415 | static listForApi (parameters: { |
416 | let where: WhereOptions | 416 | start: number |
417 | count: number | ||
418 | sort: string | ||
419 | search?: string | ||
420 | blocked?: boolean | ||
421 | }) { | ||
422 | const { start, count, sort, search, blocked } = parameters | ||
423 | const where: WhereOptions = {} | ||
417 | 424 | ||
418 | if (search) { | 425 | if (search) { |
419 | where = { | 426 | Object.assign(where, { |
420 | [Op.or]: [ | 427 | [Op.or]: [ |
421 | { | 428 | { |
422 | email: { | 429 | email: { |
@@ -429,7 +436,13 @@ export class UserModel extends Model<UserModel> { | |||
429 | } | 436 | } |
430 | } | 437 | } |
431 | ] | 438 | ] |
432 | } | 439 | }) |
440 | } | ||
441 | |||
442 | if (blocked !== undefined) { | ||
443 | Object.assign(where, { | ||
444 | blocked: blocked | ||
445 | }) | ||
433 | } | 446 | } |
434 | 447 | ||
435 | const query: FindOptions = { | 448 | const query: FindOptions = { |
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index cad954fcb..0a66bd1ce 100644 --- a/server/tests/api/users/users.ts +++ b/server/tests/api/users/users.ts | |||
@@ -819,12 +819,12 @@ describe('Test users', function () { | |||
819 | describe('User blocking', function () { | 819 | describe('User blocking', function () { |
820 | let user16Id | 820 | let user16Id |
821 | let user16AccessToken | 821 | let user16AccessToken |
822 | const user16 = { | ||
823 | username: 'user_16', | ||
824 | password: 'my super password' | ||
825 | } | ||
822 | 826 | ||
823 | it('Should block and unblock a user', async function () { | 827 | it('Should block a user', async function () { |
824 | const user16 = { | ||
825 | username: 'user_16', | ||
826 | password: 'my super password' | ||
827 | } | ||
828 | const resUser = await createUser({ | 828 | const resUser = await createUser({ |
829 | url: server.url, | 829 | url: server.url, |
830 | accessToken: server.accessToken, | 830 | accessToken: server.accessToken, |
@@ -840,7 +840,31 @@ describe('Test users', function () { | |||
840 | 840 | ||
841 | await getMyUserInformation(server.url, user16AccessToken, 401) | 841 | await getMyUserInformation(server.url, user16AccessToken, 401) |
842 | await userLogin(server, user16, 400) | 842 | await userLogin(server, user16, 400) |
843 | }) | ||
844 | |||
845 | it('Should search user by banned status', async function () { | ||
846 | { | ||
847 | const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', undefined, true) | ||
848 | const users = res.body.data as User[] | ||
849 | |||
850 | expect(res.body.total).to.equal(1) | ||
851 | expect(users.length).to.equal(1) | ||
852 | |||
853 | expect(users[0].username).to.equal(user16.username) | ||
854 | } | ||
855 | |||
856 | { | ||
857 | const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', undefined, false) | ||
858 | const users = res.body.data as User[] | ||
859 | |||
860 | expect(res.body.total).to.equal(1) | ||
861 | expect(users.length).to.equal(1) | ||
862 | |||
863 | expect(users[0].username).to.not.equal(user16.username) | ||
864 | } | ||
865 | }) | ||
843 | 866 | ||
867 | it('Should unblock a user', async function () { | ||
844 | await unblockUser(server.url, user16Id, server.accessToken) | 868 | await unblockUser(server.url, user16Id, server.accessToken) |
845 | user16AccessToken = await userLogin(server, user16) | 869 | user16AccessToken = await userLogin(server, user16) |
846 | await getMyUserInformation(server.url, user16AccessToken, 200) | 870 | await getMyUserInformation(server.url, user16AccessToken, 200) |
diff --git a/shared/extra-utils/users/users.ts b/shared/extra-utils/users/users.ts index 08b7743a6..9f193680d 100644 --- a/shared/extra-utils/users/users.ts +++ b/shared/extra-utils/users/users.ts | |||
@@ -164,14 +164,23 @@ function getUsersList (url: string, accessToken: string) { | |||
164 | .expect('Content-Type', /json/) | 164 | .expect('Content-Type', /json/) |
165 | } | 165 | } |
166 | 166 | ||
167 | function getUsersListPaginationAndSort (url: string, accessToken: string, start: number, count: number, sort: string, search?: string) { | 167 | function getUsersListPaginationAndSort ( |
168 | url: string, | ||
169 | accessToken: string, | ||
170 | start: number, | ||
171 | count: number, | ||
172 | sort: string, | ||
173 | search?: string, | ||
174 | blocked?: boolean | ||
175 | ) { | ||
168 | const path = '/api/v1/users' | 176 | const path = '/api/v1/users' |
169 | 177 | ||
170 | const query = { | 178 | const query = { |
171 | start, | 179 | start, |
172 | count, | 180 | count, |
173 | sort, | 181 | sort, |
174 | search | 182 | search, |
183 | blocked | ||
175 | } | 184 | } |
176 | 185 | ||
177 | return request(url) | 186 | return request(url) |
diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml index 2fc55b832..3c22a297f 100644 --- a/support/doc/api/openapi.yaml +++ b/support/doc/api/openapi.yaml | |||
@@ -518,10 +518,13 @@ paths: | |||
518 | get: | 518 | get: |
519 | summary: List users | 519 | summary: List users |
520 | security: | 520 | security: |
521 | - OAuth2: [] | 521 | - OAuth2: |
522 | - admin | ||
522 | tags: | 523 | tags: |
523 | - Users | 524 | - Users |
524 | parameters: | 525 | parameters: |
526 | - $ref: '#/components/parameters/usersSearch' | ||
527 | - $ref: '#/components/parameters/usersBlocked' | ||
525 | - $ref: '#/components/parameters/start' | 528 | - $ref: '#/components/parameters/start' |
526 | - $ref: '#/components/parameters/count' | 529 | - $ref: '#/components/parameters/count' |
527 | - $ref: '#/components/parameters/usersSort' | 530 | - $ref: '#/components/parameters/usersSort' |
@@ -3148,6 +3151,13 @@ components: | |||
3148 | schema: | 3151 | schema: |
3149 | type: string | 3152 | type: string |
3150 | example: -createdAt | 3153 | example: -createdAt |
3154 | search: | ||
3155 | name: search | ||
3156 | in: query | ||
3157 | required: false | ||
3158 | description: Plain text search, applied to various parts of the model depending on endpoint | ||
3159 | schema: | ||
3160 | type: string | ||
3151 | searchTarget: | 3161 | searchTarget: |
3152 | name: searchTarget | 3162 | name: searchTarget |
3153 | in: query | 3163 | in: query |
@@ -3224,6 +3234,20 @@ components: | |||
3224 | - -dislikes | 3234 | - -dislikes |
3225 | - -uuid | 3235 | - -uuid |
3226 | - -createdAt | 3236 | - -createdAt |
3237 | usersSearch: | ||
3238 | name: search | ||
3239 | in: query | ||
3240 | required: false | ||
3241 | description: Plain text search that will match with user usernames or emails | ||
3242 | schema: | ||
3243 | type: string | ||
3244 | usersBlocked: | ||
3245 | name: blocked | ||
3246 | in: query | ||
3247 | required: false | ||
3248 | description: Filter results down to (un)banned users | ||
3249 | schema: | ||
3250 | type: boolean | ||
3227 | usersSort: | 3251 | usersSort: |
3228 | name: sort | 3252 | name: sort |
3229 | in: query | 3253 | in: query |