Remove one pod (#76)
authorGreen-Star <Green-Star@users.noreply.github.com>
Wed, 2 Aug 2017 19:50:42 +0000 (21:50 +0200)
committerBigard Florian <florian.bigard@gmail.com>
Wed, 2 Aug 2017 19:50:42 +0000 (21:50 +0200)
* Client: Fix typo

* Client: Add removeFriend feature

* Server: Add removeFriend feature

* Server: Update method name

* Fix rebase onto develop issues

* Server: Fix error message

* Server: Remove useless methods in removeFriend method

* Server: Finish remove on pod feature after rebase

* Server: Type pod parameter

* Fix Travis build

* Add friend-basic test for the remove one pod feature

* Add check-params tests for the remove one pod feature

* Fix typos

* Add friend-advanced test for the remove one pod feature

* Client: Trailing new line

* Move to promises

* Add undefined id test

* Use find method instead of a for loop to find the friend to remove

* Remove setTimeout method

* Server: Remove requestScheduler operations

* Server: Fix logging messages

* Server: Remove sign request parameter

client/src/app/+admin/friends/friend-list/friend-list.component.html
client/src/app/+admin/friends/friend-list/friend-list.component.ts
client/src/app/+admin/friends/shared/friend.service.ts
server/controllers/api/pods.ts
server/lib/friends.ts
server/middlewares/validators/pods.ts
server/tests/api/check-params/pods.js
server/tests/api/friends-advanced.js
server/tests/api/friends-basic.js
server/tests/utils/pods.js

index 45695f7c86d3e7b7cf48f1f2eccd801b4ec7c14b..7b9fff3047cc8a4a85057c974f7fa9462d7c7e8f 100644 (file)
@@ -2,7 +2,7 @@
   <div class="content-padding">
     <h3>Friends list</h3>
 
-    <ng2-smart-table [settings]="tableSettings" [source]="friendsSource"></ng2-smart-table>
+    <ng2-smart-table [settings]="tableSettings" [source]="friendsSource" (delete)="removeFriend($event)"></ng2-smart-table>
 
     <a *ngIf="hasFriends()" class="btn btn-danger pull-left" (click)="quitFriends()">
       Quit friends
index 9f0256d7f3af8c7677013edf4ba065594422a817..822a112ccc05ebf9c405a46b7c097d36a6440424 100644 (file)
@@ -6,6 +6,7 @@ import { ServerDataSource } from 'ng2-smart-table'
 import { ConfirmService } from '../../../core'
 import { Utils } from '../../../shared'
 import { FriendService } from '../shared'
+import { Pod } from '../../../../../../shared'
 
 @Component({
   selector: 'my-friend-list',
@@ -15,6 +16,7 @@ import { FriendService } from '../shared'
 export class FriendListComponent {
   friendsSource = null
   tableSettings = {
+    mode: 'external',
     attr: {
       class: 'table-hover'
     },
@@ -23,7 +25,10 @@ export class FriendListComponent {
       position: 'right',
       add: false,
       edit: false,
-      delete: false
+      delete: true
+    },
+    delete: {
+      deleteButtonContent: Utils.getRowDeleteButton()
     },
     columns: {
       id: {
@@ -71,8 +76,7 @@ export class FriendListComponent {
 
         this.friendService.quitFriends().subscribe(
           status => {
-            this.notificationsService.success('Sucess', 'Friends left!')
-
+            this.notificationsService.success('Success', 'Friends left!')
             this.friendsSource.refresh()
           },
 
@@ -81,4 +85,24 @@ export class FriendListComponent {
       }
     )
   }
+
+  removeFriend ({ data }) {
+    const confirmMessage = 'Do you really want to remove this friend ? All its videos will be deleted.'
+    const friend: Pod = data
+
+    this.confirmService.confirm(confirmMessage, 'Remove').subscribe(
+      res => {
+        if (res === false) return
+
+        this.friendService.removeFriend(friend).subscribe(
+         status => {
+           this.notificationsService.success('Success', 'Friend removed')
+           this.friendsSource.refresh()
+         },
+
+         err => this.notificationsService.error('Error', err.text)
+       )
+      }
+    )
+  }
 }
index 79de4470e77402c664afe8906738b1e1101ab9c5..8bc0239ab9a62d49521921f5762d0df1521afc1b 100644 (file)
@@ -6,6 +6,7 @@ import 'rxjs/add/operator/map'
 import { ServerDataSource } from 'ng2-smart-table'
 
 import { AuthHttp, RestExtractor, RestDataSource, ResultList } from '../../../shared'
+import { Pod } from '../../../../../../shared'
 
 @Injectable()
 export class FriendService {
@@ -20,7 +21,7 @@ export class FriendService {
     return new RestDataSource(this.authHttp, FriendService.BASE_FRIEND_URL)
   }
 
-  makeFriends (notEmptyHosts) {
+  makeFriends (notEmptyHosts: String[]) {
     const body = {
       hosts: notEmptyHosts
     }
@@ -35,4 +36,10 @@ export class FriendService {
                         .map(res => res.status)
                         .catch((res) => this.restExtractor.handleError(res))
   }
+
+  removeFriend (friend: Pod) {
+    return this.authHttp.delete(FriendService.BASE_FRIEND_URL + friend.id)
+                        .map(this.restExtractor.extractDataBool)
+                        .catch((res) => this.restExtractor.handleError(res))
+  }
 }
index 5210f9fe4a2d28388b055fb3d0d48510bdf00b50..916b131d9ac568513d72a7f1fe4a73ed04aad252 100644 (file)
@@ -10,7 +10,8 @@ import {
 import {
   sendOwnedVideosToPod,
   makeFriends,
-  quitFriends
+  quitFriends,
+  removeFriend
 } from '../../lib'
 import {
   podsAddValidator,
@@ -18,7 +19,8 @@ import {
   ensureIsAdmin,
   makeFriendsValidator,
   setBodyHostPort,
-  setBodyHostsPort
+  setBodyHostsPort,
+  podRemoveValidator
 } from '../../middlewares'
 import {
   PodInstance
@@ -45,6 +47,12 @@ podsRouter.get('/quitfriends',
   ensureIsAdmin,
   quitFriendsController
 )
+podsRouter.delete('/:id',
+  authenticate,
+  ensureIsAdmin,
+  podRemoveValidator,
+  removeFriendController
+)
 
 // ---------------------------------------------------------------------------
 
@@ -93,3 +101,11 @@ function quitFriendsController (req: express.Request, res: express.Response, nex
     .then(() => res.type('json').status(204).end())
     .catch(err => next(err))
 }
+
+function removeFriendController (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const pod = res.locals.pod as PodInstance
+
+  removeFriend(pod)
+    .then(() => (res.type('json').status(204).end()))
+    .catch(err => next(err))
+}
index 50355d5d17af216936ecc0f893167c4f74ff6deb..bd3ff97a590ac5b33987145216248bc7f3c60b01 100644 (file)
@@ -242,6 +242,23 @@ function fetchRemotePreview (pod: PodInstance, video: VideoInstance) {
   return request.get(REMOTE_SCHEME.HTTP + '://' + host + path)
 }
 
+function removeFriend (pod: PodInstance) {
+  const requestParams = {
+    method: 'POST' as 'POST',
+    path: '/api/' + API_VERSION + '/remote/pods/remove',
+    toPod: pod
+  }
+
+  return makeSecureRequest(requestParams)
+    .then(() => pod.destroy())
+    .then(() => {
+      logger.info('Removed friend.')
+    })
+    .catch(err => {
+      logger.error('Some errors while quitting friend %s (id: %d).', pod.host, pod.id, err)
+    })
+}
+
 function getRequestScheduler () {
   return requestScheduler
 }
@@ -268,6 +285,7 @@ export {
   hasFriends,
   makeFriends,
   quitFriends,
+  removeFriend,
   removeVideoToFriends,
   sendOwnedVideosToPod,
   getRequestScheduler,
index 481a66957364a258c4bdc0d66800e0bdaa867d11..d0981cd5782440bbb88535ce9c780fc994d4fb09 100644 (file)
@@ -58,9 +58,33 @@ function podsAddValidator (req: express.Request, res: express.Response, next: ex
   })
 }
 
+function podRemoveValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
+  req.checkParams('id', 'Should have a valid id').notEmpty().isNumeric()
+
+  logger.debug('Checking podRemoveValidator parameters', { parameters: req.params })
+
+  checkErrors(req, res, function () {
+    db.Pod.load(req.params.id)
+      .then(pod => {
+        if (!pod) {
+          logger.error('Cannot find pod %d.', req.params.id)
+          return res.sendStatus(404)
+        }
+
+        res.locals.pod = pod
+        return next()
+      })
+      .catch(err => {
+        logger.error('Cannot load pod %d.', req.params.id, err)
+        res.sendStatus(500)
+      })
+  })
+}
+
 // ---------------------------------------------------------------------------
 
 export {
   makeFriendsValidator,
-  podsAddValidator
+  podsAddValidator,
+  podRemoveValidator
 }
index 2567fff5ff3a518a180fd23d317d50a6953ab95f..35ea590931f5552be5c4537aa4fd2ff2e21f7686 100644 (file)
@@ -17,7 +17,7 @@ describe('Test pods API validators', function () {
   // ---------------------------------------------------------------
 
   before(function (done) {
-    this.timeout(20000)
+    this.timeout(45000)
 
     series([
       function (next) {
@@ -110,7 +110,7 @@ describe('Test pods API validators', function () {
           .expect(400, done)
       })
 
-      it('Should fail with a invalid token', function (done) {
+      it('Should fail with an invalid token', function (done) {
         request(server.url)
           .post(path + '/makefriends')
           .send(body)
@@ -130,7 +130,7 @@ describe('Test pods API validators', function () {
     })
 
     describe('When quitting friends', function () {
-      it('Should fail with a invalid token', function (done) {
+      it('Should fail with an invalid token', function (done) {
         request(server.url)
           .get(path + '/quitfriends')
           .query({ start: 'hello' })
@@ -148,6 +148,50 @@ describe('Test pods API validators', function () {
           .expect(403, done)
       })
     })
+
+    describe('When removing one friend', function () {
+      it('Should fail with an invalid token', function (done) {
+       request(server.url)
+          .delete(path + '/1')
+          .set('Authorization', 'Bearer faketoken')
+          .set('Accept', 'application/json')
+          .expect(401, done)
+      })
+
+      it('Should fail if the user is not an administrator', function (done) {
+       request(server.url)
+          .delete(path + '/1')
+          .set('Authorization', 'Bearer ' + userAccessToken)
+          .set('Accept', 'application/json')
+          .expect(403, done)
+      })
+
+      it('Should fail with an undefined id', function (done) {
+        request(server.url)
+          .delete(path + '/' + undefined)
+          .set('Authorization', 'Bearer ' + server.accessToken)
+          .set('Accept', 'application/json')
+          .expect(400, done)
+      })
+
+      it('Should fail with an invalid id', function (done) {
+       request(server.url)
+          .delete(path + '/foobar')
+          .set('Authorization', 'Bearer ' + server.accessToken)
+          .set('Accept', 'application/json')
+          .expect(400, done)
+      })
+
+      it('Should fail if the pod is not a friend', function (done) {
+       request(server.url)
+          .delete(path + '/-1')
+          .set('Authorization', 'Bearer ' + server.accessToken)
+          .set('Accept', 'application/json')
+          .expect(404, done)
+      })
+
+      it('Should succeed with the correct parameters')
+    })
   })
 
   describe('When adding a pod', function () {
@@ -181,7 +225,7 @@ describe('Test pods API validators', function () {
       requestsUtils.makePostBodyRequest(server.url, path, null, data, done)
     })
 
-    it('Should fail without an host', function (done) {
+    it('Should fail without a host', function (done) {
       const data = {
         email: 'testexample.com',
         publicKey: 'mysuperpublickey'
index 917583a429f187aad9bd2c04706b277aad6207d5..89dc080bc10cacab222b9416923f0a97753a3b2b 100644 (file)
@@ -25,6 +25,20 @@ describe('Test advanced friends', function () {
     return podsUtils.quitFriends(server.url, server.accessToken, callback)
   }
 
+  function removeFriend (podNumber, podNumberToRemove, callback) {
+    const server = servers[podNumber - 1]
+    const serverToRemove = servers[podNumberToRemove - 1]
+
+    getFriendsList(podNumber, function (err, res) {
+      if (err) throw err
+
+      let friendsList = res.body.data
+      let podToRemove = friendsList.find((friend) => (friend.host === serverToRemove.host))
+
+      return podsUtils.quitOneFriend(server.url, server.accessToken, podToRemove.id, callback)
+    })
+  }
+
   function getFriendsList (podNumber, end) {
     const server = servers[podNumber - 1]
     return podsUtils.getFriendsList(server.url, end)
@@ -288,6 +302,84 @@ describe('Test advanced friends', function () {
     })
   })
 
+  it('Should allow pod 6 to quit pod 1 & 2 and be friend with pod 3', function (done) {
+    this.timeout(30000)
+
+    series([
+      // Pod 3 should have 4 friends
+      function (next) {
+        getFriendsList(3, function (err, res) {
+          if (err) throw err
+
+          const friendsList = res.body.data
+          expect(friendsList).to.be.an('array')
+          expect(friendsList.length).to.equal(4)
+
+          next()
+       })
+      },
+      // Pod 1, 2, 6 should have 3 friends each
+      function (next) {
+       each([ 1, 2, 6 ], function (i, callback) {
+          getFriendsList(i, function (err, res) {
+            if (err) throw err
+
+            const friendsList = res.body.data
+            expect(friendsList).to.be.an('array')
+            expect(friendsList.length).to.equal(3)
+
+            callback()
+          })
+        }, next)
+      },
+      function (next) {
+        removeFriend(6, 1, next)
+      },
+      function (next) {
+        removeFriend(6, 2, next)
+      },
+      // Pod 6 should now have only 1 friend (and it should be Pod 3)
+      function (next) {
+        getFriendsList(6, function (err, res) {
+          if (err) throw err
+
+          const friendsList = res.body.data
+          expect(friendsList).to.be.an('array')
+          expect(friendsList.length).to.equal(1)
+          expect(friendsList[0].host).to.equal(servers[2].host)
+
+          next()
+       })
+      },
+      // Pod 1 & 2 should not know friend 6 anymore
+      function (next) {
+        each([ 1, 2 ], function (i, callback) {
+          getFriendsList(i, function (err, res) {
+            if (err) throw err
+
+            const friendsList = res.body.data
+            expect(friendsList).to.be.an('array')
+            expect(friendsList.length).to.equal(2)
+
+            callback()
+          })
+        }, next)
+      },
+      // Pod 3 should know every pod
+      function (next) {
+        getFriendsList(3, function (err, res) {
+          if (err) throw err
+
+          const friendsList = res.body.data
+          expect(friendsList).to.be.an('array')
+          expect(friendsList.length).to.equal(4)
+
+          next()
+        })
+      }
+    ], done)
+  })
+
   after(function (done) {
     servers.forEach(function (server) {
       process.kill(-server.app.pid)
index 4c2043b3984f0432aff3150940bf04f9428877e7..5f1fdd255d728104db58d2fa433886baf20d2662 100644 (file)
@@ -198,6 +198,71 @@ describe('Test basic friends', function () {
     })
   })
 
+  it('Should allow pod 1 to quit only pod 2', function (done) {
+    series([
+      // Pod 1 quits pod 2
+      function (next) {
+        const server = servers[0]
+
+        // Get pod 2 id so we can query it
+        podsUtils.getFriendsList(server.url, function (err, res) {
+          if (err) throw err
+
+          const result = res.body.data
+          let pod = result.find((friend) => (friend.host === servers[1].host))
+
+          // Remove it from the friends list
+          podsUtils.quitOneFriend(server.url, server.accessToken, pod.id, next)
+        })
+      },
+
+      // Pod 1 should have only pod 3 in its friends list
+      function (next) {
+        podsUtils.getFriendsList(servers[0].url, function (err, res) {
+          if (err) throw err
+
+          const result = res.body.data
+          expect(result).to.be.an('array')
+          expect(result.length).to.equal(1)
+
+          const pod = result[0]
+          expect(pod.host).to.equal(servers[2].host)
+
+          next()
+        })
+      },
+
+      // Pod 2 should have only pod 3 in its friends list
+      function (next) {
+        podsUtils.getFriendsList(servers[1].url, function (err, res) {
+          if (err) throw err
+
+          const result = res.body.data
+          expect(result).to.be.an('array')
+          expect(result.length).to.equal(1)
+
+          const pod = result[0]
+          expect(pod.host).to.equal(servers[2].host)
+
+          next()
+        })
+      },
+
+      // Pod 3 should have both pods in its friends list
+      function (next) {
+        podsUtils.getFriendsList(servers[2].url, function (err, res) {
+          if (err) throw err
+
+          const result = res.body.data
+          expect(result).to.be.an('array')
+          expect(result.length).to.equal(2)
+
+          next()
+        })
+      }
+    ], done)
+  })
+
   after(function (done) {
     servers.forEach(function (server) {
       process.kill(-server.app.pid)
index 25b97edec99685dce89f5996c80cb2147b8aae76..cdabb64a63b04c9ff28c543bc3b0cf6311ca59b4 100644 (file)
@@ -5,7 +5,8 @@ const request = require('supertest')
 const podsUtils = {
   getFriendsList,
   makeFriends,
-  quitFriends
+  quitFriends,
+  quitOneFriend
 }
 
 // ---------------------- Export functions --------------------
@@ -90,6 +91,26 @@ function quitFriends (url, accessToken, expectedStatus, end) {
     })
 }
 
+function quitOneFriend (url, accessToken, friendId, expectedStatus, end) {
+  if (!end) {
+    end = expectedStatus
+    expectedStatus = 204
+  }
+
+  const path = '/api/v1/pods/' + friendId
+
+  request(url)
+    .delete(path)
+    .set('Accept', 'application/json')
+    .set('Authorization', 'Bearer ' + accessToken)
+    .expect(expectedStatus)
+    .end(function (err, res) {
+      if (err) throw err
+
+      end()
+    })
+}
+
 // ---------------------------------------------------------------------------
 
 module.exports = podsUtils