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
This commit is contained in:
Green-Star 2017-08-02 21:50:42 +02:00 committed by Bigard Florian
parent 291e8d3eed
commit d5f5a670fc
10 changed files with 324 additions and 13 deletions

View File

@ -2,7 +2,7 @@
<div class="content-padding"> <div class="content-padding">
<h3>Friends list</h3> <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()"> <a *ngIf="hasFriends()" class="btn btn-danger pull-left" (click)="quitFriends()">
Quit friends Quit friends

View File

@ -6,6 +6,7 @@ import { ServerDataSource } from 'ng2-smart-table'
import { ConfirmService } from '../../../core' import { ConfirmService } from '../../../core'
import { Utils } from '../../../shared' import { Utils } from '../../../shared'
import { FriendService } from '../shared' import { FriendService } from '../shared'
import { Pod } from '../../../../../../shared'
@Component({ @Component({
selector: 'my-friend-list', selector: 'my-friend-list',
@ -15,6 +16,7 @@ import { FriendService } from '../shared'
export class FriendListComponent { export class FriendListComponent {
friendsSource = null friendsSource = null
tableSettings = { tableSettings = {
mode: 'external',
attr: { attr: {
class: 'table-hover' class: 'table-hover'
}, },
@ -23,7 +25,10 @@ export class FriendListComponent {
position: 'right', position: 'right',
add: false, add: false,
edit: false, edit: false,
delete: false delete: true
},
delete: {
deleteButtonContent: Utils.getRowDeleteButton()
}, },
columns: { columns: {
id: { id: {
@ -71,8 +76,7 @@ export class FriendListComponent {
this.friendService.quitFriends().subscribe( this.friendService.quitFriends().subscribe(
status => { status => {
this.notificationsService.success('Sucess', 'Friends left!') this.notificationsService.success('Success', 'Friends left!')
this.friendsSource.refresh() 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)
)
}
)
}
} }

View File

@ -6,6 +6,7 @@ import 'rxjs/add/operator/map'
import { ServerDataSource } from 'ng2-smart-table' import { ServerDataSource } from 'ng2-smart-table'
import { AuthHttp, RestExtractor, RestDataSource, ResultList } from '../../../shared' import { AuthHttp, RestExtractor, RestDataSource, ResultList } from '../../../shared'
import { Pod } from '../../../../../../shared'
@Injectable() @Injectable()
export class FriendService { export class FriendService {
@ -20,7 +21,7 @@ export class FriendService {
return new RestDataSource(this.authHttp, FriendService.BASE_FRIEND_URL) return new RestDataSource(this.authHttp, FriendService.BASE_FRIEND_URL)
} }
makeFriends (notEmptyHosts) { makeFriends (notEmptyHosts: String[]) {
const body = { const body = {
hosts: notEmptyHosts hosts: notEmptyHosts
} }
@ -35,4 +36,10 @@ export class FriendService {
.map(res => res.status) .map(res => res.status)
.catch((res) => this.restExtractor.handleError(res)) .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))
}
} }

View File

@ -10,7 +10,8 @@ import {
import { import {
sendOwnedVideosToPod, sendOwnedVideosToPod,
makeFriends, makeFriends,
quitFriends quitFriends,
removeFriend
} from '../../lib' } from '../../lib'
import { import {
podsAddValidator, podsAddValidator,
@ -18,7 +19,8 @@ import {
ensureIsAdmin, ensureIsAdmin,
makeFriendsValidator, makeFriendsValidator,
setBodyHostPort, setBodyHostPort,
setBodyHostsPort setBodyHostsPort,
podRemoveValidator
} from '../../middlewares' } from '../../middlewares'
import { import {
PodInstance PodInstance
@ -45,6 +47,12 @@ podsRouter.get('/quitfriends',
ensureIsAdmin, ensureIsAdmin,
quitFriendsController 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()) .then(() => res.type('json').status(204).end())
.catch(err => next(err)) .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))
}

View File

@ -242,6 +242,23 @@ function fetchRemotePreview (pod: PodInstance, video: VideoInstance) {
return request.get(REMOTE_SCHEME.HTTP + '://' + host + path) 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 () { function getRequestScheduler () {
return requestScheduler return requestScheduler
} }
@ -268,6 +285,7 @@ export {
hasFriends, hasFriends,
makeFriends, makeFriends,
quitFriends, quitFriends,
removeFriend,
removeVideoToFriends, removeVideoToFriends,
sendOwnedVideosToPod, sendOwnedVideosToPod,
getRequestScheduler, getRequestScheduler,

View 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 { export {
makeFriendsValidator, makeFriendsValidator,
podsAddValidator podsAddValidator,
podRemoveValidator
} }

View File

@ -17,7 +17,7 @@ describe('Test pods API validators', function () {
// --------------------------------------------------------------- // ---------------------------------------------------------------
before(function (done) { before(function (done) {
this.timeout(20000) this.timeout(45000)
series([ series([
function (next) { function (next) {
@ -110,7 +110,7 @@ describe('Test pods API validators', function () {
.expect(400, done) .expect(400, done)
}) })
it('Should fail with a invalid token', function (done) { it('Should fail with an invalid token', function (done) {
request(server.url) request(server.url)
.post(path + '/makefriends') .post(path + '/makefriends')
.send(body) .send(body)
@ -130,7 +130,7 @@ describe('Test pods API validators', function () {
}) })
describe('When quitting friends', 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) request(server.url)
.get(path + '/quitfriends') .get(path + '/quitfriends')
.query({ start: 'hello' }) .query({ start: 'hello' })
@ -148,6 +148,50 @@ describe('Test pods API validators', function () {
.expect(403, done) .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 () { describe('When adding a pod', function () {
@ -181,7 +225,7 @@ describe('Test pods API validators', function () {
requestsUtils.makePostBodyRequest(server.url, path, null, data, done) 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 = { const data = {
email: 'testexample.com', email: 'testexample.com',
publicKey: 'mysuperpublickey' publicKey: 'mysuperpublickey'

View File

@ -25,6 +25,20 @@ describe('Test advanced friends', function () {
return podsUtils.quitFriends(server.url, server.accessToken, callback) 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) { function getFriendsList (podNumber, end) {
const server = servers[podNumber - 1] const server = servers[podNumber - 1]
return podsUtils.getFriendsList(server.url, end) 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) { after(function (done) {
servers.forEach(function (server) { servers.forEach(function (server) {
process.kill(-server.app.pid) process.kill(-server.app.pid)

View 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) { after(function (done) {
servers.forEach(function (server) { servers.forEach(function (server) {
process.kill(-server.app.pid) process.kill(-server.app.pid)

View File

@ -5,7 +5,8 @@ const request = require('supertest')
const podsUtils = { const podsUtils = {
getFriendsList, getFriendsList,
makeFriends, makeFriends,
quitFriends quitFriends,
quitOneFriend
} }
// ---------------------- Export functions -------------------- // ---------------------- 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 module.exports = podsUtils