Only display accepted followers/followings in about page
authorChocobozzz <me@florianbigard.com>
Thu, 28 Nov 2019 10:37:32 +0000 (11:37 +0100)
committerChocobozzz <me@florianbigard.com>
Thu, 28 Nov 2019 10:37:32 +0000 (11:37 +0100)
client/src/app/+about/about-follows/about-follows.component.ts
client/src/app/+admin/follows/followers-list/followers-list.component.ts
client/src/app/+admin/follows/following-list/following-list.component.ts
client/src/app/shared/instance/follow.service.ts
server/controllers/api/server/follows.ts
server/helpers/custom-validators/follows.ts [new file with mode: 0644]
server/middlewares/validators/follows.ts
server/models/activitypub/actor-follow.ts
server/tests/api/check-params/follows.ts
server/tests/api/server/follows.ts
shared/extra-utils/server/follows.ts

index d6030792864892728ca805721f67165e94c4c014..fc265fecbe24d24d767f80f2c391fa08e0d70be6 100644 (file)
@@ -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)
index eb3137e26db1c770a9d8264cafc99c1cf3a63630..707daef84d71d200ada60553bcd70dd0cbcb15ef 100644 (file)
@@ -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
index b97923f042728ead4812f39ecad89d4d2275debc..3d78c254f04d8c48fd5bad3d523f7e4ce2020fd1 100644 (file)
@@ -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
index 63f9e2687cd498127530cb4b0818bc306796f8b3..1477a26ae8120d737341629f43763309190965ef 100644 (file)
@@ -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<ResultList<ActorFollow>> {
+  getFollowing (options: {
+    pagination: RestPagination,
+    sort: SortMeta,
+    search?: string,
+    state?: FollowState
+  }): Observable<ResultList<ActorFollow>> {
+    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<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/following', { params })
                .pipe(
@@ -31,11 +39,19 @@ export class FollowService {
                )
   }
 
-  getFollowers (pagination: RestPagination, sort: SortMeta, search?: string): Observable<ResultList<ActorFollow>> {
+  getFollowers (options: {
+    pagination: RestPagination,
+    sort: SortMeta,
+    search?: string,
+    state?: FollowState
+  }): Observable<ResultList<ActorFollow>> {
+    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<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/followers', { params })
                .pipe(
index 37647622b6b3c525a98965fdc74c2ecf75d27d5f..e7fd3aabd884aaf4c8300552fe9211f611f4238a 100644 (file)
@@ -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 (file)
index 0000000..fbef7ad
--- /dev/null
@@ -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
+}
index 7887356630a54191824bdf3d039247f4ddd7fe19..454f9f2b8505c050d4c146ee5451520b87b1a449 100644 (file)
@@ -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
 }
index 24272a40ea87f1fb08bcac6656ae426e31b2f6eb..09bc6853d6091732f4182c9290d6201c615371bc 100644 (file)
@@ -292,12 +292,24 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
     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<ActorFollowModel> {
       })
   }
 
-  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,
index 2eb54cb0a7ee1f90f29155d3ee923f835ffd3873..488666a754c814c25cd838118db70e8bb995665c 100644 (file)
@@ -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 () {
index e8d6f5138f2e2eb94ab4efa35b5a14f332fae20d..36a0619265765626519f9f1360979e4e38e976c3 100644 (file)
@@ -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)
     }
 
     {
index 0379bb1098de61a9534541e4cbe19059d9648141..365263a223df5e36d6d6ef27df4562f9fbd1ac31 100644 (file)
@@ -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)