From d5f5a670fcf6258d0facf9a2fd3aabd550fbc78f Mon Sep 17 00:00:00 2001 From: Green-Star Date: Wed, 2 Aug 2017 21:50:42 +0200 Subject: [PATCH] Remove one pod (#76) * 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 --- .../friend-list/friend-list.component.html | 2 +- .../friend-list/friend-list.component.ts | 30 +++++- .../+admin/friends/shared/friend.service.ts | 9 +- server/controllers/api/pods.ts | 20 +++- server/lib/friends.ts | 18 ++++ server/middlewares/validators/pods.ts | 26 +++++- server/tests/api/check-params/pods.js | 52 ++++++++++- server/tests/api/friends-advanced.js | 92 +++++++++++++++++++ server/tests/api/friends-basic.js | 65 +++++++++++++ server/tests/utils/pods.js | 23 ++++- 10 files changed, 324 insertions(+), 13 deletions(-) diff --git a/client/src/app/+admin/friends/friend-list/friend-list.component.html b/client/src/app/+admin/friends/friend-list/friend-list.component.html index 45695f7c8..7b9fff304 100644 --- a/client/src/app/+admin/friends/friend-list/friend-list.component.html +++ b/client/src/app/+admin/friends/friend-list/friend-list.component.html @@ -2,7 +2,7 @@

Friends list

- + Quit friends diff --git a/client/src/app/+admin/friends/friend-list/friend-list.component.ts b/client/src/app/+admin/friends/friend-list/friend-list.component.ts index 9f0256d7f..822a112cc 100644 --- a/client/src/app/+admin/friends/friend-list/friend-list.component.ts +++ b/client/src/app/+admin/friends/friend-list/friend-list.component.ts @@ -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) + ) + } + ) + } } diff --git a/client/src/app/+admin/friends/shared/friend.service.ts b/client/src/app/+admin/friends/shared/friend.service.ts index 79de4470e..8bc0239ab 100644 --- a/client/src/app/+admin/friends/shared/friend.service.ts +++ b/client/src/app/+admin/friends/shared/friend.service.ts @@ -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)) + } } diff --git a/server/controllers/api/pods.ts b/server/controllers/api/pods.ts index 5210f9fe4..916b131d9 100644 --- a/server/controllers/api/pods.ts +++ b/server/controllers/api/pods.ts @@ -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)) +} diff --git a/server/lib/friends.ts b/server/lib/friends.ts index 50355d5d1..bd3ff97a5 100644 --- a/server/lib/friends.ts +++ b/server/lib/friends.ts @@ -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, diff --git a/server/middlewares/validators/pods.ts b/server/middlewares/validators/pods.ts index 481a66957..d0981cd57 100644 --- a/server/middlewares/validators/pods.ts +++ b/server/middlewares/validators/pods.ts @@ -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 } diff --git a/server/tests/api/check-params/pods.js b/server/tests/api/check-params/pods.js index 2567fff5f..35ea59093 100644 --- a/server/tests/api/check-params/pods.js +++ b/server/tests/api/check-params/pods.js @@ -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' diff --git a/server/tests/api/friends-advanced.js b/server/tests/api/friends-advanced.js index 917583a42..89dc080bc 100644 --- a/server/tests/api/friends-advanced.js +++ b/server/tests/api/friends-advanced.js @@ -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) diff --git a/server/tests/api/friends-basic.js b/server/tests/api/friends-basic.js index 4c2043b39..5f1fdd255 100644 --- a/server/tests/api/friends-basic.js +++ b/server/tests/api/friends-basic.js @@ -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) diff --git a/server/tests/utils/pods.js b/server/tests/utils/pods.js index 25b97edec..cdabb64a6 100644 --- a/server/tests/utils/pods.js +++ b/server/tests/utils/pods.js @@ -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 -- 2.25.1