From e62f6ef741c8d14817e321c554796ad64ea7ae1b Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 27 Jul 2016 21:15:13 +0200 Subject: [PATCH 001/105] Client: fix login state when logout --- client/src/app/shared/auth/auth.service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/app/shared/auth/auth.service.ts b/client/src/app/shared/auth/auth.service.ts index 667fbeb1f..4c08e24c0 100644 --- a/client/src/app/shared/auth/auth.service.ts +++ b/client/src/app/shared/auth/auth.service.ts @@ -107,6 +107,8 @@ export class AuthService { // TODO: make an HTTP request to revoke the tokens this.user = null; User.flush(); + + this.setStatus(AuthStatus.LoggedOut); } refreshAccessToken() { From e4c556196d7b31111f17596840d2e1d60caa7dcb Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sun, 31 Jul 2016 20:58:43 +0200 Subject: [PATCH 002/105] Server: reorganize express validators --- server.js | 2 +- server/helpers/custom-validators/index.js | 15 +++++++ server/helpers/custom-validators/misc.js | 18 +++++++++ server/helpers/custom-validators/users.js | 18 +++++++++ .../videos.js} | 40 ++++++++----------- server/initializers/constants.js | 35 +++++++++------- server/middlewares/validators/videos.js | 6 +-- server/models/video.js | 18 ++++----- 8 files changed, 100 insertions(+), 52 deletions(-) create mode 100644 server/helpers/custom-validators/index.js create mode 100644 server/helpers/custom-validators/misc.js create mode 100644 server/helpers/custom-validators/users.js rename server/helpers/{custom-validators.js => custom-validators/videos.js} (86%) diff --git a/server.js b/server.js index b2eeeff70..d38c5830f 100644 --- a/server.js +++ b/server.js @@ -53,7 +53,7 @@ app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: false })) // Validate some params for the API app.use(expressValidator({ - customValidators: customValidators + customValidators: Object.assign({}, customValidators.misc, customValidators.users, customValidators.videos) })) // ----------- Views, routes and static files ----------- diff --git a/server/helpers/custom-validators/index.js b/server/helpers/custom-validators/index.js new file mode 100644 index 000000000..ab3066822 --- /dev/null +++ b/server/helpers/custom-validators/index.js @@ -0,0 +1,15 @@ +'use strict' + +const miscValidators = require('./misc') +const usersValidators = require('./users') +const videosValidators = require('./videos') + +const validators = { + misc: miscValidators, + users: usersValidators, + videos: videosValidators +} + +// --------------------------------------------------------------------------- + +module.exports = validators diff --git a/server/helpers/custom-validators/misc.js b/server/helpers/custom-validators/misc.js new file mode 100644 index 000000000..782ae3dee --- /dev/null +++ b/server/helpers/custom-validators/misc.js @@ -0,0 +1,18 @@ +'use strict' + +const miscValidators = { + exists: exists, + isArray: isArray +} + +function exists (value) { + return value !== undefined && value !== null +} + +function isArray (value) { + return Array.isArray(value) +} + +// --------------------------------------------------------------------------- + +module.exports = miscValidators diff --git a/server/helpers/custom-validators/users.js b/server/helpers/custom-validators/users.js new file mode 100644 index 000000000..41e00d046 --- /dev/null +++ b/server/helpers/custom-validators/users.js @@ -0,0 +1,18 @@ +'use strict' + +const validator = require('express-validator').validator + +const constants = require('../../initializers/constants') +const USERS_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.USERS + +const usersValidators = { + isUserUsernameValid: isUserUsernameValid +} + +function isUserUsernameValid (value) { + return validator.isLength(value, USERS_CONSTRAINTS_FIELDS.USERNAME) +} + +// --------------------------------------------------------------------------- + +module.exports = usersValidators diff --git a/server/helpers/custom-validators.js b/server/helpers/custom-validators/videos.js similarity index 86% rename from server/helpers/custom-validators.js rename to server/helpers/custom-validators/videos.js index b666644c0..39a19cbd7 100644 --- a/server/helpers/custom-validators.js +++ b/server/helpers/custom-validators/videos.js @@ -2,13 +2,13 @@ const validator = require('express-validator').validator -const constants = require('../initializers/constants') -const VIDEOS_CONSTRAINTS_FIELDS = constants.VIDEOS_CONSTRAINTS_FIELDS +const constants = require('../../initializers/constants') +const usersValidators = require('./users') +const miscValidators = require('./misc') +const VIDEOS_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.VIDEOS -const customValidators = { - exists: exists, +const videosValidators = { isEachRemoteVideosValid: isEachRemoteVideosValid, - isArray: isArray, isVideoAuthorValid: isVideoAuthorValid, isVideoDateValid: isVideoDateValid, isVideoDescriptionValid: isVideoDescriptionValid, @@ -21,10 +21,6 @@ const customValidators = { isVideoThumbnail64Valid: isVideoThumbnail64Valid } -function exists (value) { - return value !== undefined && value !== null -} - function isEachRemoteVideosValid (requests) { return requests.every(function (request) { const video = request.data @@ -48,20 +44,8 @@ function isEachRemoteVideosValid (requests) { }) } -function isArray (value) { - return Array.isArray(value) -} - -function isRequestTypeAddValid (value) { - return value === 'add' -} - -function isRequestTypeRemoveValid (value) { - return value === 'remove' -} - function isVideoAuthorValid (value) { - return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.AUTHOR) + return usersValidators.isUserUsernameValid(usersValidators) } function isVideoDateValid (value) { @@ -90,7 +74,7 @@ function isVideoPodUrlValid (value) { } function isVideoTagsValid (tags) { - return isArray(tags) && + return miscValidators.isArray(tags) && validator.isInt(tags.length, VIDEOS_CONSTRAINTS_FIELDS.TAGS) && tags.every(function (tag) { return validator.isAlphanumeric(tag) && @@ -109,6 +93,14 @@ function isVideoThumbnail64Valid (value) { // --------------------------------------------------------------------------- -module.exports = customValidators +module.exports = videosValidators // --------------------------------------------------------------------------- + +function isRequestTypeAddValid (value) { + return value === 'add' +} + +function isRequestTypeRemoveValid (value) { + return value === 'remove' +} diff --git a/server/initializers/constants.js b/server/initializers/constants.js index 467816f2c..5f4aeccc6 100644 --- a/server/initializers/constants.js +++ b/server/initializers/constants.js @@ -3,6 +3,23 @@ // API version of our pod const API_VERSION = 'v1' +const CONSTRAINTS_FIELDS = { + USERS: { + USERNAME: { min: 3, max: 20 }, // Length + PASSWORD: { min: 6, max: 255 } // Length + }, + VIDEOS: { + NAME: { min: 3, max: 50 }, // Length + DESCRIPTION: { min: 3, max: 250 }, // Length + MAGNET_URI: { min: 10 }, // Length + DURATION: { min: 1, max: 7200 }, // Number + TAGS: { min: 1, max: 3 }, // Number of total tags + TAG: { min: 2, max: 10 }, // Length + THUMBNAIL: { min: 2, max: 30 }, + THUMBNAIL64: { min: 0, max: 20000 } // Bytes + } +} + // Score a pod has when we create it as a friend const FRIEND_SCORE = { BASE: 100, @@ -55,29 +72,18 @@ const THUMBNAILS_SIZE = '200x110' // Path for access to thumbnails with express router const THUMBNAILS_STATIC_PATH = '/static/thumbnails' -const VIDEOS_CONSTRAINTS_FIELDS = { - NAME: { min: 3, max: 50 }, // Length - DESCRIPTION: { min: 3, max: 250 }, // Length - MAGNET_URI: { min: 10 }, // Length - DURATION: { min: 1, max: 7200 }, // Number - AUTHOR: { min: 3, max: 20 }, // Length - TAGS: { min: 1, max: 3 }, // Number of total tags - TAG: { min: 2, max: 10 }, // Length - THUMBNAIL: { min: 2, max: 30 }, - THUMBNAIL64: { min: 0, max: 20000 } // Bytes -} - // Special constants for a test instance if (isTestInstance() === true) { FRIEND_SCORE.BASE = 20 INTERVAL = 10000 - VIDEOS_CONSTRAINTS_FIELDS.DURATION.max = 14 + CONSTRAINTS_FIELDS.VIDEOS.DURATION.max = 14 } // --------------------------------------------------------------------------- module.exports = { API_VERSION: API_VERSION, + CONSTRAINTS_FIELDS: CONSTRAINTS_FIELDS, FRIEND_SCORE: FRIEND_SCORE, INTERVAL: INTERVAL, OAUTH_LIFETIME: OAUTH_LIFETIME, @@ -90,8 +96,7 @@ module.exports = { SEEDS_IN_PARALLEL: SEEDS_IN_PARALLEL, SORTABLE_COLUMNS: SORTABLE_COLUMNS, THUMBNAILS_SIZE: THUMBNAILS_SIZE, - THUMBNAILS_STATIC_PATH: THUMBNAILS_STATIC_PATH, - VIDEOS_CONSTRAINTS_FIELDS: VIDEOS_CONSTRAINTS_FIELDS + THUMBNAILS_STATIC_PATH: THUMBNAILS_STATIC_PATH } // --------------------------------------------------------------------------- diff --git a/server/middlewares/validators/videos.js b/server/middlewares/validators/videos.js index 3e2af06fb..422f3642f 100644 --- a/server/middlewares/validators/videos.js +++ b/server/middlewares/validators/videos.js @@ -4,7 +4,7 @@ const mongoose = require('mongoose') const checkErrors = require('./utils').checkErrors const constants = require('../../initializers/constants') -const customValidators = require('../../helpers/custom-validators') +const customVideosValidators = require('../../helpers/custom-validators').videos const logger = require('../../helpers/logger') const Video = mongoose.model('Video') @@ -33,8 +33,8 @@ function videosAdd (req, res, next) { return res.status(400).send('Cannot retrieve metadata of the file.') } - if (!customValidators.isVideoDurationValid(duration)) { - return res.status(400).send('Duration of the video file is too big (max: ' + constants.VIDEOS_CONSTRAINTS_FIELDS.DURATION.max + 's).') + if (!customVideosValidators.isVideoDurationValid(duration)) { + return res.status(400).send('Duration of the video file is too big (max: ' + constants.CONSTRAINTS_FIELDS.VIDEOS.DURATION.max + 's).') } videoFile.duration = duration diff --git a/server/models/video.js b/server/models/video.js index 396aa505d..acb8353c2 100644 --- a/server/models/video.js +++ b/server/models/video.js @@ -9,7 +9,7 @@ const pathUtils = require('path') const mongoose = require('mongoose') const constants = require('../initializers/constants') -const customValidators = require('../helpers/custom-validators') +const customVideosValidators = require('../helpers/custom-validators').videos const logger = require('../helpers/logger') const utils = require('../helpers/utils') const webtorrent = require('../lib/webtorrent') @@ -39,18 +39,18 @@ const VideoSchema = mongoose.Schema({ } }) -VideoSchema.path('name').validate(customValidators.isVideoNameValid) -VideoSchema.path('description').validate(customValidators.isVideoDescriptionValid) -VideoSchema.path('magnetUri').validate(customValidators.isVideoMagnetUriValid) -VideoSchema.path('podUrl').validate(customValidators.isVideoPodUrlValid) -VideoSchema.path('author').validate(customValidators.isVideoAuthorValid) -VideoSchema.path('duration').validate(customValidators.isVideoDurationValid) +VideoSchema.path('name').validate(customVideosValidators.isVideoNameValid) +VideoSchema.path('description').validate(customVideosValidators.isVideoDescriptionValid) +VideoSchema.path('magnetUri').validate(customVideosValidators.isVideoMagnetUriValid) +VideoSchema.path('podUrl').validate(customVideosValidators.isVideoPodUrlValid) +VideoSchema.path('author').validate(customVideosValidators.isVideoAuthorValid) +VideoSchema.path('duration').validate(customVideosValidators.isVideoDurationValid) // The tumbnail can be the path or the data in base 64 // The pre save hook will convert the base 64 data in a file on disk and replace the thumbnail key by the filename VideoSchema.path('thumbnail').validate(function (value) { - return customValidators.isVideoThumbnailValid(value) || customValidators.isVideoThumbnail64Valid(value) + return customVideosValidators.isVideoThumbnailValid(value) || customVideosValidators.isVideoThumbnail64Valid(value) }) -VideoSchema.path('tags').validate(customValidators.isVideoTagsValid) +VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid) VideoSchema.methods = { isOwned: isOwned, From 9bd2662976a75d3b03364cdbe6419e57c80f99a6 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 4 Aug 2016 22:32:36 +0200 Subject: [PATCH 003/105] Implement user API (create, update, remove, list) --- server/controllers/api/v1/pods.js | 14 +- server/controllers/api/v1/users.js | 132 +++++++++- server/helpers/custom-validators/users.js | 15 +- server/helpers/custom-validators/videos.js | 2 +- server/initializers/constants.js | 8 +- server/initializers/installer.js | 9 +- server/middlewares/admin.js | 22 ++ server/middlewares/index.js | 22 +- server/middlewares/validators/index.js | 2 + server/middlewares/validators/users.js | 57 +++++ server/middlewares/validators/videos.js | 1 + server/models/user.js | 33 ++- server/models/video.js | 5 + server/tests/api/checkParams.js | 268 +++++++++++++++++++-- server/tests/api/users.js | 83 ++++++- server/tests/api/utils.js | 62 ++++- 16 files changed, 688 insertions(+), 47 deletions(-) create mode 100644 server/middlewares/admin.js create mode 100644 server/middlewares/validators/users.js diff --git a/server/controllers/api/v1/pods.js b/server/controllers/api/v1/pods.js index 2bc761fef..f61f2a483 100644 --- a/server/controllers/api/v1/pods.js +++ b/server/controllers/api/v1/pods.js @@ -8,6 +8,7 @@ const waterfall = require('async/waterfall') const logger = require('../../../helpers/logger') const friends = require('../../../lib/friends') const middlewares = require('../../../middlewares') +const admin = middlewares.admin const oAuth = middlewares.oauth const validators = middlewares.validators.pods const signatureValidator = middlewares.validators.remote.signature @@ -18,8 +19,17 @@ const Video = mongoose.model('Video') router.get('/', listPodsUrl) router.post('/', validators.podsAdd, addPods) -router.get('/makefriends', oAuth.authenticate, validators.makeFriends, makeFriends) -router.get('/quitfriends', oAuth.authenticate, quitFriends) +router.get('/makefriends', + oAuth.authenticate, + admin.ensureIsAdmin, + validators.makeFriends, + makeFriends +) +router.get('/quitfriends', + oAuth.authenticate, + admin.ensureIsAdmin, + quitFriends +) // Post because this is a secured request router.post('/remove', signatureValidator, removePods) diff --git a/server/controllers/api/v1/users.js b/server/controllers/api/v1/users.js index fbbe6e472..e084974ce 100644 --- a/server/controllers/api/v1/users.js +++ b/server/controllers/api/v1/users.js @@ -1,18 +1,49 @@ 'use strict' +const each = require('async/each') const config = require('config') -const mongoose = require('mongoose') const express = require('express') +const mongoose = require('mongoose') +const waterfall = require('async/waterfall') -const oAuth = require('../../../middlewares').oauth +const constants = require('../../../initializers/constants') +const friends = require('../../../lib/friends') +const logger = require('../../../helpers/logger') +const middlewares = require('../../../middlewares') +const admin = middlewares.admin +const oAuth = middlewares.oauth +const validatorsUsers = middlewares.validators.users const Client = mongoose.model('OAuthClient') +const User = mongoose.model('User') +const Video = mongoose.model('Video') const router = express.Router() +router.get('/', listUsers) + +router.post('/', + oAuth.authenticate, + admin.ensureIsAdmin, + validatorsUsers.usersAdd, + createUser +) + +router.put('/:id', + oAuth.authenticate, + validatorsUsers.usersUpdate, + updateUser +) + +router.delete('/:username', + oAuth.authenticate, + admin.ensureIsAdmin, + validatorsUsers.usersRemove, + removeUser +) router.get('/client', getAngularClient) router.post('/token', oAuth.token, success) -// TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged,, implement revoke token route +// TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged, implement revoke token route // --------------------------------------------------------------------------- @@ -20,6 +51,20 @@ module.exports = router // --------------------------------------------------------------------------- +function createUser (req, res, next) { + const user = new User({ + username: req.body.username, + password: req.body.password, + role: constants.USER_ROLES.USER + }) + + user.save(function (err, createdUser) { + if (err) return next(err) + + return res.type('json').status(204).end() + }) +} + function getAngularClient (req, res, next) { const serverHost = config.get('webserver.host') const serverPort = config.get('webserver.port') @@ -44,6 +89,87 @@ function getAngularClient (req, res, next) { }) } +function listUsers (req, res, next) { + User.list(function (err, usersList) { + if (err) return next(err) + + res.json(getFormatedUsers(usersList)) + }) +} + +function removeUser (req, res, next) { + waterfall([ + function getUser (callback) { + User.loadByUsername(req.params.username, callback) + }, + + function getVideos (user, callback) { + Video.listOwnedByAuthor(user.username, function (err, videos) { + return callback(err, user, videos) + }) + }, + + function removeVideosFromDB (user, videos, callback) { + each(videos, function (video, callbackEach) { + video.remove(callbackEach) + }, function (err) { + return callback(err, user, videos) + }) + }, + + function sendInformationToFriends (user, videos, callback) { + videos.forEach(function (video) { + const params = { + name: video.name, + magnetUri: video.magnetUri + } + + friends.removeVideoToFriends(params) + }) + + return callback(null, user) + }, + + function removeUserFromDB (user, callback) { + user.remove(callback) + } + ], function andFinally (err) { + if (err) { + logger.error('Errors when removed the user.', { error: err }) + return next(err) + } + + return res.type('json').status(204).end() + }) +} + +function updateUser (req, res, next) { + User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { + if (err) return next(err) + + user.password = req.body.password + user.save(function (err) { + if (err) return next(err) + + return res.json('json').status(204).end() + }) + }) +} + function success (req, res, next) { res.end() } + +// --------------------------------------------------------------------------- + +function getFormatedUsers (users) { + const formatedUsers = [] + + users.forEach(function (user) { + formatedUsers.push(user.toFormatedJSON()) + }) + + return { + data: formatedUsers + } +} diff --git a/server/helpers/custom-validators/users.js b/server/helpers/custom-validators/users.js index 41e00d046..0e92989e5 100644 --- a/server/helpers/custom-validators/users.js +++ b/server/helpers/custom-validators/users.js @@ -1,16 +1,29 @@ 'use strict' const validator = require('express-validator').validator +const values = require('lodash/values') const constants = require('../../initializers/constants') const USERS_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.USERS const usersValidators = { + isUserPasswordValid: isUserPasswordValid, + isUserRoleValid: isUserRoleValid, isUserUsernameValid: isUserUsernameValid } +function isUserPasswordValid (value) { + return validator.isLength(value, USERS_CONSTRAINTS_FIELDS.PASSWORD) +} + +function isUserRoleValid (value) { + return values(constants.USER_ROLES).indexOf(value) !== -1 +} + function isUserUsernameValid (value) { - return validator.isLength(value, USERS_CONSTRAINTS_FIELDS.USERNAME) + const max = USERS_CONSTRAINTS_FIELDS.USERNAME.max + const min = USERS_CONSTRAINTS_FIELDS.USERNAME.min + return validator.matches(value, new RegExp(`^[a-zA-Z0-9._]{${min},${max}}$`)) } // --------------------------------------------------------------------------- diff --git a/server/helpers/custom-validators/videos.js b/server/helpers/custom-validators/videos.js index 39a19cbd7..cffa973f8 100644 --- a/server/helpers/custom-validators/videos.js +++ b/server/helpers/custom-validators/videos.js @@ -45,7 +45,7 @@ function isEachRemoteVideosValid (requests) { } function isVideoAuthorValid (value) { - return usersValidators.isUserUsernameValid(usersValidators) + return usersValidators.isUserUsernameValid(value) } function isVideoDateValid (value) { diff --git a/server/initializers/constants.js b/server/initializers/constants.js index 5f4aeccc6..416356400 100644 --- a/server/initializers/constants.js +++ b/server/initializers/constants.js @@ -72,6 +72,11 @@ const THUMBNAILS_SIZE = '200x110' // Path for access to thumbnails with express router const THUMBNAILS_STATIC_PATH = '/static/thumbnails' +const USER_ROLES = { + ADMIN: 'admin', + USER: 'user' +} + // Special constants for a test instance if (isTestInstance() === true) { FRIEND_SCORE.BASE = 20 @@ -96,7 +101,8 @@ module.exports = { SEEDS_IN_PARALLEL: SEEDS_IN_PARALLEL, SORTABLE_COLUMNS: SORTABLE_COLUMNS, THUMBNAILS_SIZE: THUMBNAILS_SIZE, - THUMBNAILS_STATIC_PATH: THUMBNAILS_STATIC_PATH + THUMBNAILS_STATIC_PATH: THUMBNAILS_STATIC_PATH, + USER_ROLES: USER_ROLES } // --------------------------------------------------------------------------- diff --git a/server/initializers/installer.js b/server/initializers/installer.js index 32830d4da..c12187871 100644 --- a/server/initializers/installer.js +++ b/server/initializers/installer.js @@ -9,6 +9,7 @@ const path = require('path') const series = require('async/series') const checker = require('./checker') +const constants = require('./constants') const logger = require('../helpers/logger') const peertubeCrypto = require('../helpers/peertube-crypto') @@ -34,7 +35,7 @@ function installApplication (callback) { }, function createOAuthUser (callbackAsync) { - createOAuthUserIfNotExist(callbackAsync) + createOAuthAdminIfNotExist(callbackAsync) } ], callback) } @@ -80,7 +81,7 @@ function createOAuthClientIfNotExist (callback) { }) } -function createOAuthUserIfNotExist (callback) { +function createOAuthAdminIfNotExist (callback) { checker.usersExist(function (err, exist) { if (err) return callback(err) @@ -90,6 +91,7 @@ function createOAuthUserIfNotExist (callback) { logger.info('Creating the administrator.') const username = 'root' + const role = constants.USER_ROLES.ADMIN let password = '' // Do not generate a random password for tests @@ -105,7 +107,8 @@ function createOAuthUserIfNotExist (callback) { const user = new User({ username: username, - password: password + password: password, + role: role }) user.save(function (err, createdUser) { diff --git a/server/middlewares/admin.js b/server/middlewares/admin.js new file mode 100644 index 000000000..bcb60ab95 --- /dev/null +++ b/server/middlewares/admin.js @@ -0,0 +1,22 @@ +'use strict' + +const constants = require('../initializers/constants') +const logger = require('../helpers/logger') + +const adminMiddleware = { + ensureIsAdmin: ensureIsAdmin +} + +function ensureIsAdmin (req, res, next) { + const user = res.locals.oauth.token.user + if (user.role !== constants.USER_ROLES.ADMIN) { + logger.info('A non admin user is trying to access to an admin content.') + return res.sendStatus(403) + } + + return next() +} + +// --------------------------------------------------------------------------- + +module.exports = adminMiddleware diff --git a/server/middlewares/index.js b/server/middlewares/index.js index 0a233e701..1e294de5f 100644 --- a/server/middlewares/index.js +++ b/server/middlewares/index.js @@ -1,19 +1,21 @@ 'use strict' -const oauth = require('./oauth') -const pagination = require('./pagination') +const adminMiddleware = require('./admin') +const oauthMiddleware = require('./oauth') +const paginationMiddleware = require('./pagination') const validatorsMiddleware = require('./validators') -const search = require('./search') -const sort = require('./sort') +const searchMiddleware = require('./search') +const sortMiddleware = require('./sort') const secureMiddleware = require('./secure') const middlewares = { - oauth: oauth, - pagination: pagination, - validators: validatorsMiddleware, - search: search, - sort: sort, - secure: secureMiddleware + admin: adminMiddleware, + oauth: oauthMiddleware, + pagination: paginationMiddleware, + search: searchMiddleware, + secure: secureMiddleware, + sort: sortMiddleware, + validators: validatorsMiddleware } // --------------------------------------------------------------------------- diff --git a/server/middlewares/validators/index.js b/server/middlewares/validators/index.js index 0471b3f92..6c3a9c2b4 100644 --- a/server/middlewares/validators/index.js +++ b/server/middlewares/validators/index.js @@ -4,6 +4,7 @@ const paginationValidators = require('./pagination') const podsValidators = require('./pods') const remoteValidators = require('./remote') const sortValidators = require('./sort') +const usersValidators = require('./users') const videosValidators = require('./videos') const validators = { @@ -11,6 +12,7 @@ const validators = { pods: podsValidators, remote: remoteValidators, sort: sortValidators, + users: usersValidators, videos: videosValidators } diff --git a/server/middlewares/validators/users.js b/server/middlewares/validators/users.js new file mode 100644 index 000000000..175d90bcb --- /dev/null +++ b/server/middlewares/validators/users.js @@ -0,0 +1,57 @@ +'use strict' + +const mongoose = require('mongoose') + +const checkErrors = require('./utils').checkErrors +const logger = require('../../helpers/logger') + +const User = mongoose.model('User') + +const validatorsUsers = { + usersAdd: usersAdd, + usersRemove: usersRemove, + usersUpdate: usersUpdate +} + +function usersAdd (req, res, next) { + req.checkBody('username', 'Should have a valid username').isUserUsernameValid() + req.checkBody('password', 'Should have a valid password').isUserPasswordValid() + + // TODO: check we don't have already the same username + + logger.debug('Checking usersAdd parameters', { parameters: req.body }) + + checkErrors(req, res, next) +} + +function usersRemove (req, res, next) { + req.checkParams('username', 'Should have a valid username').isUserUsernameValid() + + logger.debug('Checking usersRemove parameters', { parameters: req.params }) + + checkErrors(req, res, function () { + User.loadByUsername(req.params.username, function (err, user) { + if (err) { + logger.error('Error in usersRemove request validator.', { error: err }) + return res.sendStatus(500) + } + + if (!user) return res.status(404).send('User not found') + + next() + }) + }) +} + +function usersUpdate (req, res, next) { + // Add old password verification + req.checkBody('password', 'Should have a valid password').isUserPasswordValid() + + logger.debug('Checking usersUpdate parameters', { parameters: req.body }) + + checkErrors(req, res, next) +} + +// --------------------------------------------------------------------------- + +module.exports = validatorsUsers diff --git a/server/middlewares/validators/videos.js b/server/middlewares/validators/videos.js index 422f3642f..9d21ee16f 100644 --- a/server/middlewares/validators/videos.js +++ b/server/middlewares/validators/videos.js @@ -18,6 +18,7 @@ const validatorsVideos = { function videosAdd (req, res, next) { req.checkFiles('videofile[0].originalname', 'Should have an input video').notEmpty() + // TODO: move to constants and function req.checkFiles('videofile[0].mimetype', 'Should have a correct mime type').matches(/video\/(webm)|(mp4)|(ogg)/i) req.checkBody('name', 'Should have a valid name').isVideoNameValid() req.checkBody('description', 'Should have a valid description').isVideoDescriptionValid() diff --git a/server/models/user.js b/server/models/user.js index 14ffecbff..0bbd638d4 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -1,28 +1,49 @@ const mongoose = require('mongoose') +const customUsersValidators = require('../helpers/custom-validators').users + // --------------------------------------------------------------------------- const UserSchema = mongoose.Schema({ password: String, - username: String + username: String, + role: String }) -UserSchema.path('password').required(true) -UserSchema.path('username').required(true) +UserSchema.path('password').required(customUsersValidators.isUserPasswordValid) +UserSchema.path('username').required(customUsersValidators.isUserUsernameValid) +UserSchema.path('role').validate(customUsersValidators.isUserRoleValid) + +UserSchema.methods = { + toFormatedJSON: toFormatedJSON +} UserSchema.statics = { getByUsernameAndPassword: getByUsernameAndPassword, - list: list + list: list, + loadByUsername: loadByUsername } mongoose.model('User', UserSchema) // --------------------------------------------------------------------------- +function getByUsernameAndPassword (username, password) { + return this.findOne({ username: username, password: password }) +} + function list (callback) { return this.find(callback) } -function getByUsernameAndPassword (username, password) { - return this.findOne({ username: username, password: password }) +function loadByUsername (username, callback) { + return this.findOne({ username: username }, callback) +} + +function toFormatedJSON () { + return { + id: this._id, + username: this.username, + role: this.role + } } diff --git a/server/models/video.js b/server/models/video.js index acb8353c2..14bc91b16 100644 --- a/server/models/video.js +++ b/server/models/video.js @@ -64,6 +64,7 @@ VideoSchema.statics = { listByUrlAndMagnet: listByUrlAndMagnet, listByUrls: listByUrls, listOwned: listOwned, + listOwnedByAuthor: listOwnedByAuthor, listRemotes: listRemotes, load: load, search: search, @@ -211,6 +212,10 @@ function listOwned (callback) { this.find({ filename: { $ne: null } }, callback) } +function listOwnedByAuthor (author, callback) { + this.find({ filename: { $ne: null }, author: author }, callback) +} + function listRemotes (callback) { this.find({ filename: null }, callback) } diff --git a/server/tests/api/checkParams.js b/server/tests/api/checkParams.js index c1ba9c2c0..bd7227e9c 100644 --- a/server/tests/api/checkParams.js +++ b/server/tests/api/checkParams.js @@ -11,9 +11,8 @@ const utils = require('./utils') describe('Test parameters validator', function () { let server = null - function makePostRequest (path, token, fields, attaches, done, fail) { - let statusCode = 400 - if (fail !== undefined && fail === false) statusCode = 204 + function makePostRequest (path, token, fields, attaches, done, statusCodeExpected) { + if (!statusCodeExpected) statusCodeExpected = 400 const req = request(server.url) .post(path) @@ -38,18 +37,31 @@ describe('Test parameters validator', function () { req.attach(attach, value) }) - req.expect(statusCode, done) + req.expect(statusCodeExpected, done) } - function makePostBodyRequest (path, fields, done, fail) { - let statusCode = 400 - if (fail !== undefined && fail === false) statusCode = 200 + function makePostBodyRequest (path, token, fields, done, statusCodeExpected) { + if (!statusCodeExpected) statusCodeExpected = 400 - request(server.url) + const req = request(server.url) .post(path) .set('Accept', 'application/json') - .send(fields) - .expect(statusCode, done) + + if (token) req.set('Authorization', 'Bearer ' + token) + + req.send(fields).expect(statusCodeExpected, done) + } + + function makePutBodyRequest (path, token, fields, done, statusCodeExpected) { + if (!statusCodeExpected) statusCodeExpected = 400 + + const req = request(server.url) + .put(path) + .set('Accept', 'application/json') + + if (token) req.set('Authorization', 'Bearer ' + token) + + req.send(fields).expect(statusCodeExpected, done) } // --------------------------------------------------------------- @@ -85,21 +97,21 @@ describe('Test parameters validator', function () { describe('When adding a pod', function () { it('Should fail with nothing', function (done) { const data = {} - makePostBodyRequest(path, data, done) + makePostBodyRequest(path, null, data, done) }) it('Should fail without public key', function (done) { const data = { url: 'http://coucou.com' } - makePostBodyRequest(path, data, done) + makePostBodyRequest(path, null, data, done) }) it('Should fail without an url', function (done) { const data = { publicKey: 'mysuperpublickey' } - makePostBodyRequest(path, data, done) + makePostBodyRequest(path, null, data, done) }) it('Should fail with an incorrect url', function (done) { @@ -107,11 +119,11 @@ describe('Test parameters validator', function () { url: 'coucou.com', publicKey: 'mysuperpublickey' } - makePostBodyRequest(path, data, function () { + makePostBodyRequest(path, null, data, function () { data.url = 'http://coucou' - makePostBodyRequest(path, data, function () { + makePostBodyRequest(path, null, data, function () { data.url = 'coucou' - makePostBodyRequest(path, data, done) + makePostBodyRequest(path, null, data, done) }) }) }) @@ -121,7 +133,68 @@ describe('Test parameters validator', function () { url: 'http://coucou.com', publicKey: 'mysuperpublickey' } - makePostBodyRequest(path, data, done, false) + makePostBodyRequest(path, null, data, done, 200) + }) + }) + + describe('For the friends API', function () { + let userAccessToken = null + + before(function (done) { + utils.createUser(server.url, server.accessToken, 'user1', 'password', function () { + server.user = { + username: 'user1', + password: 'password' + } + + utils.loginAndGetAccessToken(server, function (err, accessToken) { + if (err) throw err + + userAccessToken = accessToken + + done() + }) + }) + }) + + describe('When making friends', function () { + it('Should fail with a invalid token', function (done) { + request(server.url) + .get(path + '/makefriends') + .query({ start: 'hello' }) + .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) + .get(path + '/makefriends') + .query({ start: 'hello' }) + .set('Authorization', 'Bearer ' + userAccessToken) + .set('Accept', 'application/json') + .expect(403, done) + }) + }) + + describe('When quitting friends', function () { + it('Should fail with a invalid token', function (done) { + request(server.url) + .get(path + '/quitfriends') + .query({ start: 'hello' }) + .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) + .get(path + '/quitfriends') + .query({ start: 'hello' }) + .set('Authorization', 'Bearer ' + userAccessToken) + .set('Accept', 'application/json') + .expect(403, done) + }) }) }) }) @@ -361,7 +434,7 @@ describe('Test parameters validator', function () { attach.videofile = pathUtils.join(__dirname, 'fixtures', 'video_short.mp4') makePostRequest(path, server.accessToken, data, attach, function () { attach.videofile = pathUtils.join(__dirname, 'fixtures', 'video_short.ogv') - makePostRequest(path, server.accessToken, data, attach, done, false) + makePostRequest(path, server.accessToken, data, attach, done, 204) }, false) }, false) }) @@ -429,6 +502,165 @@ describe('Test parameters validator', function () { }) }) + describe('Of the users API', function () { + const path = '/api/v1/users/' + + describe('When adding a new user', function () { + it('Should fail with a too small username', function (done) { + const data = { + username: 'ji', + password: 'mysuperpassword' + } + + makePostBodyRequest(path, server.accessToken, data, done) + }) + + it('Should fail with a too long username', function (done) { + const data = { + username: 'mysuperusernamewhichisverylong', + password: 'mysuperpassword' + } + + makePostBodyRequest(path, server.accessToken, data, done) + }) + + it('Should fail with an incorrect username', function (done) { + const data = { + username: 'my username', + password: 'mysuperpassword' + } + + makePostBodyRequest(path, server.accessToken, data, done) + }) + + it('Should fail with a too small password', function (done) { + const data = { + username: 'myusername', + password: 'bla' + } + + makePostBodyRequest(path, server.accessToken, data, done) + }) + + it('Should fail with a too long password', function (done) { + const data = { + username: 'myusername', + password: 'my super long password which is very very very very very very very very very very very very very very' + + 'very very very very very very very very very very very very very very very veryv very very very very' + + 'very very very very very very very very very very very very very very very very very very very very long' + } + + makePostBodyRequest(path, server.accessToken, data, done) + }) + + it('Should fail with an non authenticated user', function (done) { + const data = { + username: 'myusername', + password: 'my super password' + } + + makePostBodyRequest(path, 'super token', data, done, 401) + }) + + it('Should succeed with the correct params', function (done) { + const data = { + username: 'user1', + password: 'my super password' + } + + makePostBodyRequest(path, server.accessToken, data, done, 204) + }) + + it('Should fail with a non admin user', function (done) { + server.user = { + username: 'user1', + password: 'my super password' + } + + utils.loginAndGetAccessToken(server, function (err, accessToken) { + if (err) throw err + + const data = { + username: 'user2', + password: 'my super password' + } + + makePostBodyRequest(path, accessToken, data, done, 403) + }) + }) + }) + + describe('When updating a user', function () { + let userId = null + + before(function (done) { + utils.getUsersList(server.url, function (err, res) { + if (err) throw err + + userId = res.body.data[1].id + done() + }) + }) + + it('Should fail with a too small password', function (done) { + const data = { + password: 'bla' + } + + makePutBodyRequest(path + '/' + userId, server.accessToken, data, done) + }) + + it('Should fail with a too long password', function (done) { + const data = { + password: 'my super long password which is very very very very very very very very very very very very very very' + + 'very very very very very very very very very very very very very very very veryv very very very very' + + 'very very very very very very very very very very very very very very very very very very very very long' + } + + makePutBodyRequest(path + '/' + userId, server.accessToken, data, done) + }) + + it('Should fail with an non authenticated user', function (done) { + const data = { + password: 'my super password' + } + + makePutBodyRequest(path + '/' + userId, 'super token', data, done, 401) + }) + + it('Should succeed with the correct params', function (done) { + const data = { + password: 'my super password' + } + + makePutBodyRequest(path + '/' + userId, server.accessToken, data, done, 204) + }) + }) + + describe('When removing an user', function () { + it('Should fail with an incorrect username', function (done) { + request(server.url) + .delete(path + 'bla-bla') + .set('Authorization', 'Bearer ' + server.accessToken) + .expect(400, done) + }) + + it('Should return 404 with a non existing username', function (done) { + request(server.url) + .delete(path + 'qzzerg') + .set('Authorization', 'Bearer ' + server.accessToken) + .expect(404, done) + }) + + it('Should success with the correct parameters', function (done) { + request(server.url) + .delete(path + 'user1') + .set('Authorization', 'Bearer ' + server.accessToken) + .expect(204, done) + }) + }) + }) + describe('Of the remote videos API', function () { describe('When making a secure request', function () { it('Should check a secure request') diff --git a/server/tests/api/users.js b/server/tests/api/users.js index 68ba9de33..c711d6b64 100644 --- a/server/tests/api/users.js +++ b/server/tests/api/users.js @@ -13,7 +13,9 @@ const utils = require('./utils') describe('Test users', function () { let server = null let accessToken = null - let videoId + let accessTokenUser = null + let videoId = null + let userId = null before(function (done) { this.timeout(20000) @@ -158,6 +160,85 @@ describe('Test users', function () { it('Should be able to upload a video again') + it('Should be able to create a new user', function (done) { + utils.createUser(server.url, accessToken, 'user_1', 'super password', done) + }) + + it('Should be able to login with this user', function (done) { + server.user = { + username: 'user_1', + password: 'super password' + } + + utils.loginAndGetAccessToken(server, function (err, token) { + if (err) throw err + + accessTokenUser = token + + done() + }) + }) + + it('Should be able to upload a video with this user', function (done) { + this.timeout(5000) + + const name = 'my super name' + const description = 'my super description' + const tags = [ 'tag1', 'tag2', 'tag3' ] + const file = 'video_short.webm' + utils.uploadVideo(server.url, accessTokenUser, name, description, tags, file, done) + }) + + it('Should list all the users', function (done) { + utils.getUsersList(server.url, function (err, res) { + if (err) throw err + + const users = res.body.data + + expect(users).to.be.an('array') + expect(users.length).to.equal(2) + + const rootUser = users[0] + expect(rootUser.username).to.equal('root') + + const user = users[1] + expect(user.username).to.equal('user_1') + userId = user.id + + done() + }) + }) + + it('Should update the user password', function (done) { + utils.updateUser(server.url, userId, accessTokenUser, 'new password', function (err, res) { + if (err) throw err + + server.user.password = 'new password' + utils.login(server.url, server.client, server.user, 200, done) + }) + }) + + it('Should be able to remove this user', function (done) { + utils.removeUser(server.url, accessToken, 'user_1', done) + }) + + it('Should not be able to login with this user', function (done) { + // server.user is already set to user 1 + utils.login(server.url, server.client, server.user, 400, done) + }) + + it('Should not have videos of this user', function (done) { + utils.getVideosList(server.url, function (err, res) { + if (err) throw err + + expect(res.body.total).to.equal(1) + const video = res.body.data[0] + expect(video.author).to.equal('root') + + done() + }) + }) + after(function (done) { process.kill(-server.app.pid) diff --git a/server/tests/api/utils.js b/server/tests/api/utils.js index 3cc769f26..f34b81e4a 100644 --- a/server/tests/api/utils.js +++ b/server/tests/api/utils.js @@ -8,11 +8,13 @@ const pathUtils = require('path') const request = require('supertest') const testUtils = { + createUser: createUser, dateIsValid: dateIsValid, flushTests: flushTests, getAllVideosListBy: getAllVideosListBy, getClient: getClient, getFriendsList: getFriendsList, + getUsersList: getUsersList, getVideo: getVideo, getVideosList: getVideosList, getVideosListPagination: getVideosListPagination, @@ -21,6 +23,7 @@ const testUtils = { loginAndGetAccessToken: loginAndGetAccessToken, makeFriends: makeFriends, quitFriends: quitFriends, + removeUser: removeUser, removeVideo: removeVideo, flushAndRunMultipleServers: flushAndRunMultipleServers, runServer: runServer, @@ -28,11 +31,29 @@ const testUtils = { searchVideoWithPagination: searchVideoWithPagination, searchVideoWithSort: searchVideoWithSort, testImage: testImage, - uploadVideo: uploadVideo + uploadVideo: uploadVideo, + updateUser: updateUser } // ---------------------- Export functions -------------------- +function createUser (url, accessToken, username, password, specialStatus, end) { + if (!end) { + end = specialStatus + specialStatus = 204 + } + + const path = '/api/v1/users' + + request(url) + .post(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .send({ username: username, password: password }) + .expect(specialStatus) + .end(end) +} + function dateIsValid (dateString) { const dateToCheck = new Date(dateString) const now = new Date() @@ -72,6 +93,17 @@ function getClient (url, end) { .end(end) } +function getUsersList (url, end) { + const path = '/api/v1/users' + + request(url) + .get(path) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) + .end(end) +} + function getFriendsList (url, end) { const path = '/api/v1/pods/' @@ -209,6 +241,22 @@ function quitFriends (url, accessToken, expectedStatus, callback) { }) } +function removeUser (url, token, username, expectedStatus, end) { + if (!end) { + end = expectedStatus + expectedStatus = 204 + } + + const path = '/api/v1/users' + + request(url) + .delete(path + '/' + username) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .expect(expectedStatus) + .end(end) +} + function removeVideo (url, token, id, expectedStatus, end) { if (!end) { end = expectedStatus @@ -414,6 +462,18 @@ function uploadVideo (url, accessToken, name, description, tags, fixture, specia .end(end) } +function updateUser (url, userId, accessToken, newPassword, end) { + const path = '/api/v1/users/' + userId + + request(url) + .put(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .send({ password: newPassword }) + .expect(200) + .end(end) +} + // --------------------------------------------------------------------------- module.exports = testUtils From 6606150c49f587bc7eb0ecec4263ce7fbb18bf15 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 5 Aug 2016 16:09:39 +0200 Subject: [PATCH 004/105] Server: move clients in its own file --- client/src/app/shared/auth/auth.service.ts | 2 +- server/controllers/api/v1/clients.js | 40 ++++++++++++++++++++++ server/controllers/api/v1/index.js | 2 ++ server/controllers/api/v1/users.js | 28 +-------------- 4 files changed, 44 insertions(+), 28 deletions(-) create mode 100644 server/controllers/api/v1/clients.js diff --git a/client/src/app/shared/auth/auth.service.ts b/client/src/app/shared/auth/auth.service.ts index 4c08e24c0..6a5b19ffe 100644 --- a/client/src/app/shared/auth/auth.service.ts +++ b/client/src/app/shared/auth/auth.service.ts @@ -8,7 +8,7 @@ import { User } from './user.model'; @Injectable() export class AuthService { - private static BASE_CLIENT_URL = '/api/v1/users/client'; + private static BASE_CLIENT_URL = '/api/v1/clients/local'; private static BASE_TOKEN_URL = '/api/v1/users/token'; loginChangedSource: Observable; diff --git a/server/controllers/api/v1/clients.js b/server/controllers/api/v1/clients.js new file mode 100644 index 000000000..0d222634b --- /dev/null +++ b/server/controllers/api/v1/clients.js @@ -0,0 +1,40 @@ +'use strict' + +const config = require('config') +const express = require('express') +const mongoose = require('mongoose') + +const Client = mongoose.model('OAuthClient') + +const router = express.Router() + +router.get('/local', getLocalClient) + +// Get the client credentials for the PeerTube front end +function getLocalClient (req, res, next) { + const serverHost = config.get('webserver.host') + const serverPort = config.get('webserver.port') + let headerHostShouldBe = serverHost + if (serverPort !== 80 && serverPort !== 443) { + headerHostShouldBe += ':' + serverPort + } + + // Don't make this check if this is a test instance + if (process.env.NODE_ENV !== 'test' && req.get('host') !== headerHostShouldBe) { + return res.type('json').status(403).end() + } + + Client.loadFirstClient(function (err, client) { + if (err) return next(err) + if (!client) return next(new Error('No client available.')) + + res.json({ + client_id: client._id, + client_secret: client.clientSecret + }) + }) +} + +// --------------------------------------------------------------------------- + +module.exports = router diff --git a/server/controllers/api/v1/index.js b/server/controllers/api/v1/index.js index e0c29a8a2..af41bc280 100644 --- a/server/controllers/api/v1/index.js +++ b/server/controllers/api/v1/index.js @@ -4,11 +4,13 @@ const express = require('express') const router = express.Router() +const clientsController = require('./clients') const podsController = require('./pods') const remoteController = require('./remote') const usersController = require('./users') const videosController = require('./videos') +router.use('/clients', clientsController) router.use('/pods', podsController) router.use('/remote', remoteController) router.use('/users', usersController) diff --git a/server/controllers/api/v1/users.js b/server/controllers/api/v1/users.js index e084974ce..fdbcc3ff5 100644 --- a/server/controllers/api/v1/users.js +++ b/server/controllers/api/v1/users.js @@ -1,7 +1,6 @@ 'use strict' const each = require('async/each') -const config = require('config') const express = require('express') const mongoose = require('mongoose') const waterfall = require('async/waterfall') @@ -14,7 +13,6 @@ const admin = middlewares.admin const oAuth = middlewares.oauth const validatorsUsers = middlewares.validators.users -const Client = mongoose.model('OAuthClient') const User = mongoose.model('User') const Video = mongoose.model('Video') @@ -41,7 +39,7 @@ router.delete('/:username', validatorsUsers.usersRemove, removeUser ) -router.get('/client', getAngularClient) + router.post('/token', oAuth.token, success) // TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged, implement revoke token route @@ -65,30 +63,6 @@ function createUser (req, res, next) { }) } -function getAngularClient (req, res, next) { - const serverHost = config.get('webserver.host') - const serverPort = config.get('webserver.port') - let headerHostShouldBe = serverHost - if (serverPort !== 80 && serverPort !== 443) { - headerHostShouldBe += ':' + serverPort - } - - // Don't make this check if this is a test instance - if (process.env.NODE_ENV !== 'test' && req.get('host') !== headerHostShouldBe) { - return res.type('json').status(403).end() - } - - Client.loadFirstClient(function (err, client) { - if (err) return next(err) - if (!client) return next(new Error('No client available.')) - - res.json({ - client_id: client._id, - client_secret: client.clientSecret - }) - }) -} - function listUsers (req, res, next) { User.list(function (err, usersList) { if (err) return next(err) From 99a64bfed25e45547df3045cf249bc895e6f220b Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 5 Aug 2016 17:19:08 +0200 Subject: [PATCH 005/105] Server: allow user to get its informations (/users/me) --- server/controllers/api/v1/users.js | 9 ++++++++ server/tests/api/checkParams.js | 34 ++++++++++++++++++++++++------ server/tests/api/users.js | 13 ++++++++++++ server/tests/api/utils.js | 13 ++++++++++++ 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/server/controllers/api/v1/users.js b/server/controllers/api/v1/users.js index fdbcc3ff5..d831a0de6 100644 --- a/server/controllers/api/v1/users.js +++ b/server/controllers/api/v1/users.js @@ -19,6 +19,7 @@ const Video = mongoose.model('Video') const router = express.Router() router.get('/', listUsers) +router.get('/me', oAuth.authenticate, getUserInformation) router.post('/', oAuth.authenticate, @@ -63,6 +64,14 @@ function createUser (req, res, next) { }) } +function getUserInformation (req, res, next) { + User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { + if (err) return next(err) + + return res.json(user.toFormatedJSON()) + }) +} + function listUsers (req, res, next) { User.list(function (err, usersList) { if (err) return next(err) diff --git a/server/tests/api/checkParams.js b/server/tests/api/checkParams.js index bd7227e9c..8b49f5f36 100644 --- a/server/tests/api/checkParams.js +++ b/server/tests/api/checkParams.js @@ -504,6 +504,8 @@ describe('Test parameters validator', function () { describe('Of the users API', function () { const path = '/api/v1/users/' + let userId = null + let userAccessToken = null describe('When adding a new user', function () { it('Should fail with a too small username', function (done) { @@ -580,19 +582,19 @@ describe('Test parameters validator', function () { utils.loginAndGetAccessToken(server, function (err, accessToken) { if (err) throw err + userAccessToken = accessToken + const data = { username: 'user2', password: 'my super password' } - makePostBodyRequest(path, accessToken, data, done, 403) + makePostBodyRequest(path, userAccessToken, data, done, 403) }) }) }) describe('When updating a user', function () { - let userId = null - before(function (done) { utils.getUsersList(server.url, function (err, res) { if (err) throw err @@ -607,7 +609,7 @@ describe('Test parameters validator', function () { password: 'bla' } - makePutBodyRequest(path + '/' + userId, server.accessToken, data, done) + makePutBodyRequest(path + userId, userAccessToken, data, done) }) it('Should fail with a too long password', function (done) { @@ -617,7 +619,7 @@ describe('Test parameters validator', function () { 'very very very very very very very very very very very very very very very very very very very very long' } - makePutBodyRequest(path + '/' + userId, server.accessToken, data, done) + makePutBodyRequest(path + userId, userAccessToken, data, done) }) it('Should fail with an non authenticated user', function (done) { @@ -625,7 +627,7 @@ describe('Test parameters validator', function () { password: 'my super password' } - makePutBodyRequest(path + '/' + userId, 'super token', data, done, 401) + makePutBodyRequest(path + userId, 'super token', data, done, 401) }) it('Should succeed with the correct params', function (done) { @@ -633,7 +635,25 @@ describe('Test parameters validator', function () { password: 'my super password' } - makePutBodyRequest(path + '/' + userId, server.accessToken, data, done, 204) + makePutBodyRequest(path + userId, userAccessToken, data, done, 204) + }) + }) + + describe('When getting my information', function () { + it('Should fail with a non authenticated user', function (done) { + request(server.url) + .get(path + 'me') + .set('Authorization', 'Bearer faketoken') + .set('Accept', 'application/json') + .expect(401, done) + }) + + it('Should success with the correct parameters', function (done) { + request(server.url) + .get(path + 'me') + .set('Authorization', 'Bearer ' + userAccessToken) + .set('Accept', 'application/json') + .expect(200, done) }) }) diff --git a/server/tests/api/users.js b/server/tests/api/users.js index c711d6b64..e1d4a8cf4 100644 --- a/server/tests/api/users.js +++ b/server/tests/api/users.js @@ -179,6 +179,19 @@ describe('Test users', function () { }) }) + it('Should be able to get the user informations', function (done) { + utils.getUserInformation(server.url, accessTokenUser, function (err, res) { + if (err) throw err + + const user = res.body + + expect(user.username).to.equal('user_1') + expect(user.id).to.exist + + done() + }) + }) + it('Should be able to upload a video with this user', function (done) { this.timeout(5000) diff --git a/server/tests/api/utils.js b/server/tests/api/utils.js index f34b81e4a..0dc309328 100644 --- a/server/tests/api/utils.js +++ b/server/tests/api/utils.js @@ -14,6 +14,7 @@ const testUtils = { getAllVideosListBy: getAllVideosListBy, getClient: getClient, getFriendsList: getFriendsList, + getUserInformation: getUserInformation, getUsersList: getUsersList, getVideo: getVideo, getVideosList: getVideosList, @@ -93,6 +94,18 @@ function getClient (url, end) { .end(end) } +function getUserInformation (url, accessToken, end) { + const path = '/api/v1/users/me' + + request(url) + .get(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(200) + .expect('Content-Type', /json/) + .end(end) +} + function getUsersList (url, end) { const path = '/api/v1/users' From 629d8d6f70cf83b55011dff53bfe1c4a95ac3433 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 5 Aug 2016 18:04:08 +0200 Subject: [PATCH 006/105] Client: implement password change --- client/src/app/account/account.component.html | 27 ++++++++++ client/src/app/account/account.component.ts | 45 +++++++++++++++++ client/src/app/account/account.routes.ts | 5 ++ client/src/app/account/account.service.ts | 19 +++++++ client/src/app/account/index.ts | 2 + client/src/app/app.component.html | 15 +++++- client/src/app/app.routes.ts | 2 + .../src/app/shared/auth/auth-http.service.ts | 6 ++- client/src/app/shared/auth/auth.service.ts | 49 +++++++++++++------ client/src/app/shared/auth/user.model.ts | 23 +++++++-- client/tsconfig.json | 6 +++ 11 files changed, 177 insertions(+), 22 deletions(-) create mode 100644 client/src/app/account/account.component.html create mode 100644 client/src/app/account/account.component.ts create mode 100644 client/src/app/account/account.routes.ts create mode 100644 client/src/app/account/account.service.ts create mode 100644 client/src/app/account/index.ts diff --git a/client/src/app/account/account.component.html b/client/src/app/account/account.component.html new file mode 100644 index 000000000..ad8f690bd --- /dev/null +++ b/client/src/app/account/account.component.html @@ -0,0 +1,27 @@ +

Account

+ +
{{ information }}
+
{{ error }}
+ +
+
+ + +
+ The password should have more than 5 characters +
+
+ +
+ + +
+ + +
diff --git a/client/src/app/account/account.component.ts b/client/src/app/account/account.component.ts new file mode 100644 index 000000000..5c42103f8 --- /dev/null +++ b/client/src/app/account/account.component.ts @@ -0,0 +1,45 @@ +import { Control, ControlGroup, Validators } from '@angular/common'; +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +import { AccountService } from './account.service'; + +@Component({ + selector: 'my-account', + template: require('./account.component.html'), + providers: [ AccountService ] +}) + +export class AccountComponent implements OnInit { + changePasswordForm: ControlGroup; + information: string = null; + error: string = null; + + constructor( + private accountService: AccountService, + private router: Router + ) {} + + ngOnInit() { + this.changePasswordForm = new ControlGroup({ + newPassword: new Control('', Validators.compose([ Validators.required, Validators.minLength(6) ])), + newConfirmedPassword: new Control('', Validators.compose([ Validators.required, Validators.minLength(6) ])), + }); + } + + changePassword(newPassword: string, newConfirmedPassword: string) { + this.information = null; + this.error = null; + + if (newPassword !== newConfirmedPassword) { + this.error = 'The new password and the confirmed password do not correspond.'; + return; + } + + this.accountService.changePassword(newPassword).subscribe( + ok => this.information = 'Password updated.', + + err => this.error = err + ); + } +} diff --git a/client/src/app/account/account.routes.ts b/client/src/app/account/account.routes.ts new file mode 100644 index 000000000..e348c6ebe --- /dev/null +++ b/client/src/app/account/account.routes.ts @@ -0,0 +1,5 @@ +import { AccountComponent } from './account.component'; + +export const AccountRoutes = [ + { path: 'account', component: AccountComponent } +]; diff --git a/client/src/app/account/account.service.ts b/client/src/app/account/account.service.ts new file mode 100644 index 000000000..19b4e0624 --- /dev/null +++ b/client/src/app/account/account.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; + +import { AuthHttp, AuthService } from '../shared'; + +@Injectable() +export class AccountService { + private static BASE_USERS_URL = '/api/v1/users/'; + + constructor(private authHttp: AuthHttp, private authService: AuthService) { } + + changePassword(newPassword: string) { + const url = AccountService.BASE_USERS_URL + this.authService.getUser().id; + const body = { + password: newPassword + }; + + return this.authHttp.put(url, body); + } +} diff --git a/client/src/app/account/index.ts b/client/src/app/account/index.ts new file mode 100644 index 000000000..7445003fd --- /dev/null +++ b/client/src/app/account/index.ts @@ -0,0 +1,2 @@ +export * from './account.component'; +export * from './account.routes'; diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index f2acffea4..ea4b31421 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html @@ -18,9 +18,20 @@
+ + + Login + + + + + Logout + +
+ +
diff --git a/client/src/app/app.routes.ts b/client/src/app/app.routes.ts index 59ef4ce55..1c414038d 100644 --- a/client/src/app/app.routes.ts +++ b/client/src/app/app.routes.ts @@ -1,5 +1,6 @@ import { RouterConfig } from '@angular/router'; +import { AccountRoutes } from './account'; import { LoginRoutes } from './login'; import { VideosRoutes } from './videos'; @@ -10,6 +11,7 @@ export const routes: RouterConfig = [ pathMatch: 'full' }, + ...AccountRoutes, ...LoginRoutes, ...VideosRoutes ]; diff --git a/client/src/app/shared/auth/auth-http.service.ts b/client/src/app/shared/auth/auth-http.service.ts index 9c7ef4389..55bb501e6 100644 --- a/client/src/app/shared/auth/auth-http.service.ts +++ b/client/src/app/shared/auth/auth-http.service.ts @@ -49,16 +49,18 @@ export class AuthHttp extends Http { return this.request(url, options); } - post(url: string, options?: RequestOptionsArgs): Observable { + post(url: string, body: any, options?: RequestOptionsArgs): Observable { if (!options) options = {}; options.method = RequestMethod.Post; + options.body = body; return this.request(url, options); } - put(url: string, options?: RequestOptionsArgs): Observable { + put(url: string, body: any, options?: RequestOptionsArgs): Observable { if (!options) options = {}; options.method = RequestMethod.Put; + options.body = body; return this.request(url, options); } diff --git a/client/src/app/shared/auth/auth.service.ts b/client/src/app/shared/auth/auth.service.ts index 6a5b19ffe..24d1a4fa2 100644 --- a/client/src/app/shared/auth/auth.service.ts +++ b/client/src/app/shared/auth/auth.service.ts @@ -10,6 +10,7 @@ import { User } from './user.model'; export class AuthService { private static BASE_CLIENT_URL = '/api/v1/clients/local'; private static BASE_TOKEN_URL = '/api/v1/users/token'; + private static BASE_USER_INFORMATIONS_URL = '/api/v1/users/me'; loginChangedSource: Observable; @@ -99,6 +100,7 @@ export class AuthService { res.username = username; return res; }) + .flatMap(res => this.fetchUserInformations(res)) .map(res => this.handleLogin(res)) .catch(this.handleError); } @@ -136,22 +138,20 @@ export class AuthService { .catch(this.handleError); } - private setStatus(status: AuthStatus) { - this.loginChanged.next(status); - } + private fetchUserInformations (obj: any) { + // Do not call authHttp here to avoid circular dependencies headaches - private handleLogin (obj: any) { - const username = obj.username; - const hash_tokens = { - access_token: obj.access_token, - token_type: obj.token_type, - refresh_token: obj.refresh_token - }; + const headers = new Headers(); + headers.set('Authorization', `Bearer ${obj.access_token}`); - this.user = new User(username, hash_tokens); - this.user.save(); - - this.setStatus(AuthStatus.LoggedIn); + return this.http.get(AuthService.BASE_USER_INFORMATIONS_URL, { headers }) + .map(res => res.json()) + .map(res => { + obj.id = res.id; + obj.role = res.role; + return obj; + } + ); } private handleError (error: Response) { @@ -159,8 +159,29 @@ export class AuthService { return Observable.throw(error.json() || { error: 'Server error' }); } + private handleLogin (obj: any) { + const id = obj.id; + const username = obj.username; + const role = obj.role; + const hash_tokens = { + access_token: obj.access_token, + token_type: obj.token_type, + refresh_token: obj.refresh_token + }; + + this.user = new User(id, username, role, hash_tokens); + this.user.save(); + + this.setStatus(AuthStatus.LoggedIn); + } + private handleRefreshToken (obj: any) { this.user.refreshTokens(obj.access_token, obj.refresh_token); this.user.save(); } + + private setStatus(status: AuthStatus) { + this.loginChanged.next(status); + } + } diff --git a/client/src/app/shared/auth/user.model.ts b/client/src/app/shared/auth/user.model.ts index 98852f835..e486873ab 100644 --- a/client/src/app/shared/auth/user.model.ts +++ b/client/src/app/shared/auth/user.model.ts @@ -1,15 +1,24 @@ export class User { private static KEYS = { + ID: 'id', + ROLE: 'role', USERNAME: 'username' }; + id: string; + role: string; username: string; tokens: Tokens; static load() { const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME); if (usernameLocalStorage) { - return new User(localStorage.getItem(this.KEYS.USERNAME), Tokens.load()); + return new User( + localStorage.getItem(this.KEYS.ID), + localStorage.getItem(this.KEYS.USERNAME), + localStorage.getItem(this.KEYS.ROLE), + Tokens.load() + ); } return null; @@ -17,11 +26,15 @@ export class User { static flush() { localStorage.removeItem(this.KEYS.USERNAME); + localStorage.removeItem(this.KEYS.ID); + localStorage.removeItem(this.KEYS.ROLE); Tokens.flush(); } - constructor(username: string, hash_tokens: any) { + constructor(id: string, username: string, role: string, hash_tokens: any) { + this.id = id; this.username = username; + this.role = role; this.tokens = new Tokens(hash_tokens); } @@ -43,12 +56,14 @@ export class User { } save() { - localStorage.setItem('username', this.username); + localStorage.setItem(User.KEYS.ID, this.id); + localStorage.setItem(User.KEYS.USERNAME, this.username); + localStorage.setItem(User.KEYS.ROLE, this.role); this.tokens.save(); } } -// Private class used only by User +// Private class only used by User class Tokens { private static KEYS = { ACCESS_TOKEN: 'access_token', diff --git a/client/tsconfig.json b/client/tsconfig.json index 67d1fb4f1..e2d61851e 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -27,6 +27,10 @@ "typings/main.d.ts" ], "files": [ + "src/app/account/account.component.ts", + "src/app/account/account.routes.ts", + "src/app/account/account.service.ts", + "src/app/account/index.ts", "src/app/app.component.ts", "src/app/app.routes.ts", "src/app/friends/friend.service.ts", @@ -45,6 +49,8 @@ "src/app/shared/search/search.component.ts", "src/app/shared/search/search.model.ts", "src/app/shared/search/search.service.ts", + "src/app/shared/user/index.ts", + "src/app/shared/user/user.service.ts", "src/app/videos/index.ts", "src/app/videos/shared/index.ts", "src/app/videos/shared/loader/index.ts", From 66af9ee16d5b61bb707f359b750b25f2faff306c Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 5 Aug 2016 18:05:27 +0200 Subject: [PATCH 007/105] Client: Redirect user to home page after logout --- client/src/app/app.component.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index b7a3d7c58..5764f24ca 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -47,6 +47,8 @@ export class AppComponent { logout() { this.authService.logout(); + // Redirect to home page + this.router.navigate(['/videos/list']); } makeFriends() { From 10431358b27ec1bb4de7ccd662465cf544f1cdcd Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 5 Aug 2016 18:08:55 +0200 Subject: [PATCH 008/105] Server: fix status code when updating/removing a user --- server/controllers/api/v1/users.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/controllers/api/v1/users.js b/server/controllers/api/v1/users.js index d831a0de6..057dcaf8d 100644 --- a/server/controllers/api/v1/users.js +++ b/server/controllers/api/v1/users.js @@ -122,7 +122,7 @@ function removeUser (req, res, next) { return next(err) } - return res.type('json').status(204).end() + return res.sendStatus(204) }) } @@ -134,7 +134,7 @@ function updateUser (req, res, next) { user.save(function (err) { if (err) return next(err) - return res.json('json').status(204).end() + return res.sendStatus(204) }) }) } From f3391f9237269ed671c23fdbcc9d86dc52134fe5 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 5 Aug 2016 19:18:11 +0200 Subject: [PATCH 009/105] Server: fix tests --- server/tests/api/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/tests/api/utils.js b/server/tests/api/utils.js index 0dc309328..8871f1f84 100644 --- a/server/tests/api/utils.js +++ b/server/tests/api/utils.js @@ -483,7 +483,7 @@ function updateUser (url, userId, accessToken, newPassword, end) { .set('Accept', 'application/json') .set('Authorization', 'Bearer ' + accessToken) .send({ password: newPassword }) - .expect(200) + .expect(204) .end(end) } From 58b2ba55a90f05f24661e664b1fb0a3486f037e8 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 5 Aug 2016 21:41:28 +0200 Subject: [PATCH 010/105] Server: do not allow a user to remove a video of another user --- client/tsconfig.json | 2 -- server/middlewares/validators/videos.js | 1 + server/tests/api/checkParams.js | 2 ++ 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/client/tsconfig.json b/client/tsconfig.json index e2d61851e..b10231b7b 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -49,8 +49,6 @@ "src/app/shared/search/search.component.ts", "src/app/shared/search/search.model.ts", "src/app/shared/search/search.service.ts", - "src/app/shared/user/index.ts", - "src/app/shared/user/user.service.ts", "src/app/videos/index.ts", "src/app/videos/shared/index.ts", "src/app/videos/shared/loader/index.ts", diff --git a/server/middlewares/validators/videos.js b/server/middlewares/validators/videos.js index 9d21ee16f..e51087d5a 100644 --- a/server/middlewares/validators/videos.js +++ b/server/middlewares/validators/videos.js @@ -77,6 +77,7 @@ function videosRemove (req, res, next) { if (!video) return res.status(404).send('Video not found') else if (video.isOwned() === false) return res.status(403).send('Cannot remove video of another pod') + else if (video.author !== res.locals.oauth.token.user.username) return res.status(403).send('Cannot remove video of another user') next() }) diff --git a/server/tests/api/checkParams.js b/server/tests/api/checkParams.js index 8b49f5f36..e489df277 100644 --- a/server/tests/api/checkParams.js +++ b/server/tests/api/checkParams.js @@ -496,6 +496,8 @@ describe('Test parameters validator', function () { .expect(404, done) }) + it('Should fail with a video of another user') + it('Should fail with a video of another pod') it('Should succeed with the correct parameters') From 8d30905858245f12a42fc327d2d57cbfe062d548 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sun, 7 Aug 2016 22:09:59 +0200 Subject: [PATCH 011/105] Server: split tests utils in multiple files --- server/tests/api/checkParams.js | 20 +- server/tests/api/friendsAdvanced.js | 23 +- server/tests/api/friendsBasic.js | 30 +- server/tests/api/multiplePods.js | 62 ++-- server/tests/api/singlePod.js | 90 ++--- server/tests/api/users.js | 59 ++-- server/tests/api/utils.js | 492 ---------------------------- server/tests/utils/clients.js | 24 ++ server/tests/utils/login.js | 48 +++ server/tests/utils/miscs.js | 21 ++ server/tests/utils/pods.js | 70 ++++ server/tests/utils/servers.js | 115 +++++++ server/tests/utils/users.js | 85 +++++ server/tests/utils/videos.js | 199 +++++++++++ 14 files changed, 712 insertions(+), 626 deletions(-) delete mode 100644 server/tests/api/utils.js create mode 100644 server/tests/utils/clients.js create mode 100644 server/tests/utils/login.js create mode 100644 server/tests/utils/miscs.js create mode 100644 server/tests/utils/pods.js create mode 100644 server/tests/utils/servers.js create mode 100644 server/tests/utils/users.js create mode 100644 server/tests/utils/videos.js diff --git a/server/tests/api/checkParams.js b/server/tests/api/checkParams.js index e489df277..675dc19e6 100644 --- a/server/tests/api/checkParams.js +++ b/server/tests/api/checkParams.js @@ -6,7 +6,9 @@ const pathUtils = require('path') const request = require('supertest') const series = require('async/series') -const utils = require('./utils') +const loginUtils = require('../utils/login') +const serversUtils = require('../utils/servers') +const usersUtils = require('../utils/users') describe('Test parameters validator', function () { let server = null @@ -71,17 +73,17 @@ describe('Test parameters validator', function () { series([ function (next) { - utils.flushTests(next) + serversUtils.flushTests(next) }, function (next) { - utils.runServer(1, function (server1) { + serversUtils.runServer(1, function (server1) { server = server1 next() }) }, function (next) { - utils.loginAndGetAccessToken(server, function (err, token) { + loginUtils.loginAndGetAccessToken(server, function (err, token) { if (err) throw err server.accessToken = token @@ -141,13 +143,13 @@ describe('Test parameters validator', function () { let userAccessToken = null before(function (done) { - utils.createUser(server.url, server.accessToken, 'user1', 'password', function () { + usersUtils.createUser(server.url, server.accessToken, 'user1', 'password', function () { server.user = { username: 'user1', password: 'password' } - utils.loginAndGetAccessToken(server, function (err, accessToken) { + loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { if (err) throw err userAccessToken = accessToken @@ -581,7 +583,7 @@ describe('Test parameters validator', function () { password: 'my super password' } - utils.loginAndGetAccessToken(server, function (err, accessToken) { + loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { if (err) throw err userAccessToken = accessToken @@ -598,7 +600,7 @@ describe('Test parameters validator', function () { describe('When updating a user', function () { before(function (done) { - utils.getUsersList(server.url, function (err, res) { + usersUtils.getUsersList(server.url, function (err, res) { if (err) throw err userId = res.body.data[1].id @@ -702,7 +704,7 @@ describe('Test parameters validator', function () { // Keep the logs if the test failed if (this.ok) { - utils.flushTests(done) + serversUtils.flushTests(done) } else { done() } diff --git a/server/tests/api/friendsAdvanced.js b/server/tests/api/friendsAdvanced.js index 603fbc16b..0d24481ef 100644 --- a/server/tests/api/friendsAdvanced.js +++ b/server/tests/api/friendsAdvanced.js @@ -5,24 +5,27 @@ const each = require('async/each') const expect = chai.expect const series = require('async/series') -const utils = require('./utils') +const loginUtils = require('../utils/login') +const podsUtils = require('../utils/pods') +const serversUtils = require('../utils/servers') +const videosUtils = require('../utils/videos') describe('Test advanced friends', function () { let servers = [] function makeFriends (podNumber, callback) { const server = servers[podNumber - 1] - return utils.makeFriends(server.url, server.accessToken, callback) + return podsUtils.makeFriends(server.url, server.accessToken, callback) } function quitFriends (podNumber, callback) { const server = servers[podNumber - 1] - return utils.quitFriends(server.url, server.accessToken, callback) + return podsUtils.quitFriends(server.url, server.accessToken, callback) } function getFriendsList (podNumber, end) { const server = servers[podNumber - 1] - return utils.getFriendsList(server.url, end) + return podsUtils.getFriendsList(server.url, end) } function uploadVideo (podNumber, callback) { @@ -32,22 +35,22 @@ describe('Test advanced friends', function () { const fixture = 'video_short.webm' const server = servers[podNumber - 1] - return utils.uploadVideo(server.url, server.accessToken, name, description, tags, fixture, callback) + return videosUtils.uploadVideo(server.url, server.accessToken, name, description, tags, fixture, callback) } function getVideos (podNumber, callback) { - return utils.getVideosList(servers[podNumber - 1].url, callback) + return videosUtils.getVideosList(servers[podNumber - 1].url, callback) } // --------------------------------------------------------------- before(function (done) { this.timeout(30000) - utils.flushAndRunMultipleServers(6, function (serversRun, urlsRun) { + serversUtils.flushAndRunMultipleServers(6, function (serversRun, urlsRun) { servers = serversRun each(servers, function (server, callbackEach) { - utils.loginAndGetAccessToken(server, function (err, accessToken) { + loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { if (err) return callbackEach(err) server.accessToken = accessToken @@ -169,7 +172,7 @@ describe('Test advanced friends', function () { }, // Rerun server 4 function (next) { - utils.runServer(4, function (server) { + serversUtils.runServer(4, function (server) { servers[3].app = server.app next() }) @@ -273,7 +276,7 @@ describe('Test advanced friends', function () { }) if (this.ok) { - utils.flushTests(done) + serversUtils.flushTests(done) } else { done() } diff --git a/server/tests/api/friendsBasic.js b/server/tests/api/friendsBasic.js index c74a7f224..2a6883acb 100644 --- a/server/tests/api/friendsBasic.js +++ b/server/tests/api/friendsBasic.js @@ -5,14 +5,16 @@ const each = require('async/each') const expect = chai.expect const series = require('async/series') -const utils = require('./utils') +const loginUtils = require('../utils/login') +const podsUtils = require('../utils/pods') +const serversUtils = require('../utils/servers') describe('Test basic friends', function () { let servers = [] function makeFriends (podNumber, callback) { const server = servers[podNumber - 1] - return utils.makeFriends(server.url, server.accessToken, callback) + return podsUtils.makeFriends(server.url, server.accessToken, callback) } function testMadeFriends (servers, serverToTest, callback) { @@ -22,7 +24,7 @@ describe('Test basic friends', function () { friends.push(servers[i].url) } - utils.getFriendsList(serverToTest.url, function (err, res) { + podsUtils.getFriendsList(serverToTest.url, function (err, res) { if (err) throw err const result = res.body @@ -43,11 +45,11 @@ describe('Test basic friends', function () { before(function (done) { this.timeout(20000) - utils.flushAndRunMultipleServers(3, function (serversRun, urlsRun) { + serversUtils.flushAndRunMultipleServers(3, function (serversRun, urlsRun) { servers = serversRun each(servers, function (server, callbackEach) { - utils.loginAndGetAccessToken(server, function (err, accessToken) { + loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { if (err) return callbackEach(err) server.accessToken = accessToken @@ -59,7 +61,7 @@ describe('Test basic friends', function () { it('Should not have friends', function (done) { each(servers, function (server, callback) { - utils.getFriendsList(server.url, function (err, res) { + podsUtils.getFriendsList(server.url, function (err, res) { if (err) throw err const result = res.body @@ -84,7 +86,7 @@ describe('Test basic friends', function () { }, // The second pod should have the third as a friend function (next) { - utils.getFriendsList(servers[1].url, function (err, res) { + podsUtils.getFriendsList(servers[1].url, function (err, res) { if (err) throw err const result = res.body @@ -97,7 +99,7 @@ describe('Test basic friends', function () { }, // Same here, the third pod should have the second pod as a friend function (next) { - utils.getFriendsList(servers[2].url, function (err, res) { + podsUtils.getFriendsList(servers[2].url, function (err, res) { if (err) throw err const result = res.body @@ -128,7 +130,7 @@ describe('Test basic friends', function () { it('Should not be allowed to make friend again', function (done) { const server = servers[1] - utils.makeFriends(server.url, server.accessToken, 409, done) + podsUtils.makeFriends(server.url, server.accessToken, 409, done) }) it('Should quit friends of pod 2', function (done) { @@ -136,11 +138,11 @@ describe('Test basic friends', function () { // Pod 1 quit friends function (next) { const server = servers[1] - utils.quitFriends(server.url, server.accessToken, next) + podsUtils.quitFriends(server.url, server.accessToken, next) }, // Pod 1 should not have friends anymore function (next) { - utils.getFriendsList(servers[1].url, function (err, res) { + podsUtils.getFriendsList(servers[1].url, function (err, res) { if (err) throw err const result = res.body @@ -153,7 +155,7 @@ describe('Test basic friends', function () { // Other pods shouldn't have pod 1 too function (next) { each([ servers[0].url, servers[2].url ], function (url, callback) { - utils.getFriendsList(url, function (err, res) { + podsUtils.getFriendsList(url, function (err, res) { if (err) throw err const result = res.body @@ -169,7 +171,7 @@ describe('Test basic friends', function () { it('Should allow pod 2 to make friend again', function (done) { const server = servers[1] - utils.makeFriends(server.url, server.accessToken, function () { + podsUtils.makeFriends(server.url, server.accessToken, function () { each(servers, function (server, callback) { testMadeFriends(servers, server, callback) }, done) @@ -182,7 +184,7 @@ describe('Test basic friends', function () { }) if (this.ok) { - utils.flushTests(done) + serversUtils.flushTests(done) } else { done() } diff --git a/server/tests/api/multiplePods.js b/server/tests/api/multiplePods.js index ac140f6bb..b86f88c22 100644 --- a/server/tests/api/multiplePods.js +++ b/server/tests/api/multiplePods.js @@ -6,7 +6,11 @@ const expect = chai.expect const pathUtils = require('path') const series = require('async/series') -const utils = require('./utils') +const loginUtils = require('../utils/login') +const miscsUtils = require('../utils/miscs') +const podsUtils = require('../utils/pods') +const serversUtils = require('../utils/servers') +const videosUtils = require('../utils/videos') const webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent')) webtorrent.silent = true @@ -20,7 +24,7 @@ describe('Test multiple pods', function () { series([ // Run servers function (next) { - utils.flushAndRunMultipleServers(3, function (serversRun) { + serversUtils.flushAndRunMultipleServers(3, function (serversRun) { servers = serversRun next() }) @@ -28,7 +32,7 @@ describe('Test multiple pods', function () { // Get the access tokens function (next) { each(servers, function (server, callbackEach) { - utils.loginAndGetAccessToken(server, function (err, accessToken) { + loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { if (err) return callbackEach(err) server.accessToken = accessToken @@ -39,7 +43,7 @@ describe('Test multiple pods', function () { // The second pod make friend with the third function (next) { const server = servers[1] - utils.makeFriends(server.url, server.accessToken, next) + podsUtils.makeFriends(server.url, server.accessToken, next) }, // Wait for the request between pods function (next) { @@ -48,7 +52,7 @@ describe('Test multiple pods', function () { // Pod 1 make friends too function (next) { const server = servers[0] - utils.makeFriends(server.url, server.accessToken, next) + podsUtils.makeFriends(server.url, server.accessToken, next) }, function (next) { webtorrent.create({ host: 'client', port: '1' }, next) @@ -58,7 +62,7 @@ describe('Test multiple pods', function () { it('Should not have videos for all pods', function (done) { each(servers, function (server, callback) { - utils.getVideosList(server.url, function (err, res) { + videosUtils.getVideosList(server.url, function (err, res) { if (err) throw err const videos = res.body.data @@ -80,7 +84,7 @@ describe('Test multiple pods', function () { const description = 'my super description for pod 1' const tags = [ 'tag1p1', 'tag2p1' ] const file = 'video_short1.webm' - utils.uploadVideo(servers[0].url, servers[0].accessToken, name, description, tags, file, next) + videosUtils.uploadVideo(servers[0].url, servers[0].accessToken, name, description, tags, file, next) }, function (next) { setTimeout(next, 11000) @@ -92,7 +96,7 @@ describe('Test multiple pods', function () { each(servers, function (server, callback) { let baseMagnet = null - utils.getVideosList(server.url, function (err, res) { + videosUtils.getVideosList(server.url, function (err, res) { if (err) throw err const videos = res.body.data @@ -105,7 +109,7 @@ describe('Test multiple pods', function () { expect(video.magnetUri).to.exist expect(video.duration).to.equal(10) expect(video.tags).to.deep.equal([ 'tag1p1', 'tag2p1' ]) - expect(utils.dateIsValid(video.createdDate)).to.be.true + expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true expect(video.author).to.equal('root') if (server.url !== 'http://localhost:9001') { @@ -121,7 +125,7 @@ describe('Test multiple pods', function () { expect(video.magnetUri).to.equal.magnetUri } - utils.testImage(server.url, 'video_short1.webm', video.thumbnailPath, function (err, test) { + videosUtils.testVideoImage(server.url, 'video_short1.webm', video.thumbnailPath, function (err, test) { if (err) throw err expect(test).to.equal(true) @@ -142,7 +146,7 @@ describe('Test multiple pods', function () { const description = 'my super description for pod 2' const tags = [ 'tag1p2', 'tag2p2', 'tag3p2' ] const file = 'video_short2.webm' - utils.uploadVideo(servers[1].url, servers[1].accessToken, name, description, tags, file, next) + videosUtils.uploadVideo(servers[1].url, servers[1].accessToken, name, description, tags, file, next) }, function (next) { setTimeout(next, 11000) @@ -154,7 +158,7 @@ describe('Test multiple pods', function () { each(servers, function (server, callback) { let baseMagnet = null - utils.getVideosList(server.url, function (err, res) { + videosUtils.getVideosList(server.url, function (err, res) { if (err) throw err const videos = res.body.data @@ -167,7 +171,7 @@ describe('Test multiple pods', function () { expect(video.magnetUri).to.exist expect(video.duration).to.equal(5) expect(video.tags).to.deep.equal([ 'tag1p2', 'tag2p2', 'tag3p2' ]) - expect(utils.dateIsValid(video.createdDate)).to.be.true + expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true expect(video.author).to.equal('root') if (server.url !== 'http://localhost:9002') { @@ -183,7 +187,7 @@ describe('Test multiple pods', function () { expect(video.magnetUri).to.equal.magnetUri } - utils.testImage(server.url, 'video_short2.webm', video.thumbnailPath, function (err, test) { + videosUtils.testVideoImage(server.url, 'video_short2.webm', video.thumbnailPath, function (err, test) { if (err) throw err expect(test).to.equal(true) @@ -204,14 +208,14 @@ describe('Test multiple pods', function () { const description = 'my super description for pod 3' const tags = [ 'tag1p3' ] const file = 'video_short3.webm' - utils.uploadVideo(servers[2].url, servers[2].accessToken, name, description, tags, file, next) + videosUtils.uploadVideo(servers[2].url, servers[2].accessToken, name, description, tags, file, next) }, function (next) { const name = 'my super name for pod 3-2' const description = 'my super description for pod 3-2' const tags = [ 'tag2p3', 'tag3p3', 'tag4p3' ] const file = 'video_short.webm' - utils.uploadVideo(servers[2].url, servers[2].accessToken, name, description, tags, file, next) + videosUtils.uploadVideo(servers[2].url, servers[2].accessToken, name, description, tags, file, next) }, function (next) { setTimeout(next, 22000) @@ -222,7 +226,7 @@ describe('Test multiple pods', function () { let baseMagnet = null // All pods should have this video each(servers, function (server, callback) { - utils.getVideosList(server.url, function (err, res) { + videosUtils.getVideosList(server.url, function (err, res) { if (err) throw err const videos = res.body.data @@ -247,7 +251,7 @@ describe('Test multiple pods', function () { expect(video1.duration).to.equal(5) expect(video1.tags).to.deep.equal([ 'tag1p3' ]) expect(video1.author).to.equal('root') - expect(utils.dateIsValid(video1.createdDate)).to.be.true + expect(miscsUtils.dateIsValid(video1.createdDate)).to.be.true expect(video2.name).to.equal('my super name for pod 3-2') expect(video2.description).to.equal('my super description for pod 3-2') @@ -256,7 +260,7 @@ describe('Test multiple pods', function () { expect(video2.duration).to.equal(5) expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ]) expect(video2.author).to.equal('root') - expect(utils.dateIsValid(video2.createdDate)).to.be.true + expect(miscsUtils.dateIsValid(video2.createdDate)).to.be.true if (server.url !== 'http://localhost:9003') { expect(video1.isLocal).to.be.false @@ -273,11 +277,11 @@ describe('Test multiple pods', function () { expect(video2.magnetUri).to.equal.magnetUri } - utils.testImage(server.url, 'video_short3.webm', video1.thumbnailPath, function (err, test) { + videosUtils.testVideoImage(server.url, 'video_short3.webm', video1.thumbnailPath, function (err, test) { if (err) throw err expect(test).to.equal(true) - utils.testImage(server.url, 'video_short.webm', video2.thumbnailPath, function (err, test) { + videosUtils.testVideoImage(server.url, 'video_short.webm', video2.thumbnailPath, function (err, test) { if (err) throw err expect(test).to.equal(true) @@ -296,7 +300,7 @@ describe('Test multiple pods', function () { // Yes, this could be long this.timeout(200000) - utils.getVideosList(servers[2].url, function (err, res) { + videosUtils.getVideosList(servers[2].url, function (err, res) { if (err) throw err const video = res.body.data[0] @@ -317,7 +321,7 @@ describe('Test multiple pods', function () { // Yes, this could be long this.timeout(200000) - utils.getVideosList(servers[0].url, function (err, res) { + videosUtils.getVideosList(servers[0].url, function (err, res) { if (err) throw err const video = res.body.data[1] @@ -336,7 +340,7 @@ describe('Test multiple pods', function () { // Yes, this could be long this.timeout(200000) - utils.getVideosList(servers[1].url, function (err, res) { + videosUtils.getVideosList(servers[1].url, function (err, res) { if (err) throw err const video = res.body.data[2] @@ -355,7 +359,7 @@ describe('Test multiple pods', function () { // Yes, this could be long this.timeout(200000) - utils.getVideosList(servers[0].url, function (err, res) { + videosUtils.getVideosList(servers[0].url, function (err, res) { if (err) throw err const video = res.body.data[3] @@ -375,10 +379,10 @@ describe('Test multiple pods', function () { series([ function (next) { - utils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[0], next) + videosUtils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[0], next) }, function (next) { - utils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[1], next) + videosUtils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[1], next) }], function (err) { if (err) throw err @@ -389,7 +393,7 @@ describe('Test multiple pods', function () { it('Should have videos 1 and 3 on each pod', function (done) { each(servers, function (server, callback) { - utils.getVideosList(server.url, function (err, res) { + videosUtils.getVideosList(server.url, function (err, res) { if (err) throw err const videos = res.body.data @@ -415,7 +419,7 @@ describe('Test multiple pods', function () { // Keep the logs if the test failed if (this.ok) { - utils.flushTests(done) + serversUtils.flushTests(done) } else { done() } diff --git a/server/tests/api/singlePod.js b/server/tests/api/singlePod.js index 6ed719f87..573eaa3a8 100644 --- a/server/tests/api/singlePod.js +++ b/server/tests/api/singlePod.js @@ -8,11 +8,13 @@ const keyBy = require('lodash/keyBy') const pathUtils = require('path') const series = require('async/series') +const loginUtils = require('../utils/login') +const miscsUtils = require('../utils/miscs') +const serversUtils = require('../utils/servers') +const videosUtils = require('../utils/videos') const webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent')) webtorrent.silent = true -const utils = require('./utils') - describe('Test a single pod', function () { let server = null let videoId = -1 @@ -23,16 +25,16 @@ describe('Test a single pod', function () { series([ function (next) { - utils.flushTests(next) + serversUtils.flushTests(next) }, function (next) { - utils.runServer(1, function (server1) { + serversUtils.runServer(1, function (server1) { server = server1 next() }) }, function (next) { - utils.loginAndGetAccessToken(server, function (err, token) { + loginUtils.loginAndGetAccessToken(server, function (err, token) { if (err) throw err server.accessToken = token next() @@ -45,7 +47,7 @@ describe('Test a single pod', function () { }) it('Should not have videos', function (done) { - utils.getVideosList(server.url, function (err, res) { + videosUtils.getVideosList(server.url, function (err, res) { if (err) throw err expect(res.body.total).to.equal(0) @@ -62,14 +64,14 @@ describe('Test a single pod', function () { const description = 'my super description' const tags = [ 'tag1', 'tag2', 'tag3' ] const file = 'video_short.webm' - utils.uploadVideo(server.url, server.accessToken, name, description, tags, file, done) + videosUtils.uploadVideo(server.url, server.accessToken, name, description, tags, file, done) }) it('Should seed the uploaded video', function (done) { // Yes, this could be long this.timeout(60000) - utils.getVideosList(server.url, function (err, res) { + videosUtils.getVideosList(server.url, function (err, res) { if (err) throw err expect(res.body.total).to.equal(1) @@ -84,9 +86,9 @@ describe('Test a single pod', function () { expect(video.author).to.equal('root') expect(video.isLocal).to.be.true expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) - expect(utils.dateIsValid(video.createdDate)).to.be.true + expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true - utils.testImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { + videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { if (err) throw err expect(test).to.equal(true) @@ -108,7 +110,7 @@ describe('Test a single pod', function () { // Yes, this could be long this.timeout(60000) - utils.getVideo(server.url, videoId, function (err, res) { + videosUtils.getVideo(server.url, videoId, function (err, res) { if (err) throw err const video = res.body @@ -119,9 +121,9 @@ describe('Test a single pod', function () { expect(video.author).to.equal('root') expect(video.isLocal).to.be.true expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) - expect(utils.dateIsValid(video.createdDate)).to.be.true + expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true - utils.testImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { + videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { if (err) throw err expect(test).to.equal(true) @@ -137,7 +139,7 @@ describe('Test a single pod', function () { }) it('Should search the video by name by default', function (done) { - utils.searchVideo(server.url, 'my', function (err, res) { + videosUtils.searchVideo(server.url, 'my', function (err, res) { if (err) throw err expect(res.body.total).to.equal(1) @@ -151,9 +153,9 @@ describe('Test a single pod', function () { expect(video.author).to.equal('root') expect(video.isLocal).to.be.true expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) - expect(utils.dateIsValid(video.createdDate)).to.be.true + expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true - utils.testImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { + videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { if (err) throw err expect(test).to.equal(true) @@ -163,7 +165,7 @@ describe('Test a single pod', function () { }) it('Should search the video by podUrl', function (done) { - utils.searchVideo(server.url, '9001', 'podUrl', function (err, res) { + videosUtils.searchVideo(server.url, '9001', 'podUrl', function (err, res) { if (err) throw err expect(res.body.total).to.equal(1) @@ -177,9 +179,9 @@ describe('Test a single pod', function () { expect(video.author).to.equal('root') expect(video.isLocal).to.be.true expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) - expect(utils.dateIsValid(video.createdDate)).to.be.true + expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true - utils.testImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { + videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { if (err) throw err expect(test).to.equal(true) @@ -189,7 +191,7 @@ describe('Test a single pod', function () { }) it('Should search the video by tag', function (done) { - utils.searchVideo(server.url, 'tag1', 'tags', function (err, res) { + videosUtils.searchVideo(server.url, 'tag1', 'tags', function (err, res) { if (err) throw err expect(res.body.total).to.equal(1) @@ -203,9 +205,9 @@ describe('Test a single pod', function () { expect(video.author).to.equal('root') expect(video.isLocal).to.be.true expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) - expect(utils.dateIsValid(video.createdDate)).to.be.true + expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true - utils.testImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { + videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { if (err) throw err expect(test).to.equal(true) @@ -215,7 +217,7 @@ describe('Test a single pod', function () { }) it('Should not find a search by name by default', function (done) { - utils.searchVideo(server.url, 'hello', function (err, res) { + videosUtils.searchVideo(server.url, 'hello', function (err, res) { if (err) throw err expect(res.body.total).to.equal(0) @@ -227,7 +229,7 @@ describe('Test a single pod', function () { }) it('Should not find a search by author', function (done) { - utils.searchVideo(server.url, 'hello', 'author', function (err, res) { + videosUtils.searchVideo(server.url, 'hello', 'author', function (err, res) { if (err) throw err expect(res.body.total).to.equal(0) @@ -239,7 +241,7 @@ describe('Test a single pod', function () { }) it('Should not find a search by tag', function (done) { - utils.searchVideo(server.url, 'tag', 'tags', function (err, res) { + videosUtils.searchVideo(server.url, 'tag', 'tags', function (err, res) { if (err) throw err expect(res.body.total).to.equal(0) @@ -251,7 +253,7 @@ describe('Test a single pod', function () { }) it('Should remove the video', function (done) { - utils.removeVideo(server.url, server.accessToken, videoId, function (err) { + videosUtils.removeVideo(server.url, server.accessToken, videoId, function (err) { if (err) throw err fs.readdir(pathUtils.join(__dirname, '../../../test1/uploads/'), function (err, files) { @@ -264,7 +266,7 @@ describe('Test a single pod', function () { }) it('Should not have videos', function (done) { - utils.getVideosList(server.url, function (err, res) { + videosUtils.getVideosList(server.url, function (err, res) { if (err) throw err expect(res.body.total).to.equal(0) @@ -286,12 +288,12 @@ describe('Test a single pod', function () { const description = video + ' description' const tags = [ 'tag1', 'tag2', 'tag3' ] - utils.uploadVideo(server.url, server.accessToken, name, description, tags, video, callbackEach) + videosUtils.uploadVideo(server.url, server.accessToken, name, description, tags, video, callbackEach) }, done) }) it('Should have the correct durations', function (done) { - utils.getVideosList(server.url, function (err, res) { + videosUtils.getVideosList(server.url, function (err, res) { if (err) throw err expect(res.body.total).to.equal(6) @@ -312,7 +314,7 @@ describe('Test a single pod', function () { }) it('Should have the correct thumbnails', function (done) { - utils.getVideosList(server.url, function (err, res) { + videosUtils.getVideosList(server.url, function (err, res) { if (err) throw err const videos = res.body.data @@ -323,7 +325,7 @@ describe('Test a single pod', function () { if (err) throw err const videoName = video.name.replace(' name', '') - utils.testImage(server.url, videoName, video.thumbnailPath, function (err, test) { + videosUtils.testVideoImage(server.url, videoName, video.thumbnailPath, function (err, test) { if (err) throw err expect(test).to.equal(true) @@ -334,7 +336,7 @@ describe('Test a single pod', function () { }) it('Should list only the two first videos', function (done) { - utils.getVideosListPagination(server.url, 0, 2, function (err, res) { + videosUtils.getVideosListPagination(server.url, 0, 2, function (err, res) { if (err) throw err const videos = res.body.data @@ -348,7 +350,7 @@ describe('Test a single pod', function () { }) it('Should list only the next three videos', function (done) { - utils.getVideosListPagination(server.url, 2, 3, function (err, res) { + videosUtils.getVideosListPagination(server.url, 2, 3, function (err, res) { if (err) throw err const videos = res.body.data @@ -363,7 +365,7 @@ describe('Test a single pod', function () { }) it('Should list the last video', function (done) { - utils.getVideosListPagination(server.url, 5, 6, function (err, res) { + videosUtils.getVideosListPagination(server.url, 5, 6, function (err, res) { if (err) throw err const videos = res.body.data @@ -376,7 +378,7 @@ describe('Test a single pod', function () { }) it('Should search the first video', function (done) { - utils.searchVideoWithPagination(server.url, 'webm', 'name', 0, 1, function (err, res) { + videosUtils.searchVideoWithPagination(server.url, 'webm', 'name', 0, 1, function (err, res) { if (err) throw err const videos = res.body.data @@ -389,7 +391,7 @@ describe('Test a single pod', function () { }) it('Should search the last two videos', function (done) { - utils.searchVideoWithPagination(server.url, 'webm', 'name', 2, 2, function (err, res) { + videosUtils.searchVideoWithPagination(server.url, 'webm', 'name', 2, 2, function (err, res) { if (err) throw err const videos = res.body.data @@ -403,7 +405,7 @@ describe('Test a single pod', function () { }) it('Should search all the webm videos', function (done) { - utils.searchVideoWithPagination(server.url, 'webm', 'name', 0, 15, function (err, res) { + videosUtils.searchVideoWithPagination(server.url, 'webm', 'name', 0, 15, function (err, res) { if (err) throw err const videos = res.body.data @@ -415,7 +417,7 @@ describe('Test a single pod', function () { }) it('Should search all the root author videos', function (done) { - utils.searchVideoWithPagination(server.url, 'root', 'author', 0, 15, function (err, res) { + videosUtils.searchVideoWithPagination(server.url, 'root', 'author', 0, 15, function (err, res) { if (err) throw err const videos = res.body.data @@ -427,7 +429,7 @@ describe('Test a single pod', function () { }) it('Should search all the 9001 port videos', function (done) { - utils.searchVideoWithPagination(server.url, '9001', 'podUrl', 0, 15, function (err, res) { + videosUtils.searchVideoWithPagination(server.url, '9001', 'podUrl', 0, 15, function (err, res) { if (err) throw err const videos = res.body.data @@ -439,7 +441,7 @@ describe('Test a single pod', function () { }) it('Should search all the localhost videos', function (done) { - utils.searchVideoWithPagination(server.url, 'localhost', 'podUrl', 0, 15, function (err, res) { + videosUtils.searchVideoWithPagination(server.url, 'localhost', 'podUrl', 0, 15, function (err, res) { if (err) throw err const videos = res.body.data @@ -452,7 +454,7 @@ describe('Test a single pod', function () { it('Should search the good magnetUri video', function (done) { const video = videosListBase[0] - utils.searchVideoWithPagination(server.url, encodeURIComponent(video.magnetUri), 'magnetUri', 0, 15, function (err, res) { + videosUtils.searchVideoWithPagination(server.url, encodeURIComponent(video.magnetUri), 'magnetUri', 0, 15, function (err, res) { if (err) throw err const videos = res.body.data @@ -465,7 +467,7 @@ describe('Test a single pod', function () { }) it('Should list and sort by name in descending order', function (done) { - utils.getVideosListSort(server.url, '-name', function (err, res) { + videosUtils.getVideosListSort(server.url, '-name', function (err, res) { if (err) throw err const videos = res.body.data @@ -483,7 +485,7 @@ describe('Test a single pod', function () { }) it('Should search and sort by name in ascending order', function (done) { - utils.searchVideoWithSort(server.url, 'webm', 'name', function (err, res) { + videosUtils.searchVideoWithSort(server.url, 'webm', 'name', function (err, res) { if (err) throw err const videos = res.body.data @@ -505,7 +507,7 @@ describe('Test a single pod', function () { // Keep the logs if the test failed if (this.ok) { - utils.flushTests(done) + serversUtils.flushTests(done) } else { done() } diff --git a/server/tests/api/users.js b/server/tests/api/users.js index e1d4a8cf4..6f9eef181 100644 --- a/server/tests/api/users.js +++ b/server/tests/api/users.js @@ -5,11 +5,14 @@ const expect = chai.expect const pathUtils = require('path') const series = require('async/series') +const loginUtils = require('../utils/login') +const podsUtils = require('../utils/pods') +const serversUtils = require('../utils/servers') +const usersUtils = require('../utils/users') +const videosUtils = require('../utils/videos') const webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent')) webtorrent.silent = true -const utils = require('./utils') - describe('Test users', function () { let server = null let accessToken = null @@ -22,10 +25,10 @@ describe('Test users', function () { series([ function (next) { - utils.flushTests(next) + serversUtils.flushTests(next) }, function (next) { - utils.runServer(1, function (server1) { + serversUtils.runServer(1, function (server1) { server = server1 next() }) @@ -41,7 +44,7 @@ describe('Test users', function () { it('Should not login with an invalid client id', function (done) { const client = { id: 'client', password: server.client.secret } - utils.login(server.url, client, server.user, 400, function (err, res) { + loginUtils.login(server.url, client, server.user, 400, function (err, res) { if (err) throw err expect(res.body.error).to.equal('invalid_client') @@ -51,7 +54,7 @@ describe('Test users', function () { it('Should not login with an invalid client password', function (done) { const client = { id: server.client.id, password: 'coucou' } - utils.login(server.url, client, server.user, 400, function (err, res) { + loginUtils.login(server.url, client, server.user, 400, function (err, res) { if (err) throw err expect(res.body.error).to.equal('invalid_client') @@ -61,7 +64,7 @@ describe('Test users', function () { it('Should not login with an invalid username', function (done) { const user = { username: 'captain crochet', password: server.user.password } - utils.login(server.url, server.client, user, 400, function (err, res) { + loginUtils.login(server.url, server.client, user, 400, function (err, res) { if (err) throw err expect(res.body.error).to.equal('invalid_grant') @@ -71,7 +74,7 @@ describe('Test users', function () { it('Should not login with an invalid password', function (done) { const user = { username: server.user.username, password: 'mewthree' } - utils.login(server.url, server.client, user, 400, function (err, res) { + loginUtils.login(server.url, server.client, user, 400, function (err, res) { if (err) throw err expect(res.body.error).to.equal('invalid_grant') @@ -86,21 +89,21 @@ describe('Test users', function () { const description = 'my super description' const tags = [ 'tag1', 'tag2' ] const video = 'video_short.webm' - utils.uploadVideo(server.url, accessToken, name, description, tags, video, 401, done) + videosUtils.uploadVideo(server.url, accessToken, name, description, tags, video, 401, done) }) it('Should not be able to make friends', function (done) { accessToken = 'mysupertoken' - utils.makeFriends(server.url, accessToken, 401, done) + podsUtils.makeFriends(server.url, accessToken, 401, done) }) it('Should not be able to quit friends', function (done) { accessToken = 'mysupertoken' - utils.quitFriends(server.url, accessToken, 401, done) + podsUtils.quitFriends(server.url, accessToken, 401, done) }) it('Should be able to login', function (done) { - utils.login(server.url, server.client, server.user, 200, function (err, res) { + loginUtils.login(server.url, server.client, server.user, 200, function (err, res) { if (err) throw err accessToken = res.body.access_token @@ -113,10 +116,10 @@ describe('Test users', function () { const description = 'my super description' const tags = [ 'tag1', 'tag2' ] const video = 'video_short.webm' - utils.uploadVideo(server.url, accessToken, name, description, tags, video, 204, function (err, res) { + videosUtils.uploadVideo(server.url, accessToken, name, description, tags, video, 204, function (err, res) { if (err) throw err - utils.getVideosList(server.url, function (err, res) { + videosUtils.getVideosList(server.url, function (err, res) { if (err) throw err const video = res.body.data[0] @@ -133,17 +136,17 @@ describe('Test users', function () { const description = 'my super description 2' const tags = [ 'tag1' ] const video = 'video_short.webm' - utils.uploadVideo(server.url, accessToken, name, description, tags, video, 204, done) + videosUtils.uploadVideo(server.url, accessToken, name, description, tags, video, 204, done) }) it('Should not be able to remove the video with an incorrect token', function (done) { - utils.removeVideo(server.url, 'bad_token', videoId, 401, done) + videosUtils.removeVideo(server.url, 'bad_token', videoId, 401, done) }) it('Should not be able to remove the video with the token of another account') it('Should be able to remove the video with the correct token', function (done) { - utils.removeVideo(server.url, accessToken, videoId, done) + videosUtils.removeVideo(server.url, accessToken, videoId, done) }) it('Should logout (revoke token)') @@ -161,7 +164,7 @@ describe('Test users', function () { it('Should be able to upload a video again') it('Should be able to create a new user', function (done) { - utils.createUser(server.url, accessToken, 'user_1', 'super password', done) + usersUtils.createUser(server.url, accessToken, 'user_1', 'super password', done) }) it('Should be able to login with this user', function (done) { @@ -170,7 +173,7 @@ describe('Test users', function () { password: 'super password' } - utils.loginAndGetAccessToken(server, function (err, token) { + loginUtils.loginAndGetAccessToken(server, function (err, token) { if (err) throw err accessTokenUser = token @@ -180,7 +183,7 @@ describe('Test users', function () { }) it('Should be able to get the user informations', function (done) { - utils.getUserInformation(server.url, accessTokenUser, function (err, res) { + usersUtils.getUserInformation(server.url, accessTokenUser, function (err, res) { if (err) throw err const user = res.body @@ -199,11 +202,11 @@ describe('Test users', function () { const description = 'my super description' const tags = [ 'tag1', 'tag2', 'tag3' ] const file = 'video_short.webm' - utils.uploadVideo(server.url, accessTokenUser, name, description, tags, file, done) + videosUtils.uploadVideo(server.url, accessTokenUser, name, description, tags, file, done) }) it('Should list all the users', function (done) { - utils.getUsersList(server.url, function (err, res) { + usersUtils.getUsersList(server.url, function (err, res) { if (err) throw err const users = res.body.data @@ -223,25 +226,25 @@ describe('Test users', function () { }) it('Should update the user password', function (done) { - utils.updateUser(server.url, userId, accessTokenUser, 'new password', function (err, res) { + usersUtils.updateUser(server.url, userId, accessTokenUser, 'new password', function (err, res) { if (err) throw err server.user.password = 'new password' - utils.login(server.url, server.client, server.user, 200, done) + loginUtils.login(server.url, server.client, server.user, 200, done) }) }) it('Should be able to remove this user', function (done) { - utils.removeUser(server.url, accessToken, 'user_1', done) + usersUtils.removeUser(server.url, accessToken, 'user_1', done) }) it('Should not be able to login with this user', function (done) { // server.user is already set to user 1 - utils.login(server.url, server.client, server.user, 400, done) + loginUtils.login(server.url, server.client, server.user, 400, done) }) it('Should not have videos of this user', function (done) { - utils.getVideosList(server.url, function (err, res) { + videosUtils.getVideosList(server.url, function (err, res) { if (err) throw err expect(res.body.total).to.equal(1) @@ -257,7 +260,7 @@ describe('Test users', function () { // Keep the logs if the test failed if (this.ok) { - utils.flushTests(done) + serversUtils.flushTests(done) } else { done() } diff --git a/server/tests/api/utils.js b/server/tests/api/utils.js deleted file mode 100644 index 8871f1f84..000000000 --- a/server/tests/api/utils.js +++ /dev/null @@ -1,492 +0,0 @@ -'use strict' - -const childProcess = require('child_process') -const exec = childProcess.exec -const fork = childProcess.fork -const fs = require('fs') -const pathUtils = require('path') -const request = require('supertest') - -const testUtils = { - createUser: createUser, - dateIsValid: dateIsValid, - flushTests: flushTests, - getAllVideosListBy: getAllVideosListBy, - getClient: getClient, - getFriendsList: getFriendsList, - getUserInformation: getUserInformation, - getUsersList: getUsersList, - getVideo: getVideo, - getVideosList: getVideosList, - getVideosListPagination: getVideosListPagination, - getVideosListSort: getVideosListSort, - login: login, - loginAndGetAccessToken: loginAndGetAccessToken, - makeFriends: makeFriends, - quitFriends: quitFriends, - removeUser: removeUser, - removeVideo: removeVideo, - flushAndRunMultipleServers: flushAndRunMultipleServers, - runServer: runServer, - searchVideo: searchVideo, - searchVideoWithPagination: searchVideoWithPagination, - searchVideoWithSort: searchVideoWithSort, - testImage: testImage, - uploadVideo: uploadVideo, - updateUser: updateUser -} - -// ---------------------- Export functions -------------------- - -function createUser (url, accessToken, username, password, specialStatus, end) { - if (!end) { - end = specialStatus - specialStatus = 204 - } - - const path = '/api/v1/users' - - request(url) - .post(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .send({ username: username, password: password }) - .expect(specialStatus) - .end(end) -} - -function dateIsValid (dateString) { - const dateToCheck = new Date(dateString) - const now = new Date() - - // Check if the interval is more than 2 minutes - if (now - dateToCheck > 120000) return false - - return true -} - -function flushTests (callback) { - exec('npm run clean:server:test', callback) -} - -function getAllVideosListBy (url, end) { - const path = '/api/v1/videos' - - request(url) - .get(path) - .query({ sort: 'createdDate' }) - .query({ start: 0 }) - .query({ count: 10000 }) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) - .end(end) -} - -function getClient (url, end) { - const path = '/api/v1/users/client' - - request(url) - .get(path) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) - .end(end) -} - -function getUserInformation (url, accessToken, end) { - const path = '/api/v1/users/me' - - request(url) - .get(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(200) - .expect('Content-Type', /json/) - .end(end) -} - -function getUsersList (url, end) { - const path = '/api/v1/users' - - request(url) - .get(path) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) - .end(end) -} - -function getFriendsList (url, end) { - const path = '/api/v1/pods/' - - request(url) - .get(path) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) - .end(end) -} - -function getVideo (url, id, end) { - const path = '/api/v1/videos/' + id - - request(url) - .get(path) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) - .end(end) -} - -function getVideosList (url, end) { - const path = '/api/v1/videos' - - request(url) - .get(path) - .query({ sort: 'name' }) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) - .end(end) -} - -function getVideosListPagination (url, start, count, end) { - const path = '/api/v1/videos' - - request(url) - .get(path) - .query({ start: start }) - .query({ count: count }) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) - .end(end) -} - -function getVideosListSort (url, sort, end) { - const path = '/api/v1/videos' - - request(url) - .get(path) - .query({ sort: sort }) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) - .end(end) -} - -function login (url, client, user, expectedStatus, end) { - if (!end) { - end = expectedStatus - expectedStatus = 200 - } - - const path = '/api/v1/users/token' - - const body = { - client_id: client.id, - client_secret: client.secret, - username: user.username, - password: user.password, - response_type: 'code', - grant_type: 'password', - scope: 'upload' - } - - request(url) - .post(path) - .type('form') - .send(body) - .expect(expectedStatus) - .end(end) -} - -function loginAndGetAccessToken (server, callback) { - login(server.url, server.client, server.user, 200, function (err, res) { - if (err) return callback(err) - - return callback(null, res.body.access_token) - }) -} - -function makeFriends (url, accessToken, expectedStatus, callback) { - if (!callback) { - callback = expectedStatus - expectedStatus = 204 - } - - const path = '/api/v1/pods/makefriends' - - // The first pod make friend with the third - request(url) - .get(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(expectedStatus) - .end(function (err, res) { - if (err) throw err - - // Wait for the request between pods - setTimeout(callback, 1000) - }) -} - -function quitFriends (url, accessToken, expectedStatus, callback) { - if (!callback) { - callback = expectedStatus - expectedStatus = 204 - } - - const path = '/api/v1/pods/quitfriends' - - // The first pod make friend with the third - request(url) - .get(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .expect(expectedStatus) - .end(function (err, res) { - if (err) throw err - - // Wait for the request between pods - setTimeout(callback, 1000) - }) -} - -function removeUser (url, token, username, expectedStatus, end) { - if (!end) { - end = expectedStatus - expectedStatus = 204 - } - - const path = '/api/v1/users' - - request(url) - .delete(path + '/' + username) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(expectedStatus) - .end(end) -} - -function removeVideo (url, token, id, expectedStatus, end) { - if (!end) { - end = expectedStatus - expectedStatus = 204 - } - - const path = '/api/v1/videos' - - request(url) - .delete(path + '/' + id) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) - .expect(expectedStatus) - .end(end) -} - -function flushAndRunMultipleServers (totalServers, serversRun) { - let apps = [] - let urls = [] - let i = 0 - - function anotherServerDone (number, app, url) { - apps[number - 1] = app - urls[number - 1] = url - i++ - if (i === totalServers) { - serversRun(apps, urls) - } - } - - flushTests(function () { - for (let j = 1; j <= totalServers; j++) { - // For the virtual buffer - setTimeout(function () { - runServer(j, function (app, url) { - anotherServerDone(j, app, url) - }) - }, 1000 * j) - } - }) -} - -function runServer (number, callback) { - const server = { - app: null, - url: `http://localhost:${9000 + number}`, - client: { - id: null, - secret: null - }, - user: { - username: null, - password: null - } - } - - // These actions are async so we need to be sure that they have both been done - const serverRunString = { - 'Connected to mongodb': false, - 'Server listening on port': false - } - - const regexps = { - client_id: 'Client id: ([a-f0-9]+)', - client_secret: 'Client secret: (.+)', - user_username: 'Username: (.+)', - user_password: 'User password: (.+)' - } - - // Share the environment - const env = Object.create(process.env) - env.NODE_ENV = 'test' - env.NODE_APP_INSTANCE = number - const options = { - silent: true, - env: env, - detached: true - } - - server.app = fork(pathUtils.join(__dirname, '../../../server.js'), [], options) - server.app.stdout.on('data', function onStdout (data) { - let dontContinue = false - - // Capture things if we want to - for (const key of Object.keys(regexps)) { - const regexp = regexps[key] - const matches = data.toString().match(regexp) - if (matches !== null) { - if (key === 'client_id') server.client.id = matches[1] - else if (key === 'client_secret') server.client.secret = matches[1] - else if (key === 'user_username') server.user.username = matches[1] - else if (key === 'user_password') server.user.password = matches[1] - } - } - - // Check if all required sentences are here - for (const key of Object.keys(serverRunString)) { - if (data.toString().indexOf(key) !== -1) serverRunString[key] = true - if (serverRunString[key] === false) dontContinue = true - } - - // If no, there is maybe one thing not already initialized (mongodb...) - if (dontContinue === true) return - - server.app.stdout.removeListener('data', onStdout) - callback(server) - }) -} - -function searchVideo (url, search, field, end) { - if (!end) { - end = field - field = null - } - - const path = '/api/v1/videos' - const req = request(url) - .get(path + '/search/' + search) - .set('Accept', 'application/json') - - if (field) req.query({ field: field }) - req.expect(200) - .expect('Content-Type', /json/) - .end(end) -} - -function searchVideoWithPagination (url, search, field, start, count, end) { - const path = '/api/v1/videos' - - request(url) - .get(path + '/search/' + search) - .query({ start: start }) - .query({ count: count }) - .query({ field: field }) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) - .end(end) -} - -function searchVideoWithSort (url, search, sort, end) { - const path = '/api/v1/videos' - - request(url) - .get(path + '/search/' + search) - .query({ sort: sort }) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', /json/) - .end(end) -} - -function testImage (url, videoName, imagePath, callback) { - // Don't test images if the node env is not set - // Because we need a special ffmpeg version for this test - if (process.env.NODE_TEST_IMAGE) { - request(url) - .get(imagePath) - .expect(200) - .end(function (err, res) { - if (err) return callback(err) - - fs.readFile(pathUtils.join(__dirname, 'fixtures', videoName + '.jpg'), function (err, data) { - if (err) return callback(err) - - callback(null, data.equals(res.body)) - }) - }) - } else { - console.log('Do not test images. Enable it by setting NODE_TEST_IMAGE env variable.') - callback(null, true) - } -} - -function uploadVideo (url, accessToken, name, description, tags, fixture, specialStatus, end) { - if (!end) { - end = specialStatus - specialStatus = 204 - } - - const path = '/api/v1/videos' - - const req = request(url) - .post(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .field('name', name) - .field('description', description) - - for (let i = 0; i < tags.length; i++) { - req.field('tags[' + i + ']', tags[i]) - } - - let filepath = '' - if (pathUtils.isAbsolute(fixture)) { - filepath = fixture - } else { - filepath = pathUtils.join(__dirname, 'fixtures', fixture) - } - - req.attach('videofile', filepath) - .expect(specialStatus) - .end(end) -} - -function updateUser (url, userId, accessToken, newPassword, end) { - const path = '/api/v1/users/' + userId - - request(url) - .put(path) - .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + accessToken) - .send({ password: newPassword }) - .expect(204) - .end(end) -} - -// --------------------------------------------------------------------------- - -module.exports = testUtils diff --git a/server/tests/utils/clients.js b/server/tests/utils/clients.js new file mode 100644 index 000000000..e3ded493e --- /dev/null +++ b/server/tests/utils/clients.js @@ -0,0 +1,24 @@ +'use strict' + +const request = require('supertest') + +const clientsUtils = { + getClient: getClient +} + +// ---------------------- Export functions -------------------- + +function getClient (url, end) { + const path = '/api/v1/users/client' + + request(url) + .get(path) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) + .end(end) +} + +// --------------------------------------------------------------------------- + +module.exports = clientsUtils diff --git a/server/tests/utils/login.js b/server/tests/utils/login.js new file mode 100644 index 000000000..1a5d75bc4 --- /dev/null +++ b/server/tests/utils/login.js @@ -0,0 +1,48 @@ +'use strict' + +const request = require('supertest') + +const loginUtils = { + login: login, + loginAndGetAccessToken: loginAndGetAccessToken +} + +// ---------------------- Export functions -------------------- + +function login (url, client, user, expectedStatus, end) { + if (!end) { + end = expectedStatus + expectedStatus = 200 + } + + const path = '/api/v1/users/token' + + const body = { + client_id: client.id, + client_secret: client.secret, + username: user.username, + password: user.password, + response_type: 'code', + grant_type: 'password', + scope: 'upload' + } + + request(url) + .post(path) + .type('form') + .send(body) + .expect(expectedStatus) + .end(end) +} + +function loginAndGetAccessToken (server, callback) { + login(server.url, server.client, server.user, 200, function (err, res) { + if (err) return callback(err) + + return callback(null, res.body.access_token) + }) +} + +// --------------------------------------------------------------------------- + +module.exports = loginUtils diff --git a/server/tests/utils/miscs.js b/server/tests/utils/miscs.js new file mode 100644 index 000000000..5414cd561 --- /dev/null +++ b/server/tests/utils/miscs.js @@ -0,0 +1,21 @@ +'use strict' + +const miscsUtils = { + dateIsValid: dateIsValid +} + +// ---------------------- Export functions -------------------- + +function dateIsValid (dateString) { + const dateToCheck = new Date(dateString) + const now = new Date() + + // Check if the interval is more than 2 minutes + if (now - dateToCheck > 120000) return false + + return true +} + +// --------------------------------------------------------------------------- + +module.exports = miscsUtils diff --git a/server/tests/utils/pods.js b/server/tests/utils/pods.js new file mode 100644 index 000000000..366492110 --- /dev/null +++ b/server/tests/utils/pods.js @@ -0,0 +1,70 @@ +'use strict' + +const request = require('supertest') + +const podsUtils = { + getFriendsList: getFriendsList, + makeFriends: makeFriends, + quitFriends: quitFriends +} + +// ---------------------- Export functions -------------------- + +function getFriendsList (url, end) { + const path = '/api/v1/pods/' + + request(url) + .get(path) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) + .end(end) +} + +function makeFriends (url, accessToken, expectedStatus, end) { + if (!end) { + end = expectedStatus + expectedStatus = 204 + } + + const path = '/api/v1/pods/makefriends' + + // The first pod make friend with the third + request(url) + .get(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(expectedStatus) + .end(function (err, res) { + if (err) throw err + + // Wait for the request between pods + setTimeout(end, 1000) + }) +} + +function quitFriends (url, accessToken, expectedStatus, end) { + if (!end) { + end = expectedStatus + expectedStatus = 204 + } + + const path = '/api/v1/pods/quitfriends' + + // The first pod make friend with the third + request(url) + .get(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(expectedStatus) + .end(function (err, res) { + if (err) throw err + + // Wait for the request between pods + setTimeout(end, 1000) + }) +} + +// --------------------------------------------------------------------------- + +module.exports = podsUtils diff --git a/server/tests/utils/servers.js b/server/tests/utils/servers.js new file mode 100644 index 000000000..ee7cd8c0a --- /dev/null +++ b/server/tests/utils/servers.js @@ -0,0 +1,115 @@ +'use strict' + +const childProcess = require('child_process') +const exec = childProcess.exec +const fork = childProcess.fork +const pathUtils = require('path') + +const serversUtils = { + flushAndRunMultipleServers: flushAndRunMultipleServers, + flushTests: flushTests, + runServer: runServer +} + +// ---------------------- Export functions -------------------- + +function flushAndRunMultipleServers (totalServers, serversRun) { + let apps = [] + let urls = [] + let i = 0 + + function anotherServerDone (number, app, url) { + apps[number - 1] = app + urls[number - 1] = url + i++ + if (i === totalServers) { + serversRun(apps, urls) + } + } + + flushTests(function () { + for (let j = 1; j <= totalServers; j++) { + // For the virtual buffer + setTimeout(function () { + runServer(j, function (app, url) { + anotherServerDone(j, app, url) + }) + }, 1000 * j) + } + }) +} + +function flushTests (callback) { + exec('npm run clean:server:test', callback) +} + +function runServer (number, callback) { + const server = { + app: null, + url: `http://localhost:${9000 + number}`, + client: { + id: null, + secret: null + }, + user: { + username: null, + password: null + } + } + + // These actions are async so we need to be sure that they have both been done + const serverRunString = { + 'Connected to mongodb': false, + 'Server listening on port': false + } + + const regexps = { + client_id: 'Client id: ([a-f0-9]+)', + client_secret: 'Client secret: (.+)', + user_username: 'Username: (.+)', + user_password: 'User password: (.+)' + } + + // Share the environment + const env = Object.create(process.env) + env.NODE_ENV = 'test' + env.NODE_APP_INSTANCE = number + const options = { + silent: true, + env: env, + detached: true + } + + server.app = fork(pathUtils.join(__dirname, '../../../server.js'), [], options) + server.app.stdout.on('data', function onStdout (data) { + let dontContinue = false + + // Capture things if we want to + for (const key of Object.keys(regexps)) { + const regexp = regexps[key] + const matches = data.toString().match(regexp) + if (matches !== null) { + if (key === 'client_id') server.client.id = matches[1] + else if (key === 'client_secret') server.client.secret = matches[1] + else if (key === 'user_username') server.user.username = matches[1] + else if (key === 'user_password') server.user.password = matches[1] + } + } + + // Check if all required sentences are here + for (const key of Object.keys(serverRunString)) { + if (data.toString().indexOf(key) !== -1) serverRunString[key] = true + if (serverRunString[key] === false) dontContinue = true + } + + // If no, there is maybe one thing not already initialized (mongodb...) + if (dontContinue === true) return + + server.app.stdout.removeListener('data', onStdout) + callback(server) + }) +} + +// --------------------------------------------------------------------------- + +module.exports = serversUtils diff --git a/server/tests/utils/users.js b/server/tests/utils/users.js new file mode 100644 index 000000000..ed7a9d672 --- /dev/null +++ b/server/tests/utils/users.js @@ -0,0 +1,85 @@ +'use strict' + +const request = require('supertest') + +const usersUtils = { + createUser: createUser, + getUserInformation: getUserInformation, + getUsersList: getUsersList, + removeUser: removeUser, + updateUser: updateUser +} + +// ---------------------- Export functions -------------------- + +function createUser (url, accessToken, username, password, specialStatus, end) { + if (!end) { + end = specialStatus + specialStatus = 204 + } + + const path = '/api/v1/users' + + request(url) + .post(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .send({ username: username, password: password }) + .expect(specialStatus) + .end(end) +} + +function getUserInformation (url, accessToken, end) { + const path = '/api/v1/users/me' + + request(url) + .get(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .expect(200) + .expect('Content-Type', /json/) + .end(end) +} + +function getUsersList (url, end) { + const path = '/api/v1/users' + + request(url) + .get(path) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) + .end(end) +} + +function removeUser (url, token, username, expectedStatus, end) { + if (!end) { + end = expectedStatus + expectedStatus = 204 + } + + const path = '/api/v1/users' + + request(url) + .delete(path + '/' + username) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .expect(expectedStatus) + .end(end) +} + +function updateUser (url, userId, accessToken, newPassword, end) { + const path = '/api/v1/users/' + userId + + request(url) + .put(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .send({ password: newPassword }) + .expect(204) + .end(end) +} + +// --------------------------------------------------------------------------- + +module.exports = usersUtils diff --git a/server/tests/utils/videos.js b/server/tests/utils/videos.js new file mode 100644 index 000000000..90ee9621e --- /dev/null +++ b/server/tests/utils/videos.js @@ -0,0 +1,199 @@ +'use strict' + +const fs = require('fs') +const pathUtils = require('path') +const request = require('supertest') + +const videosUtils = { + getAllVideosListBy: getAllVideosListBy, + getVideo: getVideo, + getVideosList: getVideosList, + getVideosListPagination: getVideosListPagination, + getVideosListSort: getVideosListSort, + removeVideo: removeVideo, + searchVideo: searchVideo, + searchVideoWithPagination: searchVideoWithPagination, + searchVideoWithSort: searchVideoWithSort, + testVideoImage: testVideoImage, + uploadVideo: uploadVideo +} + +// ---------------------- Export functions -------------------- + +function getAllVideosListBy (url, end) { + const path = '/api/v1/videos' + + request(url) + .get(path) + .query({ sort: 'createdDate' }) + .query({ start: 0 }) + .query({ count: 10000 }) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) + .end(end) +} + +function getVideo (url, id, end) { + const path = '/api/v1/videos/' + id + + request(url) + .get(path) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) + .end(end) +} + +function getVideosList (url, end) { + const path = '/api/v1/videos' + + request(url) + .get(path) + .query({ sort: 'name' }) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) + .end(end) +} + +function getVideosListPagination (url, start, count, end) { + const path = '/api/v1/videos' + + request(url) + .get(path) + .query({ start: start }) + .query({ count: count }) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) + .end(end) +} + +function getVideosListSort (url, sort, end) { + const path = '/api/v1/videos' + + request(url) + .get(path) + .query({ sort: sort }) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) + .end(end) +} + +function removeVideo (url, token, id, expectedStatus, end) { + if (!end) { + end = expectedStatus + expectedStatus = 204 + } + + const path = '/api/v1/videos' + + request(url) + .delete(path + '/' + id) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + token) + .expect(expectedStatus) + .end(end) +} + +function searchVideo (url, search, field, end) { + if (!end) { + end = field + field = null + } + + const path = '/api/v1/videos' + const req = request(url) + .get(path + '/search/' + search) + .set('Accept', 'application/json') + + if (field) req.query({ field: field }) + req.expect(200) + .expect('Content-Type', /json/) + .end(end) +} + +function searchVideoWithPagination (url, search, field, start, count, end) { + const path = '/api/v1/videos' + + request(url) + .get(path + '/search/' + search) + .query({ start: start }) + .query({ count: count }) + .query({ field: field }) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) + .end(end) +} + +function searchVideoWithSort (url, search, sort, end) { + const path = '/api/v1/videos' + + request(url) + .get(path + '/search/' + search) + .query({ sort: sort }) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) + .end(end) +} + +function testVideoImage (url, videoName, imagePath, callback) { + // Don't test images if the node env is not set + // Because we need a special ffmpeg version for this test + if (process.env.NODE_TEST_IMAGE) { + request(url) + .get(imagePath) + .expect(200) + .end(function (err, res) { + if (err) return callback(err) + + fs.readFile(pathUtils.join(__dirname, '..', 'api', 'fixtures', videoName + '.jpg'), function (err, data) { + if (err) return callback(err) + + callback(null, data.equals(res.body)) + }) + }) + } else { + console.log('Do not test images. Enable it by setting NODE_TEST_IMAGE env variable.') + callback(null, true) + } +} + +function uploadVideo (url, accessToken, name, description, tags, fixture, specialStatus, end) { + if (!end) { + end = specialStatus + specialStatus = 204 + } + + const path = '/api/v1/videos' + + const req = request(url) + .post(path) + .set('Accept', 'application/json') + .set('Authorization', 'Bearer ' + accessToken) + .field('name', name) + .field('description', description) + + for (let i = 0; i < tags.length; i++) { + req.field('tags[' + i + ']', tags[i]) + } + + let filepath = '' + if (pathUtils.isAbsolute(fixture)) { + filepath = fixture + } else { + filepath = pathUtils.join(__dirname, '..', 'api', 'fixtures', fixture) + } + + req.attach('videofile', filepath) + .expect(specialStatus) + .end(end) +} + +// --------------------------------------------------------------------------- + +module.exports = videosUtils From 25ed57f3db6301a5e87020b66d33671598e61df1 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sun, 7 Aug 2016 22:18:14 +0200 Subject: [PATCH 012/105] Server: create requests utils module --- server/tests/api/checkParams.js | 126 ++++++++++---------------------- server/tests/utils/requests.js | 68 +++++++++++++++++ 2 files changed, 105 insertions(+), 89 deletions(-) create mode 100644 server/tests/utils/requests.js diff --git a/server/tests/api/checkParams.js b/server/tests/api/checkParams.js index 675dc19e6..128b07c4a 100644 --- a/server/tests/api/checkParams.js +++ b/server/tests/api/checkParams.js @@ -7,65 +7,13 @@ const request = require('supertest') const series = require('async/series') const loginUtils = require('../utils/login') +const requestsUtils = require('../utils/requests') const serversUtils = require('../utils/servers') const usersUtils = require('../utils/users') describe('Test parameters validator', function () { let server = null - function makePostRequest (path, token, fields, attaches, done, statusCodeExpected) { - if (!statusCodeExpected) statusCodeExpected = 400 - - const req = request(server.url) - .post(path) - .set('Accept', 'application/json') - - if (token) req.set('Authorization', 'Bearer ' + token) - - Object.keys(fields).forEach(function (field) { - const value = fields[field] - - if (Array.isArray(value)) { - for (let i = 0; i < value.length; i++) { - req.field(field + '[' + i + ']', value[i]) - } - } else { - req.field(field, value) - } - }) - - Object.keys(attaches).forEach(function (attach) { - const value = attaches[attach] - req.attach(attach, value) - }) - - req.expect(statusCodeExpected, done) - } - - function makePostBodyRequest (path, token, fields, done, statusCodeExpected) { - if (!statusCodeExpected) statusCodeExpected = 400 - - const req = request(server.url) - .post(path) - .set('Accept', 'application/json') - - if (token) req.set('Authorization', 'Bearer ' + token) - - req.send(fields).expect(statusCodeExpected, done) - } - - function makePutBodyRequest (path, token, fields, done, statusCodeExpected) { - if (!statusCodeExpected) statusCodeExpected = 400 - - const req = request(server.url) - .put(path) - .set('Accept', 'application/json') - - if (token) req.set('Authorization', 'Bearer ' + token) - - req.send(fields).expect(statusCodeExpected, done) - } - // --------------------------------------------------------------- before(function (done) { @@ -99,21 +47,21 @@ describe('Test parameters validator', function () { describe('When adding a pod', function () { it('Should fail with nothing', function (done) { const data = {} - makePostBodyRequest(path, null, data, done) + requestsUtils.makePostBodyRequest(server.url, path, null, data, done) }) it('Should fail without public key', function (done) { const data = { url: 'http://coucou.com' } - makePostBodyRequest(path, null, data, done) + requestsUtils.makePostBodyRequest(server.url, path, null, data, done) }) it('Should fail without an url', function (done) { const data = { publicKey: 'mysuperpublickey' } - makePostBodyRequest(path, null, data, done) + requestsUtils.makePostBodyRequest(server.url, path, null, data, done) }) it('Should fail with an incorrect url', function (done) { @@ -121,11 +69,11 @@ describe('Test parameters validator', function () { url: 'coucou.com', publicKey: 'mysuperpublickey' } - makePostBodyRequest(path, null, data, function () { + requestsUtils.makePostBodyRequest(server.url, path, null, data, function () { data.url = 'http://coucou' - makePostBodyRequest(path, null, data, function () { + requestsUtils.makePostBodyRequest(server.url, path, null, data, function () { data.url = 'coucou' - makePostBodyRequest(path, null, data, done) + requestsUtils.makePostBodyRequest(server.url, path, null, data, done) }) }) }) @@ -135,7 +83,7 @@ describe('Test parameters validator', function () { url: 'http://coucou.com', publicKey: 'mysuperpublickey' } - makePostBodyRequest(path, null, data, done, 200) + requestsUtils.makePostBodyRequest(server.url, path, null, data, done, 200) }) }) @@ -267,7 +215,7 @@ describe('Test parameters validator', function () { it('Should fail with nothing', function (done) { const data = {} const attach = {} - makePostRequest(path, server.accessToken, data, attach, done) + requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) }) it('Should fail without name', function (done) { @@ -278,7 +226,7 @@ describe('Test parameters validator', function () { const attach = { 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') } - makePostRequest(path, server.accessToken, data, attach, done) + requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) }) it('Should fail with a long name', function (done) { @@ -290,7 +238,7 @@ describe('Test parameters validator', function () { const attach = { 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') } - makePostRequest(path, server.accessToken, data, attach, done) + requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) }) it('Should fail without description', function (done) { @@ -301,7 +249,7 @@ describe('Test parameters validator', function () { const attach = { 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') } - makePostRequest(path, server.accessToken, data, attach, done) + requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) }) it('Should fail with a long description', function (done) { @@ -315,7 +263,7 @@ describe('Test parameters validator', function () { const attach = { 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') } - makePostRequest(path, server.accessToken, data, attach, done) + requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) }) it('Should fail without tags', function (done) { @@ -326,7 +274,7 @@ describe('Test parameters validator', function () { const attach = { 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') } - makePostRequest(path, server.accessToken, data, attach, done) + requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) }) it('Should fail with too many tags', function (done) { @@ -338,7 +286,7 @@ describe('Test parameters validator', function () { const attach = { 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') } - makePostRequest(path, server.accessToken, data, attach, done) + requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) }) it('Should fail with not enough tags', function (done) { @@ -350,7 +298,7 @@ describe('Test parameters validator', function () { const attach = { 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') } - makePostRequest(path, server.accessToken, data, attach, done) + requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) }) it('Should fail with a tag length too low', function (done) { @@ -362,7 +310,7 @@ describe('Test parameters validator', function () { const attach = { 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') } - makePostRequest(path, server.accessToken, data, attach, done) + requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) }) it('Should fail with a tag length too big', function (done) { @@ -374,7 +322,7 @@ describe('Test parameters validator', function () { const attach = { 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') } - makePostRequest(path, server.accessToken, data, attach, done) + requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) }) it('Should fail with malformed tags', function (done) { @@ -386,7 +334,7 @@ describe('Test parameters validator', function () { const attach = { 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') } - makePostRequest(path, server.accessToken, data, attach, done) + requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) }) it('Should fail without an input file', function (done) { @@ -396,7 +344,7 @@ describe('Test parameters validator', function () { tags: [ 'tag1', 'tag2' ] } const attach = {} - makePostRequest(path, server.accessToken, data, attach, done) + requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) }) it('Should fail without an incorrect input file', function (done) { @@ -408,7 +356,7 @@ describe('Test parameters validator', function () { const attach = { 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short_fake.webm') } - makePostRequest(path, server.accessToken, data, attach, done) + requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) }) it('Should fail with a too big duration', function (done) { @@ -420,7 +368,7 @@ describe('Test parameters validator', function () { const attach = { 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_too_long.webm') } - makePostRequest(path, server.accessToken, data, attach, done) + requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) }) it('Should succeed with the correct parameters', function (done) { @@ -432,11 +380,11 @@ describe('Test parameters validator', function () { const attach = { 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') } - makePostRequest(path, server.accessToken, data, attach, function () { + requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, function () { attach.videofile = pathUtils.join(__dirname, 'fixtures', 'video_short.mp4') - makePostRequest(path, server.accessToken, data, attach, function () { + requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, function () { attach.videofile = pathUtils.join(__dirname, 'fixtures', 'video_short.ogv') - makePostRequest(path, server.accessToken, data, attach, done, 204) + requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done, 204) }, false) }, false) }) @@ -518,7 +466,7 @@ describe('Test parameters validator', function () { password: 'mysuperpassword' } - makePostBodyRequest(path, server.accessToken, data, done) + requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) }) it('Should fail with a too long username', function (done) { @@ -527,7 +475,7 @@ describe('Test parameters validator', function () { password: 'mysuperpassword' } - makePostBodyRequest(path, server.accessToken, data, done) + requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) }) it('Should fail with an incorrect username', function (done) { @@ -536,7 +484,7 @@ describe('Test parameters validator', function () { password: 'mysuperpassword' } - makePostBodyRequest(path, server.accessToken, data, done) + requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) }) it('Should fail with a too small password', function (done) { @@ -545,7 +493,7 @@ describe('Test parameters validator', function () { password: 'bla' } - makePostBodyRequest(path, server.accessToken, data, done) + requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) }) it('Should fail with a too long password', function (done) { @@ -556,7 +504,7 @@ describe('Test parameters validator', function () { 'very very very very very very very very very very very very very very very very very very very very long' } - makePostBodyRequest(path, server.accessToken, data, done) + requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) }) it('Should fail with an non authenticated user', function (done) { @@ -565,7 +513,7 @@ describe('Test parameters validator', function () { password: 'my super password' } - makePostBodyRequest(path, 'super token', data, done, 401) + requestsUtils.makePostBodyRequest(server.url, path, 'super token', data, done, 401) }) it('Should succeed with the correct params', function (done) { @@ -574,7 +522,7 @@ describe('Test parameters validator', function () { password: 'my super password' } - makePostBodyRequest(path, server.accessToken, data, done, 204) + requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done, 204) }) it('Should fail with a non admin user', function (done) { @@ -593,7 +541,7 @@ describe('Test parameters validator', function () { password: 'my super password' } - makePostBodyRequest(path, userAccessToken, data, done, 403) + requestsUtils.makePostBodyRequest(server.url, path, userAccessToken, data, done, 403) }) }) }) @@ -613,7 +561,7 @@ describe('Test parameters validator', function () { password: 'bla' } - makePutBodyRequest(path + userId, userAccessToken, data, done) + requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done) }) it('Should fail with a too long password', function (done) { @@ -623,7 +571,7 @@ describe('Test parameters validator', function () { 'very very very very very very very very very very very very very very very very very very very very long' } - makePutBodyRequest(path + userId, userAccessToken, data, done) + requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done) }) it('Should fail with an non authenticated user', function (done) { @@ -631,7 +579,7 @@ describe('Test parameters validator', function () { password: 'my super password' } - makePutBodyRequest(path + userId, 'super token', data, done, 401) + requestsUtils.makePutBodyRequest(server.url, path + userId, 'super token', data, done, 401) }) it('Should succeed with the correct params', function (done) { @@ -639,7 +587,7 @@ describe('Test parameters validator', function () { password: 'my super password' } - makePutBodyRequest(path + userId, userAccessToken, data, done, 204) + requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done, 204) }) }) diff --git a/server/tests/utils/requests.js b/server/tests/utils/requests.js new file mode 100644 index 000000000..410e42c77 --- /dev/null +++ b/server/tests/utils/requests.js @@ -0,0 +1,68 @@ +'use strict' + +const request = require('supertest') + +const requestsUtils = { + makePostUploadRequest: makePostUploadRequest, + makePostBodyRequest: makePostBodyRequest, + makePutBodyRequest: makePutBodyRequest +} + +// ---------------------- Export functions -------------------- + +function makePostUploadRequest (url, path, token, fields, attaches, done, statusCodeExpected) { + if (!statusCodeExpected) statusCodeExpected = 400 + + const req = request(url) + .post(path) + .set('Accept', 'application/json') + + if (token) req.set('Authorization', 'Bearer ' + token) + + Object.keys(fields).forEach(function (field) { + const value = fields[field] + + if (Array.isArray(value)) { + for (let i = 0; i < value.length; i++) { + req.field(field + '[' + i + ']', value[i]) + } + } else { + req.field(field, value) + } + }) + + Object.keys(attaches).forEach(function (attach) { + const value = attaches[attach] + req.attach(attach, value) + }) + + req.expect(statusCodeExpected, done) +} + +function makePostBodyRequest (url, path, token, fields, done, statusCodeExpected) { + if (!statusCodeExpected) statusCodeExpected = 400 + + const req = request(url) + .post(path) + .set('Accept', 'application/json') + + if (token) req.set('Authorization', 'Bearer ' + token) + + req.send(fields).expect(statusCodeExpected, done) +} + +function makePutBodyRequest (url, path, token, fields, done, statusCodeExpected) { + if (!statusCodeExpected) statusCodeExpected = 400 + + const req = request(url) + .put(path) + .set('Accept', 'application/json') + + if (token) req.set('Authorization', 'Bearer ' + token) + + req.send(fields).expect(statusCodeExpected, done) +} + +// --------------------------------------------------------------------------- + +module.exports = requestsUtils From 45b81debd6d6647980da7ad5a984bafa37cb79ea Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sun, 7 Aug 2016 22:47:11 +0200 Subject: [PATCH 013/105] Server: update dev dependencies --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9a0d4ecaa..310810377 100644 --- a/package.json +++ b/package.json @@ -67,9 +67,9 @@ "devDependencies": { "chai": "^3.3.0", "commander": "^2.9.0", - "mocha": "^2.3.3", + "mocha": "^3.0.1", "standard": "^7.0.1", - "supertest": "^1.1.0" + "supertest": "^2.0.0" }, "standard": { "ignore": [ From 68a3b9f2aacb0225ae8b883b561b144bac339cbd Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 9 Aug 2016 21:44:45 +0200 Subject: [PATCH 014/105] Server: delete user with the id and not the username --- server/controllers/api/v1/users.js | 4 ++-- server/middlewares/validators/users.js | 5 +++-- server/models/user.js | 5 +++++ server/tests/api/checkParams.js | 8 ++++---- server/tests/api/users.js | 2 +- server/tests/utils/users.js | 6 +++--- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/server/controllers/api/v1/users.js b/server/controllers/api/v1/users.js index 057dcaf8d..704df770c 100644 --- a/server/controllers/api/v1/users.js +++ b/server/controllers/api/v1/users.js @@ -34,7 +34,7 @@ router.put('/:id', updateUser ) -router.delete('/:username', +router.delete('/:id', oAuth.authenticate, admin.ensureIsAdmin, validatorsUsers.usersRemove, @@ -83,7 +83,7 @@ function listUsers (req, res, next) { function removeUser (req, res, next) { waterfall([ function getUser (callback) { - User.loadByUsername(req.params.username, callback) + User.loadById(req.params.id, callback) }, function getVideos (user, callback) { diff --git a/server/middlewares/validators/users.js b/server/middlewares/validators/users.js index 175d90bcb..e540ab0d1 100644 --- a/server/middlewares/validators/users.js +++ b/server/middlewares/validators/users.js @@ -25,12 +25,12 @@ function usersAdd (req, res, next) { } function usersRemove (req, res, next) { - req.checkParams('username', 'Should have a valid username').isUserUsernameValid() + req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId() logger.debug('Checking usersRemove parameters', { parameters: req.params }) checkErrors(req, res, function () { - User.loadByUsername(req.params.username, function (err, user) { + User.loadById(req.params.id, function (err, user) { if (err) { logger.error('Error in usersRemove request validator.', { error: err }) return res.sendStatus(500) @@ -44,6 +44,7 @@ function usersRemove (req, res, next) { } function usersUpdate (req, res, next) { + req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId() // Add old password verification req.checkBody('password', 'Should have a valid password').isUserPasswordValid() diff --git a/server/models/user.js b/server/models/user.js index 0bbd638d4..351ffef86 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -21,6 +21,7 @@ UserSchema.methods = { UserSchema.statics = { getByUsernameAndPassword: getByUsernameAndPassword, list: list, + loadById: loadById, loadByUsername: loadByUsername } @@ -36,6 +37,10 @@ function list (callback) { return this.find(callback) } +function loadById (id, callback) { + return this.findById(id, callback) +} + function loadByUsername (username, callback) { return this.findOne({ username: username }, callback) } diff --git a/server/tests/api/checkParams.js b/server/tests/api/checkParams.js index 128b07c4a..882948fac 100644 --- a/server/tests/api/checkParams.js +++ b/server/tests/api/checkParams.js @@ -610,23 +610,23 @@ describe('Test parameters validator', function () { }) describe('When removing an user', function () { - it('Should fail with an incorrect username', function (done) { + it('Should fail with an incorrect id', function (done) { request(server.url) .delete(path + 'bla-bla') .set('Authorization', 'Bearer ' + server.accessToken) .expect(400, done) }) - it('Should return 404 with a non existing username', function (done) { + it('Should return 404 with a non existing id', function (done) { request(server.url) - .delete(path + 'qzzerg') + .delete(path + '579f982228c99c221d8092b8') .set('Authorization', 'Bearer ' + server.accessToken) .expect(404, done) }) it('Should success with the correct parameters', function (done) { request(server.url) - .delete(path + 'user1') + .delete(path + userId) .set('Authorization', 'Bearer ' + server.accessToken) .expect(204, done) }) diff --git a/server/tests/api/users.js b/server/tests/api/users.js index 6f9eef181..a2557d2ab 100644 --- a/server/tests/api/users.js +++ b/server/tests/api/users.js @@ -235,7 +235,7 @@ describe('Test users', function () { }) it('Should be able to remove this user', function (done) { - usersUtils.removeUser(server.url, accessToken, 'user_1', done) + usersUtils.removeUser(server.url, userId, accessToken, done) }) it('Should not be able to login with this user', function (done) { diff --git a/server/tests/utils/users.js b/server/tests/utils/users.js index ed7a9d672..3b560e409 100644 --- a/server/tests/utils/users.js +++ b/server/tests/utils/users.js @@ -52,7 +52,7 @@ function getUsersList (url, end) { .end(end) } -function removeUser (url, token, username, expectedStatus, end) { +function removeUser (url, userId, accessToken, expectedStatus, end) { if (!end) { end = expectedStatus expectedStatus = 204 @@ -61,9 +61,9 @@ function removeUser (url, token, username, expectedStatus, end) { const path = '/api/v1/users' request(url) - .delete(path + '/' + username) + .delete(path + '/' + userId) .set('Accept', 'application/json') - .set('Authorization', 'Bearer ' + token) + .set('Authorization', 'Bearer ' + accessToken) .expect(expectedStatus) .end(end) } From 7da18e4420c4b71a8ecfda07f39324fbfec081c3 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 9 Aug 2016 21:45:21 +0200 Subject: [PATCH 015/105] Client: add user management --- client/src/app/admin/admin.component.ts | 10 ++++ client/src/app/admin/admin.routes.ts | 14 ++++++ client/src/app/admin/index.ts | 3 ++ client/src/app/admin/users/index.ts | 5 ++ client/src/app/admin/users/shared/index.ts | 1 + .../app/admin/users/shared/user.service.ts | 49 +++++++++++++++++++ client/src/app/admin/users/user-add/index.ts | 1 + .../users/user-add/user-add.component.html | 29 +++++++++++ .../users/user-add/user-add.component.ts | 33 +++++++++++++ client/src/app/admin/users/user-list/index.ts | 1 + .../users/user-list/user-list.component.html | 24 +++++++++ .../users/user-list/user-list.component.scss | 7 +++ .../users/user-list/user-list.component.ts | 44 +++++++++++++++++ client/src/app/admin/users/users.component.ts | 13 +++++ client/src/app/admin/users/users.routes.ts | 27 ++++++++++ client/src/app/app.component.html | 7 ++- client/src/app/app.component.ts | 4 ++ client/src/app/app.routes.ts | 3 +- .../{user.model.ts => auth-user.model.ts} | 28 ++++++----- client/src/app/shared/auth/auth.service.ts | 20 +++++--- client/src/app/shared/auth/index.ts | 2 +- client/src/app/shared/index.ts | 1 + client/src/app/shared/users/index.ts | 1 + client/src/app/shared/users/user.model.ts | 15 ++++++ .../videos/video-list/video-list.component.ts | 6 +-- client/src/index.html | 1 + client/tsconfig.json | 16 +++++- 27 files changed, 338 insertions(+), 27 deletions(-) create mode 100644 client/src/app/admin/admin.component.ts create mode 100644 client/src/app/admin/admin.routes.ts create mode 100644 client/src/app/admin/index.ts create mode 100644 client/src/app/admin/users/index.ts create mode 100644 client/src/app/admin/users/shared/index.ts create mode 100644 client/src/app/admin/users/shared/user.service.ts create mode 100644 client/src/app/admin/users/user-add/index.ts create mode 100644 client/src/app/admin/users/user-add/user-add.component.html create mode 100644 client/src/app/admin/users/user-add/user-add.component.ts create mode 100644 client/src/app/admin/users/user-list/index.ts create mode 100644 client/src/app/admin/users/user-list/user-list.component.html create mode 100644 client/src/app/admin/users/user-list/user-list.component.scss create mode 100644 client/src/app/admin/users/user-list/user-list.component.ts create mode 100644 client/src/app/admin/users/users.component.ts create mode 100644 client/src/app/admin/users/users.routes.ts rename client/src/app/shared/auth/{user.model.ts => auth-user.model.ts} (79%) create mode 100644 client/src/app/shared/users/index.ts create mode 100644 client/src/app/shared/users/user.model.ts diff --git a/client/src/app/admin/admin.component.ts b/client/src/app/admin/admin.component.ts new file mode 100644 index 000000000..82f2529ec --- /dev/null +++ b/client/src/app/admin/admin.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; +import { ROUTER_DIRECTIVES } from '@angular/router'; + +@Component({ + template: '', + directives: [ ROUTER_DIRECTIVES ] +}) + +export class AdminComponent { +} diff --git a/client/src/app/admin/admin.routes.ts b/client/src/app/admin/admin.routes.ts new file mode 100644 index 000000000..d375a86af --- /dev/null +++ b/client/src/app/admin/admin.routes.ts @@ -0,0 +1,14 @@ +import { RouterConfig } from '@angular/router'; + +import { AdminComponent } from './admin.component'; +import { UsersRoutes } from './users'; + +export const AdminRoutes: RouterConfig = [ + { + path: 'admin', + component: AdminComponent, + children: [ + ...UsersRoutes + ] + } +]; diff --git a/client/src/app/admin/index.ts b/client/src/app/admin/index.ts new file mode 100644 index 000000000..3b0540818 --- /dev/null +++ b/client/src/app/admin/index.ts @@ -0,0 +1,3 @@ +export * from './users'; +export * from './admin.component'; +export * from './admin.routes'; diff --git a/client/src/app/admin/users/index.ts b/client/src/app/admin/users/index.ts new file mode 100644 index 000000000..e98a81f62 --- /dev/null +++ b/client/src/app/admin/users/index.ts @@ -0,0 +1,5 @@ +export * from './shared'; +export * from './user-add'; +export * from './user-list'; +export * from './users.component'; +export * from './users.routes'; diff --git a/client/src/app/admin/users/shared/index.ts b/client/src/app/admin/users/shared/index.ts new file mode 100644 index 000000000..e17ee5c7a --- /dev/null +++ b/client/src/app/admin/users/shared/index.ts @@ -0,0 +1 @@ +export * from './user.service'; diff --git a/client/src/app/admin/users/shared/user.service.ts b/client/src/app/admin/users/shared/user.service.ts new file mode 100644 index 000000000..be433f0a1 --- /dev/null +++ b/client/src/app/admin/users/shared/user.service.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@angular/core'; +import { Response } from '@angular/http'; +import { Observable } from 'rxjs/Observable'; + +import { AuthHttp, User } from '../../../shared'; + +@Injectable() +export class UserService { + // TODO: merge this constant with account + private static BASE_USERS_URL = '/api/v1/users/'; + + constructor(private authHttp: AuthHttp) {} + + addUser(username: string, password: string) { + const body = { + username, + password + }; + + return this.authHttp.post(UserService.BASE_USERS_URL, body); + } + + getUsers() { + return this.authHttp.get(UserService.BASE_USERS_URL) + .map(res => res.json()) + .map(this.extractUsers) + .catch(this.handleError); + } + + removeUser(user: User) { + return this.authHttp.delete(UserService.BASE_USERS_URL + user.id); + } + + private extractUsers(body: any) { + const usersJson = body.data; + const totalUsers = body.total; + const users = []; + for (const userJson of usersJson) { + users.push(new User(userJson)); + } + + return { users, totalUsers }; + } + + private handleError(error: Response) { + console.error(error); + return Observable.throw(error.json().error || 'Server error'); + } +} diff --git a/client/src/app/admin/users/user-add/index.ts b/client/src/app/admin/users/user-add/index.ts new file mode 100644 index 000000000..66d5ca04f --- /dev/null +++ b/client/src/app/admin/users/user-add/index.ts @@ -0,0 +1 @@ +export * from './user-add.component'; diff --git a/client/src/app/admin/users/user-add/user-add.component.html b/client/src/app/admin/users/user-add/user-add.component.html new file mode 100644 index 000000000..aa102358a --- /dev/null +++ b/client/src/app/admin/users/user-add/user-add.component.html @@ -0,0 +1,29 @@ +

Add user

+ +
{{ error }}
+ +
+
+ + +
+ Username is required with a length >= 3 and <= 20 +
+
+ +
+ + +
+ Password is required with a length >= 6 +
+
+ + +
diff --git a/client/src/app/admin/users/user-add/user-add.component.ts b/client/src/app/admin/users/user-add/user-add.component.ts new file mode 100644 index 000000000..30ca947a0 --- /dev/null +++ b/client/src/app/admin/users/user-add/user-add.component.ts @@ -0,0 +1,33 @@ +import { Control, ControlGroup, Validators } from '@angular/common'; +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +import { UserService } from '../shared'; + +@Component({ + selector: 'my-user-add', + template: require('./user-add.component.html'), +}) +export class UserAddComponent implements OnInit { + userAddForm: ControlGroup; + error: string = null; + + constructor(private router: Router, private userService: UserService) {} + + ngOnInit() { + this.userAddForm = new ControlGroup({ + username: new Control('', Validators.compose([ Validators.required, Validators.minLength(3), Validators.maxLength(20) ])), + password: new Control('', Validators.compose([ Validators.required, Validators.minLength(6) ])), + }); + } + + addUser(username: string, password: string) { + this.error = null; + + this.userService.addUser(username, password).subscribe( + ok => this.router.navigate([ '/admin/users/list' ]), + + err => this.error = err + ); + } +} diff --git a/client/src/app/admin/users/user-list/index.ts b/client/src/app/admin/users/user-list/index.ts new file mode 100644 index 000000000..51fbefa80 --- /dev/null +++ b/client/src/app/admin/users/user-list/index.ts @@ -0,0 +1 @@ +export * from './user-list.component'; diff --git a/client/src/app/admin/users/user-list/user-list.component.html b/client/src/app/admin/users/user-list/user-list.component.html new file mode 100644 index 000000000..2aca05f2b --- /dev/null +++ b/client/src/app/admin/users/user-list/user-list.component.html @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + +
IdUsernameRemove
{{ user.id }}{{ user.username }} + +
+ + + + Add user + diff --git a/client/src/app/admin/users/user-list/user-list.component.scss b/client/src/app/admin/users/user-list/user-list.component.scss new file mode 100644 index 000000000..e9f61e900 --- /dev/null +++ b/client/src/app/admin/users/user-list/user-list.component.scss @@ -0,0 +1,7 @@ +.glyphicon-remove { + cursor: pointer; +} + +.add-user { + margin-top: 10px; +} diff --git a/client/src/app/admin/users/user-list/user-list.component.ts b/client/src/app/admin/users/user-list/user-list.component.ts new file mode 100644 index 000000000..598daa42a --- /dev/null +++ b/client/src/app/admin/users/user-list/user-list.component.ts @@ -0,0 +1,44 @@ +import { Component, OnInit } from '@angular/core'; +import { ROUTER_DIRECTIVES } from '@angular/router'; + +import { User } from '../../../shared'; +import { UserService } from '../shared'; + +@Component({ + selector: 'my-user-list', + template: require('./user-list.component.html'), + styles: [ require('./user-list.component.scss') ], + directives: [ ROUTER_DIRECTIVES ] +}) +export class UserListComponent implements OnInit { + totalUsers: number; + users: User[]; + + constructor(private userService: UserService) {} + + ngOnInit() { + this.getUsers(); + } + + getUsers() { + this.userService.getUsers().subscribe( + ({ users, totalUsers }) => { + this.users = users; + this.totalUsers = totalUsers; + }, + + err => alert(err) + ); + } + + + removeUser(user: User) { + if (confirm('Are you sure?')) { + this.userService.removeUser(user).subscribe( + () => this.getUsers(), + + err => alert(err) + ); + } + } +} diff --git a/client/src/app/admin/users/users.component.ts b/client/src/app/admin/users/users.component.ts new file mode 100644 index 000000000..46aa0862f --- /dev/null +++ b/client/src/app/admin/users/users.component.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; +import { ROUTER_DIRECTIVES } from '@angular/router'; + +import { UserService } from './shared'; + +@Component({ + template: '', + directives: [ ROUTER_DIRECTIVES ], + providers: [ UserService ] +}) + +export class UsersComponent { +} diff --git a/client/src/app/admin/users/users.routes.ts b/client/src/app/admin/users/users.routes.ts new file mode 100644 index 000000000..0457c3843 --- /dev/null +++ b/client/src/app/admin/users/users.routes.ts @@ -0,0 +1,27 @@ +import { RouterConfig } from '@angular/router'; + +import { UsersComponent } from './users.component'; +import { UserAddComponent } from './user-add'; +import { UserListComponent } from './user-list'; + +export const UsersRoutes: RouterConfig = [ + { + path: 'users', + component: UsersComponent, + children: [ + { + path: '', + redirectTo: 'list', + pathMatch: 'full' + }, + { + path: 'list', + component: UserListComponent + }, + { + path: 'add', + component: UserAddComponent + } + ] + } +]; diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index ea4b31421..58967abca 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html @@ -47,7 +47,12 @@ -
+
+
+ + List users +
+
Make friends diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 5764f24ca..444b6b3b4 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -45,6 +45,10 @@ export class AppComponent { ); } + isUserAdmin() { + return this.authService.isAdmin(); + } + logout() { this.authService.logout(); // Redirect to home page diff --git a/client/src/app/app.routes.ts b/client/src/app/app.routes.ts index 1c414038d..d7194cb4f 100644 --- a/client/src/app/app.routes.ts +++ b/client/src/app/app.routes.ts @@ -2,6 +2,7 @@ import { RouterConfig } from '@angular/router'; import { AccountRoutes } from './account'; import { LoginRoutes } from './login'; +import { AdminRoutes } from './admin'; import { VideosRoutes } from './videos'; export const routes: RouterConfig = [ @@ -10,7 +11,7 @@ export const routes: RouterConfig = [ redirectTo: '/videos/list', pathMatch: 'full' }, - + ...AdminRoutes, ...AccountRoutes, ...LoginRoutes, ...VideosRoutes diff --git a/client/src/app/shared/auth/user.model.ts b/client/src/app/shared/auth/auth-user.model.ts similarity index 79% rename from client/src/app/shared/auth/user.model.ts rename to client/src/app/shared/auth/auth-user.model.ts index e486873ab..bdd5ea5a9 100644 --- a/client/src/app/shared/auth/user.model.ts +++ b/client/src/app/shared/auth/auth-user.model.ts @@ -1,4 +1,6 @@ -export class User { +import { User } from '../users'; + +export class AuthUser extends User { private static KEYS = { ID: 'id', ROLE: 'role', @@ -13,10 +15,12 @@ export class User { static load() { const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME); if (usernameLocalStorage) { - return new User( - localStorage.getItem(this.KEYS.ID), - localStorage.getItem(this.KEYS.USERNAME), - localStorage.getItem(this.KEYS.ROLE), + return new AuthUser( + { + id: localStorage.getItem(this.KEYS.ID), + username: localStorage.getItem(this.KEYS.USERNAME), + role: localStorage.getItem(this.KEYS.ROLE) + }, Tokens.load() ); } @@ -31,11 +35,9 @@ export class User { Tokens.flush(); } - constructor(id: string, username: string, role: string, hash_tokens: any) { - this.id = id; - this.username = username; - this.role = role; - this.tokens = new Tokens(hash_tokens); + constructor(userHash: { id: string, username: string, role: string }, hashTokens: any) { + super(userHash); + this.tokens = new Tokens(hashTokens); } getAccessToken() { @@ -56,9 +58,9 @@ export class User { } save() { - localStorage.setItem(User.KEYS.ID, this.id); - localStorage.setItem(User.KEYS.USERNAME, this.username); - localStorage.setItem(User.KEYS.ROLE, this.role); + localStorage.setItem(AuthUser.KEYS.ID, this.id); + localStorage.setItem(AuthUser.KEYS.USERNAME, this.username); + localStorage.setItem(AuthUser.KEYS.ROLE, this.role); this.tokens.save(); } } diff --git a/client/src/app/shared/auth/auth.service.ts b/client/src/app/shared/auth/auth.service.ts index 24d1a4fa2..8eea0c4bf 100644 --- a/client/src/app/shared/auth/auth.service.ts +++ b/client/src/app/shared/auth/auth.service.ts @@ -4,7 +4,7 @@ import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; import { AuthStatus } from './auth-status.model'; -import { User } from './user.model'; +import { AuthUser } from './auth-user.model'; @Injectable() export class AuthService { @@ -17,7 +17,7 @@ export class AuthService { private clientId: string; private clientSecret: string; private loginChanged: Subject; - private user: User = null; + private user: AuthUser = null; constructor(private http: Http) { this.loginChanged = new Subject(); @@ -40,7 +40,7 @@ export class AuthService { ); // Return null if there is nothing to load - this.user = User.load(); + this.user = AuthUser.load(); } getRefreshToken() { @@ -65,10 +65,16 @@ export class AuthService { return this.user.getTokenType(); } - getUser(): User { + getUser(): AuthUser { return this.user; } + isAdmin() { + if (this.user === null) return false; + + return this.user.isAdmin(); + } + isLoggedIn() { if (this.getAccessToken()) { return true; @@ -108,7 +114,7 @@ export class AuthService { logout() { // TODO: make an HTTP request to revoke the tokens this.user = null; - User.flush(); + AuthUser.flush(); this.setStatus(AuthStatus.LoggedOut); } @@ -163,13 +169,13 @@ export class AuthService { const id = obj.id; const username = obj.username; const role = obj.role; - const hash_tokens = { + const hashTokens = { access_token: obj.access_token, token_type: obj.token_type, refresh_token: obj.refresh_token }; - this.user = new User(id, username, role, hash_tokens); + this.user = new AuthUser({ id, username, role }, hashTokens); this.user.save(); this.setStatus(AuthStatus.LoggedIn); diff --git a/client/src/app/shared/auth/index.ts b/client/src/app/shared/auth/index.ts index aafaacbf1..ebd9e14cd 100644 --- a/client/src/app/shared/auth/index.ts +++ b/client/src/app/shared/auth/index.ts @@ -1,4 +1,4 @@ export * from './auth-http.service'; export * from './auth-status.model'; export * from './auth.service'; -export * from './user.model'; +export * from './auth-user.model'; diff --git a/client/src/app/shared/index.ts b/client/src/app/shared/index.ts index dfea4c67c..c05e8d253 100644 --- a/client/src/app/shared/index.ts +++ b/client/src/app/shared/index.ts @@ -1,2 +1,3 @@ export * from './auth'; export * from './search'; +export * from './users'; diff --git a/client/src/app/shared/users/index.ts b/client/src/app/shared/users/index.ts new file mode 100644 index 000000000..5a670ce8f --- /dev/null +++ b/client/src/app/shared/users/index.ts @@ -0,0 +1 @@ +export * from './user.model'; diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts new file mode 100644 index 000000000..0f34d4480 --- /dev/null +++ b/client/src/app/shared/users/user.model.ts @@ -0,0 +1,15 @@ +export class User { + id: string; + username: string; + role: string; + + constructor(hash: { id: string, username: string, role: string }) { + this.id = hash.id; + this.username = hash.username; + this.role = hash.role; + } + + isAdmin() { + return this.role === 'admin'; + } +} diff --git a/client/src/app/videos/video-list/video-list.component.ts b/client/src/app/videos/video-list/video-list.component.ts index 5691d684e..062340ec5 100644 --- a/client/src/app/videos/video-list/video-list.component.ts +++ b/client/src/app/videos/video-list/video-list.component.ts @@ -12,7 +12,7 @@ import { Video, VideoService } from '../shared'; -import { AuthService, Search, SearchField, User } from '../../shared'; +import { AuthService, AuthUser, Search, SearchField } from '../../shared'; import { VideoMiniatureComponent } from './video-miniature.component'; import { VideoSortComponent } from './video-sort.component'; import { SearchService } from '../../shared'; @@ -33,7 +33,7 @@ export class VideoListComponent implements OnInit, OnDestroy { totalItems: null }; sort: SortField; - user: User = null; + user: AuthUser = null; videos: Video[] = []; private search: Search; @@ -51,7 +51,7 @@ export class VideoListComponent implements OnInit, OnDestroy { ngOnInit() { if (this.authService.isLoggedIn()) { - this.user = User.load(); + this.user = AuthUser.load(); } // Subscribe to route changes diff --git a/client/src/index.html b/client/src/index.html index 5cf491221..f39d8d2cf 100644 --- a/client/src/index.html +++ b/client/src/index.html @@ -1,3 +1,4 @@ + diff --git a/client/tsconfig.json b/client/tsconfig.json index b10231b7b..5c5f008c3 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -31,6 +31,18 @@ "src/app/account/account.routes.ts", "src/app/account/account.service.ts", "src/app/account/index.ts", + "src/app/admin/admin.component.ts", + "src/app/admin/admin.routes.ts", + "src/app/admin/index.ts", + "src/app/admin/users/index.ts", + "src/app/admin/users/shared/index.ts", + "src/app/admin/users/shared/user.service.ts", + "src/app/admin/users/user-add/index.ts", + "src/app/admin/users/user-add/user-add.component.ts", + "src/app/admin/users/user-list/index.ts", + "src/app/admin/users/user-list/user-list.component.ts", + "src/app/admin/users/users.component.ts", + "src/app/admin/users/users.routes.ts", "src/app/app.component.ts", "src/app/app.routes.ts", "src/app/friends/friend.service.ts", @@ -40,15 +52,17 @@ "src/app/login/login.routes.ts", "src/app/shared/auth/auth-http.service.ts", "src/app/shared/auth/auth-status.model.ts", + "src/app/shared/auth/auth-user.model.ts", "src/app/shared/auth/auth.service.ts", "src/app/shared/auth/index.ts", - "src/app/shared/auth/user.model.ts", "src/app/shared/index.ts", "src/app/shared/search/index.ts", "src/app/shared/search/search-field.type.ts", "src/app/shared/search/search.component.ts", "src/app/shared/search/search.model.ts", "src/app/shared/search/search.service.ts", + "src/app/shared/users/index.ts", + "src/app/shared/users/user.model.ts", "src/app/videos/index.ts", "src/app/videos/shared/index.ts", "src/app/videos/shared/loader/index.ts", From 602eb142bebb62f1774d6e17c211eef99ace584b Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 12 Aug 2016 16:52:10 +0200 Subject: [PATCH 016/105] Client: make an admin menu and a classic menu component --- .../app/{ => admin}/friends/friend.service.ts | 2 +- client/src/app/{ => admin}/friends/index.ts | 0 client/src/app/admin/index.ts | 1 + .../src/app/admin/menu-admin.component.html | 26 +++++++ client/src/app/admin/menu-admin.component.ts | 42 +++++++++++ client/src/app/app.component.html | 53 +------------ client/src/app/app.component.scss | 34 --------- client/src/app/app.component.ts | 74 +++---------------- client/src/app/menu.component.html | 39 ++++++++++ client/src/app/menu.component.ts | 51 +++++++++++++ client/src/main.ts | 2 +- client/src/sass/application.scss | 35 +++++++++ client/tsconfig.json | 6 +- 13 files changed, 213 insertions(+), 152 deletions(-) rename client/src/app/{ => admin}/friends/friend.service.ts (94%) rename client/src/app/{ => admin}/friends/index.ts (100%) create mode 100644 client/src/app/admin/menu-admin.component.html create mode 100644 client/src/app/admin/menu-admin.component.ts create mode 100644 client/src/app/menu.component.html create mode 100644 client/src/app/menu.component.ts diff --git a/client/src/app/friends/friend.service.ts b/client/src/app/admin/friends/friend.service.ts similarity index 94% rename from client/src/app/friends/friend.service.ts rename to client/src/app/admin/friends/friend.service.ts index 771046484..d4ab5e60f 100644 --- a/client/src/app/friends/friend.service.ts +++ b/client/src/app/admin/friends/friend.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { Response } from '@angular/http'; import { Observable } from 'rxjs/Observable'; -import { AuthHttp, AuthService } from '../shared'; +import { AuthHttp, AuthService } from '../../shared'; @Injectable() export class FriendService { diff --git a/client/src/app/friends/index.ts b/client/src/app/admin/friends/index.ts similarity index 100% rename from client/src/app/friends/index.ts rename to client/src/app/admin/friends/index.ts diff --git a/client/src/app/admin/index.ts b/client/src/app/admin/index.ts index 3b0540818..292973681 100644 --- a/client/src/app/admin/index.ts +++ b/client/src/app/admin/index.ts @@ -1,3 +1,4 @@ export * from './users'; export * from './admin.component'; export * from './admin.routes'; +export * from './menu-admin.component'; diff --git a/client/src/app/admin/menu-admin.component.html b/client/src/app/admin/menu-admin.component.html new file mode 100644 index 000000000..15a3c764e --- /dev/null +++ b/client/src/app/admin/menu-admin.component.html @@ -0,0 +1,26 @@ + + +
+
+ + List users +
+ +
+ + Make friends +
+ +
+ + Quit friends +
+
+ +
+
+ + Quit admin. +
+
+
diff --git a/client/src/app/admin/menu-admin.component.ts b/client/src/app/admin/menu-admin.component.ts new file mode 100644 index 000000000..eb27c1e58 --- /dev/null +++ b/client/src/app/admin/menu-admin.component.ts @@ -0,0 +1,42 @@ +import { Component, Output, EventEmitter } from '@angular/core'; +import { ROUTER_DIRECTIVES } from '@angular/router'; + +import { FriendService } from './friends'; + +@Component({ + selector: 'my-menu-admin', + template: require('./menu-admin.component.html'), + directives: [ ROUTER_DIRECTIVES ], + providers: [ FriendService ] +}) +export class MenuAdminComponent { + @Output() quittedAdmin = new EventEmitter(); + + constructor(private friendService: FriendService) {} + + makeFriends() { + this.friendService.makeFriends().subscribe( + status => { + if (status === 409) { + alert('Already made friends!'); + } else { + alert('Made friends!'); + } + }, + error => alert(error) + ); + } + + quitAdmin() { + this.quittedAdmin.emit(true); + } + + quitFriends() { + this.friendService.quitFriends().subscribe( + status => { + alert('Quit friends!'); + }, + error => alert(error) + ); + } +} diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index 58967abca..a7538ee7a 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html @@ -14,61 +14,12 @@
- - -
-
- - - Login - - - - - Logout - -
- -
- - My account -
-
- -
-
- - Get videos -
- - -
- -
-
- - List users -
- -
- - Make friends -
- -
- - Quit friends -
-
-
+ +
-
diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss index 1b02b2f57..95f306d75 100644 --- a/client/src/app/app.component.scss +++ b/client/src/app/app.component.scss @@ -12,40 +12,6 @@ header div { margin-bottom: 30px; } -menu { - @media screen and (max-width: 600px) { - margin-right: 3px !important; - padding: 3px !important; - min-height: 400px !important; - } - - min-height: 600px; - margin-right: 20px; - border-right: 1px solid rgba(0, 0, 0, 0.2); - - .panel-button { - margin: 8px; - cursor: pointer; - transition: margin 0.2s; - - &:hover { - margin-left: 15px; - } - - a { - color: #333333; - } - } - - .glyphicon { - margin: 5px; - } -} - -.panel-block:not(:last-child) { - border-bottom: 1px solid rgba(0, 0, 0, 0.1); -} - .router-outlet-container { @media screen and (max-width: 400px) { padding: 0 3px 0 3px; diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 444b6b3b4..d9549ad5b 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -1,79 +1,27 @@ import { Component } from '@angular/core'; -import { ActivatedRoute, Router, ROUTER_DIRECTIVES } from '@angular/router'; +import { ROUTER_DIRECTIVES } from '@angular/router'; -import { FriendService } from './friends'; -import { - AuthService, - AuthStatus, - SearchComponent, - SearchService -} from './shared'; +import { MenuAdminComponent } from './admin'; +import { MenuComponent } from './menu.component'; +import { SearchComponent, SearchService } from './shared'; import { VideoService } from './videos'; @Component({ selector: 'my-app', template: require('./app.component.html'), styles: [ require('./app.component.scss') ], - directives: [ ROUTER_DIRECTIVES, SearchComponent ], - providers: [ FriendService, VideoService, SearchService ] + directives: [ MenuAdminComponent, MenuComponent, ROUTER_DIRECTIVES, SearchComponent ], + providers: [ VideoService, SearchService ] }) export class AppComponent { - choices = []; - isLoggedIn: boolean; + isInAdmin = false; - constructor( - private authService: AuthService, - private friendService: FriendService, - private route: ActivatedRoute, - private router: Router - ) { - this.isLoggedIn = this.authService.isLoggedIn(); - - this.authService.loginChangedSource.subscribe( - status => { - if (status === AuthStatus.LoggedIn) { - this.isLoggedIn = true; - console.log('Logged in.'); - } else if (status === AuthStatus.LoggedOut) { - this.isLoggedIn = false; - console.log('Logged out.'); - } else { - console.error('Unknown auth status: ' + status); - } - } - ); + onEnteredInAdmin() { + this.isInAdmin = true; } - isUserAdmin() { - return this.authService.isAdmin(); - } - - logout() { - this.authService.logout(); - // Redirect to home page - this.router.navigate(['/videos/list']); - } - - makeFriends() { - this.friendService.makeFriends().subscribe( - status => { - if (status === 409) { - alert('Already made friends!'); - } else { - alert('Made friends!'); - } - }, - error => alert(error) - ); - } - - quitFriends() { - this.friendService.quitFriends().subscribe( - status => { - alert('Quit friends!'); - }, - error => alert(error) - ); + onQuittedAdmin() { + this.isInAdmin = false; } } diff --git a/client/src/app/menu.component.html b/client/src/app/menu.component.html new file mode 100644 index 000000000..922375395 --- /dev/null +++ b/client/src/app/menu.component.html @@ -0,0 +1,39 @@ + +
+
+ + + Login + + + + + Logout + +
+ +
+ + My account +
+
+ +
+
+ + Get videos +
+ + +
+ +
+ +
+
diff --git a/client/src/app/menu.component.ts b/client/src/app/menu.component.ts new file mode 100644 index 000000000..594cd996e --- /dev/null +++ b/client/src/app/menu.component.ts @@ -0,0 +1,51 @@ +import { Component, EventEmitter, OnInit, Output } from '@angular/core'; +import { Router, ROUTER_DIRECTIVES } from '@angular/router'; + +import { AuthService, AuthStatus } from './shared'; + +@Component({ + selector: 'my-menu', + template: require('./menu.component.html'), + directives: [ ROUTER_DIRECTIVES ] +}) +export class MenuComponent implements OnInit { + @Output() enteredInAdmin = new EventEmitter(); + isLoggedIn: boolean; + + constructor ( + private authService: AuthService, + private router: Router + ) {} + + ngOnInit() { + this.isLoggedIn = this.authService.isLoggedIn(); + + this.authService.loginChangedSource.subscribe( + status => { + if (status === AuthStatus.LoggedIn) { + this.isLoggedIn = true; + console.log('Logged in.'); + } else if (status === AuthStatus.LoggedOut) { + this.isLoggedIn = false; + console.log('Logged out.'); + } else { + console.error('Unknown auth status: ' + status); + } + } + ); + } + + enterInAdmin() { + this.enteredInAdmin.emit(true); + } + + isUserAdmin() { + return this.authService.isAdmin(); + } + + logout() { + this.authService.logout(); + // Redirect to home page + this.router.navigate(['/videos/list']); + } +} diff --git a/client/src/main.ts b/client/src/main.ts index a78d275ad..41fc6e0c2 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -7,9 +7,9 @@ import { import { bootstrap } from '@angular/platform-browser-dynamic'; import { provideRouter } from '@angular/router'; -import { AppComponent } from './app/app.component'; import { routes } from './app/app.routes'; import { AuthHttp, AuthService } from './app/shared'; +import { AppComponent } from './app/app.component'; if (process.env.ENV === 'production') { enableProdMode(); diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss index 9c48b4627..e03b882d6 100644 --- a/client/src/sass/application.scss +++ b/client/src/sass/application.scss @@ -6,6 +6,41 @@ body { } } +menu { + @media screen and (max-width: 600px) { + margin-right: 3px !important; + padding: 3px !important; + min-height: 400px !important; + } + + min-height: 600px; + margin-right: 20px; + border-right: 1px solid rgba(0, 0, 0, 0.2); + + .panel-block:not(:last-child) { + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + } + + .panel-button { + margin: 8px; + cursor: pointer; + transition: margin 0.2s; + + &:hover { + margin-left: 15px; + } + + a { + color: #333333; + } + } + + .glyphicon { + margin: 5px; + } +} + + footer { border-top: 1px solid rgba(0, 0, 0, 0.2); padding-top: 10px; diff --git a/client/tsconfig.json b/client/tsconfig.json index 5c5f008c3..157529133 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -33,7 +33,10 @@ "src/app/account/index.ts", "src/app/admin/admin.component.ts", "src/app/admin/admin.routes.ts", + "src/app/admin/friends/friend.service.ts", + "src/app/admin/friends/index.ts", "src/app/admin/index.ts", + "src/app/admin/menu-admin.component.ts", "src/app/admin/users/index.ts", "src/app/admin/users/shared/index.ts", "src/app/admin/users/shared/user.service.ts", @@ -45,11 +48,10 @@ "src/app/admin/users/users.routes.ts", "src/app/app.component.ts", "src/app/app.routes.ts", - "src/app/friends/friend.service.ts", - "src/app/friends/index.ts", "src/app/login/index.ts", "src/app/login/login.component.ts", "src/app/login/login.routes.ts", + "src/app/menu.component.ts", "src/app/shared/auth/auth-http.service.ts", "src/app/shared/auth/auth-status.model.ts", "src/app/shared/auth/auth-user.model.ts", From c323efb9cdc6a605242d112ac0c9db9f67eabaad Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 12 Aug 2016 17:35:00 +0200 Subject: [PATCH 017/105] Update webtorrent -> 0.96 --- client/package.json | 2 +- .../src/app/shared/search/search.component.ts | 2 +- .../video-watch/video-watch.component.ts | 21 ++++++++++++------- package.json | 2 +- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/client/package.json b/client/package.json index b01e768b3..46cbb9441 100644 --- a/client/package.json +++ b/client/package.json @@ -63,7 +63,7 @@ "webpack-md5-hash": "0.0.5", "webpack-merge": "^0.13.0", "webpack-notifier": "^1.3.0", - "webtorrent": "^0.93.2", + "webtorrent": "^0.96.0", "zone.js": "0.6.12" }, "devDependencies": { diff --git a/client/src/app/shared/search/search.component.ts b/client/src/app/shared/search/search.component.ts index e864fbc17..219997e85 100644 --- a/client/src/app/shared/search/search.component.ts +++ b/client/src/app/shared/search/search.component.ts @@ -28,7 +28,7 @@ export class SearchComponent implements OnInit { constructor(private searchService: SearchService) {} ngOnInit() { - // Subscribe is the search changed + // Subscribe if the search changed // Usually changed by videos list component this.searchService.updateSearch.subscribe( newSearchCriterias => { diff --git a/client/src/app/videos/video-watch/video-watch.component.ts b/client/src/app/videos/video-watch/video-watch.component.ts index 09255de5d..bc0e3157d 100644 --- a/client/src/app/videos/video-watch/video-watch.component.ts +++ b/client/src/app/videos/video-watch/video-watch.component.ts @@ -1,4 +1,4 @@ -import { Component, ElementRef, OnDestroy, OnInit } from '@angular/core'; +import { Component, ElementRef, NgZone, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'; @@ -31,6 +31,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { constructor( private elementRef: ElementRef, + private ngZone: NgZone, private route: ActivatedRoute, private videoService: VideoService, private webTorrentService: WebTorrentService @@ -65,12 +66,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { } }); - // Refresh each second - this.torrentInfosInterval = setInterval(() => { - this.downloadSpeed = torrent.downloadSpeed; - this.numPeers = torrent.numPeers; - this.uploadSpeed = torrent.uploadSpeed; - }, 1000); + this.runInProgress(torrent); }); } @@ -99,4 +95,15 @@ export class VideoWatchComponent implements OnInit, OnDestroy { this.error = true; console.error('The video load seems to be abnormally long.'); } + + private runInProgress(torrent: any) { + // Refresh each second + this.torrentInfosInterval = setInterval(() => { + this.ngZone.run(() => { + this.downloadSpeed = torrent.downloadSpeed; + this.numPeers = torrent.numPeers; + this.uploadSpeed = torrent.uploadSpeed; + }); + }, 1000); + } } diff --git a/package.json b/package.json index 310810377..c83ede5c2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "scripty": "^1.5.0", "segfault-handler": "^1.0.0", "ursa": "^0.9.1", - "webtorrent": "^0.93.2", + "webtorrent": "^0.96.0", "winston": "^2.1.1", "ws": "^1.1.1" }, From e2f555cab7563cd74fa790cea5fc65f2e31b0dc0 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 12 Aug 2016 18:22:58 +0200 Subject: [PATCH 018/105] Client: add friends page --- client/src/app/admin/admin.routes.ts | 2 + .../friend-list/friend-list.component.html | 21 +++++++++ .../friend-list/friend-list.component.scss | 3 ++ .../friend-list/friend-list.component.ts | 46 +++++++++++++++++++ .../app/admin/friends/friend-list/index.ts | 1 + .../src/app/admin/friends/friend.service.ts | 29 ------------ .../app/admin/friends/friends.component.ts | 13 ++++++ .../src/app/admin/friends/friends.routes.ts | 22 +++++++++ client/src/app/admin/friends/index.ts | 4 +- .../app/admin/friends/shared/friend.model.ts | 3 ++ .../admin/friends/shared/friend.service.ts | 39 ++++++++++++++++ client/src/app/admin/friends/shared/index.ts | 2 + .../src/app/admin/menu-admin.component.html | 9 +--- client/src/app/admin/menu-admin.component.ts | 29 +----------- client/tsconfig.json | 8 +++- 15 files changed, 165 insertions(+), 66 deletions(-) create mode 100644 client/src/app/admin/friends/friend-list/friend-list.component.html create mode 100644 client/src/app/admin/friends/friend-list/friend-list.component.scss create mode 100644 client/src/app/admin/friends/friend-list/friend-list.component.ts create mode 100644 client/src/app/admin/friends/friend-list/index.ts delete mode 100644 client/src/app/admin/friends/friend.service.ts create mode 100644 client/src/app/admin/friends/friends.component.ts create mode 100644 client/src/app/admin/friends/friends.routes.ts create mode 100644 client/src/app/admin/friends/shared/friend.model.ts create mode 100644 client/src/app/admin/friends/shared/friend.service.ts create mode 100644 client/src/app/admin/friends/shared/index.ts diff --git a/client/src/app/admin/admin.routes.ts b/client/src/app/admin/admin.routes.ts index d375a86af..f57deef62 100644 --- a/client/src/app/admin/admin.routes.ts +++ b/client/src/app/admin/admin.routes.ts @@ -1,6 +1,7 @@ import { RouterConfig } from '@angular/router'; import { AdminComponent } from './admin.component'; +import { FriendsRoutes } from './friends'; import { UsersRoutes } from './users'; export const AdminRoutes: RouterConfig = [ @@ -8,6 +9,7 @@ export const AdminRoutes: RouterConfig = [ path: 'admin', component: AdminComponent, children: [ + ...FriendsRoutes, ...UsersRoutes ] } 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 new file mode 100644 index 000000000..860bd2c07 --- /dev/null +++ b/client/src/app/admin/friends/friend-list/friend-list.component.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + +
Url
{{ friend.url }}
+ + + Quit friends + + + + Make friends + diff --git a/client/src/app/admin/friends/friend-list/friend-list.component.scss b/client/src/app/admin/friends/friend-list/friend-list.component.scss new file mode 100644 index 000000000..cb597e12b --- /dev/null +++ b/client/src/app/admin/friends/friend-list/friend-list.component.scss @@ -0,0 +1,3 @@ +table { + margin-bottom: 40px; +} 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 new file mode 100644 index 000000000..bf66d3ff1 --- /dev/null +++ b/client/src/app/admin/friends/friend-list/friend-list.component.ts @@ -0,0 +1,46 @@ +import { Component, OnInit } from '@angular/core'; + +import { Friend, FriendService } from '../shared'; + +@Component({ + selector: 'my-friend-list', + template: require('./friend-list.component.html'), + styles: [ require('./friend-list.component.scss') ] +}) +export class FriendListComponent implements OnInit { + friends: Friend[]; + + constructor(private friendService: FriendService) { } + + ngOnInit() { + this.friendService.getFriends().subscribe( + friends => this.friends = friends, + + err => alert(err) + ); + } + + makeFriends() { + this.friendService.makeFriends().subscribe( + status => { + if (status === 409) { + alert('Already made friends!'); + } else { + alert('Made friends!'); + } + }, + error => alert(error) + ); + } + + quitFriends() { + if (!confirm('Are you sure?')) return; + + this.friendService.quitFriends().subscribe( + status => { + alert('Quit friends!'); + }, + error => alert(error) + ); + } +} diff --git a/client/src/app/admin/friends/friend-list/index.ts b/client/src/app/admin/friends/friend-list/index.ts new file mode 100644 index 000000000..354c978a4 --- /dev/null +++ b/client/src/app/admin/friends/friend-list/index.ts @@ -0,0 +1 @@ +export * from './friend-list.component'; diff --git a/client/src/app/admin/friends/friend.service.ts b/client/src/app/admin/friends/friend.service.ts deleted file mode 100644 index d4ab5e60f..000000000 --- a/client/src/app/admin/friends/friend.service.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Response } from '@angular/http'; -import { Observable } from 'rxjs/Observable'; - -import { AuthHttp, AuthService } from '../../shared'; - -@Injectable() -export class FriendService { - private static BASE_FRIEND_URL: string = '/api/v1/pods/'; - - constructor (private authHttp: AuthHttp, private authService: AuthService) {} - - makeFriends() { - return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'makefriends') - .map(res => res.status) - .catch(this.handleError); - } - - quitFriends() { - return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'quitfriends') - .map(res => res.status) - .catch(this.handleError); - } - - private handleError (error: Response): Observable { - console.error(error); - return Observable.throw(error.json().error || 'Server error'); - } -} diff --git a/client/src/app/admin/friends/friends.component.ts b/client/src/app/admin/friends/friends.component.ts new file mode 100644 index 000000000..e66280f01 --- /dev/null +++ b/client/src/app/admin/friends/friends.component.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; +import { ROUTER_DIRECTIVES } from '@angular/router'; + +import { FriendService } from './shared'; + +@Component({ + template: '', + directives: [ ROUTER_DIRECTIVES ], + providers: [ FriendService ] +}) + +export class FriendsComponent { +} diff --git a/client/src/app/admin/friends/friends.routes.ts b/client/src/app/admin/friends/friends.routes.ts new file mode 100644 index 000000000..1e3646395 --- /dev/null +++ b/client/src/app/admin/friends/friends.routes.ts @@ -0,0 +1,22 @@ +import { RouterConfig } from '@angular/router'; + +import { FriendsComponent } from './friends.component'; +import { FriendListComponent } from './friend-list'; + +export const FriendsRoutes: RouterConfig = [ + { + path: 'friends', + component: FriendsComponent, + children: [ + { + path: '', + redirectTo: 'list', + pathMatch: 'full' + }, + { + path: 'list', + component: FriendListComponent + } + ] + } +]; diff --git a/client/src/app/admin/friends/index.ts b/client/src/app/admin/friends/index.ts index 0adc256c4..01aeedeee 100644 --- a/client/src/app/admin/friends/index.ts +++ b/client/src/app/admin/friends/index.ts @@ -1 +1,3 @@ -export * from './friend.service'; +export * from './shared'; +export * from './friend-list'; +export * from './friends.routes'; diff --git a/client/src/app/admin/friends/shared/friend.model.ts b/client/src/app/admin/friends/shared/friend.model.ts new file mode 100644 index 000000000..847eb9c9c --- /dev/null +++ b/client/src/app/admin/friends/shared/friend.model.ts @@ -0,0 +1,3 @@ +export interface Friend { + url: string; +} diff --git a/client/src/app/admin/friends/shared/friend.service.ts b/client/src/app/admin/friends/shared/friend.service.ts new file mode 100644 index 000000000..da4d64611 --- /dev/null +++ b/client/src/app/admin/friends/shared/friend.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@angular/core'; +import { Response } from '@angular/http'; +import { Observable } from 'rxjs/Observable'; + +import { Friend } from './friend.model'; +import { AuthHttp, AuthService } from '../../../shared'; + +@Injectable() +export class FriendService { + private static BASE_FRIEND_URL: string = '/api/v1/pods/'; + + constructor ( + private authHttp: AuthHttp, + private authService: AuthService + ) {} + + getFriends(): Observable { + return this.authHttp.get(FriendService.BASE_FRIEND_URL) + .map(res => res.json()) + .catch(this.handleError); + } + + makeFriends() { + return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'makefriends') + .map(res => res.status) + .catch(this.handleError); + } + + quitFriends() { + return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'quitfriends') + .map(res => res.status) + .catch(this.handleError); + } + + private handleError (error: Response) { + console.error(error); + return Observable.throw(error.json().error || 'Server error'); + } +} diff --git a/client/src/app/admin/friends/shared/index.ts b/client/src/app/admin/friends/shared/index.ts new file mode 100644 index 000000000..0d671637d --- /dev/null +++ b/client/src/app/admin/friends/shared/index.ts @@ -0,0 +1,2 @@ +export * from './friend.model'; +export * from './friend.service'; diff --git a/client/src/app/admin/menu-admin.component.html b/client/src/app/admin/menu-admin.component.html index 15a3c764e..092ab6081 100644 --- a/client/src/app/admin/menu-admin.component.html +++ b/client/src/app/admin/menu-admin.component.html @@ -6,14 +6,9 @@ List users
- diff --git a/client/src/app/admin/menu-admin.component.ts b/client/src/app/admin/menu-admin.component.ts index eb27c1e58..b23f7409e 100644 --- a/client/src/app/admin/menu-admin.component.ts +++ b/client/src/app/admin/menu-admin.component.ts @@ -1,42 +1,15 @@ import { Component, Output, EventEmitter } from '@angular/core'; import { ROUTER_DIRECTIVES } from '@angular/router'; -import { FriendService } from './friends'; - @Component({ selector: 'my-menu-admin', template: require('./menu-admin.component.html'), - directives: [ ROUTER_DIRECTIVES ], - providers: [ FriendService ] + directives: [ ROUTER_DIRECTIVES ] }) export class MenuAdminComponent { @Output() quittedAdmin = new EventEmitter(); - constructor(private friendService: FriendService) {} - - makeFriends() { - this.friendService.makeFriends().subscribe( - status => { - if (status === 409) { - alert('Already made friends!'); - } else { - alert('Made friends!'); - } - }, - error => alert(error) - ); - } - quitAdmin() { this.quittedAdmin.emit(true); } - - quitFriends() { - this.friendService.quitFriends().subscribe( - status => { - alert('Quit friends!'); - }, - error => alert(error) - ); - } } diff --git a/client/tsconfig.json b/client/tsconfig.json index 157529133..7de2a1a75 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -33,8 +33,14 @@ "src/app/account/index.ts", "src/app/admin/admin.component.ts", "src/app/admin/admin.routes.ts", - "src/app/admin/friends/friend.service.ts", + "src/app/admin/friends/friend-list/friend-list.component.ts", + "src/app/admin/friends/friend-list/index.ts", + "src/app/admin/friends/friends.routes.ts", "src/app/admin/friends/index.ts", + "src/app/admin/friends/shared/friend.model.ts", + "src/app/admin/friends/shared/friend.service.ts", + "src/app/admin/friends/shared/index.ts", + "src/app/admin/friends/users.component.ts", "src/app/admin/index.ts", "src/app/admin/menu-admin.component.ts", "src/app/admin/users/index.ts", From dfe3ec6bf66dfc5e2878f1df24f6a55b66812f46 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 12 Aug 2016 18:24:24 +0200 Subject: [PATCH 019/105] Client: add users list/friends list titles --- .../app/admin/friends/friend-list/friend-list.component.html | 2 ++ client/src/app/admin/users/user-list/user-list.component.html | 2 ++ 2 files changed, 4 insertions(+) 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 860bd2c07..f4d14293e 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 @@ -1,3 +1,5 @@ +

Friends list

+ diff --git a/client/src/app/admin/users/user-list/user-list.component.html b/client/src/app/admin/users/user-list/user-list.component.html index 2aca05f2b..fa7f71864 100644 --- a/client/src/app/admin/users/user-list/user-list.component.html +++ b/client/src/app/admin/users/user-list/user-list.component.html @@ -1,3 +1,5 @@ +

Users list

+
From ede4db9e5e55471510c687d5350f553158f82b68 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sun, 14 Aug 2016 16:50:22 +0200 Subject: [PATCH 020/105] Server: fix travis tests --- server/tests/api/singlePod.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/server/tests/api/singlePod.js b/server/tests/api/singlePod.js index 573eaa3a8..bdaaee46c 100644 --- a/server/tests/api/singlePod.js +++ b/server/tests/api/singlePod.js @@ -99,8 +99,7 @@ describe('Test a single pod', function () { expect(torrent.files.length).to.equal(1) expect(torrent.files[0].path).to.exist.and.to.not.equal('') - // We remove it because we'll add it again - webtorrent.remove(video.magnetUri, done) + done() }) }) }) @@ -127,13 +126,7 @@ describe('Test a single pod', function () { if (err) throw err expect(test).to.equal(true) - webtorrent.add(video.magnetUri, function (torrent) { - expect(torrent.files).to.exist - expect(torrent.files.length).to.equal(1) - expect(torrent.files[0].path).to.exist.and.to.not.equal('') - - done() - }) + done() }) }) }) From 28f7d2020fe3ffbe5ba8a0083386f616b0186a91 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 15 Aug 2016 18:44:30 +0200 Subject: [PATCH 021/105] Server: test filenames with hyphens --- server/tests/api/{checkParams.js => check-params.js} | 0 .../api/{friendsAdvanced.js => friends-advanced.js} | 0 server/tests/api/{friendsBasic.js => friends-basic.js} | 0 server/tests/api/index.js | 10 +++++----- server/tests/api/{multiplePods.js => multiple-pods.js} | 0 server/tests/api/{singlePod.js => single-pod.js} | 0 6 files changed, 5 insertions(+), 5 deletions(-) rename server/tests/api/{checkParams.js => check-params.js} (100%) rename server/tests/api/{friendsAdvanced.js => friends-advanced.js} (100%) rename server/tests/api/{friendsBasic.js => friends-basic.js} (100%) rename server/tests/api/{multiplePods.js => multiple-pods.js} (100%) rename server/tests/api/{singlePod.js => single-pod.js} (100%) diff --git a/server/tests/api/checkParams.js b/server/tests/api/check-params.js similarity index 100% rename from server/tests/api/checkParams.js rename to server/tests/api/check-params.js diff --git a/server/tests/api/friendsAdvanced.js b/server/tests/api/friends-advanced.js similarity index 100% rename from server/tests/api/friendsAdvanced.js rename to server/tests/api/friends-advanced.js diff --git a/server/tests/api/friendsBasic.js b/server/tests/api/friends-basic.js similarity index 100% rename from server/tests/api/friendsBasic.js rename to server/tests/api/friends-basic.js diff --git a/server/tests/api/index.js b/server/tests/api/index.js index 61c9a7aca..11f49e1e2 100644 --- a/server/tests/api/index.js +++ b/server/tests/api/index.js @@ -1,9 +1,9 @@ 'use strict' // Order of the tests we want to execute -require('./checkParams') -require('./friendsBasic') +require('./check-params') +require('./friends-basic') require('./users') -require('./singlePod') -require('./multiplePods') -require('./friendsAdvanced') +require('./single-pod') +require('./multiple-pods') +require('./friends-advanced') diff --git a/server/tests/api/multiplePods.js b/server/tests/api/multiple-pods.js similarity index 100% rename from server/tests/api/multiplePods.js rename to server/tests/api/multiple-pods.js diff --git a/server/tests/api/singlePod.js b/server/tests/api/single-pod.js similarity index 100% rename from server/tests/api/singlePod.js rename to server/tests/api/single-pod.js From 7eef95353f9202e1f3285606282fc8fd904c90ef Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 15 Aug 2016 19:05:52 +0200 Subject: [PATCH 022/105] Client: reset pagination when we search something --- .../src/app/videos/video-list/video-list.component.ts | 10 ++++++++-- client/tsconfig.json | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/client/src/app/videos/video-list/video-list.component.ts b/client/src/app/videos/video-list/video-list.component.ts index 062340ec5..7c6d4b992 100644 --- a/client/src/app/videos/video-list/video-list.component.ts +++ b/client/src/app/videos/video-list/video-list.component.ts @@ -66,6 +66,8 @@ export class VideoListComponent implements OnInit, OnDestroy { // Subscribe to search changes this.subSearch = this.searchService.searchUpdated.subscribe(search => { this.search = search; + // Reset pagination + this.pagination.currentPage = 1; this.navigateToNewParams(); }); @@ -76,7 +78,7 @@ export class VideoListComponent implements OnInit, OnDestroy { this.subSearch.unsubscribe(); } - getVideos(detectChanges = true) { + getVideos() { this.loading.next(true); this.videos = []; @@ -153,7 +155,11 @@ export class VideoListComponent implements OnInit, OnDestroy { this.sort = routeParams['sort'] || '-createdDate'; - this.pagination.currentPage = parseInt(routeParams['page']) || 1; + if (routeParams['page'] !== undefined) { + this.pagination.currentPage = parseInt(routeParams['page']); + } else { + this.pagination.currentPage = 1; + } this.changeDetector.detectChanges(); } diff --git a/client/tsconfig.json b/client/tsconfig.json index 7de2a1a75..20938ce55 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -35,12 +35,12 @@ "src/app/admin/admin.routes.ts", "src/app/admin/friends/friend-list/friend-list.component.ts", "src/app/admin/friends/friend-list/index.ts", + "src/app/admin/friends/friends.component.ts", "src/app/admin/friends/friends.routes.ts", "src/app/admin/friends/index.ts", "src/app/admin/friends/shared/friend.model.ts", "src/app/admin/friends/shared/friend.service.ts", "src/app/admin/friends/shared/index.ts", - "src/app/admin/friends/users.component.ts", "src/app/admin/index.ts", "src/app/admin/menu-admin.component.ts", "src/app/admin/users/index.ts", From 0ff21c1c086f907612e34880f76f08bc74eeb78c Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 16 Aug 2016 21:51:04 +0200 Subject: [PATCH 023/105] Server: video.list -> video.listForApi (with pagination, sort...) --- server/controllers/api/v1/videos.js | 2 +- server/models/video.js | 28 +++++----------------------- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/server/controllers/api/v1/videos.js b/server/controllers/api/v1/videos.js index 1f939b077..0a441f146 100644 --- a/server/controllers/api/v1/videos.js +++ b/server/controllers/api/v1/videos.js @@ -142,7 +142,7 @@ function getVideo (req, res, next) { } function listVideos (req, res, next) { - Video.list(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) { + Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) { if (err) return next(err) res.json(getFormatedVideos(videosList, videosTotal)) diff --git a/server/models/video.js b/server/models/video.js index 14bc91b16..a5540d127 100644 --- a/server/models/video.js +++ b/server/models/video.js @@ -11,6 +11,7 @@ const mongoose = require('mongoose') const constants = require('../initializers/constants') const customVideosValidators = require('../helpers/custom-validators').videos const logger = require('../helpers/logger') +const modelUtils = require('./utils') const utils = require('../helpers/utils') const webtorrent = require('../lib/webtorrent') @@ -60,7 +61,7 @@ VideoSchema.methods = { VideoSchema.statics = { getDurationFromFile: getDurationFromFile, - list: list, + listForApi: listForApi, listByUrlAndMagnet: listByUrlAndMagnet, listByUrls: listByUrls, listOwned: listOwned, @@ -194,9 +195,9 @@ function getDurationFromFile (videoPath, callback) { }) } -function list (start, count, sort, callback) { +function listForApi (start, count, sort, callback) { const query = {} - return findWithCount.call(this, query, start, count, sort, callback) + return modelUtils.findWithCount.call(this, query, start, count, sort, callback) } function listByUrlAndMagnet (fromUrl, magnetUri, callback) { @@ -233,7 +234,7 @@ function search (value, field, start, count, sort, callback) { query[field] = new RegExp(value) } - findWithCount.call(this, query, start, count, sort, callback) + modelUtils.findWithCount.call(this, query, start, count, sort, callback) } function seedAllExisting (callback) { @@ -249,25 +250,6 @@ function seedAllExisting (callback) { // --------------------------------------------------------------------------- -function findWithCount (query, start, count, sort, callback) { - const self = this - - parallel([ - function (asyncCallback) { - self.find(query).skip(start).limit(count).sort(sort).exec(asyncCallback) - }, - function (asyncCallback) { - self.count(query, asyncCallback) - } - ], function (err, results) { - if (err) return callback(err) - - const videos = results[0] - const totalVideos = results[1] - return callback(null, videos, totalVideos) - }) -} - function removeThumbnail (video, callback) { fs.unlink(thumbnailsDir + video.thumbnail, callback) } From 089ff2f2046fdbaf9531726eea1f8c6726ebf0c0 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 16 Aug 2016 21:51:35 +0200 Subject: [PATCH 024/105] Server: optimize function to see if there are users or not --- server/initializers/checker.js | 4 ++-- server/models/user.js | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/server/initializers/checker.js b/server/initializers/checker.js index 3831efb8d..871d3cac2 100644 --- a/server/initializers/checker.js +++ b/server/initializers/checker.js @@ -39,10 +39,10 @@ function clientsExist (callback) { } function usersExist (callback) { - User.list(function (err, users) { + User.count(function (err, totalUsers) { if (err) return callback(err) - return callback(null, users.length !== 0) + return callback(null, totalUsers !== 0) }) } diff --git a/server/models/user.js b/server/models/user.js index 351ffef86..d289da19a 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -19,6 +19,7 @@ UserSchema.methods = { } UserSchema.statics = { + count: count, getByUsernameAndPassword: getByUsernameAndPassword, list: list, loadById: loadById, @@ -29,6 +30,10 @@ mongoose.model('User', UserSchema) // --------------------------------------------------------------------------- +function count (callback) { + return this.count(callback) +} + function getByUsernameAndPassword (username, password) { return this.findOne({ username: username, password: password }) } From 5c39adb7313e0696aabb4b71196ab7b0b378c359 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 16 Aug 2016 22:31:45 +0200 Subject: [PATCH 025/105] Server: add user list sort/pagination --- server/controllers/api/v1/users.js | 20 +++++-- server/initializers/checker.js | 2 +- server/initializers/constants.js | 1 + server/middlewares/sort.js | 7 +++ server/middlewares/validators/sort.js | 11 ++++ server/models/user.js | 16 +++-- server/models/utils.js | 30 ++++++++++ server/models/video.js | 4 +- server/tests/api/check-params.js | 26 ++++++++ server/tests/api/users.js | 85 +++++++++++++++++++++++++-- server/tests/utils/users.js | 15 +++++ 11 files changed, 200 insertions(+), 17 deletions(-) create mode 100644 server/models/utils.js diff --git a/server/controllers/api/v1/users.js b/server/controllers/api/v1/users.js index 704df770c..975e25e68 100644 --- a/server/controllers/api/v1/users.js +++ b/server/controllers/api/v1/users.js @@ -11,6 +11,10 @@ const logger = require('../../../helpers/logger') const middlewares = require('../../../middlewares') const admin = middlewares.admin const oAuth = middlewares.oauth +const pagination = middlewares.pagination +const sort = middlewares.sort +const validatorsPagination = middlewares.validators.pagination +const validatorsSort = middlewares.validators.sort const validatorsUsers = middlewares.validators.users const User = mongoose.model('User') @@ -18,9 +22,16 @@ const Video = mongoose.model('Video') const router = express.Router() -router.get('/', listUsers) router.get('/me', oAuth.authenticate, getUserInformation) +router.get('/', + validatorsPagination.pagination, + validatorsSort.usersSort, + sort.setUsersSort, + pagination.setPagination, + listUsers +) + router.post('/', oAuth.authenticate, admin.ensureIsAdmin, @@ -73,10 +84,10 @@ function getUserInformation (req, res, next) { } function listUsers (req, res, next) { - User.list(function (err, usersList) { + User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) { if (err) return next(err) - res.json(getFormatedUsers(usersList)) + res.json(getFormatedUsers(usersList, usersTotal)) }) } @@ -145,7 +156,7 @@ function success (req, res, next) { // --------------------------------------------------------------------------- -function getFormatedUsers (users) { +function getFormatedUsers (users, usersTotal) { const formatedUsers = [] users.forEach(function (user) { @@ -153,6 +164,7 @@ function getFormatedUsers (users) { }) return { + total: usersTotal, data: formatedUsers } } diff --git a/server/initializers/checker.js b/server/initializers/checker.js index 871d3cac2..4b6997547 100644 --- a/server/initializers/checker.js +++ b/server/initializers/checker.js @@ -39,7 +39,7 @@ function clientsExist (callback) { } function usersExist (callback) { - User.count(function (err, totalUsers) { + User.countTotal(function (err, totalUsers) { if (err) return callback(err) return callback(null, totalUsers !== 0) diff --git a/server/initializers/constants.js b/server/initializers/constants.js index 416356400..cd2e0cfb9 100644 --- a/server/initializers/constants.js +++ b/server/initializers/constants.js @@ -63,6 +63,7 @@ const SEEDS_IN_PARALLEL = 3 // Sortable columns per schema const SORTABLE_COLUMNS = { + USERS: [ 'username', '-username', 'createdDate', '-createdDate' ], VIDEOS: [ 'name', '-name', 'duration', '-duration', 'createdDate', '-createdDate' ] } diff --git a/server/middlewares/sort.js b/server/middlewares/sort.js index 9f52290a6..8ed157805 100644 --- a/server/middlewares/sort.js +++ b/server/middlewares/sort.js @@ -1,9 +1,16 @@ 'use strict' const sortMiddleware = { + setUsersSort: setUsersSort, setVideosSort: setVideosSort } +function setUsersSort (req, res, next) { + if (!req.query.sort) req.query.sort = '-createdDate' + + return next() +} + function setVideosSort (req, res, next) { if (!req.query.sort) req.query.sort = '-createdDate' diff --git a/server/middlewares/validators/sort.js b/server/middlewares/validators/sort.js index 56b63cc8b..37b34ef52 100644 --- a/server/middlewares/validators/sort.js +++ b/server/middlewares/validators/sort.js @@ -5,9 +5,20 @@ const constants = require('../../initializers/constants') const logger = require('../../helpers/logger') const validatorsSort = { + usersSort: usersSort, videosSort: videosSort } +function usersSort (req, res, next) { + const sortableColumns = constants.SORTABLE_COLUMNS.USERS + + req.checkQuery('sort', 'Should have correct sortable column').optional().isIn(sortableColumns) + + logger.debug('Checking sort parameters', { parameters: req.query }) + + checkErrors(req, res, next) +} + function videosSort (req, res, next) { const sortableColumns = constants.SORTABLE_COLUMNS.VIDEOS diff --git a/server/models/user.js b/server/models/user.js index d289da19a..c9c35b3e2 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -1,10 +1,15 @@ const mongoose = require('mongoose') const customUsersValidators = require('../helpers/custom-validators').users +const modelUtils = require('./utils') // --------------------------------------------------------------------------- const UserSchema = mongoose.Schema({ + createdDate: { + type: Date, + default: Date.now + }, password: String, username: String, role: String @@ -19,9 +24,9 @@ UserSchema.methods = { } UserSchema.statics = { - count: count, + countTotal: countTotal, getByUsernameAndPassword: getByUsernameAndPassword, - list: list, + listForApi: listForApi, loadById: loadById, loadByUsername: loadByUsername } @@ -30,7 +35,7 @@ mongoose.model('User', UserSchema) // --------------------------------------------------------------------------- -function count (callback) { +function countTotal (callback) { return this.count(callback) } @@ -38,8 +43,9 @@ function getByUsernameAndPassword (username, password) { return this.findOne({ username: username, password: password }) } -function list (callback) { - return this.find(callback) +function listForApi (start, count, sort, callback) { + const query = {} + return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback) } function loadById (id, callback) { diff --git a/server/models/utils.js b/server/models/utils.js new file mode 100644 index 000000000..a961e8c5b --- /dev/null +++ b/server/models/utils.js @@ -0,0 +1,30 @@ +'use strict' + +const parallel = require('async/parallel') + +const utils = { + listForApiWithCount: listForApiWithCount +} + +function listForApiWithCount (query, start, count, sort, callback) { + const self = this + + parallel([ + function (asyncCallback) { + self.find(query).skip(start).limit(count).sort(sort).exec(asyncCallback) + }, + function (asyncCallback) { + self.count(query, asyncCallback) + } + ], function (err, results) { + if (err) return callback(err) + + const data = results[0] + const total = results[1] + return callback(null, data, total) + }) +} + +// --------------------------------------------------------------------------- + +module.exports = utils diff --git a/server/models/video.js b/server/models/video.js index a5540d127..63afc2efe 100644 --- a/server/models/video.js +++ b/server/models/video.js @@ -197,7 +197,7 @@ function getDurationFromFile (videoPath, callback) { function listForApi (start, count, sort, callback) { const query = {} - return modelUtils.findWithCount.call(this, query, start, count, sort, callback) + return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback) } function listByUrlAndMagnet (fromUrl, magnetUri, callback) { @@ -234,7 +234,7 @@ function search (value, field, start, count, sort, callback) { query[field] = new RegExp(value) } - modelUtils.findWithCount.call(this, query, start, count, sort, callback) + modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback) } function seedAllExisting (callback) { diff --git a/server/tests/api/check-params.js b/server/tests/api/check-params.js index 882948fac..fc8b0a42a 100644 --- a/server/tests/api/check-params.js +++ b/server/tests/api/check-params.js @@ -459,6 +459,32 @@ describe('Test parameters validator', function () { let userId = null let userAccessToken = null + describe('When listing users', function () { + it('Should fail with a bad start pagination', function (done) { + request(server.url) + .get(path) + .query({ start: 'hello' }) + .set('Accept', 'application/json') + .expect(400, done) + }) + + it('Should fail with a bad count pagination', function (done) { + request(server.url) + .get(path) + .query({ count: 'hello' }) + .set('Accept', 'application/json') + .expect(400, done) + }) + + it('Should fail with an incorrect sort', function (done) { + request(server.url) + .get(path) + .query({ sort: 'hello' }) + .set('Accept', 'application/json') + .expect(400, done) + }) + }) + describe('When adding a new user', function () { it('Should fail with a too small username', function (done) { const data = { diff --git a/server/tests/api/users.js b/server/tests/api/users.js index a2557d2ab..c6c892bf2 100644 --- a/server/tests/api/users.js +++ b/server/tests/api/users.js @@ -209,22 +209,97 @@ describe('Test users', function () { usersUtils.getUsersList(server.url, function (err, res) { if (err) throw err - const users = res.body.data + const result = res.body + const total = result.total + const users = result.data + expect(total).to.equal(2) expect(users).to.be.an('array') expect(users.length).to.equal(2) - const rootUser = users[0] - expect(rootUser.username).to.equal('root') - - const user = users[1] + const user = users[0] expect(user.username).to.equal('user_1') + + const rootUser = users[1] + expect(rootUser.username).to.equal('root') userId = user.id done() }) }) + it('Should list only the first user by username asc', function (done) { + usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, 'username', function (err, res) { + if (err) throw err + + const result = res.body + const total = result.total + const users = result.data + + expect(total).to.equal(2) + expect(users.length).to.equal(1) + + const user = users[0] + expect(user.username).to.equal('root') + + done() + }) + }) + + it('Should list only the first user by username desc', function (done) { + usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, '-username', function (err, res) { + if (err) throw err + + const result = res.body + const total = result.total + const users = result.data + + expect(total).to.equal(2) + expect(users.length).to.equal(1) + + const user = users[0] + expect(user.username).to.equal('user_1') + + done() + }) + }) + + it('Should list only the second user by createdDate desc', function (done) { + usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, '-createdDate', function (err, res) { + if (err) throw err + + const result = res.body + const total = result.total + const users = result.data + + expect(total).to.equal(2) + expect(users.length).to.equal(1) + + const user = users[0] + expect(user.username).to.equal('user_1') + + done() + }) + }) + + it('Should list all the users by createdDate asc', function (done) { + usersUtils.getUsersListPaginationAndSort(server.url, 0, 2, 'createdDate', function (err, res) { + if (err) throw err + + const result = res.body + const total = result.total + const users = result.data + + expect(total).to.equal(2) + expect(users.length).to.equal(2) + + expect(users[0].username).to.equal('root') + expect(users[1].username).to.equal('user_1') + + done() + }) + }) + it('Should update the user password', function (done) { usersUtils.updateUser(server.url, userId, accessTokenUser, 'new password', function (err, res) { if (err) throw err diff --git a/server/tests/utils/users.js b/server/tests/utils/users.js index 3b560e409..0cf4e4adb 100644 --- a/server/tests/utils/users.js +++ b/server/tests/utils/users.js @@ -6,6 +6,7 @@ const usersUtils = { createUser: createUser, getUserInformation: getUserInformation, getUsersList: getUsersList, + getUsersListPaginationAndSort: getUsersListPaginationAndSort, removeUser: removeUser, updateUser: updateUser } @@ -52,6 +53,20 @@ function getUsersList (url, end) { .end(end) } +function getUsersListPaginationAndSort (url, start, count, sort, end) { + const path = '/api/v1/users' + + request(url) + .get(path) + .query({ start: start }) + .query({ count: count }) + .query({ sort: sort }) + .set('Accept', 'application/json') + .expect(200) + .expect('Content-Type', /json/) + .end(end) +} + function removeUser (url, userId, accessToken, expectedStatus, end) { if (!end) { end = expectedStatus From e861452fb26553177ad4e32bfb18b4fd8a5b1816 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 19 Aug 2016 21:34:51 +0200 Subject: [PATCH 026/105] Server: put config in constants --- server/controllers/api/v1/clients.js | 7 ++++--- server/controllers/api/v1/videos.js | 5 ++--- server/helpers/logger.js | 10 +++++----- server/helpers/peertube-crypto.js | 29 +++++++++++++++------------- server/helpers/requests.js | 11 ++--------- server/initializers/constants.js | 27 ++++++++++++++++++++++++++ server/initializers/database.js | 8 ++------ server/lib/friends.js | 8 ++------ server/lib/webtorrent.js | 9 ++++----- server/models/video.js | 23 ++++++++-------------- 10 files changed, 72 insertions(+), 65 deletions(-) diff --git a/server/controllers/api/v1/clients.js b/server/controllers/api/v1/clients.js index 0d222634b..5b460db2e 100644 --- a/server/controllers/api/v1/clients.js +++ b/server/controllers/api/v1/clients.js @@ -1,9 +1,10 @@ 'use strict' -const config = require('config') const express = require('express') const mongoose = require('mongoose') +const constants = require('../../../initializers/constants') + const Client = mongoose.model('OAuthClient') const router = express.Router() @@ -12,8 +13,8 @@ router.get('/local', getLocalClient) // Get the client credentials for the PeerTube front end function getLocalClient (req, res, next) { - const serverHost = config.get('webserver.host') - const serverPort = config.get('webserver.port') + const serverHost = constants.CONFIG.WEBSERVER.HOST + const serverPort = constants.CONFIG.WEBSERVER.PORT let headerHostShouldBe = serverHost if (serverPort !== 80 && serverPort !== 443) { headerHostShouldBe += ':' + serverPort diff --git a/server/controllers/api/v1/videos.js b/server/controllers/api/v1/videos.js index 0a441f146..70d22f139 100644 --- a/server/controllers/api/v1/videos.js +++ b/server/controllers/api/v1/videos.js @@ -1,11 +1,11 @@ 'use strict' -const config = require('config') const express = require('express') const mongoose = require('mongoose') const multer = require('multer') const waterfall = require('async/waterfall') +const constants = require('../../../initializers/constants') const logger = require('../../../helpers/logger') const friends = require('../../../lib/friends') const middlewares = require('../../../middlewares') @@ -20,13 +20,12 @@ const sort = middlewares.sort const utils = require('../../../helpers/utils') const router = express.Router() -const uploads = config.get('storage.uploads') const Video = mongoose.model('Video') // multer configuration const storage = multer.diskStorage({ destination: function (req, file, cb) { - cb(null, uploads) + cb(null, constants.CONFIG.STORAGE.UPLOAD_DIR) }, filename: function (req, file, cb) { diff --git a/server/helpers/logger.js b/server/helpers/logger.js index 8ae90a4b2..590ceaeb6 100644 --- a/server/helpers/logger.js +++ b/server/helpers/logger.js @@ -1,23 +1,23 @@ // Thanks http://tostring.it/2014/06/23/advanced-logging-with-nodejs/ 'use strict' -const config = require('config') const mkdirp = require('mkdirp') const path = require('path') const winston = require('winston') winston.emitErrs = true -const logDir = path.join(__dirname, '..', '..', config.get('storage.logs')) -const label = config.get('webserver.host') + ':' + config.get('webserver.port') +const constants = require('../initializers/constants') + +const label = constants.CONFIG.WEBSERVER.HOST + ':' + constants.CONFIG.WEBSERVER.PORT // Create the directory if it does not exist -mkdirp.sync(logDir) +mkdirp.sync(constants.CONFIG.STORAGE.LOG_DIR) const logger = new winston.Logger({ transports: [ new winston.transports.File({ level: 'debug', - filename: path.join(logDir, 'all-logs.log'), + filename: path.join(constants.CONFIG.STORAGE.LOG_DIR, 'all-logs.log'), handleExceptions: true, json: true, maxsize: 5242880, diff --git a/server/helpers/peertube-crypto.js b/server/helpers/peertube-crypto.js index 46dff8d03..ef130ea5c 100644 --- a/server/helpers/peertube-crypto.js +++ b/server/helpers/peertube-crypto.js @@ -1,15 +1,13 @@ 'use strict' -const config = require('config') const crypto = require('crypto') const fs = require('fs') const openssl = require('openssl-wrapper') -const path = require('path') const ursa = require('ursa') +const constants = require('../initializers/constants') const logger = require('./logger') -const certDir = path.join(__dirname, '..', '..', config.get('storage.certs')) const algorithm = 'aes-256-ctr' const peertubeCrypto = { @@ -17,7 +15,6 @@ const peertubeCrypto = { createCertsIfNotExist: createCertsIfNotExist, decrypt: decrypt, encrypt: encrypt, - getCertDir: getCertDir, sign: sign } @@ -40,7 +37,7 @@ function createCertsIfNotExist (callback) { } function decrypt (key, data, callback) { - fs.readFile(getCertDir() + 'peertube.key.pem', function (err, file) { + fs.readFile(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem', function (err, file) { if (err) return callback(err) const myPrivateKey = ursa.createPrivateKey(file) @@ -67,12 +64,8 @@ function encrypt (publicKey, data, callback) { }) } -function getCertDir () { - return certDir -} - function sign (data) { - const myKey = ursa.createPrivateKey(fs.readFileSync(certDir + 'peertube.key.pem')) + const myKey = ursa.createPrivateKey(fs.readFileSync(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem')) const signature = myKey.hashAndSign('sha256', data, 'utf8', 'hex') return signature @@ -85,7 +78,7 @@ module.exports = peertubeCrypto // --------------------------------------------------------------------------- function certsExist (callback) { - fs.exists(certDir + 'peertube.key.pem', function (exists) { + fs.exists(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem', function (exists) { return callback(exists) }) } @@ -99,15 +92,25 @@ function createCerts (callback) { } logger.info('Generating a RSA key...') - openssl.exec('genrsa', { 'out': certDir + 'peertube.key.pem', '2048': false }, function (err) { + + let options = { + 'out': constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem', + '2048': false + } + openssl.exec('genrsa', options, function (err) { if (err) { logger.error('Cannot create private key on this pod.') return callback(err) } logger.info('RSA key generated.') + options = { + 'in': constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem', + 'pubout': true, + 'out': constants.CONFIG.STORAGE.CERT_DIR + 'peertube.pub' + } logger.info('Manage public key...') - openssl.exec('rsa', { 'in': certDir + 'peertube.key.pem', 'pubout': true, 'out': certDir + 'peertube.pub' }, function (err) { + openssl.exec('rsa', options, function (err) { if (err) { logger.error('Cannot create public key on this pod.') return callback(err) diff --git a/server/helpers/requests.js b/server/helpers/requests.js index 547230adc..f76ff3473 100644 --- a/server/helpers/requests.js +++ b/server/helpers/requests.js @@ -1,16 +1,11 @@ 'use strict' -const config = require('config') const replay = require('request-replay') const request = require('request') const constants = require('../initializers/constants') const peertubeCrypto = require('./peertube-crypto') -const http = config.get('webserver.https') ? 'https' : 'http' -const host = config.get('webserver.host') -const port = config.get('webserver.port') - const requests = { makeRetryRequest: makeRetryRequest, makeSecureRequest: makeSecureRequest @@ -29,8 +24,6 @@ function makeRetryRequest (params, callback) { } function makeSecureRequest (params, callback) { - const myUrl = http + '://' + host + ':' + port - const requestParams = { url: params.toPod.url + params.path } @@ -42,8 +35,8 @@ function makeSecureRequest (params, callback) { // Add signature if it is specified in the params if (params.sign === true) { requestParams.json.signature = { - url: myUrl, - signature: peertubeCrypto.sign(myUrl) + url: constants.CONFIG.WEBSERVER.URL, + signature: peertubeCrypto.sign(constants.CONFIG.WEBSERVER.URL) } } diff --git a/server/initializers/constants.js b/server/initializers/constants.js index cd2e0cfb9..ce9f8ad6c 100644 --- a/server/initializers/constants.js +++ b/server/initializers/constants.js @@ -1,8 +1,34 @@ 'use strict' +const config = require('config') +const path = require('path') + // API version of our pod const API_VERSION = 'v1' +const CONFIG = { + DATABASE: { + DBNAME: 'peertube' + config.get('database.suffix'), + HOST: config.get('database.host'), + PORT: config.get('database.port') + }, + ELECTRON: { + DEBUG: config.get('electron.debug') + }, + STORAGE: { + CERT_DIR: path.join(__dirname, '..', '..', config.get('storage.certs')), + LOG_DIR: path.join(__dirname, '..', '..', config.get('storage.logs')), + UPLOAD_DIR: path.join(__dirname, '..', '..', config.get('storage.uploads')), + THUMBNAILS_DIR: path.join(__dirname, '..', '..', config.get('storage.thumbnails')) + }, + WEBSERVER: { + SCHEME: config.get('webserver.https') === true ? 'https' : 'http', + HOST: config.get('webserver.host'), + PORT: config.get('webserver.port') + } +} +CONFIG.WEBSERVER.URL = CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOST + ':' + CONFIG.WEBSERVER.PORT + const CONSTRAINTS_FIELDS = { USERS: { USERNAME: { min: 3, max: 20 }, // Length @@ -89,6 +115,7 @@ if (isTestInstance() === true) { module.exports = { API_VERSION: API_VERSION, + CONFIG: CONFIG, CONSTRAINTS_FIELDS: CONSTRAINTS_FIELDS, FRIEND_SCORE: FRIEND_SCORE, INTERVAL: INTERVAL, diff --git a/server/initializers/database.js b/server/initializers/database.js index 8626895ee..20dcc056e 100644 --- a/server/initializers/database.js +++ b/server/initializers/database.js @@ -1,8 +1,8 @@ 'use strict' -const config = require('config') const mongoose = require('mongoose') +const constants = require('../initializers/constants') const logger = require('../helpers/logger') // Bootstrap models @@ -14,17 +14,13 @@ require('../models/video') // Request model needs Video model require('../models/request') -const dbname = 'peertube' + config.get('database.suffix') -const host = config.get('database.host') -const port = config.get('database.port') - const database = { connect: connect } function connect () { mongoose.Promise = global.Promise - mongoose.connect('mongodb://' + host + ':' + port + '/' + dbname) + mongoose.connect('mongodb://' + constants.CONFIG.DATABASE.HOST + ':' + constants.CONFIG.DATABASE.PORT + '/' + constants.CONFIG.DATABASE.DBNAME) mongoose.connection.on('error', function () { throw new Error('Mongodb connection error.') }) diff --git a/server/lib/friends.js b/server/lib/friends.js index 6e1516b94..6a2c37fd7 100644 --- a/server/lib/friends.js +++ b/server/lib/friends.js @@ -11,12 +11,8 @@ const waterfall = require('async/waterfall') const constants = require('../initializers/constants') const logger = require('../helpers/logger') -const peertubeCrypto = require('../helpers/peertube-crypto') const requests = require('../helpers/requests') -const http = config.get('webserver.https') ? 'https' : 'http' -const host = config.get('webserver.host') -const port = config.get('webserver.port') const Pod = mongoose.model('Pod') const Request = mongoose.model('Request') const Video = mongoose.model('Video') @@ -45,7 +41,7 @@ function hasFriends (callback) { } function getMyCertificate (callback) { - fs.readFile(peertubeCrypto.getCertDir() + 'peertube.pub', 'utf8', callback) + fs.readFile(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.pub', 'utf8', callback) } function makeFriends (callback) { @@ -220,7 +216,7 @@ function makeRequestsToWinningPods (cert, podsList, callback) { url: pod.url + '/api/' + constants.API_VERSION + '/pods/', method: 'POST', json: { - url: http + '://' + host + ':' + port, + url: constants.CONFIG.WEBSERVER.URL, publicKey: cert } } diff --git a/server/lib/webtorrent.js b/server/lib/webtorrent.js index bcd30139e..2090b792b 100644 --- a/server/lib/webtorrent.js +++ b/server/lib/webtorrent.js @@ -1,15 +1,14 @@ 'use strict' -const config = require('config') const ipc = require('node-ipc') const pathUtils = require('path') const spawn = require('electron-spawn') +const constants = require('../initializers/constants') const logger = require('../helpers/logger') -const electronDebug = config.get('electron.debug') -let host = config.get('webserver.host') -let port = config.get('webserver.port') +let host = constants.CONFIG.WEBSERVER.HOST +let port = constants.CONFIG.WEBSERVER.PORT let nodeKey = 'webtorrentnode' + port let processKey = 'webtorrentprocess' + port ipc.config.silent = true @@ -59,7 +58,7 @@ function create (options, callback) { const webtorrentProcess = spawn(pathUtils.join(__dirname, 'webtorrent-process.js'), host, port, { detached: true }) - if (electronDebug === true) { + if (constants.CONFIG.ELECTRON.DEBUG === true) { webtorrentProcess.stderr.on('data', function (data) { logger.debug('Webtorrent process stderr: ', data.toString()) }) diff --git a/server/models/video.js b/server/models/video.js index 63afc2efe..0f60b6cd4 100644 --- a/server/models/video.js +++ b/server/models/video.js @@ -1,6 +1,5 @@ 'use strict' -const config = require('config') const eachLimit = require('async/eachLimit') const ffmpeg = require('fluent-ffmpeg') const fs = require('fs') @@ -15,12 +14,6 @@ const modelUtils = require('./utils') const utils = require('../helpers/utils') const webtorrent = require('../lib/webtorrent') -const http = config.get('webserver.https') === true ? 'https' : 'http' -const host = config.get('webserver.host') -const port = config.get('webserver.port') -const uploadsDir = pathUtils.join(__dirname, '..', '..', config.get('storage.uploads')) -const thumbnailsDir = pathUtils.join(__dirname, '..', '..', config.get('storage.thumbnails')) - // --------------------------------------------------------------------------- // TODO: add indexes on searchable columns @@ -101,8 +94,8 @@ VideoSchema.pre('save', function (next) { const tasks = [] if (video.isOwned()) { - const videoPath = pathUtils.join(uploadsDir, video.filename) - this.podUrl = http + '://' + host + ':' + port + const videoPath = pathUtils.join(constants.CONFIG.STORAGE.UPLOAD_DIR, video.filename) + this.podUrl = constants.CONFIG.WEBSERVER.URL tasks.push( function (callback) { @@ -162,7 +155,7 @@ function toRemoteJSON (callback) { const self = this // Convert thumbnail to base64 - fs.readFile(pathUtils.join(thumbnailsDir, this.thumbnail), function (err, thumbnailData) { + fs.readFile(pathUtils.join(constants.CONFIG.STORAGE.THUMBNAILS_DIR, this.thumbnail), function (err, thumbnailData) { if (err) { logger.error('Cannot read the thumbnail of the video') return callback(err) @@ -242,7 +235,7 @@ function seedAllExisting (callback) { if (err) return callback(err) eachLimit(videos, constants.SEEDS_IN_PARALLEL, function (video, callbackEach) { - const videoPath = pathUtils.join(uploadsDir, video.filename) + const videoPath = pathUtils.join(constants.CONFIG.STORAGE.UPLOAD_DIR, video.filename) seed(videoPath, callbackEach) }, callback) }) @@ -251,11 +244,11 @@ function seedAllExisting (callback) { // --------------------------------------------------------------------------- function removeThumbnail (video, callback) { - fs.unlink(thumbnailsDir + video.thumbnail, callback) + fs.unlink(constants.CONFIG.STORAGE.THUMBNAILS_DIR + video.thumbnail, callback) } function removeFile (video, callback) { - fs.unlink(uploadsDir + video.filename, callback) + fs.unlink(constants.CONFIG.STORAGE.UPLOAD_DIR + video.filename, callback) } // Maybe the torrent is not seeded, but we catch the error to don't stop the removing process @@ -277,7 +270,7 @@ function createThumbnail (videoPath, callback) { }) .thumbnail({ count: 1, - folder: thumbnailsDir, + folder: constants.CONFIG.STORAGE.THUMBNAILS_DIR, size: constants.THUMBNAILS_SIZE, filename: filename }) @@ -299,7 +292,7 @@ function generateThumbnailFromBase64 (data, callback) { if (err) return callback(err) const thumbnailName = randomString + '.jpg' - fs.writeFile(thumbnailsDir + thumbnailName, data, { encoding: 'base64' }, function (err) { + fs.writeFile(constants.CONFIG.STORAGE.THUMBNAILS_DIR + thumbnailName, data, { encoding: 'base64' }, function (err) { if (err) return callback(err) return callback(null, thumbnailName) From 1e2564d3927ce4ca4ca9a09930da6da7ebb4e9a1 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sat, 20 Aug 2016 16:59:25 +0200 Subject: [PATCH 027/105] Server: make friends urls come from the request instead of the configuration file --- config/default.yaml | 3 -- config/test-1.yaml | 4 --- config/test-2.yaml | 4 --- config/test-3.yaml | 4 --- config/test-4.yaml | 4 --- config/test-5.yaml | 5 ---- config/test-6.yaml | 6 ---- server/controllers/api/v1/pods.js | 6 ++-- server/helpers/custom-validators/misc.js | 11 ++++++- server/initializers/checker.js | 4 +-- server/lib/friends.js | 5 +--- server/middlewares/validators/pods.js | 5 ++++ server/tests/api/check-params.js | 38 +++++++++++++++++++++--- server/tests/utils/pods.js | 27 ++++++++++++++++- 14 files changed, 82 insertions(+), 44 deletions(-) diff --git a/config/default.yaml b/config/default.yaml index b5fd7b06d..d32d3e17b 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -18,8 +18,5 @@ storage: logs: 'logs/' thumbnails: 'thumbnails/' -network: - friends: [] - electron: debug: false diff --git a/config/test-1.yaml b/config/test-1.yaml index 3afcb1b04..0a8dd3937 100644 --- a/config/test-1.yaml +++ b/config/test-1.yaml @@ -14,7 +14,3 @@ storage: uploads: 'test1/uploads/' logs: 'test1/logs/' thumbnails: 'test1/thumbnails/' - -network: - friends: - - 'http://localhost:9002' diff --git a/config/test-2.yaml b/config/test-2.yaml index 51cc186eb..40f410559 100644 --- a/config/test-2.yaml +++ b/config/test-2.yaml @@ -14,7 +14,3 @@ storage: uploads: 'test2/uploads/' logs: 'test2/logs/' thumbnails: 'test2/thumbnails/' - -network: - friends: - - 'http://localhost:9003' diff --git a/config/test-3.yaml b/config/test-3.yaml index 7ef01ba4d..87b335228 100644 --- a/config/test-3.yaml +++ b/config/test-3.yaml @@ -14,7 +14,3 @@ storage: uploads: 'test3/uploads/' logs: 'test3/logs/' thumbnails: 'test3/thumbnails/' - -network: - friends: - - 'http://localhost:9001' diff --git a/config/test-4.yaml b/config/test-4.yaml index a4d3bb164..22abc0a58 100644 --- a/config/test-4.yaml +++ b/config/test-4.yaml @@ -14,7 +14,3 @@ storage: uploads: 'test4/uploads/' logs: 'test4/logs/' thumbnails: 'test4/thumbnails/' - -network: - friends: - - 'http://localhost:9002' diff --git a/config/test-5.yaml b/config/test-5.yaml index 0435c17fe..af5619994 100644 --- a/config/test-5.yaml +++ b/config/test-5.yaml @@ -14,8 +14,3 @@ storage: uploads: 'test5/uploads/' logs: 'test5/logs/' thumbnails: 'test5/thumbnails/' - -network: - friends: - - 'http://localhost:9001' - - 'http://localhost:9004' diff --git a/config/test-6.yaml b/config/test-6.yaml index b7dbd4bdd..5b8bf306b 100644 --- a/config/test-6.yaml +++ b/config/test-6.yaml @@ -14,9 +14,3 @@ storage: uploads: 'test6/uploads/' logs: 'test6/logs/' thumbnails: 'test6/thumbnails/' - -network: - friends: - - 'http://localhost:9001' - - 'http://localhost:9002' - - 'http://localhost:9003' diff --git a/server/controllers/api/v1/pods.js b/server/controllers/api/v1/pods.js index f61f2a483..982a1e364 100644 --- a/server/controllers/api/v1/pods.js +++ b/server/controllers/api/v1/pods.js @@ -19,7 +19,7 @@ const Video = mongoose.model('Video') router.get('/', listPodsUrl) router.post('/', validators.podsAdd, addPods) -router.get('/makefriends', +router.post('/makefriends', oAuth.authenticate, admin.ensureIsAdmin, validators.makeFriends, @@ -83,7 +83,9 @@ function listPodsUrl (req, res, next) { } function makeFriends (req, res, next) { - friends.makeFriends(function (err) { + const urls = req.body.urls + + friends.makeFriends(urls, function (err) { if (err) return next(err) res.type('json').status(204).end() diff --git a/server/helpers/custom-validators/misc.js b/server/helpers/custom-validators/misc.js index 782ae3dee..13904ea1b 100644 --- a/server/helpers/custom-validators/misc.js +++ b/server/helpers/custom-validators/misc.js @@ -1,8 +1,11 @@ 'use strict' +const validator = require('express-validator').validator + const miscValidators = { exists: exists, - isArray: isArray + isArray: isArray, + isEachUrl: isEachUrl } function exists (value) { @@ -13,6 +16,12 @@ function isArray (value) { return Array.isArray(value) } +function isEachUrl (urls) { + return urls.every(function (url) { + return validator.isURL(url) + }) +} + // --------------------------------------------------------------------------- module.exports = miscValidators diff --git a/server/initializers/checker.js b/server/initializers/checker.js index 4b6997547..2a33009b4 100644 --- a/server/initializers/checker.js +++ b/server/initializers/checker.js @@ -17,8 +17,8 @@ function checkConfig () { const required = [ 'listen.port', 'webserver.https', 'webserver.host', 'webserver.port', 'database.host', 'database.port', 'database.suffix', - 'storage.certs', 'storage.uploads', 'storage.logs', - 'network.friends', 'electron.debug' ] + 'storage.certs', 'storage.uploads', 'storage.logs', 'storage.thumbnails', + 'electron.debug' ] const miss = [] for (const key of required) { diff --git a/server/lib/friends.js b/server/lib/friends.js index 6a2c37fd7..667055d4c 100644 --- a/server/lib/friends.js +++ b/server/lib/friends.js @@ -1,6 +1,5 @@ 'use strict' -const config = require('config') const each = require('async/each') const eachLimit = require('async/eachLimit') const eachSeries = require('async/eachSeries') @@ -44,7 +43,7 @@ function getMyCertificate (callback) { fs.readFile(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.pub', 'utf8', callback) } -function makeFriends (callback) { +function makeFriends (urls, callback) { const podsScore = {} logger.info('Make friends!') @@ -54,8 +53,6 @@ function makeFriends (callback) { return callback(err) } - const urls = config.get('network.friends') - eachSeries(urls, function (url, callbackEach) { computeForeignPodsList(url, podsScore, callbackEach) }, function (err) { diff --git a/server/middlewares/validators/pods.js b/server/middlewares/validators/pods.js index fda2e865f..7c4d04aff 100644 --- a/server/middlewares/validators/pods.js +++ b/server/middlewares/validators/pods.js @@ -10,6 +10,11 @@ const validatorsPod = { } function makeFriends (req, res, next) { + req.checkBody('urls', 'Should have an array of urls').isArray() + req.checkBody('urls', 'Should be an url').isEachUrl() + + logger.debug('Checking makeFriends parameters', { parameters: req.body }) + friends.hasFriends(function (err, hasFriends) { if (err) { logger.error('Cannot know if we have friends.', { error: err }) diff --git a/server/tests/api/check-params.js b/server/tests/api/check-params.js index fc8b0a42a..ec666417c 100644 --- a/server/tests/api/check-params.js +++ b/server/tests/api/check-params.js @@ -108,10 +108,40 @@ describe('Test parameters validator', function () { }) describe('When making friends', function () { + const body = { + urls: [ 'http://localhost:9002' ] + } + + it('Should fail without urls', function (done) { + request(server.url) + .post(path + '/makefriends') + .set('Authorization', 'Bearer faketoken') + .set('Accept', 'application/json') + .expect(401, done) + }) + + it('Should fail with urls is not an array', function (done) { + request(server.url) + .post(path + '/makefriends') + .send({ urls: 'http://localhost:9002' }) + .set('Authorization', 'Bearer faketoken') + .set('Accept', 'application/json') + .expect(401, done) + }) + + it('Should fail if the array is not composed by urls', function (done) { + request(server.url) + .post(path + '/makefriends') + .send({ urls: [ 'http://localhost:9002', 'localhost:coucou' ] }) + .set('Authorization', 'Bearer faketoken') + .set('Accept', 'application/json') + .expect(401, done) + }) + it('Should fail with a invalid token', function (done) { request(server.url) - .get(path + '/makefriends') - .query({ start: 'hello' }) + .post(path + '/makefriends') + .send(body) .set('Authorization', 'Bearer faketoken') .set('Accept', 'application/json') .expect(401, done) @@ -119,8 +149,8 @@ describe('Test parameters validator', function () { it('Should fail if the user is not an administrator', function (done) { request(server.url) - .get(path + '/makefriends') - .query({ start: 'hello' }) + .post(path + '/makefriends') + .send(body) .set('Authorization', 'Bearer ' + userAccessToken) .set('Accept', 'application/json') .expect(403, done) diff --git a/server/tests/utils/pods.js b/server/tests/utils/pods.js index 366492110..9a9148856 100644 --- a/server/tests/utils/pods.js +++ b/server/tests/utils/pods.js @@ -27,13 +27,38 @@ function makeFriends (url, accessToken, expectedStatus, end) { expectedStatus = 204 } + // Which pod makes friends with which pod + const friendsMatrix = { + 'http://localhost:9001': [ + 'http://localhost:9002' + ], + 'http://localhost:9002': [ + 'http://localhost:9003' + ], + 'http://localhost:9003': [ + 'http://localhost:9001' + ], + 'http://localhost:9004': [ + 'http://localhost:9002' + ], + 'http://localhost:9005': [ + 'http://localhost:9001', + 'http://localhost:9004' + ], + 'http://localhost:9006': [ + 'http://localhost:9001', + 'http://localhost:9002', + 'http://localhost:9003' + ] + } const path = '/api/v1/pods/makefriends' // The first pod make friend with the third request(url) - .get(path) + .post(path) .set('Accept', 'application/json') .set('Authorization', 'Bearer ' + accessToken) + .send({ 'urls': friendsMatrix[url] }) .expect(expectedStatus) .end(function (err, res) { if (err) throw err From 4613274479968f58f62c63178c92c1391de84297 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sat, 20 Aug 2016 17:19:27 +0200 Subject: [PATCH 028/105] Server: fix real world simulation script --- server/tests/real-world/real-world.js | 31 +++++++++++++++------------ 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/server/tests/real-world/real-world.js b/server/tests/real-world/real-world.js index b28796852..dba1970c5 100644 --- a/server/tests/real-world/real-world.js +++ b/server/tests/real-world/real-world.js @@ -1,6 +1,6 @@ 'use strict' -const each = require('each') +const each = require('async/each') const isEqual = require('lodash/isEqual') const program = require('commander') const series = require('async/series') @@ -8,7 +8,10 @@ const series = require('async/series') process.env.NODE_ENV = 'test' const constants = require('../../initializers/constants') -const utils = require('../api/utils') +const loginUtils = require('../utils/login') +const podsUtils = require('../utils/pods') +const serversUtils = require('../utils/servers') +const videosUtils = require('../utils/videos') program .option('-c, --create [weight]', 'Weight for creating videos') @@ -97,7 +100,7 @@ function runServers (numberOfPods, callback) { series([ // Run servers function (next) { - utils.flushAndRunMultipleServers(numberOfPods, function (serversRun) { + serversUtils.flushAndRunMultipleServers(numberOfPods, function (serversRun) { servers = serversRun next() }) @@ -105,7 +108,7 @@ function runServers (numberOfPods, callback) { // Get the access tokens function (next) { each(servers, function (server, callbackEach) { - utils.loginAndGetAccessToken(server, function (err, accessToken) { + loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { if (err) return callbackEach(err) server.accessToken = accessToken @@ -115,26 +118,26 @@ function runServers (numberOfPods, callback) { }, function (next) { const server = servers[1] - utils.makeFriends(server.url, server.accessToken, next) + podsUtils.makeFriends(server.url, server.accessToken, next) }, function (next) { const server = servers[0] - utils.makeFriends(server.url, server.accessToken, next) + podsUtils.makeFriends(server.url, server.accessToken, next) }, function (next) { setTimeout(next, 1000) }, function (next) { const server = servers[3] - utils.makeFriends(server.url, server.accessToken, next) + podsUtils.makeFriends(server.url, server.accessToken, next) }, function (next) { const server = servers[5] - utils.makeFriends(server.url, server.accessToken, next) + podsUtils.makeFriends(server.url, server.accessToken, next) }, function (next) { const server = servers[4] - utils.makeFriends(server.url, server.accessToken, next) + podsUtils.makeFriends(server.url, server.accessToken, next) }, function (next) { setTimeout(next, 1000) @@ -151,7 +154,7 @@ function exitServers (servers, callback) { if (server.app) process.kill(-server.app.pid) }) - if (flushAtExit) utils.flushTests(callback) + if (flushAtExit) serversUtils.flushTests(callback) } function upload (servers, numServer, callback) { @@ -164,13 +167,13 @@ function upload (servers, numServer, callback) { console.log('Upload video to server ' + numServer) - utils.uploadVideo(servers[numServer].url, servers[numServer].accessToken, name, description, tags, file, callback) + videosUtils.uploadVideo(servers[numServer].url, servers[numServer].accessToken, name, description, tags, file, callback) } function remove (servers, numServer, callback) { if (!callback) callback = function () {} - utils.getVideosList(servers[numServer].url, function (err, res) { + videosUtils.getVideosList(servers[numServer].url, function (err, res) { if (err) throw err const videos = res.body.data @@ -179,14 +182,14 @@ function remove (servers, numServer, callback) { const toRemove = videos[getRandomInt(0, videos.length)].id console.log('Removing video from server ' + numServer) - utils.removeVideo(servers[numServer].url, servers[numServer].accessToken, toRemove, callback) + videosUtils.removeVideo(servers[numServer].url, servers[numServer].accessToken, toRemove, callback) }) } function checkIntegrity (servers, callback) { const videos = [] each(servers, function (server, callback) { - utils.getAllVideosListBy(server.url, function (err, res) { + videosUtils.getAllVideosListBy(server.url, function (err, res) { if (err) throw err const serverVideos = res.body.data for (const serverVideo of serverVideos) { From 6c1a098b4107cc923631d8cd94ed54c184fcec7d Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sun, 21 Aug 2016 09:54:46 +0200 Subject: [PATCH 029/105] Server: fix remote videos requests validator --- server/helpers/custom-validators/videos.js | 41 +++++++++++----------- server/middlewares/validators/remote.js | 1 - 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/server/helpers/custom-validators/videos.js b/server/helpers/custom-validators/videos.js index cffa973f8..ebe927208 100644 --- a/server/helpers/custom-validators/videos.js +++ b/server/helpers/custom-validators/videos.js @@ -22,26 +22,27 @@ const videosValidators = { } function isEachRemoteVideosValid (requests) { - return requests.every(function (request) { - const video = request.data - return ( - isRequestTypeAddValid(request.type) && - isVideoAuthorValid(video.author) && - isVideoDateValid(video.createdDate) && - isVideoDescriptionValid(video.description) && - isVideoDurationValid(video.duration) && - isVideoMagnetUriValid(video.magnetUri) && - isVideoNameValid(video.name) && - isVideoPodUrlValid(video.podUrl) && - isVideoTagsValid(video.tags) && - isVideoThumbnail64Valid(video.thumbnailBase64) - ) || - ( - isRequestTypeRemoveValid(request.type) && - isVideoNameValid(video.name) && - isVideoMagnetUriValid(video.magnetUri) - ) - }) + return miscValidators.isArray(requests) && + requests.every(function (request) { + const video = request.data + return ( + isRequestTypeAddValid(request.type) && + isVideoAuthorValid(video.author) && + isVideoDateValid(video.createdDate) && + isVideoDescriptionValid(video.description) && + isVideoDurationValid(video.duration) && + isVideoMagnetUriValid(video.magnetUri) && + isVideoNameValid(video.name) && + isVideoPodUrlValid(video.podUrl) && + isVideoTagsValid(video.tags) && + isVideoThumbnail64Valid(video.thumbnailBase64) + ) || + ( + isRequestTypeRemoveValid(request.type) && + isVideoNameValid(video.name) && + isVideoMagnetUriValid(video.magnetUri) + ) + }) } function isVideoAuthorValid (value) { diff --git a/server/middlewares/validators/remote.js b/server/middlewares/validators/remote.js index 1be119458..87dc524a2 100644 --- a/server/middlewares/validators/remote.js +++ b/server/middlewares/validators/remote.js @@ -19,7 +19,6 @@ function dataToDecrypt (req, res, next) { } function remoteVideos (req, res, next) { - req.checkBody('data').isArray() req.checkBody('data').isEachRemoteVideosValid() logger.debug('Checking remoteVideos parameters', { parameters: req.body }) From d57d6f2605f4ac4a81f9a8594433bb7b65f108b9 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sun, 21 Aug 2016 10:08:40 +0200 Subject: [PATCH 030/105] Server: fix makefriends validation and tests --- server.js | 8 +- server/helpers/custom-validators/index.js | 2 + server/helpers/custom-validators/misc.js | 11 +- server/helpers/custom-validators/pods.js | 21 +++ server/middlewares/validators/pods.js | 27 +-- server/tests/api/check-params.js | 191 +++++++++++----------- 6 files changed, 145 insertions(+), 115 deletions(-) create mode 100644 server/helpers/custom-validators/pods.js diff --git a/server.js b/server.js index d38c5830f..676597fae 100644 --- a/server.js +++ b/server.js @@ -53,7 +53,13 @@ app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: false })) // Validate some params for the API app.use(expressValidator({ - customValidators: Object.assign({}, customValidators.misc, customValidators.users, customValidators.videos) + customValidators: Object.assign( + {}, + customValidators.misc, + customValidators.pods, + customValidators.users, + customValidators.videos + ) })) // ----------- Views, routes and static files ----------- diff --git a/server/helpers/custom-validators/index.js b/server/helpers/custom-validators/index.js index ab3066822..96b5b20b9 100644 --- a/server/helpers/custom-validators/index.js +++ b/server/helpers/custom-validators/index.js @@ -1,11 +1,13 @@ 'use strict' const miscValidators = require('./misc') +const podsValidators = require('./pods') const usersValidators = require('./users') const videosValidators = require('./videos') const validators = { misc: miscValidators, + pods: podsValidators, users: usersValidators, videos: videosValidators } diff --git a/server/helpers/custom-validators/misc.js b/server/helpers/custom-validators/misc.js index 13904ea1b..782ae3dee 100644 --- a/server/helpers/custom-validators/misc.js +++ b/server/helpers/custom-validators/misc.js @@ -1,11 +1,8 @@ 'use strict' -const validator = require('express-validator').validator - const miscValidators = { exists: exists, - isArray: isArray, - isEachUrl: isEachUrl + isArray: isArray } function exists (value) { @@ -16,12 +13,6 @@ function isArray (value) { return Array.isArray(value) } -function isEachUrl (urls) { - return urls.every(function (url) { - return validator.isURL(url) - }) -} - // --------------------------------------------------------------------------- module.exports = miscValidators diff --git a/server/helpers/custom-validators/pods.js b/server/helpers/custom-validators/pods.js new file mode 100644 index 000000000..28d04a05d --- /dev/null +++ b/server/helpers/custom-validators/pods.js @@ -0,0 +1,21 @@ +'use strict' + +const validator = require('express-validator').validator + +const miscValidators = require('./misc') + +const podsValidators = { + isEachUniqueUrlValid: isEachUniqueUrlValid +} + +function isEachUniqueUrlValid (urls) { + return miscValidators.isArray(urls) && + urls.length !== 0 && + urls.every(function (url) { + return validator.isURL(url) && urls.indexOf(url) === urls.lastIndexOf(url) + }) +} + +// --------------------------------------------------------------------------- + +module.exports = podsValidators diff --git a/server/middlewares/validators/pods.js b/server/middlewares/validators/pods.js index 7c4d04aff..3c605c45e 100644 --- a/server/middlewares/validators/pods.js +++ b/server/middlewares/validators/pods.js @@ -10,23 +10,24 @@ const validatorsPod = { } function makeFriends (req, res, next) { - req.checkBody('urls', 'Should have an array of urls').isArray() - req.checkBody('urls', 'Should be an url').isEachUrl() + req.checkBody('urls', 'Should have an array of unique urls').isEachUniqueUrlValid() logger.debug('Checking makeFriends parameters', { parameters: req.body }) - friends.hasFriends(function (err, hasFriends) { - if (err) { - logger.error('Cannot know if we have friends.', { error: err }) - res.sendStatus(500) - } + checkErrors(req, res, function () { + friends.hasFriends(function (err, hasFriends) { + if (err) { + logger.error('Cannot know if we have friends.', { error: err }) + res.sendStatus(500) + } - if (hasFriends === true) { - // We need to quit our friends before make new ones - res.sendStatus(409) - } else { - return next() - } + if (hasFriends === true) { + // We need to quit our friends before make new ones + res.sendStatus(409) + } else { + return next() + } + }) }) } diff --git a/server/tests/api/check-params.js b/server/tests/api/check-params.js index ec666417c..4f7b26561 100644 --- a/server/tests/api/check-params.js +++ b/server/tests/api/check-params.js @@ -44,6 +44,106 @@ describe('Test parameters validator', function () { describe('Of the pods API', function () { const path = '/api/v1/pods/' + describe('When making friends', function () { + let userAccessToken = null + + before(function (done) { + usersUtils.createUser(server.url, server.accessToken, 'user1', 'password', function () { + server.user = { + username: 'user1', + password: 'password' + } + + loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { + if (err) throw err + + userAccessToken = accessToken + + done() + }) + }) + }) + + describe('When making friends', function () { + const body = { + urls: [ 'http://localhost:9002' ] + } + + it('Should fail without urls', function (done) { + request(server.url) + .post(path + '/makefriends') + .set('Authorization', 'Bearer ' + server.accessToken) + .set('Accept', 'application/json') + .expect(400, done) + }) + + it('Should fail with urls is not an array', function (done) { + request(server.url) + .post(path + '/makefriends') + .send({ urls: 'http://localhost:9002' }) + .set('Authorization', 'Bearer ' + server.accessToken) + .set('Accept', 'application/json') + .expect(400, done) + }) + + it('Should fail if the array is not composed by urls', function (done) { + request(server.url) + .post(path + '/makefriends') + .send({ urls: [ 'http://localhost:9002', 'localhost:coucou' ] }) + .set('Authorization', 'Bearer ' + server.accessToken) + .set('Accept', 'application/json') + .expect(400, done) + }) + + it('Should fail if urls are not unique', function (done) { + request(server.url) + .post(path + '/makefriends') + .send({ urls: [ 'http://localhost:9002', 'http://localhost:9002' ] }) + .set('Authorization', 'Bearer ' + server.accessToken) + .set('Accept', 'application/json') + .expect(400, done) + }) + + it('Should fail with a invalid token', function (done) { + request(server.url) + .post(path + '/makefriends') + .send(body) + .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) + .post(path + '/makefriends') + .send(body) + .set('Authorization', 'Bearer ' + userAccessToken) + .set('Accept', 'application/json') + .expect(403, done) + }) + }) + + describe('When quitting friends', function () { + it('Should fail with a invalid token', function (done) { + request(server.url) + .get(path + '/quitfriends') + .query({ start: 'hello' }) + .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) + .get(path + '/quitfriends') + .query({ start: 'hello' }) + .set('Authorization', 'Bearer ' + userAccessToken) + .set('Accept', 'application/json') + .expect(403, done) + }) + }) + }) + describe('When adding a pod', function () { it('Should fail with nothing', function (done) { const data = {} @@ -86,97 +186,6 @@ describe('Test parameters validator', function () { requestsUtils.makePostBodyRequest(server.url, path, null, data, done, 200) }) }) - - describe('For the friends API', function () { - let userAccessToken = null - - before(function (done) { - usersUtils.createUser(server.url, server.accessToken, 'user1', 'password', function () { - server.user = { - username: 'user1', - password: 'password' - } - - loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { - if (err) throw err - - userAccessToken = accessToken - - done() - }) - }) - }) - - describe('When making friends', function () { - const body = { - urls: [ 'http://localhost:9002' ] - } - - it('Should fail without urls', function (done) { - request(server.url) - .post(path + '/makefriends') - .set('Authorization', 'Bearer faketoken') - .set('Accept', 'application/json') - .expect(401, done) - }) - - it('Should fail with urls is not an array', function (done) { - request(server.url) - .post(path + '/makefriends') - .send({ urls: 'http://localhost:9002' }) - .set('Authorization', 'Bearer faketoken') - .set('Accept', 'application/json') - .expect(401, done) - }) - - it('Should fail if the array is not composed by urls', function (done) { - request(server.url) - .post(path + '/makefriends') - .send({ urls: [ 'http://localhost:9002', 'localhost:coucou' ] }) - .set('Authorization', 'Bearer faketoken') - .set('Accept', 'application/json') - .expect(401, done) - }) - - it('Should fail with a invalid token', function (done) { - request(server.url) - .post(path + '/makefriends') - .send(body) - .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) - .post(path + '/makefriends') - .send(body) - .set('Authorization', 'Bearer ' + userAccessToken) - .set('Accept', 'application/json') - .expect(403, done) - }) - }) - - describe('When quitting friends', function () { - it('Should fail with a invalid token', function (done) { - request(server.url) - .get(path + '/quitfriends') - .query({ start: 'hello' }) - .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) - .get(path + '/quitfriends') - .query({ start: 'hello' }) - .set('Authorization', 'Bearer ' + userAccessToken) - .set('Accept', 'application/json') - .expect(403, done) - }) - }) - }) }) describe('Of the videos API', function () { From e105c19c8ebe56b6828ba82948895ad0ca71d8c2 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sun, 21 Aug 2016 10:41:21 +0200 Subject: [PATCH 031/105] Client: support the new make friends method --- client/src/app/admin/admin.routes.ts | 5 + .../friend-add/friend-add.component.html | 18 ++++ .../friend-add/friend-add.component.scss | 3 + .../friend-add/friend-add.component.ts | 99 +++++++++++++++++++ .../src/app/admin/friends/friend-add/index.ts | 1 + .../friend-list/friend-list.component.html | 2 +- .../friend-list/friend-list.component.ts | 4 +- .../src/app/admin/friends/friends.routes.ts | 5 + client/src/app/admin/friends/index.ts | 3 +- .../admin/friends/shared/friend.service.ts | 8 +- .../src/app/admin/menu-admin.component.html | 4 +- client/src/app/menu.component.html | 2 +- client/tsconfig.json | 2 + 13 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 client/src/app/admin/friends/friend-add/friend-add.component.html create mode 100644 client/src/app/admin/friends/friend-add/friend-add.component.scss create mode 100644 client/src/app/admin/friends/friend-add/friend-add.component.ts create mode 100644 client/src/app/admin/friends/friend-add/index.ts diff --git a/client/src/app/admin/admin.routes.ts b/client/src/app/admin/admin.routes.ts index f57deef62..80b3ecbc1 100644 --- a/client/src/app/admin/admin.routes.ts +++ b/client/src/app/admin/admin.routes.ts @@ -9,6 +9,11 @@ export const AdminRoutes: RouterConfig = [ path: 'admin', component: AdminComponent, children: [ + { + path: '', + redirectTo: 'users', + pathMatch: 'full' + }, ...FriendsRoutes, ...UsersRoutes ] diff --git a/client/src/app/admin/friends/friend-add/friend-add.component.html b/client/src/app/admin/friends/friend-add/friend-add.component.html new file mode 100644 index 000000000..a52965e8f --- /dev/null +++ b/client/src/app/admin/friends/friend-add/friend-add.component.html @@ -0,0 +1,18 @@ +

Make friends

+ +
{{ error }}
+ +
+
+ +
+ + + + + +
+
+ + + diff --git a/client/src/app/admin/friends/friend-add/friend-add.component.scss b/client/src/app/admin/friends/friend-add/friend-add.component.scss new file mode 100644 index 000000000..cb597e12b --- /dev/null +++ b/client/src/app/admin/friends/friend-add/friend-add.component.scss @@ -0,0 +1,3 @@ +table { + margin-bottom: 40px; +} diff --git a/client/src/app/admin/friends/friend-add/friend-add.component.ts b/client/src/app/admin/friends/friend-add/friend-add.component.ts new file mode 100644 index 000000000..30dbf4d36 --- /dev/null +++ b/client/src/app/admin/friends/friend-add/friend-add.component.ts @@ -0,0 +1,99 @@ +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; + +import { FriendService } from '../shared'; + +@Component({ + selector: 'my-friend-add', + template: require('./friend-add.component.html'), + styles: [ require('./friend-add.component.scss') ] +}) +export class FriendAddComponent { + urls = [ '' ]; + error: string = null; + + constructor(private router: Router, private friendService: FriendService) {} + + addField() { + this.urls.push(''); + } + + customTrackBy(index: number, obj: any): any { + return index; + } + + displayAddField(index: number) { + return index === (this.urls.length - 1); + } + + displayRemoveField(index: number) { + return (index !== 0 || this.urls.length > 1) && index !== (this.urls.length - 1); + } + + removeField(index: number) { + this.urls.splice(index, 1); + } + + makeFriends() { + this.error = ''; + + const notEmptyUrls = this.getNotEmptyUrls(); + if (notEmptyUrls.length === 0) { + this.error = 'You need to specify at less 1 url.'; + return; + } + + if (!this.isUrlsRegexValid(notEmptyUrls)) { + this.error = 'Some url(s) are not valid.'; + return; + } + + if (!this.isUrlsUnique(notEmptyUrls)) { + this.error = 'Urls need to be unique.'; + return; + } + + const confirmMessage = 'Are you sure to make friends with:\n - ' + this.urls.join('\n - '); + if (!confirm(confirmMessage)) return; + + this.friendService.makeFriends(notEmptyUrls).subscribe( + status => { + if (status === 409) { + alert('Already made friends!'); + } else { + alert('Made friends!'); + } + }, + error => alert(error) + ); + } + + private getNotEmptyUrls() { + const notEmptyUrls = []; + + this.urls.forEach((url) => { + if (url !== '') notEmptyUrls.push(url); + }); + + return notEmptyUrls; + } + + // Temporary + // Use HTML validators + private isUrlsRegexValid(urls: string[]) { + let res = true; + + const urlRegex = new RegExp('^https?://(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$'); + urls.forEach((url) => { + if (urlRegex.test(url) === false) { + res = false; + } + }); + + return res; + } + + private isUrlsUnique(urls: string[]) { + return urls.every(url => urls.indexOf(url) === urls.lastIndexOf(url)); + } +} diff --git a/client/src/app/admin/friends/friend-add/index.ts b/client/src/app/admin/friends/friend-add/index.ts new file mode 100644 index 000000000..a101b3be5 --- /dev/null +++ b/client/src/app/admin/friends/friend-add/index.ts @@ -0,0 +1 @@ +export * from './friend-add.component'; 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 f4d14293e..4be3d364f 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 @@ -18,6 +18,6 @@ Quit friends - + Make 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 bf66d3ff1..aa92c1b1e 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 @@ -1,11 +1,13 @@ import { Component, OnInit } from '@angular/core'; +import { ROUTER_DIRECTIVES } from '@angular/router'; import { Friend, FriendService } from '../shared'; @Component({ selector: 'my-friend-list', template: require('./friend-list.component.html'), - styles: [ require('./friend-list.component.scss') ] + styles: [ require('./friend-list.component.scss') ], + directives: [ ROUTER_DIRECTIVES ] }) export class FriendListComponent implements OnInit { friends: Friend[]; diff --git a/client/src/app/admin/friends/friends.routes.ts b/client/src/app/admin/friends/friends.routes.ts index 1e3646395..42b4a6c14 100644 --- a/client/src/app/admin/friends/friends.routes.ts +++ b/client/src/app/admin/friends/friends.routes.ts @@ -1,6 +1,7 @@ import { RouterConfig } from '@angular/router'; import { FriendsComponent } from './friends.component'; +import { FriendAddComponent } from './friend-add'; import { FriendListComponent } from './friend-list'; export const FriendsRoutes: RouterConfig = [ @@ -16,6 +17,10 @@ export const FriendsRoutes: RouterConfig = [ { path: 'list', component: FriendListComponent + }, + { + path: 'add', + component: FriendAddComponent } ] } diff --git a/client/src/app/admin/friends/index.ts b/client/src/app/admin/friends/index.ts index 01aeedeee..f3110e31d 100644 --- a/client/src/app/admin/friends/index.ts +++ b/client/src/app/admin/friends/index.ts @@ -1,3 +1,4 @@ -export * from './shared'; +export * from './friend-add'; export * from './friend-list'; +export * from './shared'; export * from './friends.routes'; diff --git a/client/src/app/admin/friends/shared/friend.service.ts b/client/src/app/admin/friends/shared/friend.service.ts index da4d64611..e4e680c29 100644 --- a/client/src/app/admin/friends/shared/friend.service.ts +++ b/client/src/app/admin/friends/shared/friend.service.ts @@ -20,8 +20,12 @@ export class FriendService { .catch(this.handleError); } - makeFriends() { - return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'makefriends') + makeFriends(notEmptyUrls) { + const body = { + urls: notEmptyUrls + }; + + return this.authHttp.post(FriendService.BASE_FRIEND_URL + 'makefriends', body) .map(res => res.status) .catch(this.handleError); } diff --git a/client/src/app/admin/menu-admin.component.html b/client/src/app/admin/menu-admin.component.html index 092ab6081..26a3f3492 100644 --- a/client/src/app/admin/menu-admin.component.html +++ b/client/src/app/admin/menu-admin.component.html @@ -8,14 +8,14 @@ diff --git a/client/src/app/menu.component.html b/client/src/app/menu.component.html index 922375395..8ea99138d 100644 --- a/client/src/app/menu.component.html +++ b/client/src/app/menu.component.html @@ -33,7 +33,7 @@ diff --git a/client/tsconfig.json b/client/tsconfig.json index 20938ce55..87a06b0c6 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -33,6 +33,8 @@ "src/app/account/index.ts", "src/app/admin/admin.component.ts", "src/app/admin/admin.routes.ts", + "src/app/admin/friends/friend-add/friend-add.component.ts", + "src/app/admin/friends/friend-add/index.ts", "src/app/admin/friends/friend-list/friend-list.component.ts", "src/app/admin/friends/friend-list/index.ts", "src/app/admin/friends/friends.component.ts", From 9aa46b0c7bade696a477626ad7590ffdd281e03c Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sun, 21 Aug 2016 11:21:45 +0200 Subject: [PATCH 032/105] Client: navigate to /videos/list when do search on another page --- client/src/app/shared/search/search.component.ts | 7 ++++++- client/src/app/shared/search/search.service.ts | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/client/src/app/shared/search/search.component.ts b/client/src/app/shared/search/search.component.ts index 219997e85..853f5dc7c 100644 --- a/client/src/app/shared/search/search.component.ts +++ b/client/src/app/shared/search/search.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; import { DROPDOWN_DIRECTIVES} from 'ng2-bootstrap/components/dropdown'; @@ -25,7 +26,7 @@ export class SearchComponent implements OnInit { value: '' }; - constructor(private searchService: SearchService) {} + constructor(private searchService: SearchService, private router: Router) {} ngOnInit() { // Subscribe if the search changed @@ -58,6 +59,10 @@ export class SearchComponent implements OnInit { } doSearch() { + if (this.router.url.indexOf('/videos/list') === -1) { + this.router.navigate([ '/videos/list' ]); + } + this.searchService.searchUpdated.next(this.searchCriterias); } diff --git a/client/src/app/shared/search/search.service.ts b/client/src/app/shared/search/search.service.ts index c7993db3d..717a7fa50 100644 --- a/client/src/app/shared/search/search.service.ts +++ b/client/src/app/shared/search/search.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { Subject } from 'rxjs/Subject'; +import { ReplaySubject } from 'rxjs/ReplaySubject'; import { Search } from './search.model'; @@ -12,6 +13,6 @@ export class SearchService { constructor() { this.updateSearch = new Subject(); - this.searchUpdated = new Subject(); + this.searchUpdated = new ReplaySubject(1); } } From beacf6993c93f93bf5ea86665827154eb291d1fd Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sun, 21 Aug 2016 11:27:24 +0200 Subject: [PATCH 033/105] Client: simplify simple menu/admin menu displaying logic --- client/src/app/admin/menu-admin.component.html | 2 +- client/src/app/admin/menu-admin.component.ts | 10 ++-------- client/src/app/app.component.html | 7 ++----- client/src/app/app.component.ts | 12 ++++-------- client/src/app/menu.component.html | 2 +- client/src/app/menu.component.ts | 7 +------ 6 files changed, 11 insertions(+), 29 deletions(-) diff --git a/client/src/app/admin/menu-admin.component.html b/client/src/app/admin/menu-admin.component.html index 26a3f3492..f821974bd 100644 --- a/client/src/app/admin/menu-admin.component.html +++ b/client/src/app/admin/menu-admin.component.html @@ -15,7 +15,7 @@ diff --git a/client/src/app/admin/menu-admin.component.ts b/client/src/app/admin/menu-admin.component.ts index b23f7409e..788592872 100644 --- a/client/src/app/admin/menu-admin.component.ts +++ b/client/src/app/admin/menu-admin.component.ts @@ -1,4 +1,4 @@ -import { Component, Output, EventEmitter } from '@angular/core'; +import { Component } from '@angular/core'; import { ROUTER_DIRECTIVES } from '@angular/router'; @Component({ @@ -6,10 +6,4 @@ import { ROUTER_DIRECTIVES } from '@angular/router'; template: require('./menu-admin.component.html'), directives: [ ROUTER_DIRECTIVES ] }) -export class MenuAdminComponent { - @Output() quittedAdmin = new EventEmitter(); - - quitAdmin() { - this.quittedAdmin.emit(true); - } -} +export class MenuAdminComponent { } diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index a7538ee7a..ead491968 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html @@ -14,14 +14,11 @@
- - + +
-
-
-
PeerTube, CopyLeft 2015-2016 diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index d9549ad5b..2e0fd13f1 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { ROUTER_DIRECTIVES } from '@angular/router'; +import { Router, ROUTER_DIRECTIVES } from '@angular/router'; import { MenuAdminComponent } from './admin'; import { MenuComponent } from './menu.component'; @@ -15,13 +15,9 @@ import { VideoService } from './videos'; }) export class AppComponent { - isInAdmin = false; + constructor(private router: Router) {} - onEnteredInAdmin() { - this.isInAdmin = true; - } - - onQuittedAdmin() { - this.isInAdmin = false; + isInAdmin() { + return this.router.url.indexOf('/admin/') !== -1; } } diff --git a/client/src/app/menu.component.html b/client/src/app/menu.component.html index 8ea99138d..29ef7f9cf 100644 --- a/client/src/app/menu.component.html +++ b/client/src/app/menu.component.html @@ -33,7 +33,7 @@ diff --git a/client/src/app/menu.component.ts b/client/src/app/menu.component.ts index 594cd996e..6b08301df 100644 --- a/client/src/app/menu.component.ts +++ b/client/src/app/menu.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, OnInit, Output } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { Router, ROUTER_DIRECTIVES } from '@angular/router'; import { AuthService, AuthStatus } from './shared'; @@ -9,7 +9,6 @@ import { AuthService, AuthStatus } from './shared'; directives: [ ROUTER_DIRECTIVES ] }) export class MenuComponent implements OnInit { - @Output() enteredInAdmin = new EventEmitter(); isLoggedIn: boolean; constructor ( @@ -35,10 +34,6 @@ export class MenuComponent implements OnInit { ); } - enterInAdmin() { - this.enteredInAdmin.emit(true); - } - isUserAdmin() { return this.authService.isAdmin(); } From 96b0c2bf708a20c7e0fe1995223b0fdefbdd9f41 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sun, 21 Aug 2016 11:36:43 +0200 Subject: [PATCH 034/105] Client: remove makeFriend from friend-list (in friend-add now) --- .../friends/friend-list/friend-list.component.ts | 13 ------------- 1 file changed, 13 deletions(-) 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 aa92c1b1e..379d44c97 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 @@ -22,19 +22,6 @@ export class FriendListComponent implements OnInit { ); } - makeFriends() { - this.friendService.makeFriends().subscribe( - status => { - if (status === 409) { - alert('Already made friends!'); - } else { - alert('Made friends!'); - } - }, - error => alert(error) - ); - } - quitFriends() { if (!confirm('Are you sure?')) return; From 0c8e4ebee9fd3d347177b7766eb30344f28ac1d5 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sun, 21 Aug 2016 12:06:01 +0200 Subject: [PATCH 035/105] Update README roadmap --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a3470711..e5c113bc8 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ Thanks to [WebTorrent](https://github.com/feross/webtorrent), we can make P2P (t - [ ] Admin panel - [ ] Stats about the network (how many friends, how many requests per hour...) - [ ] Stats about videos - - [ ] Manage users (create/remove) + - [X] Manage users (create/remove) ## Installation From 50e708d5a07299dad3550f2f283c5131d0cec0f3 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 23 Aug 2016 11:45:18 +0200 Subject: [PATCH 036/105] Update README roadmap --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e5c113bc8..23189d667 100644 --- a/README.md +++ b/README.md @@ -96,8 +96,8 @@ Thanks to [WebTorrent](https://github.com/feross/webtorrent), we can make P2P (t - [ ] Manage API breaks - [ ] Add "DDOS" security (check if a pod don't send too many requests for example) - [ ] Admin panel - - [ ] Stats about the network (how many friends, how many requests per hour...) - [ ] Stats about videos + - [X] Friends list - [X] Manage users (create/remove) From 52672600223d840ab123cd48c4c4d0457ac2e1a1 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 23 Aug 2016 11:45:28 +0200 Subject: [PATCH 037/105] Client: fix malformed div --- client/src/app/app.component.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index ead491968..04c32f596 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html @@ -19,6 +19,8 @@
+
+
PeerTube, CopyLeft 2015-2016 From 39f87cb21689a912559d0498641db7d2de4a784d Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 23 Aug 2016 14:37:36 +0200 Subject: [PATCH 038/105] Server: catch JSON.parse exceptions --- server/lib/friends.js | 7 ++++++- server/middlewares/secure.js | 9 +++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/server/lib/friends.js b/server/lib/friends.js index 667055d4c..6c4383d8e 100644 --- a/server/lib/friends.js +++ b/server/lib/friends.js @@ -198,7 +198,12 @@ function getForeignPodsList (url, callback) { request.get(url + path, function (err, response, body) { if (err) return callback(err) - callback(null, JSON.parse(body)) + try { + const json = JSON.parse(body) + return callback(null, json) + } catch (err) { + return callback(err) + } }) } diff --git a/server/middlewares/secure.js b/server/middlewares/secure.js index 9779c14ac..fa000c6f0 100644 --- a/server/middlewares/secure.js +++ b/server/middlewares/secure.js @@ -34,8 +34,13 @@ function decryptBody (req, res, next) { return res.sendStatus(500) } - req.body.data = JSON.parse(decrypted) - delete req.body.key + try { + req.body.data = JSON.parse(decrypted) + delete req.body.key + } catch (err) { + logger.error('Error in JSON.parse', { error: err }) + return res.sendStatus(500) + } next() }) From 0f6da32b148c0f4146b2ae9ad1add9a9f00cc339 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 23 Aug 2016 14:37:49 +0200 Subject: [PATCH 039/105] Client: update to new form api --- client/package.json | 3 ++- client/src/app/account/account.component.html | 8 +++---- client/src/app/account/account.component.ts | 22 ++++++++++-------- .../friend-add/friend-add.component.html | 4 ++-- .../friend-add/friend-add.component.ts | 2 +- .../users/user-add/user-add.component.html | 16 ++++++------- .../users/user-add/user-add.component.ts | 18 +++++++++------ client/src/app/login/login.component.html | 17 +++++++------- client/src/app/login/login.component.ts | 23 +++++++++++++++---- .../videos/video-add/video-add.component.html | 14 +++++------ .../videos/video-add/video-add.component.ts | 15 ++++++------ client/src/main.ts | 8 ++++++- client/src/vendor.ts | 1 + 13 files changed, 90 insertions(+), 61 deletions(-) diff --git a/client/package.json b/client/package.json index 46cbb9441..6fc48d11e 100644 --- a/client/package.json +++ b/client/package.json @@ -22,6 +22,7 @@ "@angular/common": "2.0.0-rc.4", "@angular/compiler": "2.0.0-rc.4", "@angular/core": "2.0.0-rc.4", + "@angular/forms": "^0.2.0", "@angular/http": "2.0.0-rc.4", "@angular/platform-browser": "2.0.0-rc.4", "@angular/platform-browser-dynamic": "2.0.0-rc.4", @@ -42,7 +43,7 @@ "ie-shim": "^0.1.0", "intl": "^1.2.4", "json-loader": "^0.5.4", - "ng2-bootstrap": "1.0.16", + "ng2-bootstrap": "1.0.24", "ng2-file-upload": "^1.0.3", "node-sass": "^3.7.0", "normalize.css": "^4.1.1", diff --git a/client/src/app/account/account.component.html b/client/src/app/account/account.component.html index ad8f690bd..4797fa914 100644 --- a/client/src/app/account/account.component.html +++ b/client/src/app/account/account.component.html @@ -3,14 +3,14 @@
{{ information }}
{{ error }}
-
+
-
+
The password should have more than 5 characters
@@ -19,7 +19,7 @@
diff --git a/client/src/app/account/account.component.ts b/client/src/app/account/account.component.ts index 5c42103f8..54939f43b 100644 --- a/client/src/app/account/account.component.ts +++ b/client/src/app/account/account.component.ts @@ -1,5 +1,6 @@ -import { Control, ControlGroup, Validators } from '@angular/common'; +import { Validators } from '@angular/common'; import { Component, OnInit } from '@angular/core'; +import { FormControl, FormGroup, REACTIVE_FORM_DIRECTIVES } from '@angular/forms'; import { Router } from '@angular/router'; import { AccountService } from './account.service'; @@ -7,11 +8,14 @@ import { AccountService } from './account.service'; @Component({ selector: 'my-account', template: require('./account.component.html'), - providers: [ AccountService ] + providers: [ AccountService ], + directives: [ REACTIVE_FORM_DIRECTIVES ] }) export class AccountComponent implements OnInit { - changePasswordForm: ControlGroup; + newPassword = ''; + newConfirmedPassword = ''; + changePasswordForm: FormGroup; information: string = null; error: string = null; @@ -21,22 +25,22 @@ export class AccountComponent implements OnInit { ) {} ngOnInit() { - this.changePasswordForm = new ControlGroup({ - newPassword: new Control('', Validators.compose([ Validators.required, Validators.minLength(6) ])), - newConfirmedPassword: new Control('', Validators.compose([ Validators.required, Validators.minLength(6) ])), + this.changePasswordForm = new FormGroup({ + 'new-password': new FormControl('', [ Validators.required, Validators.minLength(6) ]), + 'new-confirmed-password': new FormControl('', [ Validators.required, Validators.minLength(6) ]), }); } - changePassword(newPassword: string, newConfirmedPassword: string) { + changePassword() { this.information = null; this.error = null; - if (newPassword !== newConfirmedPassword) { + if (this.newPassword !== this.newConfirmedPassword) { this.error = 'The new password and the confirmed password do not correspond.'; return; } - this.accountService.changePassword(newPassword).subscribe( + this.accountService.changePassword(this.newPassword).subscribe( ok => this.information = 'Password updated.', err => this.error = err diff --git a/client/src/app/admin/friends/friend-add/friend-add.component.html b/client/src/app/admin/friends/friend-add/friend-add.component.html index a52965e8f..d8bb740b4 100644 --- a/client/src/app/admin/friends/friend-add/friend-add.component.html +++ b/client/src/app/admin/friends/friend-add/friend-add.component.html @@ -2,11 +2,11 @@
{{ error }}
- +
- + diff --git a/client/src/app/admin/friends/friend-add/friend-add.component.ts b/client/src/app/admin/friends/friend-add/friend-add.component.ts index 30dbf4d36..07888a781 100644 --- a/client/src/app/admin/friends/friend-add/friend-add.component.ts +++ b/client/src/app/admin/friends/friend-add/friend-add.component.ts @@ -53,7 +53,7 @@ export class FriendAddComponent { return; } - const confirmMessage = 'Are you sure to make friends with:\n - ' + this.urls.join('\n - '); + const confirmMessage = 'Are you sure to make friends with:\n - ' + notEmptyUrls.join('\n - '); if (!confirm(confirmMessage)) return; this.friendService.makeFriends(notEmptyUrls).subscribe( diff --git a/client/src/app/admin/users/user-add/user-add.component.html b/client/src/app/admin/users/user-add/user-add.component.html index aa102358a..09219893b 100644 --- a/client/src/app/admin/users/user-add/user-add.component.html +++ b/client/src/app/admin/users/user-add/user-add.component.html @@ -2,14 +2,14 @@
{{ error }}
- +
-
+
Username is required with a length >= 3 and <= 20
@@ -17,13 +17,13 @@
-
+
Password is required with a length >= 6
- + diff --git a/client/src/app/admin/users/user-add/user-add.component.ts b/client/src/app/admin/users/user-add/user-add.component.ts index 30ca947a0..b7efd3a80 100644 --- a/client/src/app/admin/users/user-add/user-add.component.ts +++ b/client/src/app/admin/users/user-add/user-add.component.ts @@ -1,5 +1,6 @@ -import { Control, ControlGroup, Validators } from '@angular/common'; +import { Validators } from '@angular/common'; import { Component, OnInit } from '@angular/core'; +import { FormGroup, FormControl, REACTIVE_FORM_DIRECTIVES } from '@angular/forms'; import { Router } from '@angular/router'; import { UserService } from '../shared'; @@ -7,24 +8,27 @@ import { UserService } from '../shared'; @Component({ selector: 'my-user-add', template: require('./user-add.component.html'), + directives: [ REACTIVE_FORM_DIRECTIVES ] }) export class UserAddComponent implements OnInit { - userAddForm: ControlGroup; + userAddForm: FormGroup; error: string = null; + username = ''; + password = ''; constructor(private router: Router, private userService: UserService) {} ngOnInit() { - this.userAddForm = new ControlGroup({ - username: new Control('', Validators.compose([ Validators.required, Validators.minLength(3), Validators.maxLength(20) ])), - password: new Control('', Validators.compose([ Validators.required, Validators.minLength(6) ])), + this.userAddForm = new FormGroup({ + username: new FormControl('', [ Validators.required, Validators.minLength(3), Validators.maxLength(20) ]), + password: new FormControl('', [ Validators.required, Validators.minLength(6) ]), }); } - addUser(username: string, password: string) { + addUser() { this.error = null; - this.userService.addUser(username, password).subscribe( + this.userService.addUser(this.username, this.password).subscribe( ok => this.router.navigate([ '/admin/users/list' ]), err => this.error = err diff --git a/client/src/app/login/login.component.html b/client/src/app/login/login.component.html index 5848fcba3..636872942 100644 --- a/client/src/app/login/login.component.html +++ b/client/src/app/login/login.component.html @@ -1,16 +1,15 @@

Login

-
{{ error }}
-
+
-
+
Username is required
@@ -18,13 +17,13 @@
-
+
Password is required
- + diff --git a/client/src/app/login/login.component.ts b/client/src/app/login/login.component.ts index ddd62462e..fe867b7b4 100644 --- a/client/src/app/login/login.component.ts +++ b/client/src/app/login/login.component.ts @@ -1,23 +1,36 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { Validators } from '@angular/common'; +import { FormControl, FormGroup, REACTIVE_FORM_DIRECTIVES } from '@angular/forms'; import { Router } from '@angular/router'; import { AuthService } from '../shared'; @Component({ selector: 'my-login', - template: require('./login.component.html') + template: require('./login.component.html'), + directives: [ REACTIVE_FORM_DIRECTIVES ] }) -export class LoginComponent { +export class LoginComponent implements OnInit { error: string = null; + username = ''; + password: ''; + loginForm: FormGroup; constructor( private authService: AuthService, private router: Router ) {} - login(username: string, password: string) { - this.authService.login(username, password).subscribe( + ngOnInit() { + this.loginForm = new FormGroup({ + username: new FormControl('', [ Validators.required ]), + password: new FormControl('', [ Validators.required ]), + }); + } + + login() { + this.authService.login(this.username, this.password).subscribe( result => { this.error = null; diff --git a/client/src/app/videos/video-add/video-add.component.html b/client/src/app/videos/video-add/video-add.component.html index bcd78c7cb..76bb61f7d 100644 --- a/client/src/app/videos/video-add/video-add.component.html +++ b/client/src/app/videos/video-add/video-add.component.html @@ -2,14 +2,14 @@
{{ error }}
-
+
-
+
A name is required and should be between 3 and 50 characters long
@@ -18,9 +18,9 @@ -
+
A tag should be between 2 and 10 characters (alphanumeric) long
@@ -54,10 +54,10 @@ -
+
A description is required and should be between 3 and 250 characters long
diff --git a/client/src/app/videos/video-add/video-add.component.ts b/client/src/app/videos/video-add/video-add.component.ts index c0f8cb9c4..900ef1da3 100644 --- a/client/src/app/videos/video-add/video-add.component.ts +++ b/client/src/app/videos/video-add/video-add.component.ts @@ -1,5 +1,6 @@ -import { Control, ControlGroup, Validators } from '@angular/common'; +import { Validators } from '@angular/common'; import { Component, ElementRef, OnInit } from '@angular/core'; +import { FormControl, FormGroup, REACTIVE_FORM_DIRECTIVES } from '@angular/forms'; import { Router } from '@angular/router'; import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'; @@ -12,14 +13,14 @@ import { AuthService } from '../../shared'; selector: 'my-videos-add', styles: [ require('./video-add.component.scss') ], template: require('./video-add.component.html'), - directives: [ FileSelectDirective, PROGRESSBAR_DIRECTIVES ], + directives: [ FileSelectDirective, PROGRESSBAR_DIRECTIVES, REACTIVE_FORM_DIRECTIVES ], pipes: [ BytesPipe ] }) export class VideoAddComponent implements OnInit { currentTag: string; // Tag the user is writing in the input error: string = null; - videoForm: ControlGroup; + videoForm: FormGroup; uploader: FileUploader; video = { name: '', @@ -70,10 +71,10 @@ export class VideoAddComponent implements OnInit { } ngOnInit() { - this.videoForm = new ControlGroup({ - name: new Control('', Validators.compose([ Validators.required, Validators.minLength(3), Validators.maxLength(50) ])), - description: new Control('', Validators.compose([ Validators.required, Validators.minLength(3), Validators.maxLength(250) ])), - tags: new Control('', Validators.pattern('^[a-zA-Z0-9]{2,10}$')) + this.videoForm = new FormGroup({ + name: new FormControl('', [ Validators.required, Validators.minLength(3), Validators.maxLength(50) ]), + description: new FormControl('', [ Validators.required, Validators.minLength(3), Validators.maxLength(250) ]), + tags: new FormControl('', Validators.pattern('^[a-zA-Z0-9]{2,10}$')) }); diff --git a/client/src/main.ts b/client/src/main.ts index 41fc6e0c2..7c058e12f 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -1,4 +1,5 @@ import { enableProdMode, provide } from '@angular/core'; +import { disableDeprecatedForms, provideForms } from '@angular/forms'; import { HTTP_PROVIDERS, RequestOptions, @@ -23,6 +24,11 @@ bootstrap(AppComponent, [ }, deps: [ XHRBackend, RequestOptions, AuthService ] }), + AuthService, - provideRouter(routes) + + provideRouter(routes), + + disableDeprecatedForms(), + provideForms() ]); diff --git a/client/src/vendor.ts b/client/src/vendor.ts index 8f029191a..df03bc5f4 100644 --- a/client/src/vendor.ts +++ b/client/src/vendor.ts @@ -8,6 +8,7 @@ import '@angular/platform-browser'; import '@angular/platform-browser-dynamic'; import '@angular/core'; import '@angular/common'; +import '@angular/forms'; import '@angular/http'; import '@angular/router'; From 6be622478a36a20ec36605c772b20009d9d270ac Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 23 Aug 2016 14:43:20 +0200 Subject: [PATCH 040/105] Client: display make/quit friends according to the situation --- .../app/admin/friends/friend-list/friend-list.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 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 4be3d364f..1f3789265 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 @@ -14,10 +14,10 @@
- + Quit friends - + Make friends From 9ab1071c8d90d112f1031cf006ed621011553e84 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 23 Aug 2016 14:48:59 +0200 Subject: [PATCH 041/105] Do not wait the make friends process ends to send a response to the request --- .../app/admin/friends/friend-add/friend-add.component.ts | 3 ++- server/controllers/api/v1/pods.js | 9 +++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/client/src/app/admin/friends/friend-add/friend-add.component.ts b/client/src/app/admin/friends/friend-add/friend-add.component.ts index 07888a781..ffc499b92 100644 --- a/client/src/app/admin/friends/friend-add/friend-add.component.ts +++ b/client/src/app/admin/friends/friend-add/friend-add.component.ts @@ -61,7 +61,8 @@ export class FriendAddComponent { if (status === 409) { alert('Already made friends!'); } else { - alert('Made friends!'); + alert('Make friends request sent!'); + this.router.navigate([ '/admin/friends/list' ]); } }, error => alert(error) diff --git a/server/controllers/api/v1/pods.js b/server/controllers/api/v1/pods.js index 982a1e364..360575a0d 100644 --- a/server/controllers/api/v1/pods.js +++ b/server/controllers/api/v1/pods.js @@ -86,10 +86,15 @@ function makeFriends (req, res, next) { const urls = req.body.urls friends.makeFriends(urls, function (err) { - if (err) return next(err) + if (err) { + logger.error('Could not make friends.', { error: err }) + return + } - res.type('json').status(204).end() + logger.info('Made friends!') }) + + res.type('json').status(204).end() } function removePods (req, res, next) { From b5d6b94c1e192cc7b6ace0e73fc8edee2dc510ef Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 23 Aug 2016 15:28:03 +0200 Subject: [PATCH 042/105] Client: make friends url button (+/-) -> same width --- .../app/admin/friends/friend-add/friend-add.component.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/src/app/admin/friends/friend-add/friend-add.component.scss b/client/src/app/admin/friends/friend-add/friend-add.component.scss index cb597e12b..5fde51636 100644 --- a/client/src/app/admin/friends/friend-add/friend-add.component.scss +++ b/client/src/app/admin/friends/friend-add/friend-add.component.scss @@ -1,3 +1,7 @@ table { margin-bottom: 40px; } + +.input-group-btn button { + width: 35px; +} From 9e8aa10d94e4642ec1d20a522c41b06e9df7758b Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 23 Aug 2016 15:49:16 +0200 Subject: [PATCH 043/105] Client: change url validation for friend add --- .../friend-add/friend-add.component.html | 16 ++++-- .../friend-add/friend-add.component.ts | 53 ++++++++++--------- .../src/app/shared/form-validators/index.ts | 1 + .../shared/form-validators/url.validator.ts | 11 ++++ client/src/app/shared/index.ts | 1 + client/tsconfig.json | 2 + 6 files changed, 56 insertions(+), 28 deletions(-) create mode 100644 client/src/app/shared/form-validators/index.ts create mode 100644 client/src/app/shared/form-validators/url.validator.ts diff --git a/client/src/app/admin/friends/friend-add/friend-add.component.html b/client/src/app/admin/friends/friend-add/friend-add.component.html index d8bb740b4..5b8dc8d87 100644 --- a/client/src/app/admin/friends/friend-add/friend-add.component.html +++ b/client/src/app/admin/friends/friend-add/friend-add.component.html @@ -2,17 +2,25 @@
{{ error }}
- +
+
- + - +
+ +
+ It should be a valid url. +
- + diff --git a/client/src/app/admin/friends/friend-add/friend-add.component.ts b/client/src/app/admin/friends/friend-add/friend-add.component.ts index ffc499b92..16cfd8a3a 100644 --- a/client/src/app/admin/friends/friend-add/friend-add.component.ts +++ b/client/src/app/admin/friends/friend-add/friend-add.component.ts @@ -1,20 +1,30 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { FormControl, FormGroup, REACTIVE_FORM_DIRECTIVES } from '@angular/forms'; import { Router } from '@angular/router'; +import { validateUrl } from '../../../shared'; import { FriendService } from '../shared'; @Component({ selector: 'my-friend-add', template: require('./friend-add.component.html'), - styles: [ require('./friend-add.component.scss') ] + styles: [ require('./friend-add.component.scss') ], + directives: [ REACTIVE_FORM_DIRECTIVES ] }) -export class FriendAddComponent { - urls = [ '' ]; +export class FriendAddComponent implements OnInit { + friendAddForm: FormGroup; + urls = [ ]; error: string = null; constructor(private router: Router, private friendService: FriendService) {} + ngOnInit() { + this.friendAddForm = new FormGroup({}); + this.addField(); + } + addField() { + this.friendAddForm.addControl(`url-${this.urls.length}`, new FormControl('', [ validateUrl ])); this.urls.push(''); } @@ -30,6 +40,21 @@ export class FriendAddComponent { return (index !== 0 || this.urls.length > 1) && index !== (this.urls.length - 1); } + isFormValid() { + // Do not check the last input + for (let i = 0; i < this.urls.length - 1; i++) { + if (!this.friendAddForm.controls[`url-${i}`].valid) return false; + } + + const lastIndex = this.urls.length - 1; + // If the last input (which is not the first) is empty, it's ok + if (this.urls[lastIndex] === '' && lastIndex !== 0) { + return true; + } else { + return this.friendAddForm.controls[`url-${lastIndex}`].valid; + } + } + removeField(index: number) { this.urls.splice(index, 1); } @@ -43,11 +68,6 @@ export class FriendAddComponent { return; } - if (!this.isUrlsRegexValid(notEmptyUrls)) { - this.error = 'Some url(s) are not valid.'; - return; - } - if (!this.isUrlsUnique(notEmptyUrls)) { this.error = 'Urls need to be unique.'; return; @@ -79,21 +99,6 @@ export class FriendAddComponent { return notEmptyUrls; } - // Temporary - // Use HTML validators - private isUrlsRegexValid(urls: string[]) { - let res = true; - - const urlRegex = new RegExp('^https?://(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$'); - urls.forEach((url) => { - if (urlRegex.test(url) === false) { - res = false; - } - }); - - return res; - } - private isUrlsUnique(urls: string[]) { return urls.every(url => urls.indexOf(url) === urls.lastIndexOf(url)); } diff --git a/client/src/app/shared/form-validators/index.ts b/client/src/app/shared/form-validators/index.ts new file mode 100644 index 000000000..f9e9a6191 --- /dev/null +++ b/client/src/app/shared/form-validators/index.ts @@ -0,0 +1 @@ +export * from './url.validator'; diff --git a/client/src/app/shared/form-validators/url.validator.ts b/client/src/app/shared/form-validators/url.validator.ts new file mode 100644 index 000000000..67163b4e9 --- /dev/null +++ b/client/src/app/shared/form-validators/url.validator.ts @@ -0,0 +1,11 @@ +import { FormControl } from '@angular/forms'; + +export function validateUrl(c: FormControl) { + let URL_REGEXP = new RegExp('^https?://(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$'); + + return URL_REGEXP.test(c.value) ? null : { + validateUrl: { + valid: false + } + }; +} diff --git a/client/src/app/shared/index.ts b/client/src/app/shared/index.ts index c05e8d253..9edf9b4a0 100644 --- a/client/src/app/shared/index.ts +++ b/client/src/app/shared/index.ts @@ -1,3 +1,4 @@ export * from './auth'; +export * from './form-validators'; export * from './search'; export * from './users'; diff --git a/client/tsconfig.json b/client/tsconfig.json index 87a06b0c6..53e6fd571 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -65,6 +65,8 @@ "src/app/shared/auth/auth-user.model.ts", "src/app/shared/auth/auth.service.ts", "src/app/shared/auth/index.ts", + "src/app/shared/form-validators/index.ts", + "src/app/shared/form-validators/url.validator.ts", "src/app/shared/index.ts", "src/app/shared/search/index.ts", "src/app/shared/search/search-field.type.ts", From def16d33d19153c6583fa8a30634760b3d64d34c Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 23 Aug 2016 16:24:11 +0200 Subject: [PATCH 044/105] Server: avoid request entity too large for requests between pods --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index 676597fae..8010d3e06 100644 --- a/server.js +++ b/server.js @@ -49,7 +49,7 @@ const port = config.get('listen.port') // For the logger app.use(morgan('combined', { stream: logger.stream })) // For body requests -app.use(bodyParser.json()) +app.use(bodyParser.json({ limit: '500kb' })) app.use(bodyParser.urlencoded({ extended: false })) // Validate some params for the API app.use(expressValidator({ From de59c48f5f317018e3f746bbe4a7b7efe00109f2 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 23 Aug 2016 16:54:21 +0200 Subject: [PATCH 045/105] Client: centralize http res extraction in a service --- client/src/app/account/account.service.ts | 12 +++- .../admin/friends/shared/friend.service.ts | 22 +++---- .../app/admin/users/shared/user.service.ts | 28 ++++----- client/src/app/app.component.ts | 4 +- client/src/app/login/login.component.ts | 6 +- client/src/app/shared/auth/auth.service.ts | 22 +++---- client/src/app/shared/index.ts | 1 + client/src/app/shared/rest/index.ts | 3 + .../app/shared/rest/rest-extractor.service.ts | 46 ++++++++++++++ .../rest/rest-pagination.ts} | 4 +- client/src/app/shared/rest/rest.service.ts | 27 ++++++++ client/src/app/videos/shared/index.ts | 1 - client/src/app/videos/shared/video.service.ts | 62 +++++++------------ .../videos/video-list/video-list.component.ts | 5 +- client/src/main.ts | 3 +- client/tsconfig.json | 10 +++ 16 files changed, 160 insertions(+), 96 deletions(-) create mode 100644 client/src/app/shared/rest/index.ts create mode 100644 client/src/app/shared/rest/rest-extractor.service.ts rename client/src/app/{videos/shared/pagination.model.ts => shared/rest/rest-pagination.ts} (65%) create mode 100644 client/src/app/shared/rest/rest.service.ts diff --git a/client/src/app/account/account.service.ts b/client/src/app/account/account.service.ts index 19b4e0624..355bcef74 100644 --- a/client/src/app/account/account.service.ts +++ b/client/src/app/account/account.service.ts @@ -1,12 +1,16 @@ import { Injectable } from '@angular/core'; -import { AuthHttp, AuthService } from '../shared'; +import { AuthHttp, AuthService, RestExtractor } from '../shared'; @Injectable() export class AccountService { private static BASE_USERS_URL = '/api/v1/users/'; - constructor(private authHttp: AuthHttp, private authService: AuthService) { } + constructor( + private authHttp: AuthHttp, + private authService: AuthService, + private restExtractor: RestExtractor + ) {} changePassword(newPassword: string) { const url = AccountService.BASE_USERS_URL + this.authService.getUser().id; @@ -14,6 +18,8 @@ export class AccountService { password: newPassword }; - return this.authHttp.put(url, body); + return this.authHttp.put(url, body) + .map(this.restExtractor.extractDataBool) + .catch((res) => this.restExtractor.handleError(res)); } } diff --git a/client/src/app/admin/friends/shared/friend.service.ts b/client/src/app/admin/friends/shared/friend.service.ts index e4e680c29..75826fc17 100644 --- a/client/src/app/admin/friends/shared/friend.service.ts +++ b/client/src/app/admin/friends/shared/friend.service.ts @@ -1,9 +1,8 @@ import { Injectable } from '@angular/core'; -import { Response } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import { Friend } from './friend.model'; -import { AuthHttp, AuthService } from '../../../shared'; +import { AuthHttp, RestExtractor } from '../../../shared'; @Injectable() export class FriendService { @@ -11,13 +10,15 @@ export class FriendService { constructor ( private authHttp: AuthHttp, - private authService: AuthService + private restExtractor: RestExtractor ) {} getFriends(): Observable { return this.authHttp.get(FriendService.BASE_FRIEND_URL) - .map(res => res.json()) - .catch(this.handleError); + // Not implemented as a data list by the server yet + // .map(this.restExtractor.extractDataList) + .map((res) => res.json()) + .catch((res) => this.restExtractor.handleError(res)); } makeFriends(notEmptyUrls) { @@ -26,18 +27,13 @@ export class FriendService { }; return this.authHttp.post(FriendService.BASE_FRIEND_URL + 'makefriends', body) - .map(res => res.status) - .catch(this.handleError); + .map(this.restExtractor.extractDataBool) + .catch((res) => this.restExtractor.handleError(res)); } quitFriends() { return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'quitfriends') .map(res => res.status) - .catch(this.handleError); - } - - private handleError (error: Response) { - console.error(error); - return Observable.throw(error.json().error || 'Server error'); + .catch((res) => this.restExtractor.handleError(res)); } } diff --git a/client/src/app/admin/users/shared/user.service.ts b/client/src/app/admin/users/shared/user.service.ts index be433f0a1..d96db4575 100644 --- a/client/src/app/admin/users/shared/user.service.ts +++ b/client/src/app/admin/users/shared/user.service.ts @@ -1,15 +1,16 @@ import { Injectable } from '@angular/core'; -import { Response } from '@angular/http'; -import { Observable } from 'rxjs/Observable'; -import { AuthHttp, User } from '../../../shared'; +import { AuthHttp, RestExtractor, ResultList, User } from '../../../shared'; @Injectable() export class UserService { // TODO: merge this constant with account private static BASE_USERS_URL = '/api/v1/users/'; - constructor(private authHttp: AuthHttp) {} + constructor( + private authHttp: AuthHttp, + private restExtractor: RestExtractor + ) {} addUser(username: string, password: string) { const body = { @@ -17,23 +18,25 @@ export class UserService { password }; - return this.authHttp.post(UserService.BASE_USERS_URL, body); + return this.authHttp.post(UserService.BASE_USERS_URL, body) + .map(this.restExtractor.extractDataBool) + .catch((res) => this.restExtractor.handleError(res)); } getUsers() { return this.authHttp.get(UserService.BASE_USERS_URL) - .map(res => res.json()) + .map(this.restExtractor.extractDataList) .map(this.extractUsers) - .catch(this.handleError); + .catch((res) => this.restExtractor.handleError(res)); } removeUser(user: User) { return this.authHttp.delete(UserService.BASE_USERS_URL + user.id); } - private extractUsers(body: any) { - const usersJson = body.data; - const totalUsers = body.total; + private extractUsers(result: ResultList) { + const usersJson = result.data; + const totalUsers = result.total; const users = []; for (const userJson of usersJson) { users.push(new User(userJson)); @@ -41,9 +44,4 @@ export class UserService { return { users, totalUsers }; } - - private handleError(error: Response) { - console.error(error); - return Observable.throw(error.json().error || 'Server error'); - } } diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 2e0fd13f1..9d05c272f 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -3,7 +3,7 @@ import { Router, ROUTER_DIRECTIVES } from '@angular/router'; import { MenuAdminComponent } from './admin'; import { MenuComponent } from './menu.component'; -import { SearchComponent, SearchService } from './shared'; +import { RestExtractor, RestService, SearchComponent, SearchService } from './shared'; import { VideoService } from './videos'; @Component({ @@ -11,7 +11,7 @@ import { VideoService } from './videos'; template: require('./app.component.html'), styles: [ require('./app.component.scss') ], directives: [ MenuAdminComponent, MenuComponent, ROUTER_DIRECTIVES, SearchComponent ], - providers: [ VideoService, SearchService ] + providers: [ RestExtractor, RestService, VideoService, SearchService ] }) export class AppComponent { diff --git a/client/src/app/login/login.component.ts b/client/src/app/login/login.component.ts index fe867b7b4..1e0ba0fe8 100644 --- a/client/src/app/login/login.component.ts +++ b/client/src/app/login/login.component.ts @@ -37,12 +37,12 @@ export class LoginComponent implements OnInit { this.router.navigate(['/videos/list']); }, error => { - console.error(error); + console.error(error.json); - if (error.error === 'invalid_grant') { + if (error.json.error === 'invalid_grant') { this.error = 'Credentials are invalid.'; } else { - this.error = `${error.error}: ${error.error_description}`; + this.error = `${error.json.error}: ${error.json.error_description}`; } } ); diff --git a/client/src/app/shared/auth/auth.service.ts b/client/src/app/shared/auth/auth.service.ts index 8eea0c4bf..2273048c8 100644 --- a/client/src/app/shared/auth/auth.service.ts +++ b/client/src/app/shared/auth/auth.service.ts @@ -1,10 +1,11 @@ import { Injectable } from '@angular/core'; -import { Headers, Http, Response, URLSearchParams } from '@angular/http'; +import { Headers, Http, URLSearchParams } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; import { AuthStatus } from './auth-status.model'; import { AuthUser } from './auth-user.model'; +import { RestExtractor } from '../rest'; @Injectable() export class AuthService { @@ -19,15 +20,15 @@ export class AuthService { private loginChanged: Subject; private user: AuthUser = null; - constructor(private http: Http) { + constructor(private http: Http, private restExtractor: RestExtractor) { this.loginChanged = new Subject(); this.loginChangedSource = this.loginChanged.asObservable(); // Fetch the client_id/client_secret // FIXME: save in local storage? this.http.get(AuthService.BASE_CLIENT_URL) - .map(res => res.json()) - .catch(this.handleError) + .map(this.restExtractor.extractDataGet) + .catch((res) => this.restExtractor.handleError(res)) .subscribe( result => { this.clientId = result.client_id; @@ -101,14 +102,14 @@ export class AuthService { }; return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) - .map(res => res.json()) + .map(this.restExtractor.extractDataGet) .map(res => { res.username = username; return res; }) .flatMap(res => this.fetchUserInformations(res)) .map(res => this.handleLogin(res)) - .catch(this.handleError); + .catch((res) => this.restExtractor.handleError(res)); } logout() { @@ -139,9 +140,9 @@ export class AuthService { }; return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) - .map(res => res.json()) + .map(this.restExtractor.extractDataGet) .map(res => this.handleRefreshToken(res)) - .catch(this.handleError); + .catch((res) => this.restExtractor.handleError(res)); } private fetchUserInformations (obj: any) { @@ -160,11 +161,6 @@ export class AuthService { ); } - private handleError (error: Response) { - console.error(error); - return Observable.throw(error.json() || { error: 'Server error' }); - } - private handleLogin (obj: any) { const id = obj.id; const username = obj.username; diff --git a/client/src/app/shared/index.ts b/client/src/app/shared/index.ts index 9edf9b4a0..c362a0e4a 100644 --- a/client/src/app/shared/index.ts +++ b/client/src/app/shared/index.ts @@ -1,4 +1,5 @@ export * from './auth'; export * from './form-validators'; +export * from './rest'; export * from './search'; export * from './users'; diff --git a/client/src/app/shared/rest/index.ts b/client/src/app/shared/rest/index.ts new file mode 100644 index 000000000..3c9509dc7 --- /dev/null +++ b/client/src/app/shared/rest/index.ts @@ -0,0 +1,3 @@ +export * from './rest-extractor.service'; +export * from './rest-pagination'; +export * from './rest.service'; diff --git a/client/src/app/shared/rest/rest-extractor.service.ts b/client/src/app/shared/rest/rest-extractor.service.ts new file mode 100644 index 000000000..aa44799af --- /dev/null +++ b/client/src/app/shared/rest/rest-extractor.service.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@angular/core'; +import { Response } from '@angular/http'; +import { Observable } from 'rxjs/Observable'; + +export interface ResultList { + data: any[]; + total: number; +} + +@Injectable() +export class RestExtractor { + + constructor () { ; } + + extractDataBool(res: Response) { + return true; + } + + extractDataList(res: Response) { + const body = res.json(); + + const ret: ResultList = { + data: body.data, + total: body.total + }; + + return ret; + } + + extractDataGet(res: Response) { + return res.json(); + } + + handleError(res: Response) { + let text = 'Server error: '; + text += res.text(); + let json = res.json(); + + const error = { + json, + text + }; + + return Observable.throw(error); + } +} diff --git a/client/src/app/videos/shared/pagination.model.ts b/client/src/app/shared/rest/rest-pagination.ts similarity index 65% rename from client/src/app/videos/shared/pagination.model.ts rename to client/src/app/shared/rest/rest-pagination.ts index eda44ebfb..0cfa4f468 100644 --- a/client/src/app/videos/shared/pagination.model.ts +++ b/client/src/app/shared/rest/rest-pagination.ts @@ -1,5 +1,5 @@ -export interface Pagination { +export interface RestPagination { currentPage: number; itemsPerPage: number; totalItems: number; -} +}; diff --git a/client/src/app/shared/rest/rest.service.ts b/client/src/app/shared/rest/rest.service.ts new file mode 100644 index 000000000..16b47e957 --- /dev/null +++ b/client/src/app/shared/rest/rest.service.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@angular/core'; +import { URLSearchParams } from '@angular/http'; + +import { RestPagination } from './rest-pagination'; + +@Injectable() +export class RestService { + + buildRestGetParams(pagination?: RestPagination, sort?: string) { + const params = new URLSearchParams(); + + if (pagination) { + const start: number = (pagination.currentPage - 1) * pagination.itemsPerPage; + const count: number = pagination.itemsPerPage; + + params.set('start', start.toString()); + params.set('count', count.toString()); + } + + if (sort) { + params.set('sort', sort); + } + + return params; + } + +} diff --git a/client/src/app/videos/shared/index.ts b/client/src/app/videos/shared/index.ts index a54120f5d..67d16ead1 100644 --- a/client/src/app/videos/shared/index.ts +++ b/client/src/app/videos/shared/index.ts @@ -1,5 +1,4 @@ export * from './loader'; -export * from './pagination.model'; export * from './sort-field.type'; export * from './video.model'; export * from './video.service'; diff --git a/client/src/app/videos/shared/video.service.ts b/client/src/app/videos/shared/video.service.ts index b4396f767..ad8557533 100644 --- a/client/src/app/videos/shared/video.service.ts +++ b/client/src/app/videos/shared/video.service.ts @@ -1,11 +1,10 @@ import { Injectable } from '@angular/core'; -import { Http, Response, URLSearchParams } from '@angular/http'; +import { Http } from '@angular/http'; import { Observable } from 'rxjs/Observable'; -import { Pagination } from './pagination.model'; import { Search } from '../../shared'; import { SortField } from './sort-field.type'; -import { AuthHttp, AuthService } from '../../shared'; +import { AuthHttp, AuthService, RestExtractor, RestPagination, RestService, ResultList } from '../../shared'; import { Video } from './video.model'; @Injectable() @@ -15,68 +14,51 @@ export class VideoService { constructor( private authService: AuthService, private authHttp: AuthHttp, - private http: Http + private http: Http, + private restExtractor: RestExtractor, + private restService: RestService ) {} - getVideo(id: string) { + getVideo(id: string): Observable
+
+ Maximum number of requests per interval: + {{ stats.maxRequestsInParallel }} +
+
Remaining requests: {{ stats.requests.length }} diff --git a/client/src/app/admin/requests/shared/request-stats.model.ts b/client/src/app/admin/requests/shared/request-stats.model.ts index dfa956f10..766e80836 100644 --- a/client/src/app/admin/requests/shared/request-stats.model.ts +++ b/client/src/app/admin/requests/shared/request-stats.model.ts @@ -4,15 +4,18 @@ export interface Request { } export class RequestStats { + maxRequestsInParallel: number; milliSecondsInterval: number; - remainingMilliSeconds: number; + remainingMilliSeconds: number; requests: Request[]; constructor(hash: { + maxRequestsInParallel: number, milliSecondsInterval: number, remainingMilliSeconds: number, requests: Request[]; }) { + this.maxRequestsInParallel = hash.maxRequestsInParallel; this.milliSecondsInterval = hash.milliSecondsInterval; this.remainingMilliSeconds = hash.remainingMilliSeconds; this.requests = hash.requests; diff --git a/server/controllers/api/v1/requests.js b/server/controllers/api/v1/requests.js index 9610e5cd6..97616424d 100644 --- a/server/controllers/api/v1/requests.js +++ b/server/controllers/api/v1/requests.js @@ -30,6 +30,7 @@ function getStatsRequests (req, res, next) { return res.json({ requests: requests, + maxRequestsInParallel: constants.REQUESTS_IN_PARALLEL, remainingMilliSeconds: Request.remainingMilliSeconds(), milliSecondsInterval: constants.REQUESTS_INTERVAL }) From d6cf31b7e03ad7d8f9e0408f70a88dae3a276d1e Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sat, 1 Oct 2016 16:10:18 +0200 Subject: [PATCH 102/105] Server: add informations when removing requests of unexisting pod --- server/models/request.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/models/request.js b/server/models/request.js index 2d50d94e0..2d1c5af15 100644 --- a/server/models/request.js +++ b/server/models/request.js @@ -176,6 +176,7 @@ function makeRequests () { // Maybe the pod is not our friend anymore so simply remove it if (!toPod) { + logger.info('Removing %d requests of unexisting pod %s.', requestToMake.ids.length, toPodId) removePodOf.call(self, requestToMake.ids, toPodId) return callbackEach() } From a67b3e7619353d7eb1f3e6064ebe3041094a8b70 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sat, 1 Oct 2016 18:19:57 +0200 Subject: [PATCH 103/105] Add experimental demo servers link in readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 975ec1695..777df6d7d 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Want to see in action? * You can directly test in your browser with this [demo server](http://peertube.cpy.re). Don't forget to use the latest version of Firefox/Chromium/(Opera?) and check your firewall configuration (for WebRTC) * You can find [a video](https://vimeo.com/164881662 "Yes Vimeo, please don't judge me") to see how the "decentralization feature" looks like + * Experimental demo servers that share videos (they are in the same network): [peertube2](http://peertube2.cpy.re), [peertube3](http://peertube3.cpy.re). Since I do experiments with them, sometimes they might not work correctly. ## Why From 9f6bae3a9db13bf827f8aaff903aac06ec430903 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sun, 2 Oct 2016 11:14:08 +0200 Subject: [PATCH 104/105] Server: reorganize constant file --- server/initializers/constants.js | 111 ++++++++++++++++++------------- 1 file changed, 64 insertions(+), 47 deletions(-) diff --git a/server/initializers/constants.js b/server/initializers/constants.js index 02043bd45..b1d033377 100644 --- a/server/initializers/constants.js +++ b/server/initializers/constants.js @@ -3,10 +3,31 @@ const config = require('config') const path = require('path') -// API version of our pod +// --------------------------------------------------------------------------- + +// API version const API_VERSION = 'v1' -const BCRYPT_SALT_SIZE = 10 +// Number of results by default for the pagination +const PAGINATION_COUNT_DEFAULT = 15 + +// Sortable columns per schema +const SEARCHABLE_COLUMNS = { + VIDEOS: [ 'name', 'magnetUri', 'podUrl', 'author', 'tags' ] +} + +// Sortable columns per schema +const SORTABLE_COLUMNS = { + USERS: [ 'username', '-username', 'createdDate', '-createdDate' ], + VIDEOS: [ 'name', '-name', 'duration', '-duration', 'createdDate', '-createdDate' ] +} + +const OAUTH_LIFETIME = { + ACCESS_TOKEN: 3600 * 4, // 4 hours + REFRESH_TOKEN: 1209600 // 2 weeks +} + +// --------------------------------------------------------------------------- const CONFIG = { DATABASE: { @@ -31,6 +52,8 @@ const CONFIG = { } CONFIG.WEBSERVER.URL = CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOST + ':' + CONFIG.WEBSERVER.PORT +// --------------------------------------------------------------------------- + const CONSTRAINTS_FIELDS = { USERS: { USERNAME: { min: 3, max: 20 }, // Length @@ -48,12 +71,16 @@ const CONSTRAINTS_FIELDS = { } } +// --------------------------------------------------------------------------- + // Score a pod has when we create it as a friend const FRIEND_SCORE = { BASE: 100, MAX: 1000 } +// --------------------------------------------------------------------------- + const MONGO_MIGRATION_SCRIPTS = [ { script: '0005-create-application', @@ -70,16 +97,7 @@ const MONGO_MIGRATION_SCRIPTS = [ ] const LAST_MONGO_SCHEMA_VERSION = 15 -// Time to wait between requests to the friends (10 min) -let REQUESTS_INTERVAL = 600000 - -const OAUTH_LIFETIME = { - ACCESS_TOKEN: 3600 * 4, // 4 hours - REFRESH_TOKEN: 1209600 // 2 weeks -} - -// Number of results by default for the pagination -const PAGINATION_COUNT_DEFAULT = 15 +// --------------------------------------------------------------------------- // Number of points we add/remove from a friend after a successful/bad request const PODS_SCORE = { @@ -87,29 +105,22 @@ const PODS_SCORE = { BONUS: 10 } +// Time to wait between requests to the friends (10 min) +let REQUESTS_INTERVAL = 600000 + // Number of requests in parallel we can make const REQUESTS_IN_PARALLEL = 10 -// How many requests we put in request (request scheduler) +// How many requests we put in request const REQUESTS_LIMIT = 10 // Number of requests to retry for replay requests module const RETRY_REQUESTS = 5 -// Sortable columns per schema -const SEARCHABLE_COLUMNS = { - VIDEOS: [ 'name', 'magnetUri', 'podUrl', 'author', 'tags' ] -} +// --------------------------------------------------------------------------- -// Seeds in parallel we send to electron when "seed all" -// Once a video is in seeding state we seed another video etc -const SEEDS_IN_PARALLEL = 3 - -// Sortable columns per schema -const SORTABLE_COLUMNS = { - USERS: [ 'username', '-username', 'createdDate', '-createdDate' ], - VIDEOS: [ 'name', '-name', 'duration', '-duration', 'createdDate', '-createdDate' ] -} +// Password encryption +const BCRYPT_SALT_SIZE = 10 // Videos thumbnail size const THUMBNAILS_SIZE = '200x110' @@ -122,36 +133,42 @@ const USER_ROLES = { USER: 'user' } +// Seeds in parallel we send to electron when "seed all" +// Once a video is in seeding state we seed another video etc +const SEEDS_IN_PARALLEL = 3 + +// --------------------------------------------------------------------------- + // Special constants for a test instance if (isTestInstance() === true) { + CONSTRAINTS_FIELDS.VIDEOS.DURATION.max = 14 FRIEND_SCORE.BASE = 20 REQUESTS_INTERVAL = 10000 - CONSTRAINTS_FIELDS.VIDEOS.DURATION.max = 14 } // --------------------------------------------------------------------------- module.exports = { - API_VERSION: API_VERSION, - BCRYPT_SALT_SIZE: BCRYPT_SALT_SIZE, - CONFIG: CONFIG, - CONSTRAINTS_FIELDS: CONSTRAINTS_FIELDS, - FRIEND_SCORE: FRIEND_SCORE, - LAST_MONGO_SCHEMA_VERSION: LAST_MONGO_SCHEMA_VERSION, - MONGO_MIGRATION_SCRIPTS: MONGO_MIGRATION_SCRIPTS, - OAUTH_LIFETIME: OAUTH_LIFETIME, - PAGINATION_COUNT_DEFAULT: PAGINATION_COUNT_DEFAULT, - PODS_SCORE: PODS_SCORE, - REQUESTS_IN_PARALLEL: REQUESTS_IN_PARALLEL, - REQUESTS_INTERVAL: REQUESTS_INTERVAL, - REQUESTS_LIMIT: REQUESTS_LIMIT, - RETRY_REQUESTS: RETRY_REQUESTS, - SEARCHABLE_COLUMNS: SEARCHABLE_COLUMNS, - SEEDS_IN_PARALLEL: SEEDS_IN_PARALLEL, - SORTABLE_COLUMNS: SORTABLE_COLUMNS, - THUMBNAILS_SIZE: THUMBNAILS_SIZE, - THUMBNAILS_STATIC_PATH: THUMBNAILS_STATIC_PATH, - USER_ROLES: USER_ROLES + API_VERSION, + BCRYPT_SALT_SIZE, + CONFIG, + CONSTRAINTS_FIELDS, + FRIEND_SCORE, + LAST_MONGO_SCHEMA_VERSION, + MONGO_MIGRATION_SCRIPTS, + OAUTH_LIFETIME, + PAGINATION_COUNT_DEFAULT, + PODS_SCORE, + REQUESTS_IN_PARALLEL, + REQUESTS_INTERVAL, + REQUESTS_LIMIT, + RETRY_REQUESTS, + SEARCHABLE_COLUMNS, + SEEDS_IN_PARALLEL, + SORTABLE_COLUMNS, + THUMBNAILS_SIZE, + THUMBNAILS_STATIC_PATH, + USER_ROLES } // --------------------------------------------------------------------------- From c4403b29ad4db097af528a7f04eea07e0ed320d0 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sun, 2 Oct 2016 12:19:02 +0200 Subject: [PATCH 105/105] Server: remove useless hash affectations --- server/helpers/custom-validators/misc.js | 4 ++-- server/helpers/custom-validators/pods.js | 2 +- server/helpers/custom-validators/users.js | 6 ++--- server/helpers/custom-validators/videos.js | 22 ++++++++--------- server/helpers/peertube-crypto.js | 14 +++++------ server/helpers/requests.js | 4 ++-- server/helpers/utils.js | 4 ++-- server/initializers/checker.js | 6 ++--- server/initializers/installer.js | 8 +++---- server/lib/friends.js | 14 +++++------ server/lib/oauth-model.js | 12 +++++----- server/middlewares/admin.js | 2 +- server/middlewares/oauth.js | 4 ++-- server/middlewares/pagination.js | 2 +- server/middlewares/pods.js | 4 ++-- server/middlewares/search.js | 2 +- server/middlewares/secure.js | 4 ++-- server/middlewares/sort.js | 4 ++-- server/middlewares/validators/pagination.js | 2 +- server/middlewares/validators/pods.js | 4 ++-- server/middlewares/validators/remote.js | 6 ++--- server/middlewares/validators/sort.js | 4 ++-- server/middlewares/validators/users.js | 6 ++--- server/middlewares/validators/utils.js | 2 +- server/middlewares/validators/videos.js | 8 +++---- server/models/application.js | 4 ++-- server/models/oauth-client.js | 6 ++--- server/models/oauth-token.js | 8 +++---- server/models/pods.js | 18 +++++++------- server/models/user.js | 16 ++++++------- server/models/utils.js | 2 +- server/models/video.js | 26 ++++++++++----------- server/tests/utils/login.js | 4 ++-- server/tests/utils/miscs.js | 2 +- server/tests/utils/pods.js | 6 ++--- server/tests/utils/requests.js | 6 ++--- server/tests/utils/servers.js | 6 ++--- server/tests/utils/users.js | 12 +++++----- server/tests/utils/videos.js | 22 ++++++++--------- 39 files changed, 144 insertions(+), 144 deletions(-) diff --git a/server/helpers/custom-validators/misc.js b/server/helpers/custom-validators/misc.js index 782ae3dee..052726241 100644 --- a/server/helpers/custom-validators/misc.js +++ b/server/helpers/custom-validators/misc.js @@ -1,8 +1,8 @@ 'use strict' const miscValidators = { - exists: exists, - isArray: isArray + exists, + isArray } function exists (value) { diff --git a/server/helpers/custom-validators/pods.js b/server/helpers/custom-validators/pods.js index 28d04a05d..40f8b5d0b 100644 --- a/server/helpers/custom-validators/pods.js +++ b/server/helpers/custom-validators/pods.js @@ -5,7 +5,7 @@ const validator = require('express-validator').validator const miscValidators = require('./misc') const podsValidators = { - isEachUniqueUrlValid: isEachUniqueUrlValid + isEachUniqueUrlValid } function isEachUniqueUrlValid (urls) { diff --git a/server/helpers/custom-validators/users.js b/server/helpers/custom-validators/users.js index 0e92989e5..88fa1592e 100644 --- a/server/helpers/custom-validators/users.js +++ b/server/helpers/custom-validators/users.js @@ -7,9 +7,9 @@ const constants = require('../../initializers/constants') const USERS_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.USERS const usersValidators = { - isUserPasswordValid: isUserPasswordValid, - isUserRoleValid: isUserRoleValid, - isUserUsernameValid: isUserUsernameValid + isUserPasswordValid, + isUserRoleValid, + isUserUsernameValid } function isUserPasswordValid (value) { diff --git a/server/helpers/custom-validators/videos.js b/server/helpers/custom-validators/videos.js index ebe927208..a507ff686 100644 --- a/server/helpers/custom-validators/videos.js +++ b/server/helpers/custom-validators/videos.js @@ -8,17 +8,17 @@ const miscValidators = require('./misc') const VIDEOS_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.VIDEOS const videosValidators = { - isEachRemoteVideosValid: isEachRemoteVideosValid, - isVideoAuthorValid: isVideoAuthorValid, - isVideoDateValid: isVideoDateValid, - isVideoDescriptionValid: isVideoDescriptionValid, - isVideoDurationValid: isVideoDurationValid, - isVideoMagnetUriValid: isVideoMagnetUriValid, - isVideoNameValid: isVideoNameValid, - isVideoPodUrlValid: isVideoPodUrlValid, - isVideoTagsValid: isVideoTagsValid, - isVideoThumbnailValid: isVideoThumbnailValid, - isVideoThumbnail64Valid: isVideoThumbnail64Valid + isEachRemoteVideosValid, + isVideoAuthorValid, + isVideoDateValid, + isVideoDescriptionValid, + isVideoDurationValid, + isVideoMagnetUriValid, + isVideoNameValid, + isVideoPodUrlValid, + isVideoTagsValid, + isVideoThumbnailValid, + isVideoThumbnail64Valid } function isEachRemoteVideosValid (requests) { diff --git a/server/helpers/peertube-crypto.js b/server/helpers/peertube-crypto.js index 4783e9965..1ff638b04 100644 --- a/server/helpers/peertube-crypto.js +++ b/server/helpers/peertube-crypto.js @@ -12,13 +12,13 @@ const logger = require('./logger') const algorithm = 'aes-256-ctr' const peertubeCrypto = { - checkSignature: checkSignature, - comparePassword: comparePassword, - createCertsIfNotExist: createCertsIfNotExist, - cryptPassword: cryptPassword, - decrypt: decrypt, - encrypt: encrypt, - sign: sign + checkSignature, + comparePassword, + createCertsIfNotExist, + cryptPassword, + decrypt, + encrypt, + sign } function checkSignature (publicKey, rawData, hexSignature) { diff --git a/server/helpers/requests.js b/server/helpers/requests.js index f76ff3473..95775c981 100644 --- a/server/helpers/requests.js +++ b/server/helpers/requests.js @@ -7,8 +7,8 @@ const constants = require('../initializers/constants') const peertubeCrypto = require('./peertube-crypto') const requests = { - makeRetryRequest: makeRetryRequest, - makeSecureRequest: makeSecureRequest + makeRetryRequest, + makeSecureRequest } function makeRetryRequest (params, callback) { diff --git a/server/helpers/utils.js b/server/helpers/utils.js index a77116e08..9c2d402e3 100644 --- a/server/helpers/utils.js +++ b/server/helpers/utils.js @@ -5,8 +5,8 @@ const crypto = require('crypto') const logger = require('./logger') const utils = { - cleanForExit: cleanForExit, - generateRandomString: generateRandomString + cleanForExit, + generateRandomString } function generateRandomString (size, callback) { diff --git a/server/initializers/checker.js b/server/initializers/checker.js index 2a33009b4..91fbcfaf9 100644 --- a/server/initializers/checker.js +++ b/server/initializers/checker.js @@ -7,9 +7,9 @@ const Client = mongoose.model('OAuthClient') const User = mongoose.model('User') const checker = { - checkConfig: checkConfig, - clientsExist: clientsExist, - usersExist: usersExist + checkConfig, + clientsExist, + usersExist } // Check the config files diff --git a/server/initializers/installer.js b/server/initializers/installer.js index 8c3148e79..1df300ba8 100644 --- a/server/initializers/installer.js +++ b/server/initializers/installer.js @@ -18,7 +18,7 @@ const Client = mongoose.model('OAuthClient') const User = mongoose.model('User') const installer = { - installApplication: installApplication + installApplication } function installApplication (callback) { @@ -107,9 +107,9 @@ function createOAuthAdminIfNotExist (callback) { } const user = new User({ - username: username, - password: password, - role: role + username, + password, + role }) user.save(function (err, createdUser) { diff --git a/server/lib/friends.js b/server/lib/friends.js index 6c4383d8e..556d2e773 100644 --- a/server/lib/friends.js +++ b/server/lib/friends.js @@ -17,13 +17,13 @@ const Request = mongoose.model('Request') const Video = mongoose.model('Video') const friends = { - addVideoToFriends: addVideoToFriends, - hasFriends: hasFriends, - getMyCertificate: getMyCertificate, - makeFriends: makeFriends, - quitFriends: quitFriends, - removeVideoToFriends: removeVideoToFriends, - sendOwnedVideosToPod: sendOwnedVideosToPod + addVideoToFriends, + hasFriends, + getMyCertificate, + makeFriends, + quitFriends, + removeVideoToFriends, + sendOwnedVideosToPod } function addVideoToFriends (video) { diff --git a/server/lib/oauth-model.js b/server/lib/oauth-model.js index 6dab02fca..45f796796 100644 --- a/server/lib/oauth-model.js +++ b/server/lib/oauth-model.js @@ -8,12 +8,12 @@ const User = mongoose.model('User') // See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications const OAuthModel = { - getAccessToken: getAccessToken, - getClient: getClient, - getRefreshToken: getRefreshToken, - getUser: getUser, - revokeToken: revokeToken, - saveToken: saveToken + getAccessToken, + getClient, + getRefreshToken, + getUser, + revokeToken, + saveToken } // --------------------------------------------------------------------------- diff --git a/server/middlewares/admin.js b/server/middlewares/admin.js index bcb60ab95..e6d9dc887 100644 --- a/server/middlewares/admin.js +++ b/server/middlewares/admin.js @@ -4,7 +4,7 @@ const constants = require('../initializers/constants') const logger = require('../helpers/logger') const adminMiddleware = { - ensureIsAdmin: ensureIsAdmin + ensureIsAdmin } function ensureIsAdmin (req, res, next) { diff --git a/server/middlewares/oauth.js b/server/middlewares/oauth.js index 08584c41c..3a02b9b48 100644 --- a/server/middlewares/oauth.js +++ b/server/middlewares/oauth.js @@ -12,8 +12,8 @@ const oAuthServer = new OAuthServer({ }) const oAuth = { - authenticate: authenticate, - token: token + authenticate, + token } function authenticate (req, res, next) { diff --git a/server/middlewares/pagination.js b/server/middlewares/pagination.js index a571e51f6..a90f60aab 100644 --- a/server/middlewares/pagination.js +++ b/server/middlewares/pagination.js @@ -3,7 +3,7 @@ const constants = require('../initializers/constants') const paginationMiddleware = { - setPagination: setPagination + setPagination } function setPagination (req, res, next) { diff --git a/server/middlewares/pods.js b/server/middlewares/pods.js index 116b02b3c..6e0874a76 100644 --- a/server/middlewares/pods.js +++ b/server/middlewares/pods.js @@ -5,8 +5,8 @@ const urlModule = require('url') const logger = require('../helpers/logger') const podsMiddleware = { - setBodyUrlsPort: setBodyUrlsPort, - setBodyUrlPort: setBodyUrlPort + setBodyUrlsPort, + setBodyUrlPort } function setBodyUrlsPort (req, res, next) { diff --git a/server/middlewares/search.js b/server/middlewares/search.js index 89302a564..bb88faf54 100644 --- a/server/middlewares/search.js +++ b/server/middlewares/search.js @@ -1,7 +1,7 @@ 'use strict' const searchMiddleware = { - setVideosSearch: setVideosSearch + setVideosSearch } function setVideosSearch (req, res, next) { diff --git a/server/middlewares/secure.js b/server/middlewares/secure.js index 33a52e8d9..58f824d14 100644 --- a/server/middlewares/secure.js +++ b/server/middlewares/secure.js @@ -7,8 +7,8 @@ const peertubeCrypto = require('../helpers/peertube-crypto') const Pod = mongoose.model('Pod') const secureMiddleware = { - checkSignature: checkSignature, - decryptBody: decryptBody + checkSignature, + decryptBody } function checkSignature (req, res, next) { diff --git a/server/middlewares/sort.js b/server/middlewares/sort.js index 8ed157805..f0b7274eb 100644 --- a/server/middlewares/sort.js +++ b/server/middlewares/sort.js @@ -1,8 +1,8 @@ 'use strict' const sortMiddleware = { - setUsersSort: setUsersSort, - setVideosSort: setVideosSort + setUsersSort, + setVideosSort } function setUsersSort (req, res, next) { diff --git a/server/middlewares/validators/pagination.js b/server/middlewares/validators/pagination.js index 8e9a01053..16682696e 100644 --- a/server/middlewares/validators/pagination.js +++ b/server/middlewares/validators/pagination.js @@ -4,7 +4,7 @@ const checkErrors = require('./utils').checkErrors const logger = require('../../helpers/logger') const validatorsPagination = { - pagination: pagination + pagination } function pagination (req, res, next) { diff --git a/server/middlewares/validators/pods.js b/server/middlewares/validators/pods.js index 3c605c45e..fd3d1e2f2 100644 --- a/server/middlewares/validators/pods.js +++ b/server/middlewares/validators/pods.js @@ -5,8 +5,8 @@ const friends = require('../../lib/friends') const logger = require('../../helpers/logger') const validatorsPod = { - makeFriends: makeFriends, - podsAdd: podsAdd + makeFriends, + podsAdd } function makeFriends (req, res, next) { diff --git a/server/middlewares/validators/remote.js b/server/middlewares/validators/remote.js index 87dc524a2..8c29ef8ca 100644 --- a/server/middlewares/validators/remote.js +++ b/server/middlewares/validators/remote.js @@ -4,9 +4,9 @@ const checkErrors = require('./utils').checkErrors const logger = require('../../helpers/logger') const validatorsRemote = { - dataToDecrypt: dataToDecrypt, - remoteVideos: remoteVideos, - signature: signature + dataToDecrypt, + remoteVideos, + signature } function dataToDecrypt (req, res, next) { diff --git a/server/middlewares/validators/sort.js b/server/middlewares/validators/sort.js index 37b34ef52..431d3fffd 100644 --- a/server/middlewares/validators/sort.js +++ b/server/middlewares/validators/sort.js @@ -5,8 +5,8 @@ const constants = require('../../initializers/constants') const logger = require('../../helpers/logger') const validatorsSort = { - usersSort: usersSort, - videosSort: videosSort + usersSort, + videosSort } function usersSort (req, res, next) { diff --git a/server/middlewares/validators/users.js b/server/middlewares/validators/users.js index 5defdf4e3..d541e9124 100644 --- a/server/middlewares/validators/users.js +++ b/server/middlewares/validators/users.js @@ -8,9 +8,9 @@ const logger = require('../../helpers/logger') const User = mongoose.model('User') const validatorsUsers = { - usersAdd: usersAdd, - usersRemove: usersRemove, - usersUpdate: usersUpdate + usersAdd, + usersRemove, + usersUpdate } function usersAdd (req, res, next) { diff --git a/server/middlewares/validators/utils.js b/server/middlewares/validators/utils.js index f6e5b2b38..3741b84c6 100644 --- a/server/middlewares/validators/utils.js +++ b/server/middlewares/validators/utils.js @@ -5,7 +5,7 @@ const util = require('util') const logger = require('../../helpers/logger') const validatorsUtils = { - checkErrors: checkErrors + checkErrors } function checkErrors (req, res, next, statusCode) { diff --git a/server/middlewares/validators/videos.js b/server/middlewares/validators/videos.js index e51087d5a..76e943e77 100644 --- a/server/middlewares/validators/videos.js +++ b/server/middlewares/validators/videos.js @@ -10,10 +10,10 @@ const logger = require('../../helpers/logger') const Video = mongoose.model('Video') const validatorsVideos = { - videosAdd: videosAdd, - videosGet: videosGet, - videosRemove: videosRemove, - videosSearch: videosSearch + videosAdd, + videosGet, + videosRemove, + videosSearch } function videosAdd (req, res, next) { diff --git a/server/models/application.js b/server/models/application.js index 8185f0915..452ac4283 100644 --- a/server/models/application.js +++ b/server/models/application.js @@ -10,8 +10,8 @@ const ApplicationSchema = mongoose.Schema({ }) ApplicationSchema.statics = { - loadMongoSchemaVersion: loadMongoSchemaVersion, - updateMongoSchemaVersion: updateMongoSchemaVersion + loadMongoSchemaVersion, + updateMongoSchemaVersion } mongoose.model('Application', ApplicationSchema) diff --git a/server/models/oauth-client.js b/server/models/oauth-client.js index 45834c5a5..a1aefa985 100644 --- a/server/models/oauth-client.js +++ b/server/models/oauth-client.js @@ -11,9 +11,9 @@ const OAuthClientSchema = mongoose.Schema({ OAuthClientSchema.path('clientSecret').required(true) OAuthClientSchema.statics = { - getByIdAndSecret: getByIdAndSecret, - list: list, - loadFirstClient: loadFirstClient + getByIdAndSecret, + list, + loadFirstClient } mongoose.model('OAuthClient', OAuthClientSchema) diff --git a/server/models/oauth-token.js b/server/models/oauth-token.js index d53fdcf31..5beb47bed 100644 --- a/server/models/oauth-token.js +++ b/server/models/oauth-token.js @@ -18,10 +18,10 @@ OAuthTokenSchema.path('client').required(true) OAuthTokenSchema.path('user').required(true) OAuthTokenSchema.statics = { - getByRefreshTokenAndPopulateClient: getByRefreshTokenAndPopulateClient, - getByTokenAndPopulateUser: getByTokenAndPopulateUser, - getByRefreshToken: getByRefreshToken, - removeByUserId: removeByUserId + getByRefreshTokenAndPopulateClient, + getByTokenAndPopulateUser, + getByRefreshToken, + removeByUserId } mongoose.model('OAuthToken', OAuthTokenSchema) diff --git a/server/models/pods.js b/server/models/pods.js index 59de2d60c..4020a9603 100644 --- a/server/models/pods.js +++ b/server/models/pods.js @@ -24,18 +24,18 @@ PodSchema.path('publicKey').required(true) PodSchema.path('score').validate(function (value) { return !isNaN(value) }) PodSchema.methods = { - toFormatedJSON: toFormatedJSON + toFormatedJSON } PodSchema.statics = { - countAll: countAll, - incrementScores: incrementScores, - list: list, - listAllIds: listAllIds, - listBadPods: listBadPods, - load: load, - loadByUrl: loadByUrl, - removeAll: removeAll + countAll, + incrementScores, + list, + listAllIds, + listBadPods, + load, + loadByUrl, + removeAll } PodSchema.pre('save', function (next) { diff --git a/server/models/user.js b/server/models/user.js index 91e8aeae1..a19de7072 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -23,17 +23,17 @@ UserSchema.path('username').required(customUsersValidators.isUserUsernameValid) UserSchema.path('role').validate(customUsersValidators.isUserRoleValid) UserSchema.methods = { - isPasswordMatch: isPasswordMatch, - toFormatedJSON: toFormatedJSON + isPasswordMatch, + toFormatedJSON } UserSchema.statics = { - countTotal: countTotal, - getByUsername: getByUsername, - list: list, - listForApi: listForApi, - loadById: loadById, - loadByUsername: loadByUsername + countTotal, + getByUsername, + list, + listForApi, + loadById, + loadByUsername } UserSchema.pre('save', function (next) { diff --git a/server/models/utils.js b/server/models/utils.js index a961e8c5b..e798aabe6 100644 --- a/server/models/utils.js +++ b/server/models/utils.js @@ -3,7 +3,7 @@ const parallel = require('async/parallel') const utils = { - listForApiWithCount: listForApiWithCount + listForApiWithCount } function listForApiWithCount (query, start, count, sort, callback) { diff --git a/server/models/video.js b/server/models/video.js index 0f60b6cd4..b9999c8f6 100644 --- a/server/models/video.js +++ b/server/models/video.js @@ -47,22 +47,22 @@ VideoSchema.path('thumbnail').validate(function (value) { VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid) VideoSchema.methods = { - isOwned: isOwned, - toFormatedJSON: toFormatedJSON, - toRemoteJSON: toRemoteJSON + isOwned, + toFormatedJSON, + toRemoteJSON } VideoSchema.statics = { - getDurationFromFile: getDurationFromFile, - listForApi: listForApi, - listByUrlAndMagnet: listByUrlAndMagnet, - listByUrls: listByUrls, - listOwned: listOwned, - listOwnedByAuthor: listOwnedByAuthor, - listRemotes: listRemotes, - load: load, - search: search, - seedAllExisting: seedAllExisting + getDurationFromFile, + listForApi, + listByUrlAndMagnet, + listByUrls, + listOwned, + listOwnedByAuthor, + listRemotes, + load, + search, + seedAllExisting } VideoSchema.pre('remove', function (next) { diff --git a/server/tests/utils/login.js b/server/tests/utils/login.js index 1a5d75bc4..465564e14 100644 --- a/server/tests/utils/login.js +++ b/server/tests/utils/login.js @@ -3,8 +3,8 @@ const request = require('supertest') const loginUtils = { - login: login, - loginAndGetAccessToken: loginAndGetAccessToken + login, + loginAndGetAccessToken } // ---------------------- Export functions -------------------- diff --git a/server/tests/utils/miscs.js b/server/tests/utils/miscs.js index 5414cd561..4ceff65df 100644 --- a/server/tests/utils/miscs.js +++ b/server/tests/utils/miscs.js @@ -1,7 +1,7 @@ 'use strict' const miscsUtils = { - dateIsValid: dateIsValid + dateIsValid } // ---------------------- Export functions -------------------- diff --git a/server/tests/utils/pods.js b/server/tests/utils/pods.js index 9a9148856..a8551a49d 100644 --- a/server/tests/utils/pods.js +++ b/server/tests/utils/pods.js @@ -3,9 +3,9 @@ const request = require('supertest') const podsUtils = { - getFriendsList: getFriendsList, - makeFriends: makeFriends, - quitFriends: quitFriends + getFriendsList, + makeFriends, + quitFriends } // ---------------------- Export functions -------------------- diff --git a/server/tests/utils/requests.js b/server/tests/utils/requests.js index 410e42c77..b1470814d 100644 --- a/server/tests/utils/requests.js +++ b/server/tests/utils/requests.js @@ -3,9 +3,9 @@ const request = require('supertest') const requestsUtils = { - makePostUploadRequest: makePostUploadRequest, - makePostBodyRequest: makePostBodyRequest, - makePutBodyRequest: makePutBodyRequest + makePostUploadRequest, + makePostBodyRequest, + makePutBodyRequest } // ---------------------- Export functions -------------------- diff --git a/server/tests/utils/servers.js b/server/tests/utils/servers.js index ee7cd8c0a..d62838bc7 100644 --- a/server/tests/utils/servers.js +++ b/server/tests/utils/servers.js @@ -6,9 +6,9 @@ const fork = childProcess.fork const pathUtils = require('path') const serversUtils = { - flushAndRunMultipleServers: flushAndRunMultipleServers, - flushTests: flushTests, - runServer: runServer + flushAndRunMultipleServers, + flushTests, + runServer } // ---------------------- Export functions -------------------- diff --git a/server/tests/utils/users.js b/server/tests/utils/users.js index 0cf4e4adb..2bf9c6e3e 100644 --- a/server/tests/utils/users.js +++ b/server/tests/utils/users.js @@ -3,12 +3,12 @@ const request = require('supertest') const usersUtils = { - createUser: createUser, - getUserInformation: getUserInformation, - getUsersList: getUsersList, - getUsersListPaginationAndSort: getUsersListPaginationAndSort, - removeUser: removeUser, - updateUser: updateUser + createUser, + getUserInformation, + getUsersList, + getUsersListPaginationAndSort, + removeUser, + updateUser } // ---------------------- Export functions -------------------- diff --git a/server/tests/utils/videos.js b/server/tests/utils/videos.js index 90ee9621e..536093db1 100644 --- a/server/tests/utils/videos.js +++ b/server/tests/utils/videos.js @@ -5,17 +5,17 @@ const pathUtils = require('path') const request = require('supertest') const videosUtils = { - getAllVideosListBy: getAllVideosListBy, - getVideo: getVideo, - getVideosList: getVideosList, - getVideosListPagination: getVideosListPagination, - getVideosListSort: getVideosListSort, - removeVideo: removeVideo, - searchVideo: searchVideo, - searchVideoWithPagination: searchVideoWithPagination, - searchVideoWithSort: searchVideoWithSort, - testVideoImage: testVideoImage, - uploadVideo: uploadVideo + getAllVideosListBy, + getVideo, + getVideosList, + getVideosListPagination, + getVideosListSort, + removeVideo, + searchVideo, + searchVideoWithPagination, + searchVideoWithSort, + testVideoImage, + uploadVideo } // ---------------------- Export functions --------------------