diff options
author | Chocobozzz <me@florianbigard.com> | 2018-10-10 09:43:53 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-10-10 09:43:53 +0200 |
commit | b014b6b9c7cb68d09c52b44046afe486c0736426 (patch) | |
tree | dcc10748eac50400d670e9fa0163f73b96f6194a | |
parent | 9ccff23877ec8d740fcd5a9254fcd2424b62d2c8 (diff) | |
download | PeerTube-b014b6b9c7cb68d09c52b44046afe486c0736426.tar.gz PeerTube-b014b6b9c7cb68d09c52b44046afe486c0736426.tar.zst PeerTube-b014b6b9c7cb68d09c52b44046afe486c0736426.zip |
Add ability to search on followers/following
13 files changed, 166 insertions, 49 deletions
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.html b/client/src/app/+admin/follows/followers-list/followers-list.component.html index 5645a60cc..fc022bdb4 100644 --- a/client/src/app/+admin/follows/followers-list/followers-list.component.html +++ b/client/src/app/+admin/follows/followers-list/followers-list.component.html | |||
@@ -2,6 +2,15 @@ | |||
2 | [value]="followers" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" | 2 | [value]="followers" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" |
3 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" | 3 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" |
4 | > | 4 | > |
5 | <ng-template pTemplate="caption"> | ||
6 | <div class="caption"> | ||
7 | <input | ||
8 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." | ||
9 | (keyup)="onSearch($event.target.value)" | ||
10 | > | ||
11 | </div> | ||
12 | </ng-template> | ||
13 | |||
5 | <ng-template pTemplate="header"> | 14 | <ng-template pTemplate="header"> |
6 | <tr> | 15 | <tr> |
7 | <th i18n style="width: 60px">ID</th> | 16 | <th i18n style="width: 60px">ID</th> |
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.scss b/client/src/app/+admin/follows/followers-list/followers-list.component.scss index e69de29bb..a6f0656b8 100644 --- a/client/src/app/+admin/follows/followers-list/followers-list.component.scss +++ b/client/src/app/+admin/follows/followers-list/followers-list.component.scss | |||
@@ -0,0 +1,10 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .caption { | ||
5 | justify-content: flex-end; | ||
6 | |||
7 | input { | ||
8 | @include peertube-input-text(250px); | ||
9 | } | ||
10 | } \ No newline at end of file | ||
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.html b/client/src/app/+admin/follows/following-list/following-list.component.html index 8af624ac5..5bc8fbc2d 100644 --- a/client/src/app/+admin/follows/following-list/following-list.component.html +++ b/client/src/app/+admin/follows/following-list/following-list.component.html | |||
@@ -2,6 +2,17 @@ | |||
2 | [value]="following" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" | 2 | [value]="following" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" |
3 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" | 3 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" |
4 | > | 4 | > |
5 | <ng-template pTemplate="caption"> | ||
6 | <div class="caption"> | ||
7 | <div> | ||
8 | <input | ||
9 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." | ||
10 | (keyup)="onSearch($event.target.value)" | ||
11 | > | ||
12 | </div> | ||
13 | </div> | ||
14 | </ng-template> | ||
15 | |||
5 | <ng-template pTemplate="header"> | 16 | <ng-template pTemplate="header"> |
6 | <tr> | 17 | <tr> |
7 | <th i18n style="width: 60px">ID</th> | 18 | <th i18n style="width: 60px">ID</th> |
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.scss b/client/src/app/+admin/follows/following-list/following-list.component.scss index bfcdcaa49..b3bb7f5f8 100644 --- a/client/src/app/+admin/follows/following-list/following-list.component.scss +++ b/client/src/app/+admin/follows/following-list/following-list.component.scss | |||
@@ -10,4 +10,12 @@ my-redundancy-checkbox /deep/ my-peertube-checkbox { | |||
10 | label { | 10 | label { |
11 | margin: 0; | 11 | margin: 0; |
12 | } | 12 | } |
13 | } | ||
14 | |||
15 | .caption { | ||
16 | justify-content: flex-end; | ||
17 | |||
18 | input { | ||
19 | @include peertube-input-text(250px); | ||
20 | } | ||
13 | } \ No newline at end of file | 21 | } \ No newline at end of file |
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 70235a48d..9b7029f75 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 | |||
@@ -53,7 +53,7 @@ export class FollowingListComponent extends RestTable implements OnInit { | |||
53 | } | 53 | } |
54 | 54 | ||
55 | protected loadData () { | 55 | protected loadData () { |
56 | this.followService.getFollowing(this.pagination, this.sort) | 56 | this.followService.getFollowing(this.pagination, this.sort, this.search) |
57 | .subscribe( | 57 | .subscribe( |
58 | resultList => { | 58 | resultList => { |
59 | this.following = resultList.data | 59 | this.following = resultList.data |
diff --git a/client/src/app/+admin/follows/shared/follow.service.ts b/client/src/app/+admin/follows/shared/follow.service.ts index 27169a9cd..a2904179e 100644 --- a/client/src/app/+admin/follows/shared/follow.service.ts +++ b/client/src/app/+admin/follows/shared/follow.service.ts | |||
@@ -18,10 +18,12 @@ export class FollowService { | |||
18 | ) { | 18 | ) { |
19 | } | 19 | } |
20 | 20 | ||
21 | getFollowing (pagination: RestPagination, sort: SortMeta): Observable<ResultList<ActorFollow>> { | 21 | getFollowing (pagination: RestPagination, sort: SortMeta, search?: string): Observable<ResultList<ActorFollow>> { |
22 | let params = new HttpParams() | 22 | let params = new HttpParams() |
23 | params = this.restService.addRestGetParams(params, pagination, sort) | 23 | params = this.restService.addRestGetParams(params, pagination, sort) |
24 | 24 | ||
25 | if (search) params = params.append('search', search) | ||
26 | |||
25 | return this.authHttp.get<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/following', { params }) | 27 | return this.authHttp.get<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/following', { params }) |
26 | .pipe( | 28 | .pipe( |
27 | map(res => this.restExtractor.convertResultListDateToHuman(res)), | 29 | map(res => this.restExtractor.convertResultListDateToHuman(res)), |
@@ -29,10 +31,12 @@ export class FollowService { | |||
29 | ) | 31 | ) |
30 | } | 32 | } |
31 | 33 | ||
32 | getFollowers (pagination: RestPagination, sort: SortMeta): Observable<ResultList<ActorFollow>> { | 34 | getFollowers (pagination: RestPagination, sort: SortMeta, search?: string): Observable<ResultList<ActorFollow>> { |
33 | let params = new HttpParams() | 35 | let params = new HttpParams() |
34 | params = this.restService.addRestGetParams(params, pagination, sort) | 36 | params = this.restService.addRestGetParams(params, pagination, sort) |
35 | 37 | ||
38 | if (search) params = params.append('search', search) | ||
39 | |||
36 | return this.authHttp.get<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/followers', { params }) | 40 | return this.authHttp.get<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/followers', { params }) |
37 | .pipe( | 41 | .pipe( |
38 | map(res => this.restExtractor.convertResultListDateToHuman(res)), | 42 | map(res => this.restExtractor.convertResultListDateToHuman(res)), |
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 01f43dfe1..f235769f0 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 | |||
@@ -18,10 +18,7 @@ tr.banned { | |||
18 | } | 18 | } |
19 | 19 | ||
20 | .caption { | 20 | .caption { |
21 | height: 40px; | ||
22 | display: flex; | ||
23 | justify-content: space-between; | 21 | justify-content: space-between; |
24 | align-items: center; | ||
25 | 22 | ||
26 | input { | 23 | input { |
27 | @include peertube-input-text(250px); | 24 | @include peertube-input-text(250px); |
diff --git a/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.html b/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.html index 69b198faa..7c0df850d 100644 --- a/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.html +++ b/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.html | |||
@@ -22,9 +22,9 @@ | |||
22 | </span> | 22 | </span> |
23 | 23 | ||
24 | <input | 24 | <input |
25 | type="submit" i18n-value value="Submit" class="action-button-submit" | 25 | type="submit" i18n-value value="Submit" class="action-button-submit" |
26 | [disabled]="!form.valid" | 26 | [disabled]="!form.valid" |
27 | (click)="close()" | 27 | (click)="close()" |
28 | /> | 28 | /> |
29 | </div> | 29 | </div> |
30 | </div> | 30 | </div> |
diff --git a/client/src/sass/primeng-custom.scss b/client/src/sass/primeng-custom.scss index 4a19e0275..0568de4e2 100644 --- a/client/src/sass/primeng-custom.scss +++ b/client/src/sass/primeng-custom.scss | |||
@@ -16,6 +16,12 @@ p-table { | |||
16 | 16 | ||
17 | .ui-table-caption { | 17 | .ui-table-caption { |
18 | border: none; | 18 | border: none; |
19 | |||
20 | .caption { | ||
21 | height: 40px; | ||
22 | display: flex; | ||
23 | align-items: center; | ||
24 | } | ||
19 | } | 25 | } |
20 | 26 | ||
21 | td { | 27 | td { |
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts index d62400e42..9fa6c34ba 100644 --- a/server/controllers/api/server/follows.ts +++ b/server/controllers/api/server/follows.ts | |||
@@ -61,14 +61,26 @@ export { | |||
61 | 61 | ||
62 | async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) { | 62 | async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) { |
63 | const serverActor = await getServerActor() | 63 | const serverActor = await getServerActor() |
64 | const resultList = await ActorFollowModel.listFollowingForApi(serverActor.id, req.query.start, req.query.count, req.query.sort) | 64 | const resultList = await ActorFollowModel.listFollowingForApi( |
65 | serverActor.id, | ||
66 | req.query.start, | ||
67 | req.query.count, | ||
68 | req.query.sort, | ||
69 | req.query.search | ||
70 | ) | ||
65 | 71 | ||
66 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 72 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
67 | } | 73 | } |
68 | 74 | ||
69 | async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) { | 75 | async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) { |
70 | const serverActor = await getServerActor() | 76 | const serverActor = await getServerActor() |
71 | const resultList = await ActorFollowModel.listFollowersForApi(serverActor.id, req.query.start, req.query.count, req.query.sort) | 77 | const resultList = await ActorFollowModel.listFollowersForApi( |
78 | serverActor.id, | ||
79 | req.query.start, | ||
80 | req.query.count, | ||
81 | req.query.sort, | ||
82 | req.query.search | ||
83 | ) | ||
72 | 84 | ||
73 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 85 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
74 | } | 86 | } |
diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts index 27bb43dae..3373355ef 100644 --- a/server/models/activitypub/actor-follow.ts +++ b/server/models/activitypub/actor-follow.ts | |||
@@ -280,7 +280,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
280 | return ActorFollowModel.findAll(query) | 280 | return ActorFollowModel.findAll(query) |
281 | } | 281 | } |
282 | 282 | ||
283 | static listFollowingForApi (id: number, start: number, count: number, sort: string) { | 283 | static listFollowingForApi (id: number, start: number, count: number, sort: string, search?: string) { |
284 | const query = { | 284 | const query = { |
285 | distinct: true, | 285 | distinct: true, |
286 | offset: start, | 286 | offset: start, |
@@ -299,7 +299,17 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
299 | model: ActorModel, | 299 | model: ActorModel, |
300 | as: 'ActorFollowing', | 300 | as: 'ActorFollowing', |
301 | required: true, | 301 | required: true, |
302 | include: [ ServerModel ] | 302 | include: [ |
303 | { | ||
304 | model: ServerModel, | ||
305 | required: true, | ||
306 | where: search ? { | ||
307 | host: { | ||
308 | [Sequelize.Op.iLike]: '%' + search + '%' | ||
309 | } | ||
310 | } : undefined | ||
311 | } | ||
312 | ] | ||
303 | } | 313 | } |
304 | ] | 314 | ] |
305 | } | 315 | } |
@@ -313,6 +323,49 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
313 | }) | 323 | }) |
314 | } | 324 | } |
315 | 325 | ||
326 | static listFollowersForApi (id: number, start: number, count: number, sort: string, search?: string) { | ||
327 | const query = { | ||
328 | distinct: true, | ||
329 | offset: start, | ||
330 | limit: count, | ||
331 | order: getSort(sort), | ||
332 | include: [ | ||
333 | { | ||
334 | model: ActorModel, | ||
335 | required: true, | ||
336 | as: 'ActorFollower', | ||
337 | include: [ | ||
338 | { | ||
339 | model: ServerModel, | ||
340 | required: true, | ||
341 | where: search ? { | ||
342 | host: { | ||
343 | [ Sequelize.Op.iLike ]: '%' + search + '%' | ||
344 | } | ||
345 | } : undefined | ||
346 | } | ||
347 | ] | ||
348 | }, | ||
349 | { | ||
350 | model: ActorModel, | ||
351 | as: 'ActorFollowing', | ||
352 | required: true, | ||
353 | where: { | ||
354 | id | ||
355 | } | ||
356 | } | ||
357 | ] | ||
358 | } | ||
359 | |||
360 | return ActorFollowModel.findAndCountAll(query) | ||
361 | .then(({ rows, count }) => { | ||
362 | return { | ||
363 | data: rows, | ||
364 | total: count | ||
365 | } | ||
366 | }) | ||
367 | } | ||
368 | |||
316 | static listSubscriptionsForApi (id: number, start: number, count: number, sort: string) { | 369 | static listSubscriptionsForApi (id: number, start: number, count: number, sort: string) { |
317 | const query = { | 370 | const query = { |
318 | attributes: [], | 371 | attributes: [], |
@@ -370,39 +423,6 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
370 | }) | 423 | }) |
371 | } | 424 | } |
372 | 425 | ||
373 | static listFollowersForApi (id: number, start: number, count: number, sort: string) { | ||
374 | const query = { | ||
375 | distinct: true, | ||
376 | offset: start, | ||
377 | limit: count, | ||
378 | order: getSort(sort), | ||
379 | include: [ | ||
380 | { | ||
381 | model: ActorModel, | ||
382 | required: true, | ||
383 | as: 'ActorFollower', | ||
384 | include: [ ServerModel ] | ||
385 | }, | ||
386 | { | ||
387 | model: ActorModel, | ||
388 | as: 'ActorFollowing', | ||
389 | required: true, | ||
390 | where: { | ||
391 | id | ||
392 | } | ||
393 | } | ||
394 | ] | ||
395 | } | ||
396 | |||
397 | return ActorFollowModel.findAndCountAll(query) | ||
398 | .then(({ rows, count }) => { | ||
399 | return { | ||
400 | data: rows, | ||
401 | total: count | ||
402 | } | ||
403 | }) | ||
404 | } | ||
405 | |||
406 | static listAcceptedFollowerUrlsForApi (actorIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { | 426 | static listAcceptedFollowerUrlsForApi (actorIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { |
407 | return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, start, count) | 427 | return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, start, count) |
408 | } | 428 | } |
diff --git a/server/tests/api/server/follows.ts b/server/tests/api/server/follows.ts index 310c291bf..e80e93e7f 100644 --- a/server/tests/api/server/follows.ts +++ b/server/tests/api/server/follows.ts | |||
@@ -93,7 +93,26 @@ describe('Test follows', function () { | |||
93 | expect(server3Follow.state).to.equal('accepted') | 93 | expect(server3Follow.state).to.equal('accepted') |
94 | }) | 94 | }) |
95 | 95 | ||
96 | it('Should have 0 followings on server 1 and 2', async function () { | 96 | it('Should search followings on server 1', async function () { |
97 | { | ||
98 | const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt', ':9002') | ||
99 | const follows = res.body.data | ||
100 | |||
101 | expect(res.body.total).to.equal(1) | ||
102 | expect(follows.length).to.equal(1) | ||
103 | expect(follows[ 0 ].following.host).to.equal('localhost:9002') | ||
104 | } | ||
105 | |||
106 | { | ||
107 | const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt', 'bla') | ||
108 | const follows = res.body.data | ||
109 | |||
110 | expect(res.body.total).to.equal(0) | ||
111 | expect(follows.length).to.equal(0) | ||
112 | } | ||
113 | }) | ||
114 | |||
115 | it('Should have 0 followings on server 2 and 3', async function () { | ||
97 | for (const server of [ servers[1], servers[2] ]) { | 116 | for (const server of [ servers[1], servers[2] ]) { |
98 | const res = await getFollowingListPaginationAndSort(server.url, 0, 5, 'createdAt') | 117 | const res = await getFollowingListPaginationAndSort(server.url, 0, 5, 'createdAt') |
99 | const follows = res.body.data | 118 | const follows = res.body.data |
@@ -116,6 +135,25 @@ describe('Test follows', function () { | |||
116 | } | 135 | } |
117 | }) | 136 | }) |
118 | 137 | ||
138 | it('Should search followers on server 2', async function () { | ||
139 | { | ||
140 | const res = await getFollowersListPaginationAndSort(servers[ 2 ].url, 0, 5, 'createdAt', '9001') | ||
141 | const follows = res.body.data | ||
142 | |||
143 | expect(res.body.total).to.equal(1) | ||
144 | expect(follows.length).to.equal(1) | ||
145 | expect(follows[ 0 ].following.host).to.equal('localhost:9003') | ||
146 | } | ||
147 | |||
148 | { | ||
149 | const res = await getFollowersListPaginationAndSort(servers[ 2 ].url, 0, 5, 'createdAt', 'bla') | ||
150 | const follows = res.body.data | ||
151 | |||
152 | expect(res.body.total).to.equal(0) | ||
153 | expect(follows.length).to.equal(0) | ||
154 | } | ||
155 | }) | ||
156 | |||
119 | it('Should have 0 followers on server 1', async function () { | 157 | it('Should have 0 followers on server 1', async function () { |
120 | const res = await getFollowersListPaginationAndSort(servers[0].url, 0, 5, 'createdAt') | 158 | const res = await getFollowersListPaginationAndSort(servers[0].url, 0, 5, 'createdAt') |
121 | const follows = res.body.data | 159 | const follows = res.body.data |
diff --git a/server/tests/utils/server/follows.ts b/server/tests/utils/server/follows.ts index 8a65a958b..7741757a6 100644 --- a/server/tests/utils/server/follows.ts +++ b/server/tests/utils/server/follows.ts | |||
@@ -2,7 +2,7 @@ import * as request from 'supertest' | |||
2 | import { ServerInfo } from './servers' | 2 | import { ServerInfo } from './servers' |
3 | import { waitJobs } from './jobs' | 3 | import { waitJobs } from './jobs' |
4 | 4 | ||
5 | function getFollowersListPaginationAndSort (url: string, start: number, count: number, sort: string) { | 5 | function getFollowersListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { |
6 | const path = '/api/v1/server/followers' | 6 | const path = '/api/v1/server/followers' |
7 | 7 | ||
8 | return request(url) | 8 | return request(url) |
@@ -10,12 +10,13 @@ function getFollowersListPaginationAndSort (url: string, start: number, count: n | |||
10 | .query({ start }) | 10 | .query({ start }) |
11 | .query({ count }) | 11 | .query({ count }) |
12 | .query({ sort }) | 12 | .query({ sort }) |
13 | .query({ search }) | ||
13 | .set('Accept', 'application/json') | 14 | .set('Accept', 'application/json') |
14 | .expect(200) | 15 | .expect(200) |
15 | .expect('Content-Type', /json/) | 16 | .expect('Content-Type', /json/) |
16 | } | 17 | } |
17 | 18 | ||
18 | function getFollowingListPaginationAndSort (url: string, start: number, count: number, sort: string) { | 19 | function getFollowingListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { |
19 | const path = '/api/v1/server/following' | 20 | const path = '/api/v1/server/following' |
20 | 21 | ||
21 | return request(url) | 22 | return request(url) |
@@ -23,6 +24,7 @@ function getFollowingListPaginationAndSort (url: string, start: number, count: n | |||
23 | .query({ start }) | 24 | .query({ start }) |
24 | .query({ count }) | 25 | .query({ count }) |
25 | .query({ sort }) | 26 | .query({ sort }) |
27 | .query({ search }) | ||
26 | .set('Accept', 'application/json') | 28 | .set('Accept', 'application/json') |
27 | .expect(200) | 29 | .expect(200) |
28 | .expect('Content-Type', /json/) | 30 | .expect('Content-Type', /json/) |