From aa64ec7db19df296d3bd32af815de38008334433 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 17 Feb 2017 22:33:41 +0300 Subject: [PATCH 01/17] remove unnecessary code --- src/socket.io/topics/move.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/socket.io/topics/move.js b/src/socket.io/topics/move.js index 25f5f8a19d..938fe22792 100644 --- a/src/socket.io/topics/move.js +++ b/src/socket.io/topics/move.js @@ -23,9 +23,7 @@ module.exports = function (SocketTopics) { if (!canMove) { return next(new Error('[[error:no-privileges]]')); } - next(); - }, - function (next) { + topics.getTopicFields(tid, ['cid', 'slug'], next); }, function (_topicData, next) { From 8dab8864f62c6915bc7751be734030d0931cc0ad Mon Sep 17 00:00:00 2001 From: pichalite Date: Fri, 17 Feb 2017 19:42:02 +0000 Subject: [PATCH 02/17] Add extensions for cropped images --- public/src/modules/pictureCropper.js | 38 ++++++++------- src/file.js | 19 ++++++-- src/user/picture.js | 72 ++++++++++++++++++++-------- 3 files changed, 87 insertions(+), 42 deletions(-) diff --git a/public/src/modules/pictureCropper.js b/public/src/modules/pictureCropper.js index 40be206c19..b04bc48e97 100644 --- a/public/src/modules/pictureCropper.js +++ b/public/src/modules/pictureCropper.js @@ -18,7 +18,7 @@ define('pictureCropper', ['translator', 'cropper'], function (translator, croppe uploadModal = $(uploadModal); uploadModal.modal('show'); - uploadModal.on('hidden.bs.modal', function () { + uploadModal.on('hidden.bs.modal', function () { uploadModal.remove(); }); @@ -31,9 +31,11 @@ define('pictureCropper', ['translator', 'cropper'], function (translator, croppe }); }; - module.handleImageCrop = function (data, callback) { + module.handleImageCrop = function (data, callback) { $('#crop-picture-modal').remove(); - templates.parse('modals/crop_picture', {url: data.url}, function (cropperHtml) { + templates.parse('modals/crop_picture', { + url: data.url + }, function (cropperHtml) { translator.translate(cropperHtml, function (translated) { var cropperModal = $(translated); cropperModal.modal('show'); @@ -58,28 +60,28 @@ define('pictureCropper', ['translator', 'cropper'], function (translator, croppe cropperModal.find('.reset').on('click', function () { cropperTool.reset(); }); - + cropperModal.find('.crop-btn').on('click', function () { $(this).addClass('disabled'); var imageData = data.imageType ? cropperTool.getCroppedCanvas().toDataURL(data.imageType) : cropperTool.getCroppedCanvas().toDataURL(); - + cropperModal.find('#upload-progress-bar').css('width', '100%'); cropperModal.find('#upload-progress-box').show().removeClass('hide'); - + var socketData = {}; socketData[data.paramName] = data.paramValue; socketData['imageData'] = imageData; - - socket.emit(data.socketMethod, socketData, function (err, imageData) { - if (err) { - cropperModal.find('#upload-progress-box').hide(); - cropperModal.find('.upload-btn').removeClass('disabled'); - cropperModal.find('.crop-btn').removeClass('disabled'); - return app.alertError(err.message); - } - callback(imageData.url); - cropperModal.modal('hide'); + socket.emit(data.socketMethod, socketData, function (err, imageData) { + if (err) { + cropperModal.find('#upload-progress-box').hide(); + cropperModal.find('.upload-btn').removeClass('disabled'); + cropperModal.find('.crop-btn').removeClass('disabled'); + return app.alertError(err.message); + } + + callback(imageData.url); + cropperModal.modal('hide'); }); }); @@ -115,8 +117,8 @@ define('pictureCropper', ['translator', 'cropper'], function (translator, croppe return showAlert('error', '[[uploads:select-file-to-upload]]'); } - var file = fileInput[0].files[0]; - var reader = new FileReader(); + var file = fileInput[0].files[0]; + var reader = new FileReader(); var imageUrl; var imageType = file.type; diff --git a/src/file.js b/src/file.js index 5e13c5b2a9..afdcef82bf 100644 --- a/src/file.js +++ b/src/file.js @@ -6,6 +6,7 @@ var path = require('path'); var winston = require('winston'); var jimp = require('jimp'); var mkdirp = require('mkdirp'); +var mime = require('mime'); var utils = require('../public/src/utils'); @@ -13,8 +14,8 @@ var file = {}; file.saveFileToLocal = function (filename, folder, tempPath, callback) { /* - * remarkable doesn't allow spaces in hyperlinks, once that's fixed, remove this. - */ + * remarkable doesn't allow spaces in hyperlinks, once that's fixed, remove this. + */ filename = filename.split('.'); filename.forEach(function (name, idx) { filename[idx] = utils.slugify(name); @@ -100,7 +101,8 @@ file.existsSync = function (path) { var exists = false; try { exists = fs.statSync(path); - } catch(err) { + } + catch (err) { exists = false; } @@ -110,7 +112,8 @@ file.existsSync = function (path) { file.link = function link(filePath, destPath, cb) { if (process.platform === 'win32') { fs.link(filePath, destPath, cb); - } else { + } + else { fs.symlink(filePath, destPath, 'file', cb); } }; @@ -120,4 +123,12 @@ file.linkDirs = function linkDirs(sourceDir, destDir, callback) { fs.symlink(sourceDir, destDir, type, callback); }; +file.typeToExtension = function (type) { + var extension; + if (type) { + extension = '.' + mime.extension(type); + } + return extension; +}; + module.exports = file; diff --git a/src/user/picture.js b/src/user/picture.js index bc0b8f597f..116724e339 100644 --- a/src/user/picture.js +++ b/src/user/picture.js @@ -8,7 +8,6 @@ var nconf = require('nconf'); var crypto = require('crypto'); var winston = require('winston'); var request = require('request'); -var mime = require('mime'); var plugins = require('../plugins'); var file = require('../file'); @@ -43,7 +42,10 @@ module.exports = function (User) { async.waterfall([ function (next) { if (plugins.hasListeners('filter:uploadImage')) { - return plugins.fireHook('filter:uploadImage', {image: picture, uid: updateUid}, next); + return plugins.fireHook('filter:uploadImage', { + image: picture, + uid: updateUid + }, next); } var filename = updateUid + '-profileimg' + (keepAllVersions ? '-' + Date.now() : '') + (convertToPNG ? '.png' : extension); @@ -79,7 +81,10 @@ module.exports = function (User) { }, function (_image, next) { uploadedImage = _image; - User.setUserFields(updateUid, {uploadedpicture: uploadedImage.url, picture: uploadedImage.url}, next); + User.setUserFields(updateUid, { + uploadedpicture: uploadedImage.url, + picture: uploadedImage.url + }, next); }, function (next) { next(null, uploadedImage); @@ -99,7 +104,7 @@ module.exports = function (User) { var uploadSize = parseInt(meta.config.maximumProfileImageSize, 10) || 256; var size = res.headers['content-length']; var type = res.headers['content-type']; - var extension = mime.extension(type); + var extension = file.typeToExtension(type); if (['png', 'jpeg', 'jpg', 'gif'].indexOf(extension) === -1) { return callback(new Error('[[error:invalid-image-extension]]')); @@ -109,12 +114,21 @@ module.exports = function (User) { return callback(new Error('[[error:file-too-big, ' + uploadSize + ']]')); } - var picture = {url: url, name: ''}; - plugins.fireHook('filter:uploadImage', {image: picture, uid: uid}, function (err, image) { + var picture = { + url: url, + name: '' + }; + plugins.fireHook('filter:uploadImage', { + image: picture, + uid: uid + }, function (err, image) { if (err) { return callback(err); } - User.setUserFields(uid, {uploadedpicture: image.url, picture: image.url}); + User.setUserFields(uid, { + uploadedpicture: image.url, + picture: image.url + }); callback(null, image); }); }); @@ -170,10 +184,14 @@ module.exports = function (User) { }; if (plugins.hasListeners('filter:uploadImage')) { - return plugins.fireHook('filter:uploadImage', {image: image, uid: data.uid}, next); + return plugins.fireHook('filter:uploadImage', { + image: image, + uid: data.uid + }, next); } - var filename = data.uid + '-profilecover' + (keepAllVersions ? '-' + Date.now() : ''); + var extension = file.typeToExtension(data.imageData.slice(5, data.imageData.indexOf('base64') - 1)); + var filename = data.uid + '-profilecover' + (keepAllVersions ? '-' + Date.now() : '') + (extension || ''); async.waterfall([ function (next) { file.isFileTypeAllowed(data.file.path, next); @@ -208,20 +226,25 @@ module.exports = function (User) { winston.error(unlinkErr); } - callback(err); // send back the original error + callback(err); // send back the original error }); } if (data.position) { User.updateCoverPosition(data.uid, data.position, function (err) { - callback(err, {url: url}); + callback(err, { + url: url + }); + }); + } + else { + callback(err, { + url: url }); - } else { - callback(err, {url: url}); } }); }; - + User.uploadCroppedPicture = function (data, callback) { var keepAllVersions = parseInt(meta.config['profile:keepAllUserImages'], 10) === 1; var url, md5sum; @@ -229,7 +252,7 @@ module.exports = function (User) { if (!data.imageData) { return callback(new Error('[[error:invalid-data]]')); } - + async.waterfall([ function (next) { var size = data.file ? data.file.size : data.imageData.length; @@ -260,10 +283,14 @@ module.exports = function (User) { }; if (plugins.hasListeners('filter:uploadImage')) { - return plugins.fireHook('filter:uploadImage', {image: image, uid: data.uid}, next); + return plugins.fireHook('filter:uploadImage', { + image: image, + uid: data.uid + }, next); } - var filename = data.uid + '-profileavatar' + (keepAllVersions ? '-' + Date.now() : ''); + var extension = file.typeToExtension(data.imageData.slice(5, data.imageData.indexOf('base64') - 1)); + var filename = data.uid + '-profileavatar' + (keepAllVersions ? '-' + Date.now() : '') + (extension || ''); async.waterfall([ function (next) { file.isFileTypeAllowed(data.file.path, next); @@ -281,7 +308,10 @@ module.exports = function (User) { }, function (uploadData, next) { url = uploadData.url; - User.setUserFields(data.uid, {uploadedpicture: url, picture: url}, next); + User.setUserFields(data.uid, { + uploadedpicture: url, + picture: url + }, next); }, function (next) { fs.unlink(data.file.path, function (err) { @@ -293,10 +323,12 @@ module.exports = function (User) { } ], function (err) { if (err) { - callback(err); // send back the original error + callback(err); // send back the original error } - callback(err, {url: url}); + callback(err, { + url: url + }); }); }; From 4d755bad0c62bb877c9600aa9bb161ac4e0bc72c Mon Sep 17 00:00:00 2001 From: pichalite Date: Fri, 17 Feb 2017 19:57:18 +0000 Subject: [PATCH 03/17] Use typeToExtension from file.js --- src/controllers/uploads.js | 42 ++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/controllers/uploads.js b/src/controllers/uploads.js index 18b0b63dda..eefbb89d85 100644 --- a/src/controllers/uploads.js +++ b/src/controllers/uploads.js @@ -43,7 +43,8 @@ uploadsController.uploadPost = function (req, res, next) { var isImage = uploadedFile.type.match(/image./); if (isImage) { uploadAsImage(req, uploadedFile, next); - } else { + } + else { uploadAsFile(req, uploadedFile, next); } }, next); @@ -59,7 +60,10 @@ function uploadAsImage(req, uploadedFile, callback) { return next(new Error('[[error:no-privileges]]')); } if (plugins.hasListeners('filter:uploadImage')) { - return plugins.fireHook('filter:uploadImage', {image: uploadedFile, uid: req.uid}, callback); + return plugins.fireHook('filter:uploadImage', { + image: uploadedFile, + uid: req.uid + }, callback); } file.isFileTypeAllowed(uploadedFile.path, next); }, @@ -156,7 +160,10 @@ uploadsController.uploadThumb = function (req, res, next) { } if (plugins.hasListeners('filter:uploadImage')) { - return plugins.fireHook('filter:uploadImage', {image: uploadedFile, uid: req.uid}, next); + return plugins.fireHook('filter:uploadImage', { + image: uploadedFile, + uid: req.uid + }, next); } uploadFile(req.uid, uploadedFile, next); @@ -167,11 +174,17 @@ uploadsController.uploadThumb = function (req, res, next) { uploadsController.uploadGroupCover = function (uid, uploadedFile, callback) { if (plugins.hasListeners('filter:uploadImage')) { - return plugins.fireHook('filter:uploadImage', {image: uploadedFile, uid: uid}, callback); + return plugins.fireHook('filter:uploadImage', { + image: uploadedFile, + uid: uid + }, callback); } if (plugins.hasListeners('filter:uploadFile')) { - return plugins.fireHook('filter:uploadFile', {file: uploadedFile, uid: uid}, callback); + return plugins.fireHook('filter:uploadFile', { + file: uploadedFile, + uid: uid + }, callback); } file.isFileTypeAllowed(uploadedFile.path, function (err) { @@ -184,7 +197,10 @@ uploadsController.uploadGroupCover = function (uid, uploadedFile, callback) { function uploadFile(uid, uploadedFile, callback) { if (plugins.hasListeners('filter:uploadFile')) { - return plugins.fireHook('filter:uploadFile', {file: uploadedFile, uid: uid}, callback); + return plugins.fireHook('filter:uploadFile', { + file: uploadedFile, + uid: uid + }, callback); } if (!uploadedFile) { @@ -197,7 +213,7 @@ function uploadFile(uid, uploadedFile, callback) { if (meta.config.hasOwnProperty('allowedFileExtensions')) { var allowed = file.allowedExtensions(); - var extension = typeToExtension(uploadedFile.type); + var extension = file.typeToExtension(uploadedFile.type); if (!extension || (allowed.length > 0 && allowed.indexOf(extension) === -1)) { return callback(new Error('[[error:invalid-file-type, ' + allowed.join(', ') + ']]')); } @@ -207,7 +223,7 @@ function uploadFile(uid, uploadedFile, callback) { } function saveFileToLocal(uploadedFile, callback) { - var extension = typeToExtension(uploadedFile.type); + var extension = file.typeToExtension(uploadedFile.type); if (!extension) { return callback(new Error('[[error:invalid-extension]]')); } @@ -228,14 +244,6 @@ function saveFileToLocal(uploadedFile, callback) { }); } -function typeToExtension(type) { - var extension; - if (type) { - extension = '.' + mime.extension(type); - } - return extension; -} - function deleteTempFiles(files) { async.each(files, function (file, next) { fs.unlink(file.path, function (err) { @@ -247,6 +255,4 @@ function deleteTempFiles(files) { }); } - - module.exports = uploadsController; From 7f4e4c8e2e9822e0f1157b90cf710f366b5a6bca Mon Sep 17 00:00:00 2001 From: pichalite Date: Fri, 17 Feb 2017 20:02:26 +0000 Subject: [PATCH 04/17] Fix styling --- src/controllers/uploads.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/controllers/uploads.js b/src/controllers/uploads.js index eefbb89d85..923e3cf7d8 100644 --- a/src/controllers/uploads.js +++ b/src/controllers/uploads.js @@ -43,8 +43,7 @@ uploadsController.uploadPost = function (req, res, next) { var isImage = uploadedFile.type.match(/image./); if (isImage) { uploadAsImage(req, uploadedFile, next); - } - else { + } else { uploadAsFile(req, uploadedFile, next); } }, next); From aacd8a242217e1182610646991fb5d855f58288c Mon Sep 17 00:00:00 2001 From: pichalite Date: Fri, 17 Feb 2017 20:36:13 +0000 Subject: [PATCH 05/17] Fix return and tests --- src/user/picture.js | 8 ++++---- test/mocha.opts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/user/picture.js b/src/user/picture.js index 116724e339..af615a1cc1 100644 --- a/src/user/picture.js +++ b/src/user/picture.js @@ -8,6 +8,7 @@ var nconf = require('nconf'); var crypto = require('crypto'); var winston = require('winston'); var request = require('request'); +var mime = require('mime'); var plugins = require('../plugins'); var file = require('../file'); @@ -104,7 +105,7 @@ module.exports = function (User) { var uploadSize = parseInt(meta.config.maximumProfileImageSize, 10) || 256; var size = res.headers['content-length']; var type = res.headers['content-type']; - var extension = file.typeToExtension(type); + var extension = mime.extension(type); if (['png', 'jpeg', 'jpg', 'gif'].indexOf(extension) === -1) { return callback(new Error('[[error:invalid-image-extension]]')); @@ -236,8 +237,7 @@ module.exports = function (User) { url: url }); }); - } - else { + } else { callback(err, { url: url }); @@ -323,7 +323,7 @@ module.exports = function (User) { } ], function (err) { if (err) { - callback(err); // send back the original error + return callback(err); // send back the original error } callback(err, { diff --git a/test/mocha.opts b/test/mocha.opts index 49399dd418..9455d82707 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,2 +1,2 @@ --reporter dot ---timeout 15000 +--timeout 25000 From b33d34f7cf303c0b5d8d7645a889c362203bb0d1 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 18 Feb 2017 20:27:58 +0300 Subject: [PATCH 06/17] refactor user/picture.js --- src/user/picture.js | 277 +++++++++++++++++++------------------------- test/user.js | 56 ++++++--- 2 files changed, 156 insertions(+), 177 deletions(-) diff --git a/src/user/picture.js b/src/user/picture.js index af615a1cc1..ceb018b917 100644 --- a/src/user/picture.js +++ b/src/user/picture.js @@ -98,41 +98,41 @@ module.exports = function (User) { return callback(new Error('[[error:no-plugin]]')); } - request.head(url, function (err, res) { - if (err) { - return callback(err); - } - var uploadSize = parseInt(meta.config.maximumProfileImageSize, 10) || 256; - var size = res.headers['content-length']; - var type = res.headers['content-type']; - var extension = mime.extension(type); + async.waterfall([ + function (next) { + request.head(url, next); + }, + function (res, body, next) { + var uploadSize = parseInt(meta.config.maximumProfileImageSize, 10) || 256; + var size = res.headers['content-length']; + var type = res.headers['content-type']; + var extension = mime.extension(type); - if (['png', 'jpeg', 'jpg', 'gif'].indexOf(extension) === -1) { - return callback(new Error('[[error:invalid-image-extension]]')); - } - - if (size > uploadSize * 1024) { - return callback(new Error('[[error:file-too-big, ' + uploadSize + ']]')); - } - - var picture = { - url: url, - name: '' - }; - plugins.fireHook('filter:uploadImage', { - image: picture, - uid: uid - }, function (err, image) { - if (err) { - return callback(err); + if (['png', 'jpeg', 'jpg', 'gif'].indexOf(extension) === -1) { + return callback(new Error('[[error:invalid-image-extension]]')); } + + if (size > uploadSize * 1024) { + return callback(new Error('[[error:file-too-big, ' + uploadSize + ']]')); + } + + plugins.fireHook('filter:uploadImage', { + uid: uid, + image: { + url: url, + name: '' + } + }, next); + }, + function (image, next) { User.setUserFields(uid, { uploadedpicture: image.url, picture: image.url + }, function (err) { + next(err, image); }); - callback(null, image); - }); - }); + } + ], callback); }; User.updateCoverPosition = function (uid, position, callback) { @@ -140,8 +140,12 @@ module.exports = function (User) { }; User.updateCoverPicture = function (data, callback) { - var keepAllVersions = parseInt(meta.config['profile:keepAllUserImages'], 10) === 1; - var url, md5sum; + + var url; + var image = { + name: 'profileCover', + uid: data.uid + }; if (!data.imageData && data.position) { return User.updateCoverPosition(data.uid, data.position, callback); @@ -160,94 +164,42 @@ module.exports = function (User) { } if (data.file) { - return next(); + return setImmediate(next, null, data.file.path); } - md5sum = crypto.createHash('md5'); - md5sum.update(data.imageData); - md5sum = md5sum.digest('hex'); - - data.file = { - path: path.join(os.tmpdir(), md5sum) - }; - - var buffer = new Buffer(data.imageData.slice(data.imageData.indexOf('base64') + 7), 'base64'); - - fs.writeFile(data.file.path, buffer, { - encoding: 'base64' - }, next); + saveImageDataToTempFile(data.imageData, next); }, - function (next) { - var image = { - name: 'profileCover', - path: data.file.path, - uid: data.uid - }; + function (path, next) { + image.path = path; - if (plugins.hasListeners('filter:uploadImage')) { - return plugins.fireHook('filter:uploadImage', { - image: image, - uid: data.uid - }, next); - } - - var extension = file.typeToExtension(data.imageData.slice(5, data.imageData.indexOf('base64') - 1)); - var filename = data.uid + '-profilecover' + (keepAllVersions ? '-' + Date.now() : '') + (extension || ''); - async.waterfall([ - function (next) { - file.isFileTypeAllowed(data.file.path, next); - }, - function (next) { - file.saveFileToLocal(filename, 'profile', image.path, next); - }, - function (upload, next) { - next(null, { - url: nconf.get('relative_path') + upload.url, - name: image.name - }); - } - ], next); + uploadProfileOrCover('profilecover', image, data.imageData, next); }, function (uploadData, next) { url = uploadData.url; User.setUserField(data.uid, 'cover:url', uploadData.url, next); }, function (next) { - fs.unlink(data.file.path, function (err) { - if (err) { - winston.error(err); - } - next(); - }); + if (data.position) { + User.updateCoverPosition(data.uid, data.position, next); + } else { + setImmediate(next); + } } ], function (err) { - if (err) { - return fs.unlink(data.file.path, function (unlinkErr) { - if (unlinkErr) { - winston.error(unlinkErr); - } - - callback(err); // send back the original error - }); - } - - if (data.position) { - User.updateCoverPosition(data.uid, data.position, function (err) { - callback(err, { - url: url - }); - }); - } else { - callback(err, { - url: url - }); - } + deleteFile(image.path); + callback(err, { + url: url + }); }); }; User.uploadCroppedPicture = function (data, callback) { - var keepAllVersions = parseInt(meta.config['profile:keepAllUserImages'], 10) === 1; - var url, md5sum; + + var url; + var image = { + name: 'profileAvatar', + uid: data.uid + }; if (!data.imageData) { return callback(new Error('[[error:invalid-data]]')); @@ -255,56 +207,18 @@ module.exports = function (User) { async.waterfall([ function (next) { - var size = data.file ? data.file.size : data.imageData.length; + var size = data.imageData.length; var uploadSize = parseInt(meta.config.maximumProfileImageSize, 10) || 256; if (size > uploadSize * 1024) { return next(new Error('[[error:file-too-big, ' + meta.config.maximumProfileImageSize + ']]')); } - md5sum = crypto.createHash('md5'); - md5sum.update(data.imageData); - md5sum = md5sum.digest('hex'); - - data.file = { - path: path.join(os.tmpdir(), md5sum) - }; - - var buffer = new Buffer(data.imageData.slice(data.imageData.indexOf('base64') + 7), 'base64'); - - fs.writeFile(data.file.path, buffer, { - encoding: 'base64' - }, next); + saveImageDataToTempFile(data.imageData, next); }, - function (next) { - var image = { - name: 'profileAvatar', - path: data.file.path, - uid: data.uid - }; + function (path, next) { + image.path = path; - if (plugins.hasListeners('filter:uploadImage')) { - return plugins.fireHook('filter:uploadImage', { - image: image, - uid: data.uid - }, next); - } - - var extension = file.typeToExtension(data.imageData.slice(5, data.imageData.indexOf('base64') - 1)); - var filename = data.uid + '-profileavatar' + (keepAllVersions ? '-' + Date.now() : '') + (extension || ''); - async.waterfall([ - function (next) { - file.isFileTypeAllowed(data.file.path, next); - }, - function (next) { - file.saveFileToLocal(filename, 'profile', image.path, next); - }, - function (upload, next) { - next(null, { - url: nconf.get('relative_path') + upload.url, - name: image.name - }); - } - ], next); + uploadProfileOrCover('profileavatar', image, data.imageData, next); }, function (uploadData, next) { url = uploadData.url; @@ -312,26 +226,73 @@ module.exports = function (User) { uploadedpicture: url, picture: url }, next); - }, - function (next) { - fs.unlink(data.file.path, function (err) { - if (err) { - winston.error(err); - } - next(); - }); } ], function (err) { - if (err) { - return callback(err); // send back the original error - } - + deleteFile(image.path); callback(err, { url: url }); }); }; + function saveImageDataToTempFile(imageData, callback) { + var filename = crypto.createHash('md5').update(imageData).digest('hex'); + var filepath = path.join(os.tmpdir(), filename); + + var buffer = new Buffer(imageData.slice(imageData.indexOf('base64') + 7), 'base64'); + + fs.writeFile(filepath, buffer, { + encoding: 'base64' + }, function (err) { + callback(err, filepath); + }); + } + + function uploadProfileOrCover(type, image, imageData, callback) { + if (plugins.hasListeners('filter:uploadImage')) { + return plugins.fireHook('filter:uploadImage', { + image: image, + uid: image.uid + }, callback); + } + var filename = generateProfileImageFilename(image.uid, type, imageData); + saveFileToLocal(filename, image, callback); + } + + function generateProfileImageFilename(uid, type, imageData) { + var extension = file.typeToExtension(imageData.slice(5, imageData.indexOf('base64') - 1)); + var keepAllVersions = parseInt(meta.config['profile:keepAllUserImages'], 10) === 1; + var filename = uid + '-' + type + (keepAllVersions ? '-' + Date.now() : '') + (extension || ''); + return filename; + } + + function saveFileToLocal(filename, image, callback) { + async.waterfall([ + function (next) { + file.isFileTypeAllowed(image.path, next); + }, + function (next) { + file.saveFileToLocal(filename, 'profile', image.path, next); + }, + function (upload, next) { + next(null, { + url: nconf.get('relative_path') + upload.url, + name: image.name + }); + } + ], callback); + } + + function deleteFile(path) { + if (path) { + fs.unlink(path, function (err) { + if (err) { + winston.error(err); + } + }); + } + } + User.removeCoverPicture = function (data, callback) { db.deleteObjectFields('user:' + data.uid, ['cover:url', 'cover:position'], callback); }; diff --git a/test/user.js b/test/user.js index 43b5b3f847..6342509ecf 100644 --- a/test/user.js +++ b/test/user.js @@ -448,6 +448,24 @@ describe('User', function () { }); }); + it('should upload cropped profile picture', function (done) { + var imageData = 'data:image/png;base64,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'; + var socketUser = require('../src/socket.io/user'); + socketUser.uploadCroppedPicture({uid: uid}, {uid: uid, imageData: imageData}, function (err, result) { + assert.ifError(err); + console.log(result); + assert(result.url); + db.getObjectFields('user:' + uid, ['uploadedpicture', 'picture'], function (err, data) { + assert.ifError(err); + console.log(data); + //{ url: '/assets/uploads/profile/4-profileavatar.png' } + assert.equal(result.url, data.uploadedpicture); + assert.equal(result.url, data.picture); + done(); + }); + }); + }); + it('should remove cover image', function (done) { io.emit('user.removeCover', {uid: uid}, function (err) { assert.ifError(err); @@ -508,7 +526,7 @@ describe('User', function () { done(); }); }); - + it('should return error if profile image uploads disabled', function (done) { meta.config.allowProfileImageUploads = 0; var path = require('path'); @@ -517,12 +535,12 @@ describe('User', function () { size: 7189, name: 'logo.png' }; - User.uploadPicture(uid, picture, function (err, uploadedPicture) { + User.uploadPicture(uid, picture, function (err) { assert.equal(err.message, '[[error:profile-image-uploads-disabled]]'); done(); }); }); - + it('should return error if profile image is too big', function (done) { meta.config.allowProfileImageUploads = 1; var path = require('path'); @@ -531,12 +549,12 @@ describe('User', function () { size: 265000, name: 'logo.png' }; - User.uploadPicture(uid, picture, function (err, uploadedPicture) { + User.uploadPicture(uid, picture, function (err) { assert.equal(err.message, '[[error:file-too-big, 256]]'); done(); }); }); - + it('should return error if profile image file has no extension', function (done) { var path = require('path'); var picture = { @@ -544,64 +562,64 @@ describe('User', function () { size: 7189, name: 'logo' }; - User.uploadPicture(uid, picture, function (err, uploadedPicture) { + User.uploadPicture(uid, picture, function (err) { assert.equal(err.message, '[[error:invalid-image-extension]]'); done(); }); }); - + it('should return error if no plugins listening for filter:uploadImage when uploading from url', function (done) { var url = nconf.get('url') + '/logo.png'; - User.uploadFromUrl(uid, url, function (err, uploadedPicture) { + User.uploadFromUrl(uid, url, function (err) { assert.equal(err.message, '[[error:no-plugin]]'); done(); }); }); - + it('should return error if the extension is invalid when uploading from url', function (done) { var url = nconf.get('url') + '/favicon.ico'; - + function filterMethod(data, callback) { data.foo += 5; callback(null, data); } plugins.registerHook('test-plugin', {hook: 'filter:uploadImage', method: filterMethod}); - - User.uploadFromUrl(uid, url, function (err, uploadedPicture) { + + User.uploadFromUrl(uid, url, function (err) { assert.equal(err.message, '[[error:invalid-image-extension]]'); done(); }); }); - + it('should return error if the file is too big when uploading from url', function (done) { var url = nconf.get('url') + '/logo.png'; meta.config.maximumProfileImageSize = 1; - + function filterMethod(data, callback) { data.foo += 5; callback(null, data); } plugins.registerHook('test-plugin', {hook: 'filter:uploadImage', method: filterMethod}); - - User.uploadFromUrl(uid, url, function (err, uploadedPicture) { + + User.uploadFromUrl(uid, url, function (err) { assert.equal(err.message, '[[error:file-too-big, ' + meta.config.maximumProfileImageSize + ']]'); done(); }); }); - + it('should upload picture when uploading from url', function (done) { var url = nconf.get('url') + '/logo.png'; meta.config.maximumProfileImageSize = ''; - + function filterMethod(data, callback) { data.foo += 5; callback(null, {url: url}); } plugins.registerHook('test-plugin', {hook: 'filter:uploadImage', method: filterMethod}); - + User.uploadFromUrl(uid, url, function (err, uploadedPicture) { assert.ifError(err); assert.equal(uploadedPicture.url, url); From 7842c3411cde41e0ab548fe296b45517d2f33146 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 18 Feb 2017 20:52:45 +0300 Subject: [PATCH 07/17] remove logs --- test/user.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/user.js b/test/user.js index 6342509ecf..62a4a0401f 100644 --- a/test/user.js +++ b/test/user.js @@ -453,12 +453,9 @@ describe('User', function () { var socketUser = require('../src/socket.io/user'); socketUser.uploadCroppedPicture({uid: uid}, {uid: uid, imageData: imageData}, function (err, result) { assert.ifError(err); - console.log(result); assert(result.url); db.getObjectFields('user:' + uid, ['uploadedpicture', 'picture'], function (err, data) { assert.ifError(err); - console.log(data); - //{ url: '/assets/uploads/profile/4-profileavatar.png' } assert.equal(result.url, data.uploadedpicture); assert.equal(result.url, data.picture); done(); From 1b43faba1ab0680b6bc753dd596d0c2bf8c65e44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sun, 19 Feb 2017 02:44:38 +0300 Subject: [PATCH 08/17] closes #5441 --- src/start.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/start.js b/src/start.js index 0a8ed18e67..06ffc93589 100644 --- a/src/start.js +++ b/src/start.js @@ -99,6 +99,7 @@ function setupConfigs() { nconf.set('use_port', !!urlObject.port); nconf.set('relative_path', relativePath); nconf.set('port', urlObject.port || nconf.get('port') || nconf.get('PORT') || (nconf.get('PORT_ENV_VAR') ? nconf.get(nconf.get('PORT_ENV_VAR')) : false) || 4567); + nconf.set('upload_url', '/assets/uploads'); } function printStartupInfo() { From 2e27ce640c5457e54fea83a3b5a097926f7b8165 Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Sun, 19 Feb 2017 09:25:24 +0000 Subject: [PATCH 09/17] Latest translations and fallbacks --- public/language/ja/topic.json | 8 ++++---- public/language/tr/admin/settings/uploads.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/public/language/ja/topic.json b/public/language/ja/topic.json index 6f7cac850b..3a70be2b0f 100644 --- a/public/language/ja/topic.json +++ b/public/language/ja/topic.json @@ -107,10 +107,10 @@ "more_guests": "ゲストさんが%1人", "users_and_others": "%1と他は%2", "sort_by": "並び替え", - "oldest_to_newest": "新しい順に", - "newest_to_oldest": "古い順に", - "most_votes": "最高評価", - "most_posts": "最高投稿", + "oldest_to_newest": "古い\bものから新しい順", + "newest_to_oldest": "新しいものから古い順", + "most_votes": "最も投票された順", + "most_posts": "最も投稿された順", "stale.title": "新しいスレッドを作りますか?", "stale.warning": "あなたが返信しようとしてるスレッドが古いスレッドです。新しいスレッドを作って、そしてこのスレッドが参考として入れた方を勧めます。そうしますか?", "stale.create": "新しいスレッドを作ります。", diff --git a/public/language/tr/admin/settings/uploads.json b/public/language/tr/admin/settings/uploads.json index b83ea976ce..4daa55400b 100644 --- a/public/language/tr/admin/settings/uploads.json +++ b/public/language/tr/admin/settings/uploads.json @@ -1,7 +1,7 @@ { "posts": "İletiler", "allow-files": "Allow users to upload regular files", - "private": "Make uploaded files private", + "private": "Yüklenen dosyaları gizli yap", "max-image-width": "Resize images down to specified width (in pixels)", "max-image-width-help": "(in pixels, default: 760 pixels, set to 0 to disable)", "max-file-size": "Maksimum Dosya Boyutu (KiB)", @@ -19,10 +19,10 @@ "profile-image-dimension-help": "(in pixels, default: 128 pixels)", "max-profile-image-size": "Maximum Profile Image File Size", "max-profile-image-size-help": "(in kilobytes, default: 256 KiB)", - "max-cover-image-size": "Maximum Cover Image File Size", + "max-cover-image-size": "Maksimum Kapak Görseli Dosya Boyutu", "max-cover-image-size-help": "(in kilobytes, default: 2,048 KiB)", "keep-all-user-images": "Keep old versions of avatars and profile covers on the server", - "profile-covers": "Profile Covers", - "default-covers": "Default Cover Images", + "profile-covers": "Profil Kapakları", + "default-covers": "Varsayılan Kapak Görseli", "default-covers-help": "Add comma-separated default cover images for accounts that don't have an uploaded cover image" } From f302c0d739472417c04aead83729b41d3b807fd8 Mon Sep 17 00:00:00 2001 From: Anil Mandepudi Date: Sun, 19 Feb 2017 08:01:38 -0800 Subject: [PATCH 10/17] Update gruntfile to build on initialization (#5458) * Update gruntfile to build on initialization * Skip option skips build --- Gruntfile.js | 57 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 85ea76938f..f3c4f1723e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,24 +1,24 @@ "use strict"; -var fork = require('child_process').fork, - env = process.env, - worker, updateWorker, - incomplete = [], - running = 0; - +var fork = require('child_process').fork; +var env = process.env; +var worker, updateWorker, initWorker; +var incomplete = []; +var running = 0; module.exports = function (grunt) { var args = []; + var initArgs = ['--build']; if (!grunt.option('verbose')) { args.push('--log-level=info'); + initArgs.push('--log-level=info'); } function update(action, filepath, target) { - var updateArgs = args.slice(), - fromFile = '', - compiling = '', - time = Date.now(); - + var updateArgs = args.slice(); + var compiling = ''; + var time = Date.now(); + if (target === 'lessUpdated_Client') { compiling = 'clientCSS'; } else if (target === 'lessUpdated_Admin') { @@ -44,12 +44,16 @@ module.exports = function (grunt) { if (updateWorker) { updateWorker.kill('SIGKILL'); } - updateWorker = fork('app.js', updateArgs, { env: env }); + updateWorker = fork('app.js', updateArgs, { + env: env + }); ++running; updateWorker.on('exit', function () { --running; if (running === 0) { - worker = fork('app.js', args, { env: env }); + worker = fork('app.js', args, { + env: env + }); worker.on('message', function () { if (incomplete.length) { incomplete = []; @@ -131,15 +135,24 @@ module.exports = function (grunt) { grunt.loadNpmTasks('grunt-contrib-watch'); - if (grunt.option('skip')) { - grunt.registerTask('default', ['watch:serverUpdated']); - } else { - grunt.registerTask('default', ['watch']); - } - - + grunt.registerTask('default', ['watch']); env.NODE_ENV = 'development'; - worker = fork('app.js', args, { env: env }); + if (grunt.option('skip')) { + worker = fork('app.js', args, { + env: env + }); + } else { + initWorker = fork('app.js', initArgs, { + env: env + }); + + initWorker.on('exit', function () { + worker = fork('app.js', args, { + env: env + }); + }); + } + grunt.event.on('watch', update); -}; \ No newline at end of file +}; From 7d8477289d7a0b32fbd97bbb43cc8d77e4d2f999 Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Mon, 20 Feb 2017 09:22:34 +0000 Subject: [PATCH 11/17] Latest translations and fallbacks --- public/language/sk/admin/advanced/errors.json | 8 +-- public/language/sk/admin/advanced/events.json | 6 +- public/language/sk/admin/advanced/logs.json | 10 +-- .../language/sk/admin/appearance/skins.json | 14 ++-- .../language/sk/admin/appearance/themes.json | 18 ++--- .../language/sk/admin/general/dashboard.json | 70 +++++++++---------- public/language/sk/admin/manage/tags.json | 2 +- 7 files changed, 64 insertions(+), 64 deletions(-) diff --git a/public/language/sk/admin/advanced/errors.json b/public/language/sk/admin/advanced/errors.json index 1b12ed100f..02a4a0c461 100644 --- a/public/language/sk/admin/advanced/errors.json +++ b/public/language/sk/admin/advanced/errors.json @@ -1,14 +1,14 @@ { "figure-x": "Znázorniť %1", "error-events-per-day": "%1 events per day", - "error.404": "404 Not Found", - "error.503": "503 Service Unavailable", + "error.404": "404 Nenájdené", + "error.503": "503 Služba nie je k dispozícií", "manage-error-log": "Manage Error Log", "export-error-log": "Export Error Log (CSV)", "clear-error-log": "Clear Error Log", "route": "Route", "count": "Count", - "no-routes-not-found": "Hooray! No 404 errors!", + "no-routes-not-found": "Hurá! Žiadne chyby 404!", "clear404-confirm": "Are you sure you wish to clear the 404 error logs?", - "clear404-success": "\"404 Not Found\" errors cleared" + "clear404-success": "Chybné hlásenia \"404 Nenájdené\" vyčistené" } \ No newline at end of file diff --git a/public/language/sk/admin/advanced/events.json b/public/language/sk/admin/advanced/events.json index cce4546e34..ef963e4ce2 100644 --- a/public/language/sk/admin/advanced/events.json +++ b/public/language/sk/admin/advanced/events.json @@ -1,6 +1,6 @@ { "events": "Udalosti", - "no-events": "There are no events", - "control-panel": "Events Control Panel", - "delete-events": "Delete Events" + "no-events": "Zatiaľ neexistujô žiadne udalosti", + "control-panel": "Ovládací panel udalostí", + "delete-events": "Odstrániť udalosť" } \ No newline at end of file diff --git a/public/language/sk/admin/advanced/logs.json b/public/language/sk/admin/advanced/logs.json index 0dd71a3f30..1c30f4dca7 100644 --- a/public/language/sk/admin/advanced/logs.json +++ b/public/language/sk/admin/advanced/logs.json @@ -1,7 +1,7 @@ { - "logs": "Protokoly", - "control-panel": "Logs Control Panel", - "reload": "Reload Logs", - "clear": "Clear Logs", - "clear-success": "Logs Cleared!" + "logs": "Záznamy", + "control-panel": "Ovládací panel záznamov", + "reload": "Znovu načítať záznamy", + "clear": "Vyčistiť záznamy", + "clear-success": "Záznamy vyčistené!" } \ No newline at end of file diff --git a/public/language/sk/admin/appearance/skins.json b/public/language/sk/admin/appearance/skins.json index 4db6fbdd8a..f7440bfa8e 100644 --- a/public/language/sk/admin/appearance/skins.json +++ b/public/language/sk/admin/appearance/skins.json @@ -1,9 +1,9 @@ { - "loading": "Loading Skins...", - "homepage": "Homepage", - "select-skin": "Select Skin", - "current-skin": "Current Skin", - "skin-updated": "Skin Updated", - "applied-success": "%1 skin was succesfully applied", - "revert-success": "Skin reverted to base colours" + "loading": "Načítať vzhľady...", + "homepage": "Domovska stránka", + "select-skin": "Vybrať vzhľad", + "current-skin": "Aktuálny vzhľad", + "skin-updated": "Vzhľad aktualizovaný", + "applied-success": "%1 vzhľad bol úspešne aplikovaný", + "revert-success": "Vzhľad bol obnovený do základných farieb" } \ No newline at end of file diff --git a/public/language/sk/admin/appearance/themes.json b/public/language/sk/admin/appearance/themes.json index 3148a01337..34dca8603e 100644 --- a/public/language/sk/admin/appearance/themes.json +++ b/public/language/sk/admin/appearance/themes.json @@ -1,11 +1,11 @@ { - "checking-for-installed": "Checking for installed themes...", - "homepage": "Homepage", - "select-theme": "Select Theme", - "current-theme": "Current Theme", - "no-themes": "No installed themes found", - "revert-confirm": "Are you sure you wish to restore the default NodeBB theme?", - "theme-changed": "Theme Changed", - "revert-success": "You have successfully reverted your NodeBB back to it's default theme.", - "restart-to-activate": "Please restart your NodeBB to fully activate this theme" + "checking-for-installed": "Kontrola nainštalovaných motívov...", + "homepage": "Domovská stránka", + "select-theme": "Vybrať motív", + "current-theme": "Aktuálny motív", + "no-themes": "Žiadne nainštalované motívy neboli nájdené", + "revert-confirm": "Ste si istý, že chcete obnoviť predvolený NodeBB motív?", + "theme-changed": "Motív zmenený", + "revert-success": "Úspešne sa Vám podarilo obnoviť Váš NodeBB do predvoleného motívu.", + "restart-to-activate": "Prosím, reštartujte Váš NodeBB pre úplne aktivovanie tohto motívu." } \ No newline at end of file diff --git a/public/language/sk/admin/general/dashboard.json b/public/language/sk/admin/general/dashboard.json index a70dda322e..12a34ba046 100644 --- a/public/language/sk/admin/general/dashboard.json +++ b/public/language/sk/admin/general/dashboard.json @@ -1,20 +1,20 @@ { - "forum-traffic": "Forum Traffic", - "page-views": "Page Views", - "unique-visitors": "Unique Visitors", - "users": "Users", - "posts": "Posts", - "topics": "Topics", - "page-views-last-month": "Page views Last Month", - "page-views-this-month": "Page views This Month", - "page-views-last-day": "Page views in last 24 hours", + "forum-traffic": "Prevádzka fóra", + "page-views": "Zobrazenia stránok", + "unique-visitors": "Unikátne návštevy", + "users": "Užívatelia", + "posts": "Príspevky", + "topics": "Témy", + "page-views-last-month": "Zobrazenia stránok za posledný mesiac", + "page-views-this-month": "Zobrazenia stránok za tento mesiac", + "page-views-last-day": "Zobrazenia stránok za posledných 24 hodín", - "stats.day": "Day", - "stats.week": "Week", - "stats.month": "Month", - "stats.all": "All Time", + "stats.day": "Deň", + "stats.week": "Týždeň", + "stats.month": "Mesiac", + "stats.all": "Celé obdobie", - "updates": "Updates", + "updates": "Aktualizácie", "running-version": "You are running NodeBB v%1.", "keep-updated": "Always make sure that your NodeBB is up to date for the latest security patches and bug fixes.", "up-to-date": "

You are up-to-date

", @@ -22,42 +22,42 @@ "prerelease-upgrade-available": "

This is an outdated pre-release version of NodeBB. A new version (v%1) has been released. Consider upgrading your NodeBB.

", "prerelease-warning": "

This is a pre-release version of NodeBB. Unintended bugs may occur.

", - "notices": "Notices", - "restart-not-required": "Restart not required", - "restart-required": "Restart required", - "search-plugin-installed": "Search Plugin installed", - "search-plugin-not-installed": "Search Plugin not installed", + "notices": "Upozornenie", + "restart-not-required": "Reštart nie je potrebný", + "restart-required": "Reštart je potrebný", + "search-plugin-installed": "Vyhľadávací doplnok bol nainštalovaný", + "search-plugin-not-installed": "Vyhľadávací doplnok nebol nainštalovaný", "search-plugin-tooltip": "Install a search plugin from the plugin page in order to activate search functionality", "control-panel": "System Control", - "reload": "Reload", - "restart": "Restart", + "reload": "Obnoviť", + "restart": "Reštartovať", "restart-warning": "Reloading or Restarting your NodeBB will drop all existing connections for a few seconds.", "maintenance-mode": "Maintenance Mode", "maintenance-mode-title": "Click here to set up maintenance mode for NodeBB", "realtime-chart-updates": "Realtime Chart Updates", - "active-users": "Active Users", - "active-users.users": "Users", - "active-users.guests": "Guests", - "active-users.total": "Total", - "active-users.connections": "Connections", + "active-users": "Aktívny užívatelia", + "active-users.users": "Užívatelia", + "active-users.guests": "Hostia", + "active-users.total": "Celkovo", + "active-users.connections": "Pripojení", - "anonymous-registered-users": "Anonymous vs Registered Users", - "anonymous": "Anonymous", - "registered": "Registered", + "anonymous-registered-users": "Neznámy vs Zaregistrovaný užívatelia", + "anonymous": "Neznámy", + "registered": "Zaregistrovaný", "user-presence": "User Presence", "on-categories": "On categories list", "reading-posts": "Reading posts", "browsing-topics": "Browsing topics", - "recent": "Recent", - "unread": "Unread", + "recent": "Nedávne", + "unread": "Neprečitané", "high-presence-topics": "High Presence Topics", - "graphs.page-views": "Page Views", - "graphs.unique-visitors": "Unique Visitors", - "graphs.registered-users": "Registered Users", - "graphs.anonymous-users": "Anonymous Users" + "graphs.page-views": "Zobrazenia stránok", + "graphs.unique-visitors": "Unikátny navštevníci", + "graphs.registered-users": "Zarestrovaný užívatelia", + "graphs.anonymous-users": "Neznámy užívatelia" } diff --git a/public/language/sk/admin/manage/tags.json b/public/language/sk/admin/manage/tags.json index db40e9f098..775a9aed63 100644 --- a/public/language/sk/admin/manage/tags.json +++ b/public/language/sk/admin/manage/tags.json @@ -6,7 +6,7 @@ "description": "Select tags via clicking and/or dragging, use shift to select multiple.", "create": "Create Tag", "modify": "Modify Tags", - "delete": "Delete Selected Tags", + "delete": "Odstrániť vybraté značky", "search": "Search for tags...", "settings": "Click here to visit the tag settings page.", "name": "Tag Name", From ef90702049a023dc0f0c633879a542691370393e Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 20 Feb 2017 21:27:56 +0300 Subject: [PATCH 12/17] closes #5459 --- src/groups/cover.js | 21 +---- src/image.js | 37 ++++++-- src/user/picture.js | 192 ++++++++++++++++------------------------- test/files/503.html | 177 +++++++++++++++++++++++++++++++++++++ test/files/favicon.ico | Bin 0 -> 1150 bytes test/files/test.png | Bin 0 -> 7189 bytes test/groups.js | 4 +- test/uploads.js | 16 ++-- test/user.js | 46 +++++----- 9 files changed, 321 insertions(+), 172 deletions(-) create mode 100644 test/files/503.html create mode 100644 test/files/favicon.ico create mode 100644 test/files/test.png diff --git a/src/groups/cover.js b/src/groups/cover.js index 3512a235d5..8f18c60ad7 100644 --- a/src/groups/cover.js +++ b/src/groups/cover.js @@ -10,6 +10,7 @@ var mime = require('mime'); var winston = require('winston'); var db = require('../database'); +var image = require('../image'); var uploadsController = require('../controllers/uploads'); module.exports = function (Groups) { @@ -37,7 +38,7 @@ module.exports = function (Groups) { if (tempPath) { return next(null, tempPath); } - writeImageDataToFile(data.imageData, next); + image.writeImageDataToTempFile(data.imageData, next); }, function (_tempPath, next) { tempPath = _tempPath; @@ -97,24 +98,6 @@ module.exports = function (Groups) { }); } - function writeImageDataToFile(imageData, callback) { - // Calculate md5sum of image - // This is required because user data can be private - var md5sum = crypto.createHash('md5'); - md5sum.update(imageData); - md5sum = md5sum.digest('hex'); - - // Save image - var tempPath = path.join(nconf.get('upload_path'), md5sum + '.png'); - var buffer = new Buffer(imageData.slice(imageData.indexOf('base64') + 7), 'base64'); - - fs.writeFile(tempPath, buffer, { - encoding: 'base64' - }, function (err) { - callback(err, tempPath); - }); - } - Groups.removeCover = function (data, callback) { db.deleteObjectFields('group:' + data.groupName, ['cover:url', 'cover:thumb:url', 'cover:position'], callback); }; diff --git a/src/image.js b/src/image.js index 0ab68e5a23..7b428f2331 100644 --- a/src/image.js +++ b/src/image.js @@ -1,8 +1,13 @@ 'use strict'; +var os = require('os'); var fs = require('fs'); +var path = require('path'); var Jimp = require('jimp'); var async = require('async'); +var crypto = require('crypto'); + +var file = require('./file'); var plugins = require('./plugins'); var image = module.exports; @@ -65,9 +70,6 @@ image.resizeImage = function (data, callback) { } }, function (image, next) { - if (data.write === false) { - return next(); - } image.write(data.target || data.path, next); } ], function (err) { @@ -83,7 +85,7 @@ image.normalise = function (path, extension, callback) { path: path, extension: extension }, function (err) { - callback(err); + callback(err, path + '.png'); }); } else { new Jimp(path, function (err, image) { @@ -91,7 +93,7 @@ image.normalise = function (path, extension, callback) { return callback(err); } image.write(path + '.png', function (err) { - callback(err); + callback(err, path + '.png'); }); }); } @@ -116,3 +118,28 @@ image.convertImageToBase64 = function (path, callback) { callback(err, data ? data.toString('base64') : null); }); }; + +image.mimeFromBase64 = function (imageData) { + return imageData.slice(5, imageData.indexOf('base64') - 1); +}; + +image.extensionFromBase64 = function (imageData) { + return file.typeToExtension(image.mimeFromBase64(imageData)); +}; + +image.writeImageDataToTempFile = function (imageData, callback) { + var filename = crypto.createHash('md5').update(imageData).digest('hex'); + + var type = image.mimeFromBase64(imageData); + var extension = file.typeToExtension(type); + + var filepath = path.join(os.tmpdir(), filename + extension); + + var buffer = new Buffer(imageData.slice(imageData.indexOf('base64') + 7), 'base64'); + + fs.writeFile(filepath, buffer, { + encoding: 'base64' + }, function (err) { + callback(err, filepath); + }); +}; \ No newline at end of file diff --git a/src/user/picture.js b/src/user/picture.js index ceb018b917..09b7d636e8 100644 --- a/src/user/picture.js +++ b/src/user/picture.js @@ -1,11 +1,8 @@ 'use strict'; var async = require('async'); -var path = require('path'); var fs = require('fs'); -var os = require('os'); var nconf = require('nconf'); -var crypto = require('crypto'); var winston = require('winston'); var request = require('request'); var mime = require('mime'); @@ -19,78 +16,7 @@ var db = require('../database'); module.exports = function (User) { User.uploadPicture = function (uid, picture, callback) { - - var uploadSize = parseInt(meta.config.maximumProfileImageSize, 10) || 256; - var extension = path.extname(picture.name); - var updateUid = uid; - var imageDimension = parseInt(meta.config.profileImageDimension, 10) || 128; - var convertToPNG = parseInt(meta.config['profile:convertProfileImageToPNG'], 10) === 1; - var keepAllVersions = parseInt(meta.config['profile:keepAllUserImages'], 10) === 1; - var uploadedImage; - - if (parseInt(meta.config.allowProfileImageUploads) !== 1) { - return callback(new Error('[[error:profile-image-uploads-disabled]]')); - } - - if (picture.size > uploadSize * 1024) { - return callback(new Error('[[error:file-too-big, ' + uploadSize + ']]')); - } - - if (!extension) { - return callback(new Error('[[error:invalid-image-extension]]')); - } - - async.waterfall([ - function (next) { - if (plugins.hasListeners('filter:uploadImage')) { - return plugins.fireHook('filter:uploadImage', { - image: picture, - uid: updateUid - }, next); - } - - var filename = updateUid + '-profileimg' + (keepAllVersions ? '-' + Date.now() : '') + (convertToPNG ? '.png' : extension); - - async.waterfall([ - function (next) { - file.isFileTypeAllowed(picture.path, next); - }, - function (next) { - image.resizeImage({ - path: picture.path, - extension: extension, - width: imageDimension, - height: imageDimension, - write: false, - }, next); - }, - function (next) { - if (!convertToPNG) { - return next(); - } - async.series([ - async.apply(image.normalise, picture.path, extension), - async.apply(fs.rename, picture.path + '.png', picture.path) - ], function (err) { - next(err); - }); - }, - function (next) { - file.saveFileToLocal(filename, 'profile', picture.path, next); - }, - ], next); - }, - function (_image, next) { - uploadedImage = _image; - User.setUserFields(updateUid, { - uploadedpicture: uploadedImage.url, - picture: uploadedImage.url - }, next); - }, - function (next) { - next(null, uploadedImage); - } - ], callback); + User.uploadCroppedPicture({uid: uid, file: picture}, callback); }; User.uploadFromUrl = function (uid, url, callback) { @@ -142,7 +68,7 @@ module.exports = function (User) { User.updateCoverPicture = function (data, callback) { var url; - var image = { + var picture = { name: 'profileCover', uid: data.uid }; @@ -167,12 +93,14 @@ module.exports = function (User) { return setImmediate(next, null, data.file.path); } - saveImageDataToTempFile(data.imageData, next); + image.writeImageDataToTempFile(data.imageData, next); }, function (path, next) { - image.path = path; + picture.path = path; - uploadProfileOrCover('profilecover', image, data.imageData, next); + var extension = data.file ? file.typeToExtension(data.file.type) : image.extensionFromBase64(data.imageData); + var filename = generateProfileImageFilename(data.uid, 'profilecover', extension); + uploadProfileOrCover(filename, picture, next); }, function (uploadData, next) { url = uploadData.url; @@ -186,7 +114,7 @@ module.exports = function (User) { } } ], function (err) { - deleteFile(image.path); + deleteFile(picture.path); callback(err, { url: url }); @@ -195,75 +123,102 @@ module.exports = function (User) { User.uploadCroppedPicture = function (data, callback) { - var url; - var image = { + if (parseInt(meta.config.allowProfileImageUploads) !== 1) { + return callback(new Error('[[error:profile-image-uploads-disabled]]')); + } + + if (!data.imageData && !data.file) { + return callback(new Error('[[error:invalid-data]]')); + } + + var size = data.file ? data.file.size : data.imageData.length; + var uploadSize = parseInt(meta.config.maximumProfileImageSize, 10) || 256; + if (size > uploadSize * 1024) { + return callback(new Error('[[error:file-too-big, ' + meta.config.maximumProfileImageSize + ']]')); + } + + var type = data.file ? data.file.type : image.mimeFromBase64(data.imageData); + var extension = file.typeToExtension(type); + if (!extension) { + return callback(new Error('[[error:invalid-image-extension]]')); + } + + var uploadedImage; + + var picture = { name: 'profileAvatar', uid: data.uid }; - if (!data.imageData) { - return callback(new Error('[[error:invalid-data]]')); - } - async.waterfall([ function (next) { - var size = data.imageData.length; - var uploadSize = parseInt(meta.config.maximumProfileImageSize, 10) || 256; - if (size > uploadSize * 1024) { - return next(new Error('[[error:file-too-big, ' + meta.config.maximumProfileImageSize + ']]')); + if (data.file) { + return setImmediate(next, null, data.file.path); } - - saveImageDataToTempFile(data.imageData, next); + image.writeImageDataToTempFile(data.imageData, next); }, function (path, next) { - image.path = path; - - uploadProfileOrCover('profileavatar', image, data.imageData, next); + convertToPNG(path, extension, next); }, - function (uploadData, next) { - url = uploadData.url; + function (path, next) { + picture.path = path; + + var imageDimension = parseInt(meta.config.profileImageDimension, 10) || 128; + image.resizeImage({ + path: picture.path, + extension: extension, + width: imageDimension, + height: imageDimension + }, next); + }, + function (next) { + var filename = generateProfileImageFilename(data.uid, 'profileavatar', extension); + uploadProfileOrCover(filename, picture, next); + }, + function (_uploadedImage, next) { + uploadedImage = _uploadedImage; + User.setUserFields(data.uid, { - uploadedpicture: url, - picture: url + uploadedpicture: uploadedImage.url, + picture: uploadedImage.url }, next); } ], function (err) { - deleteFile(image.path); - callback(err, { - url: url - }); + deleteFile(picture.path); + callback(err, uploadedImage); }); }; - function saveImageDataToTempFile(imageData, callback) { - var filename = crypto.createHash('md5').update(imageData).digest('hex'); - var filepath = path.join(os.tmpdir(), filename); + function convertToPNG(path, extension, callback) { + var convertToPNG = parseInt(meta.config['profile:convertProfileImageToPNG'], 10) === 1; + if (!convertToPNG) { + return setImmediate(callback, null, path); + } - var buffer = new Buffer(imageData.slice(imageData.indexOf('base64') + 7), 'base64'); - - fs.writeFile(filepath, buffer, { - encoding: 'base64' - }, function (err) { - callback(err, filepath); + image.normalise(path, extension, function (err, newPath) { + if (err) { + return callback(err); + } + deleteFile(path); + callback(null, newPath); }); } - function uploadProfileOrCover(type, image, imageData, callback) { + function uploadProfileOrCover(filename, image, callback) { if (plugins.hasListeners('filter:uploadImage')) { return plugins.fireHook('filter:uploadImage', { image: image, uid: image.uid }, callback); } - var filename = generateProfileImageFilename(image.uid, type, imageData); + saveFileToLocal(filename, image, callback); } - function generateProfileImageFilename(uid, type, imageData) { - var extension = file.typeToExtension(imageData.slice(5, imageData.indexOf('base64') - 1)); + function generateProfileImageFilename(uid, type, extension) { var keepAllVersions = parseInt(meta.config['profile:keepAllUserImages'], 10) === 1; - var filename = uid + '-' + type + (keepAllVersions ? '-' + Date.now() : '') + (extension || ''); - return filename; + var convertToPNG = parseInt(meta.config['profile:convertProfileImageToPNG'], 10) === 1; + return uid + '-' + type + (keepAllVersions ? '-' + Date.now() : '') + (convertToPNG ? '.png' : extension); } function saveFileToLocal(filename, image, callback) { @@ -277,6 +232,7 @@ module.exports = function (User) { function (upload, next) { next(null, { url: nconf.get('relative_path') + upload.url, + path: upload.path, name: image.name }); } diff --git a/test/files/503.html b/test/files/503.html new file mode 100644 index 0000000000..119c710ab7 --- /dev/null +++ b/test/files/503.html @@ -0,0 +1,177 @@ + + + Excessive Load Warning + + + + + + +
+
+

503

+

+ This forum is temporarily unavailable due to excessive load. +

+

+ We shouldn't be down for long. Please check back shortly. Sorry for the inconvenience! +

+

+  Alright. You can stop clicking... it's not going to make the site come back sooner! +

+
+
+ + diff --git a/test/files/favicon.ico b/test/files/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..70b00c430113ac15f50aec20d6d2d7bb892bd160 GIT binary patch literal 1150 zcmZQzU<5(|0R|wcz>vYhz#zuJz@P!dKp~(AL>x#lFaYJy!Tw_`QoH;|Z{)UEz|Ns8|gJ}iRpm2bN404&tx=wyp78VljL%oASFg_e{rfjFOdTN_=y$GZ)220dcX#)F o`0zoHkb3gv85sW6Gcf$IXJGii&%khi89xT80jUM)0R|xh0FW@8)c^nh literal 0 HcmV?d00001 diff --git a/test/files/test.png b/test/files/test.png new file mode 100644 index 0000000000000000000000000000000000000000..6a6c184126221ac0b44a53d08ef3fe012ed73b45 GIT binary patch literal 7189 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 Date: Mon, 20 Feb 2017 21:58:35 +0300 Subject: [PATCH 13/17] test uploadProfileImageFromUrl --- test/user.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/test/user.js b/test/user.js index 4f013e4019..a1cc142afb 100644 --- a/test/user.js +++ b/test/user.js @@ -583,7 +583,6 @@ describe('User', function () { var url = nconf.get('url') + '/favicon.ico'; function filterMethod(data, callback) { - data.foo += 5; callback(null, data); } @@ -600,7 +599,6 @@ describe('User', function () { meta.config.maximumProfileImageSize = 1; function filterMethod(data, callback) { - data.foo += 5; callback(null, data); } @@ -612,20 +610,29 @@ describe('User', function () { }); }); + it('should error with invalid data', function (done) { + var socketUser = require('../src/socket.io/user'); + + socketUser.uploadProfileImageFromUrl({uid: uid}, {uid: uid, url: ''}, function (err, uploadedPicture) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + it('should upload picture when uploading from url', function (done) { + var socketUser = require('../src/socket.io/user'); var url = nconf.get('url') + '/logo.png'; meta.config.maximumProfileImageSize = ''; function filterMethod(data, callback) { - data.foo += 5; callback(null, {url: url}); } plugins.registerHook('test-plugin', {hook: 'filter:uploadImage', method: filterMethod}); - User.uploadFromUrl(uid, url, function (err, uploadedPicture) { + socketUser.uploadProfileImageFromUrl({uid: uid}, {uid: uid, url: url}, function (err, uploadedPicture) { assert.ifError(err); - assert.equal(uploadedPicture.url, url); + assert.equal(uploadedPicture, url); done(); }); }); From 4b21679d6f97c7541eb40689d2e6858bb20ae233 Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Tue, 21 Feb 2017 09:24:38 +0000 Subject: [PATCH 14/17] Latest translations and fallbacks --- .../language/cs/admin/appearance/skins.json | 8 ++-- public/language/cs/admin/settings/user.json | 44 +++++++++---------- public/language/cs/login.json | 2 +- public/language/cs/modules.json | 2 +- public/language/cs/register.json | 2 +- public/language/cs/user.json | 2 +- 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/public/language/cs/admin/appearance/skins.json b/public/language/cs/admin/appearance/skins.json index 64edb7c071..c84298d5a2 100644 --- a/public/language/cs/admin/appearance/skins.json +++ b/public/language/cs/admin/appearance/skins.json @@ -1,9 +1,9 @@ { "loading": "Načítání motivů…", - "homepage": "Homepage", - "select-skin": "Select Skin", - "current-skin": "Current Skin", - "skin-updated": "Skin Updated", + "homepage": "Domovská stránka", + "select-skin": "Vyber motiv", + "current-skin": "Současný motiv", + "skin-updated": "Motiv aktualizován", "applied-success": "%1 skin was succesfully applied", "revert-success": "Skin reverted to base colours" } \ No newline at end of file diff --git a/public/language/cs/admin/settings/user.json b/public/language/cs/admin/settings/user.json index bdabb075e9..607a2fbd92 100644 --- a/public/language/cs/admin/settings/user.json +++ b/public/language/cs/admin/settings/user.json @@ -1,21 +1,21 @@ { - "authentication": "Authentication", - "allow-local-login": "Allow local login", - "require-email-confirmation": "Require Email Confirmation", + "authentication": "Ověření", + "allow-local-login": "Povolit místní přihlášení", + "require-email-confirmation": "Vyžadovat potvrzení e-mailem", "email-confirm-interval": "User may not resend a confirmation email until", - "email-confirm-email2": "minutes have elapsed", + "email-confirm-email2": "minut uplynulo", "allow-login-with": "Allow login with", - "allow-login-with.username-email": "Username or Email", - "allow-login-with.username": "Username Only", - "allow-login-with.email": "Email Only", - "account-settings": "Account Settings", - "disable-username-changes": "Disable username changes", - "disable-email-changes": "Disable email changes", - "disable-password-changes": "Disable password changes", - "allow-account-deletion": "Allow account deletion", + "allow-login-with.username-email": "Uživatelské jméno nebo e-mail", + "allow-login-with.username": "Pouze uživatelské jméno", + "allow-login-with.email": "Pouze e-mail", + "account-settings": "Nastavení účtu", + "disable-username-changes": "Zakázat změnu uživatelského jména", + "disable-email-changes": "Zakázat změnu e-mailu", + "disable-password-changes": "Zakázat změnu hesla", + "allow-account-deletion": "Povolit smazání účtu", "user-info-private": "Make user info private", - "themes": "Themes", - "disable-user-skins": "Prevent users from choosing a custom skin", + "themes": "Témata", + "disable-user-skins": "Zabránit uživateli ve výběru vlastního vzhledu", "account-protection": "Account Protection", "login-attempts": "Login attempts per hour", "login-attempts-help": "If login attempts to a user's account exceeds this threshold, that account will be locked for a pre-configured amount of time", @@ -34,10 +34,10 @@ "registration.max-invites": "Maximum Invitations per User", "max-invites": "Maximum Invitations per User", "max-invites-help": "0 for no restriction. Admins get infinite invitations
Only applicable for \"Invite Only\"", - "min-username-length": "Minimum Username Length", - "max-username-length": "Maximum Username Length", - "min-password-length": "Minimum Password Length", - "max-about-me-length": "Maximum About Me Length", + "min-username-length": "Minimální délka uživatelského jména", + "max-username-length": "Maximální délka uživatelského jména", + "min-password-length": "Minimální délka hesla", + "max-about-me-length": "Maximální délka hesla", "terms-of-use": "Forum Terms of Use (Leave blank to disable)", "user-search": "User Search", "user-search-results-per-page": "Number of results to display", @@ -48,10 +48,10 @@ "outgoing-new-tab": "Open outgoing links in new tab", "topic-search": "Enable In-Topic Searching", "digest-freq": "Subscribe to Digest", - "digest-freq.off": "Off", - "digest-freq.daily": "Daily", - "digest-freq.weekly": "Weekly", - "digest-freq.monthly": "Monthly", + "digest-freq.off": "Vypnuto", + "digest-freq.daily": "Denně", + "digest-freq.weekly": "Týdně", + "digest-freq.monthly": "Měsíčně", "email-chat-notifs": "Send an email if a new chat message arrives and I am not online", "email-post-notif": "Send an email when replies are made to topics I am subscribed to", "follow-created-topics": "Follow topics you create", diff --git a/public/language/cs/login.json b/public/language/cs/login.json index 8d189a2d0e..e8652b4d33 100644 --- a/public/language/cs/login.json +++ b/public/language/cs/login.json @@ -8,5 +8,5 @@ "failed_login_attempt": "Přihlášení neúspěšné", "login_successful": "Přihlášení proběhlo úspěšně!", "dont_have_account": "Nemáte účet?", - "logged-out-due-to-inactivity": "You have been logged out of the Admin Control Panel due to inactivity" + "logged-out-due-to-inactivity": "Z důvodu nečinnosti jste byl odhlášen z ovládacího panelu administrátora" } \ No newline at end of file diff --git a/public/language/cs/modules.json b/public/language/cs/modules.json index 28a90319de..84afa3b052 100644 --- a/public/language/cs/modules.json +++ b/public/language/cs/modules.json @@ -13,7 +13,7 @@ "chat.contacts": "Kontakty", "chat.message-history": "Historie zpráv", "chat.pop-out": "Skrýt chat", - "chat.minimize": "Minimize", + "chat.minimize": "Minimalizovat", "chat.maximize": "Maximalizovat", "chat.seven_days": "7 dní", "chat.thirty_days": "30 dní", diff --git a/public/language/cs/register.json b/public/language/cs/register.json index fae5335f6a..fccff9ac25 100644 --- a/public/language/cs/register.json +++ b/public/language/cs/register.json @@ -1,6 +1,6 @@ { "register": "Registrace", - "cancel_registration": "Cancel Registration", + "cancel_registration": "Zrušit registraci", "help.email": "Ve výchozím nastavení bude váš e-mail skrytý.", "help.username_restrictions": "Jedinečné uživatelské jméno dlouhé %1 až %2 znaků. Ostatní uživatelé Vás mohou zmínit jako @uživatelské-jméno.", "help.minimum_password_length": "Délka vašeho hesla musí být alespoň %1 znaků.", diff --git a/public/language/cs/user.json b/public/language/cs/user.json index 01cf965cc8..25b2599e43 100644 --- a/public/language/cs/user.json +++ b/public/language/cs/user.json @@ -94,7 +94,7 @@ "topics_per_page": "Témat na stránce", "posts_per_page": "Příspěvků na stránce", "notification_sounds": "Přehrát zvuk když dostanete notifikaci", - "notifications_and_sounds": "Notifications & Sounds", + "notifications_and_sounds": "Upozornění a zvuky", "incoming-message-sound": "Incoming message sound", "outgoing-message-sound": "Outgoing message sound", "notification-sound": "Notification sound", From 799f6885037c05feb3e974f455ca28b4437eb387 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 21 Feb 2017 15:08:11 +0300 Subject: [PATCH 15/17] closes #5463 , invitation tests --- src/socket.io/user.js | 35 +++++----- src/user/invite.js | 44 +++++++++--- test/user.js | 154 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 201 insertions(+), 32 deletions(-) diff --git a/src/socket.io/user.js b/src/socket.io/user.js index c10a510e71..060c9e7b2e 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -272,35 +272,34 @@ SocketUser.invite = function (socket, email, callback) { return callback(new Error('[[error:forum-not-invite-only]]')); } - var max = meta.config.maximumInvites; + async.waterfall([ + function (next) { + user.isAdministrator(socket.uid, next); + }, + function (isAdmin, next) { + if (registrationType === 'admin-invite-only' && !isAdmin) { + return next(new Error('[[error:no-privileges]]')); + } + + var max = parseInt(meta.config.maximumInvites, 10); + if (!max) { + return user.sendInvitationEmail(socket.uid, email, callback); + } - user.isAdministrator(socket.uid, function (err, admin) { - if (err) { - return callback(err); - } - if (registrationType === 'admin-invite-only' && !admin) { - return callback(new Error('[[error:no-privileges]]')); - } - if (max) { async.waterfall([ function (next) { user.getInvitesNumber(socket.uid, next); }, function (invites, next) { - if (!admin && invites > max) { + if (!isAdmin && invites >= max) { return next(new Error('[[error:invite-maximum-met, ' + invites + ', ' + max + ']]')); } - next(); - }, - function (next) { + user.sendInvitationEmail(socket.uid, email, next); } - ], callback); - } else { - user.sendInvitationEmail(socket.uid, email, callback); + ], next); } - }); - + ], callback); }; SocketUser.getUserByUID = function (socket, uid, callback) { diff --git a/src/user/invite.js b/src/user/invite.js index 1066a662ac..043bf62a7b 100644 --- a/src/user/invite.js +++ b/src/user/invite.js @@ -61,9 +61,7 @@ module.exports = function (User) { if (exists) { return next(new Error('[[error:email-taken]]')); } - next(); - }, - function (next) { + async.parallel([ function (next) { db.setAdd('invitation:uid:' + uid, email, next); @@ -131,11 +129,11 @@ module.exports = function (User) { return next(new Error('[[error:invalid-username]]')); } async.parallel([ - function deleteFromReferenceList(next) { - db.setRemove('invitation:uid:' + invitedByUid, email, next); + function (next) { + deleteFromReferenceList(invitedByUid, email, next); }, - function deleteInviteKey(next) { - db.delete('invitation:email:' + email, callback); + function (next) { + db.delete('invitation:email:' + email, next); } ], function (err) { next(err); @@ -146,7 +144,37 @@ module.exports = function (User) { User.deleteInvitationKey = function (email, callback) { callback = callback || function () {}; - db.delete('invitation:email:' + email, callback); + + async.waterfall([ + function (next) { + User.getInvitingUsers(next); + }, + function (uids, next) { + async.each(uids, function (uid, next) { + deleteFromReferenceList(uid, email, next); + }, next); + }, + function (next) { + db.delete('invitation:email:' + email, next); + } + ], callback); }; + function deleteFromReferenceList(uid, email, callback) { + async.waterfall([ + function (next) { + db.setRemove('invitation:uid:' + uid, email, next); + }, + function (next) { + db.setCount('invitation:uid:' + uid, next); + }, + function (count, next) { + if (count === 0) { + return db.setRemove('invitation:uids', uid, next); + } + setImmediate(next); + } + ], callback); + } + }; diff --git a/test/user.js b/test/user.js index a1cc142afb..a666d2c115 100644 --- a/test/user.js +++ b/test/user.js @@ -513,8 +513,7 @@ describe('User', function () { it('should upload profile picture', function (done) { helpers.copyFile( path.join(nconf.get('base_dir'), 'test/files/test.png'), - path.join(nconf.get('base_dir'), 'test/files/test_copy.png') - , function (err) { + path.join(nconf.get('base_dir'), 'test/files/test_copy.png'), function (err) { assert.ifError(err); var picture = { path: path.join(nconf.get('base_dir'), 'test/files/test_copy.png'), @@ -572,7 +571,7 @@ describe('User', function () { }); it('should return error if no plugins listening for filter:uploadImage when uploading from url', function (done) { - var url = nconf.get('url') + '/logo.png'; + var url = nconf.get('url') + '/assets/logo.png'; User.uploadFromUrl(uid, url, function (err) { assert.equal(err.message, '[[error:no-plugin]]'); done(); @@ -595,7 +594,7 @@ describe('User', function () { }); it('should return error if the file is too big when uploading from url', function (done) { - var url = nconf.get('url') + '/logo.png'; + var url = nconf.get('url') + '/assets/logo.png'; meta.config.maximumProfileImageSize = 1; function filterMethod(data, callback) { @@ -613,7 +612,7 @@ describe('User', function () { it('should error with invalid data', function (done) { var socketUser = require('../src/socket.io/user'); - socketUser.uploadProfileImageFromUrl({uid: uid}, {uid: uid, url: ''}, function (err, uploadedPicture) { + socketUser.uploadProfileImageFromUrl({uid: uid}, {uid: uid, url: ''}, function (err) { assert.equal(err.message, '[[error:invalid-data]]'); done(); }); @@ -621,7 +620,7 @@ describe('User', function () { it('should upload picture when uploading from url', function (done) { var socketUser = require('../src/socket.io/user'); - var url = nconf.get('url') + '/logo.png'; + var url = nconf.get('url') + '/assets/logo.png'; meta.config.maximumProfileImageSize = ''; function filterMethod(data, callback) { @@ -1006,6 +1005,149 @@ describe('User', function () { }); }); }); + }); + + describe('invites', function () { + var socketUser = require('../src/socket.io/user'); + var inviterUid; + + before(function (done) { + User.create({ + username: 'inviter', + email: 'inviter@nodebb.org' + }, function (err, uid) { + assert.ifError(err); + inviterUid = uid; + done(); + }); + }); + + it('should error with invalid data', function (done) { + socketUser.invite({uid: inviterUid}, null, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should eror if forum is not invite only', function (done) { + socketUser.invite({uid: inviterUid}, 'invite1@test.com', function (err) { + assert.equal(err.message, '[[error:forum-not-invite-only]]'); + done(); + }); + }); + + it('should error if user is not admin and type is admin-invite-only', function (done) { + meta.config.registrationType = 'admin-invite-only'; + socketUser.invite({uid: inviterUid}, 'invite1@test.com', function (err) { + assert.equal(err.message, '[[error:no-privileges]]'); + done(); + }); + }); + + it('should send invitation email', function (done) { + meta.config.registrationType = 'invite-only'; + socketUser.invite({uid: inviterUid}, 'invite1@test.com', function (err) { + assert.ifError(err); + done(); + }); + }); + + it('should error if ouf of invitations', function (done) { + meta.config.maximumInvites = 1; + socketUser.invite({uid: inviterUid}, 'invite2@test.com', function (err) { + assert.equal(err.message, '[[error:invite-maximum-met, ' + 1 + ', ' + 1 + ']]'); + meta.config.maximumInvites = 5; + done(); + }); + }); + + it('should error if email exists', function (done) { + socketUser.invite({uid: inviterUid}, 'inviter@nodebb.org', function (err) { + assert.equal(err.message, '[[error:email-taken]]'); + done(); + }); + }); + + it('should send invitation email', function (done) { + socketUser.invite({uid: inviterUid}, 'invite2@test.com', function (err) { + assert.ifError(err); + done(); + }); + }); + + it('should get user\'s invites', function (done) { + User.getInvites(inviterUid, function (err, data) { + assert.ifError(err); + assert.deepEqual(data, ['invite1@test.com', 'invite2@test.com']); + done(); + }); + }); + + it('should get all invites', function (done) { + User.getAllInvites(function (err, data) { + assert.ifError(err); + assert.deepEqual(data, [ { uid: inviterUid, invitations: [ 'invite1@test.com', 'invite2@test.com' ] } ]); + done(); + }); + }); + + it('should fail to verify invitation with invalid data', function (done) { + User.verifyInvitation({token: '', email: ''}, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should fail to verify invitation with invalid email', function (done) { + User.verifyInvitation({token: 'test', email: 'doesnotexist@test.com'}, function (err) { + assert.equal(err.message, '[[error:invalid-token]]'); + done(); + }); + }); + + it('should verify installation with no errors', function (done) { + db.get('invitation:email:' + 'invite1@test.com', function (err, token) { + assert.ifError(err); + User.verifyInvitation({token: token, email: 'invite1@test.com'}, function (err) { + assert.ifError(err); + done(); + }); + }); + }); + + it('should error with invalid username', function (done) { + User.deleteInvitation('doesnotexist', 'test@test.com', function (err) { + assert.equal(err.message, '[[error:invalid-username]]'); + done(); + }); + }); + + it('should delete invitation', function (done) { + var socketAdmin = require('../src/socket.io/admin'); + socketAdmin.user.deleteInvitation({uid: inviterUid}, {invitedBy: 'inviter', email: 'invite1@test.com'}, function (err) { + assert.ifError(err); + db.isSetMember('invitation:uid:' + inviterUid, 'invite1@test.com', function (err, isMember) { + assert.ifError(err); + assert.equal(isMember, false); + done(); + }); + }); + }); + + it('should delete invitation key', function (done) { + User.deleteInvitationKey('invite2@test.com', function (err) { + assert.ifError(err); + db.isSetMember('invitation:uid:' + inviterUid, 'invite2@test.com', function (err, isMember) { + assert.ifError(err); + assert.equal(isMember, false); + db.isSetMember('invitation:uids', inviterUid, function (err, isMember) { + assert.ifError(err); + assert.equal(isMember, false); + done(); + }); + }); + }); + }); }); From f2a795a9b7a46fe08f5da45a8a54db07fc2425e3 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 21 Feb 2017 15:18:01 +0300 Subject: [PATCH 16/17] fix tests --- test/user.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/user.js b/test/user.js index a666d2c115..70334d04e1 100644 --- a/test/user.js +++ b/test/user.js @@ -1078,7 +1078,8 @@ describe('User', function () { it('should get user\'s invites', function (done) { User.getInvites(inviterUid, function (err, data) { assert.ifError(err); - assert.deepEqual(data, ['invite1@test.com', 'invite2@test.com']); + assert.notEqual(data.indexOf('invite1@test.com'), -1); + assert.notEqual(data.indexOf('invite2@test.com'), -1); done(); }); }); @@ -1086,7 +1087,9 @@ describe('User', function () { it('should get all invites', function (done) { User.getAllInvites(function (err, data) { assert.ifError(err); - assert.deepEqual(data, [ { uid: inviterUid, invitations: [ 'invite1@test.com', 'invite2@test.com' ] } ]); + assert.equal(data[0].uid, inviterUid); + assert.notEqual(data[0].invitations.indexOf('invite1@test.com'), -1); + assert.notEqual(data[0].invitations.indexOf('invite2@test.com'), -1); done(); }); }); From 89380824f892e8e2e96b148c0f7c6dd0228c7b52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 21 Feb 2017 17:00:54 +0300 Subject: [PATCH 17/17] navigation test --- src/install.js | 4 ++-- src/navigation/admin.js | 6 +----- test/controllers.js | 6 ++++++ 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/install.js b/src/install.js index 8c14302d80..c5ae1e214b 100644 --- a/src/install.js +++ b/src/install.js @@ -389,8 +389,8 @@ function createMenuItems(next) { if (err || exists) { return next(err); } - var navigation = require('./navigation/admin'), - data = require('../install/data/navigation.json'); + var navigation = require('./navigation/admin'); + var data = require('../install/data/navigation.json'); navigation.save(data, next); }); diff --git a/src/navigation/admin.js b/src/navigation/admin.js index 39258e6702..ab1c5aff42 100644 --- a/src/navigation/admin.js +++ b/src/navigation/admin.js @@ -1,14 +1,12 @@ "use strict"; - - var async = require('async'); var plugins = require('../plugins'); var db = require('../database'); var translator = require('../../public/src/modules/translator'); var pubsub = require('../pubsub'); -var admin = {}; +var admin = module.exports; admin.cache = null; pubsub.on('admin:navigation:save', function () { @@ -71,5 +69,3 @@ function getAvailable(callback) { plugins.fireHook('filter:navigation.available', core, callback); } - -module.exports = admin; \ No newline at end of file diff --git a/test/controllers.js b/test/controllers.js index 54578f76c3..42070e4c36 100644 --- a/test/controllers.js +++ b/test/controllers.js @@ -29,6 +29,12 @@ describe('Controllers', function () { }, user: function (next) { user.create({username: 'foo', password: 'barbar'}, next); + }, + navigation: function (next) { + var navigation = require('../src/navigation/admin'); + var data = require('../install/data/navigation.json'); + + navigation.save(data, next); } }, function (err, results) { if (err) {