From b8f4167fb6fa448125aeecff80b201d74e27fe6a Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 28 Nov 2019 11:37:32 +0100 Subject: Only display accepted followers/followings in about page --- .../about-follows/about-follows.component.ts | 4 +- .../followers-list/followers-list.component.ts | 2 +- .../following-list/following-list.component.ts | 2 +- client/src/app/shared/instance/follow.service.ts | 22 +++++++++-- server/controllers/api/server/follows.ts | 35 +++++++++-------- server/helpers/custom-validators/follows.ts | 14 +++++++ server/middlewares/validators/follows.ts | 18 ++++++++- server/models/activitypub/actor-follow.ts | 28 +++++++++++++- server/tests/api/check-params/follows.ts | 44 +++++++++++++++++++++- server/tests/api/server/follows.ts | 26 +++++++++++-- shared/extra-utils/server/follows.ts | 11 ++++-- 11 files changed, 171 insertions(+), 35 deletions(-) create mode 100644 server/helpers/custom-validators/follows.ts diff --git a/client/src/app/+about/about-follows/about-follows.component.ts b/client/src/app/+about/about-follows/about-follows.component.ts index d60307928..fc265fecb 100644 --- a/client/src/app/+about/about-follows/about-follows.component.ts +++ b/client/src/app/+about/about-follows/about-follows.component.ts @@ -74,7 +74,7 @@ export class AboutFollowsComponent implements OnInit { private loadMoreFollowers () { const pagination = this.restService.componentPaginationToRestPagination(this.followersPagination) - this.followService.getFollowers(pagination, this.sort) + this.followService.getFollowers({ pagination: pagination, sort: this.sort, state: 'accepted' }) .subscribe( resultList => { const newFollowers = resultList.data.map(r => r.follower.host) @@ -92,7 +92,7 @@ export class AboutFollowsComponent implements OnInit { private loadMoreFollowings () { const pagination = this.restService.componentPaginationToRestPagination(this.followingsPagination) - this.followService.getFollowing(pagination, this.sort) + this.followService.getFollowing({ pagination, sort: this.sort, state: 'accepted' }) .subscribe( resultList => { const newFollowings = resultList.data.map(r => r.following.host) 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 eb3137e26..707daef84 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 @@ -88,7 +88,7 @@ export class FollowersListComponent extends RestTable implements OnInit { } protected loadData () { - this.followService.getFollowers(this.pagination, this.sort, this.search) + this.followService.getFollowers({ pagination: this.pagination, sort: this.sort, search: this.search }) .subscribe( resultList => { this.followers = resultList.data 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 b97923f04..3d78c254f 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 @@ -50,7 +50,7 @@ export class FollowingListComponent extends RestTable implements OnInit { } protected loadData () { - this.followService.getFollowing(this.pagination, this.sort, this.search) + this.followService.getFollowing({ pagination: this.pagination, sort: this.sort, search: this.search }) .subscribe( resultList => { this.following = resultList.data diff --git a/client/src/app/shared/instance/follow.service.ts b/client/src/app/shared/instance/follow.service.ts index 63f9e2687..1477a26ae 100644 --- a/client/src/app/shared/instance/follow.service.ts +++ b/client/src/app/shared/instance/follow.service.ts @@ -2,7 +2,7 @@ import { catchError, map } from 'rxjs/operators' import { HttpClient, HttpParams } from '@angular/common/http' import { Injectable } from '@angular/core' import { Observable } from 'rxjs' -import { ActorFollow, ResultList } from '@shared/index' +import { ActorFollow, FollowState, ResultList } from '@shared/index' import { environment } from '../../../environments/environment' import { RestExtractor, RestPagination, RestService } from '../rest' import { SortMeta } from 'primeng/api' @@ -18,11 +18,19 @@ export class FollowService { ) { } - getFollowing (pagination: RestPagination, sort: SortMeta, search?: string): Observable> { + getFollowing (options: { + pagination: RestPagination, + sort: SortMeta, + search?: string, + state?: FollowState + }): Observable> { + const { pagination, sort, search, state } = options + let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination, sort) if (search) params = params.append('search', search) + if (state) params = params.append('state', state) return this.authHttp.get>(FollowService.BASE_APPLICATION_URL + '/following', { params }) .pipe( @@ -31,11 +39,19 @@ export class FollowService { ) } - getFollowers (pagination: RestPagination, sort: SortMeta, search?: string): Observable> { + getFollowers (options: { + pagination: RestPagination, + sort: SortMeta, + search?: string, + state?: FollowState + }): Observable> { + const { pagination, sort, search, state } = options + let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination, sort) if (search) params = params.append('search', search) + if (state) params = params.append('state', state) return this.authHttp.get>(FollowService.BASE_APPLICATION_URL + '/followers', { params }) .pipe( diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts index 37647622b..e7fd3aabd 100644 --- a/server/controllers/api/server/follows.ts +++ b/server/controllers/api/server/follows.ts @@ -19,7 +19,8 @@ import { followingSortValidator, followValidator, getFollowerValidator, - removeFollowingValidator + removeFollowingValidator, + listFollowsValidator } from '../../../middlewares/validators' import { ActorFollowModel } from '../../../models/activitypub/actor-follow' import { JobQueue } from '../../../lib/job-queue' @@ -29,6 +30,7 @@ import { autoFollowBackIfNeeded } from '../../../lib/activitypub/follow' const serverFollowsRouter = express.Router() serverFollowsRouter.get('/following', + listFollowsValidator, paginationValidator, followingSortValidator, setDefaultSort, @@ -52,6 +54,7 @@ serverFollowsRouter.delete('/following/:host', ) serverFollowsRouter.get('/followers', + listFollowsValidator, paginationValidator, followersSortValidator, setDefaultSort, @@ -92,26 +95,28 @@ export { async function listFollowing (req: express.Request, res: express.Response) { const serverActor = await getServerActor() - const resultList = await ActorFollowModel.listFollowingForApi( - serverActor.id, - req.query.start, - req.query.count, - req.query.sort, - req.query.search - ) + const resultList = await ActorFollowModel.listFollowingForApi({ + id: serverActor.id, + start: req.query.start, + count: req.query.count, + sort: req.query.sort, + search: req.query.search, + state: req.query.state + }) return res.json(getFormattedObjects(resultList.data, resultList.total)) } async function listFollowers (req: express.Request, res: express.Response) { const serverActor = await getServerActor() - const resultList = await ActorFollowModel.listFollowersForApi( - serverActor.id, - req.query.start, - req.query.count, - req.query.sort, - req.query.search - ) + const resultList = await ActorFollowModel.listFollowersForApi({ + actorId: serverActor.id, + start: req.query.start, + count: req.query.count, + sort: req.query.sort, + search: req.query.search, + state: req.query.state + }) return res.json(getFormattedObjects(resultList.data, resultList.total)) } diff --git a/server/helpers/custom-validators/follows.ts b/server/helpers/custom-validators/follows.ts new file mode 100644 index 000000000..fbef7ad87 --- /dev/null +++ b/server/helpers/custom-validators/follows.ts @@ -0,0 +1,14 @@ +import { exists } from './misc' +import { FollowState } from '@shared/models' + +function isFollowStateValid (value: FollowState) { + if (!exists(value)) return false + + return value === 'pending' || value === 'accepted' +} + +// --------------------------------------------------------------------------- + +export { + isFollowStateValid +} diff --git a/server/middlewares/validators/follows.ts b/server/middlewares/validators/follows.ts index 788735663..454f9f2b8 100644 --- a/server/middlewares/validators/follows.ts +++ b/server/middlewares/validators/follows.ts @@ -1,5 +1,5 @@ import * as express from 'express' -import { body, param } from 'express-validator' +import { body, param, query } from 'express-validator' import { isTestInstance } from '../../helpers/core-utils' import { isEachUniqueHostValid, isHostValid } from '../../helpers/custom-validators/servers' import { logger } from '../../helpers/logger' @@ -11,6 +11,19 @@ import { ActorModel } from '../../models/activitypub/actor' import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' import { isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' import { MActorFollowActorsDefault } from '@server/typings/models' +import { isFollowStateValid } from '@server/helpers/custom-validators/follows' + +const listFollowsValidator = [ + query('state') + .optional() + .custom(isFollowStateValid).withMessage('Should have a valid follow state'), + + (req: express.Request, res: express.Response, next: express.NextFunction) => { + if (areValidationErrors(req, res)) return + + return next() + } +] const followValidator = [ body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'), @@ -110,5 +123,6 @@ export { followValidator, removeFollowingValidator, getFollowerValidator, - acceptOrRejectFollowerValidator + acceptOrRejectFollowerValidator, + listFollowsValidator } diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts index 24272a40e..09bc6853d 100644 --- a/server/models/activitypub/actor-follow.ts +++ b/server/models/activitypub/actor-follow.ts @@ -292,12 +292,24 @@ export class ActorFollowModel extends Model { return ActorFollowModel.findAll(query) } - static listFollowingForApi (id: number, start: number, count: number, sort: string, search?: string) { + static listFollowingForApi (options: { + id: number, + start: number, + count: number, + sort: string, + state?: FollowState, + search?: string + }) { + const { id, start, count, sort, search, state } = options + + const followWhere = state ? { state } : {} + const query = { distinct: true, offset: start, limit: count, order: getSort(sort), + where: followWhere, include: [ { model: ActorModel, @@ -335,12 +347,24 @@ export class ActorFollowModel extends Model { }) } - static listFollowersForApi (actorId: number, start: number, count: number, sort: string, search?: string) { + static listFollowersForApi (options: { + actorId: number, + start: number, + count: number, + sort: string, + state?: FollowState, + search?: string + }) { + const { actorId, start, count, sort, search, state } = options + + const followWhere = state ? { state } : {} + const query = { distinct: true, offset: start, limit: count, order: getSort(sort), + where: followWhere, include: [ { model: ActorModel, diff --git a/server/tests/api/check-params/follows.ts b/server/tests/api/check-params/follows.ts index 2eb54cb0a..488666a75 100644 --- a/server/tests/api/check-params/follows.ts +++ b/server/tests/api/check-params/follows.ts @@ -6,7 +6,7 @@ import { cleanupTests, createUser, flushAndRunServer, - makeDeleteRequest, + makeDeleteRequest, makeGetRequest, makePostBodyRequest, ServerInfo, setAccessTokensToServers, @@ -131,6 +131,27 @@ describe('Test server follows API validators', function () { it('Should fail with an incorrect sort', async function () { await checkBadSortPagination(server.url, path) }) + + it('Should fail with an incorrect state', async function () { + await makeGetRequest({ + url: server.url, + path, + query: { + state: 'blabla' + } + }) + }) + + it('Should fail succeed with the correct params', async function () { + await makeGetRequest({ + url: server.url, + path, + statusCodeExpected: 200, + query: { + state: 'accepted' + } + }) + }) }) describe('When listing followers', function () { @@ -147,6 +168,27 @@ describe('Test server follows API validators', function () { it('Should fail with an incorrect sort', async function () { await checkBadSortPagination(server.url, path) }) + + it('Should fail with an incorrect state', async function () { + await makeGetRequest({ + url: server.url, + path, + query: { + state: 'blabla' + } + }) + }) + + it('Should fail succeed with the correct params', async function () { + await makeGetRequest({ + url: server.url, + path, + statusCodeExpected: 200, + query: { + state: 'accepted' + } + }) + }) }) describe('When removing a follower', function () { diff --git a/server/tests/api/server/follows.ts b/server/tests/api/server/follows.ts index e8d6f5138..36a061926 100644 --- a/server/tests/api/server/follows.ts +++ b/server/tests/api/server/follows.ts @@ -97,14 +97,23 @@ describe('Test follows', function () { expect(server3Follow.state).to.equal('accepted') }) - it('Should search followings on server 1', async function () { + it('Should search/filter followings on server 1', async function () { { - const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt', ':' + servers[1].port) + const search = ':' + servers[1].port + const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt', search) const follows = res.body.data expect(res.body.total).to.equal(1) expect(follows.length).to.equal(1) expect(follows[ 0 ].following.host).to.equal('localhost:' + servers[1].port) + + const res2 = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt', search, 'accepted') + expect(res2.body.total).to.equal(1) + expect(res2.body.data).to.have.lengthOf(1) + + const res3 = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt', search, 'pending') + expect(res3.body.total).to.equal(0) + expect(res3.body.data).to.have.lengthOf(0) } { @@ -139,14 +148,23 @@ describe('Test follows', function () { } }) - it('Should search followers on server 2', async function () { + it('Should search/filter followers on server 2', async function () { { - const res = await getFollowersListPaginationAndSort(servers[ 2 ].url, 0, 5, 'createdAt', servers[0].port + '') + const search = servers[0].port + '' + const res = await getFollowersListPaginationAndSort(servers[ 2 ].url, 0, 5, 'createdAt', search) const follows = res.body.data expect(res.body.total).to.equal(1) expect(follows.length).to.equal(1) expect(follows[ 0 ].following.host).to.equal('localhost:' + servers[2].port) + + const res2 = await getFollowersListPaginationAndSort(servers[ 2 ].url, 0, 5, 'createdAt', search, 'accepted') + expect(res2.body.total).to.equal(1) + expect(res2.body.data).to.have.lengthOf(1) + + const res3 = await getFollowersListPaginationAndSort(servers[ 2 ].url, 0, 5, 'createdAt', search, 'pending') + expect(res3.body.total).to.equal(0) + expect(res3.body.data).to.have.lengthOf(0) } { diff --git a/shared/extra-utils/server/follows.ts b/shared/extra-utils/server/follows.ts index 0379bb109..365263a22 100644 --- a/shared/extra-utils/server/follows.ts +++ b/shared/extra-utils/server/follows.ts @@ -2,15 +2,17 @@ import * as request from 'supertest' import { ServerInfo } from './servers' import { waitJobs } from './jobs' import { makePostBodyRequest } from '../requests/requests' +import { FollowState } from '@shared/models' -function getFollowersListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { +function getFollowersListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string, state?: FollowState) { const path = '/api/v1/server/followers' const query = { start, count, sort, - search + search, + state } return request(url) @@ -43,14 +45,15 @@ function rejectFollower (url: string, token: string, follower: string, statusCod }) } -function getFollowingListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { +function getFollowingListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string, state?: FollowState) { const path = '/api/v1/server/following' const query = { start, count, sort, - search + search, + state } return request(url) -- cgit v1.2.3