aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-10-10 09:43:53 +0200
committerChocobozzz <me@florianbigard.com>2018-10-10 09:43:53 +0200
commitb014b6b9c7cb68d09c52b44046afe486c0736426 (patch)
treedcc10748eac50400d670e9fa0163f73b96f6194a
parent9ccff23877ec8d740fcd5a9254fcd2424b62d2c8 (diff)
downloadPeerTube-b014b6b9c7cb68d09c52b44046afe486c0736426.tar.gz
PeerTube-b014b6b9c7cb68d09c52b44046afe486c0736426.tar.zst
PeerTube-b014b6b9c7cb68d09c52b44046afe486c0736426.zip
Add ability to search on followers/following
-rw-r--r--client/src/app/+admin/follows/followers-list/followers-list.component.html9
-rw-r--r--client/src/app/+admin/follows/followers-list/followers-list.component.scss10
-rw-r--r--client/src/app/+admin/follows/following-list/following-list.component.html11
-rw-r--r--client/src/app/+admin/follows/following-list/following-list.component.scss8
-rw-r--r--client/src/app/+admin/follows/following-list/following-list.component.ts2
-rw-r--r--client/src/app/+admin/follows/shared/follow.service.ts8
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.scss3
-rw-r--r--client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.html6
-rw-r--r--client/src/sass/primeng-custom.scss6
-rw-r--r--server/controllers/api/server/follows.ts16
-rw-r--r--server/models/activitypub/actor-follow.ts90
-rw-r--r--server/tests/api/server/follows.ts40
-rw-r--r--server/tests/utils/server/follows.ts6
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
62async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) { 62async 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
69async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) { 75async 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'
2import { ServerInfo } from './servers' 2import { ServerInfo } from './servers'
3import { waitJobs } from './jobs' 3import { waitJobs } from './jobs'
4 4
5function getFollowersListPaginationAndSort (url: string, start: number, count: number, sort: string) { 5function 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
18function getFollowingListPaginationAndSort (url: string, start: number, count: number, sort: string) { 19function 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/)