Save profile images to Amazon S3 (#1857)

* Profile Image to S3

* Delete image from S3
Fix file deletion

* S3 refactoring
This commit is contained in:
Ghalleb
2017-09-20 04:47:41 +08:00
committed by Michael Leanos
parent 36887dc3a8
commit f146cbc4b2
6 changed files with 93 additions and 18 deletions

View File

@@ -211,6 +211,15 @@ $ docker run -p 27017:27017 -d --name db mongo
$ docker run -p 3000:3000 --link db:db_1 mean
```
### Amazon S3 configuration
To save the profile images to S3, simply set those environment variables:
UPLOADS_STORAGE: s3
S3_BUCKET: the name of the bucket where the images will be saved
S3_ACCESS_KEY_ID: Your S3 access key
S3_SECRET_ACCESS_KEY: Your S3 access key password
## Getting Started With MEAN.JS
You have your application running, but there is a lot of stuff to understand. We recommend you go over the [Official Documentation](http://meanjs.org/docs.html).
In the docs we'll try to explain both general concepts of MEAN components and give you some guidelines to help you improve your development process. We tried covering as many aspects as possible, and will keep it updated by your request. You can also help us develop and improve the documentation by checking out the *gh-pages* branch of this repository.

View File

@@ -45,7 +45,16 @@ module.exports = {
illegalUsernames: ['meanjs', 'administrator', 'password', 'admin', 'user',
'unknown', 'anonymous', 'null', 'undefined', 'api'
],
aws: {
s3: {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
bucket: process.env.S3_BUCKET
}
},
uploads: {
// Storage can be 'local' or 's3'
storage: process.env.UPLOADS_STORAGE || 'local',
profile: {
image: {
dest: './modules/users/client/img/profile/uploads/',

View File

@@ -32,7 +32,7 @@
<ul class="nav navbar-nav navbar-right" ng-show="vm.authentication.user">
<li class="dropdown" uib-dropdown>
<a class="dropdown-toggle user-header-dropdown-toggle" uib-dropdown-toggle role="button">
<img ng-src="/{{vm.authentication.user.profileImageURL}}" alt="{{vm.authentication.user.displayName}}" class="header-profile-image" />
<img ng-src="{{vm.authentication.user.profileImageURL}}" alt="{{vm.authentication.user.displayName}}" class="header-profile-image" />
<span ng-bind="vm.authentication.user.displayName"></span> <b class="caret"></b>
</a>
<ul class="dropdown-menu" role="menu">

View File

@@ -3,7 +3,7 @@
<form class="signin form-horizontal">
<fieldset>
<div class="form-group text-center">
<img ngf-src="vm.fileSelected ? picFile : '/' + vm.user.profileImageURL" alt="{{vm.user.displayName}}" class="img-thumbnail user-profile-picture" ngf-drop>
<img ngf-src="vm.fileSelected ? picFile : vm.user.profileImageURL" alt="{{vm.user.displayName}}" class="img-thumbnail user-profile-picture" ngf-drop>
</div>
<div ng-show="vm.loading" class="form-group text-center">
<img ng-src="/modules/core/client/img/loaders/loader.gif" height="50" width="50" alt="Loading image...">

View File

@@ -9,12 +9,27 @@ var _ = require('lodash'),
errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller')),
mongoose = require('mongoose'),
multer = require('multer'),
multerS3 = require('multer-s3'),
aws = require('aws-sdk'),
amazonS3URI = require('amazon-s3-uri'),
config = require(path.resolve('./config/config')),
User = mongoose.model('User'),
validator = require('validator');
var whitelistedFields = ['firstName', 'lastName', 'email', 'username'];
var useS3Storage = config.uploads.storage === 's3' && config.aws.s3;
var s3;
if (useS3Storage) {
aws.config.update({
accessKeyId: config.aws.s3.accessKeyId,
secretAccessKey: config.aws.s3.secretAccessKey
});
s3 = new aws.S3();
}
/**
* Update user details
*/
@@ -57,10 +72,24 @@ exports.update = function (req, res) {
exports.changeProfilePicture = function (req, res) {
var user = req.user;
var existingImageUrl;
var multerConfig;
if (useS3Storage) {
multerConfig = {
storage: multerS3({
s3: s3,
bucket: config.aws.s3.bucket,
acl: 'public-read'
})
};
} else {
multerConfig = config.uploads.profile.image;
}
// Filtering to upload only images
var multerConfig = config.uploads.profile.image;
multerConfig.fileFilter = require(path.resolve('./config/lib/multer')).imageFileFilter;
var upload = multer(multerConfig).single('newProfilePicture');
if (user) {
@@ -95,7 +124,9 @@ exports.changeProfilePicture = function (req, res) {
function updateUser() {
return new Promise(function (resolve, reject) {
user.profileImageURL = config.uploads.profile.image.dest + req.file.filename;
user.profileImageURL = config.uploads.storage === 's3' && config.aws.s3 ?
req.file.location :
'/' + req.file.path;
user.save(function (err, theuser) {
if (err) {
reject(err);
@@ -109,24 +140,47 @@ exports.changeProfilePicture = function (req, res) {
function deleteOldImage() {
return new Promise(function (resolve, reject) {
if (existingImageUrl !== User.schema.path('profileImageURL').defaultValue) {
fs.unlink(existingImageUrl, function (unlinkError) {
if (unlinkError) {
if (useS3Storage) {
try {
var { region, bucket, key } = amazonS3URI(existingImageUrl);
var params = {
Bucket: config.aws.s3.bucket,
Key: key
};
// If file didn't exist, no need to reject promise
if (unlinkError.code === 'ENOENT') {
console.log('Removing profile image failed because file did not exist.');
return resolve();
}
s3.deleteObject(params, function (err) {
if (err) {
console.log('Error occurred while deleting old profile picture.');
console.log('Check if you have sufficient permissions : ' + err);
}
console.error(unlinkError);
reject({
message: 'Error occurred while deleting old profile picture'
resolve();
});
} else {
resolve();
} catch (err) {
console.warn(`${existingImageUrl} is not a valid S3 uri`);
return resolve();
}
});
} else {
fs.unlink(path.resolve('.' + existingImageUrl), function (unlinkError) {
if (unlinkError) {
// If file didn't exist, no need to reject promise
if (unlinkError.code === 'ENOENT') {
console.log('Removing profile image failed because file did not exist.');
return resolve();
}
console.error(unlinkError);
reject({
message: 'Error occurred while deleting old profile picture'
});
} else {
resolve();
}
});
}
} else {
resolve();
}

View File

@@ -38,7 +38,9 @@
},
"dependencies": {
"acl": "~0.4.10",
"amazon-s3-uri": "0.0.3",
"async": "~2.5.0",
"aws-sdk": "^2.104.0",
"body-parser": "~1.17.1",
"bower": "~1.8.0",
"chalk": "~2.1.0",
@@ -79,6 +81,7 @@
"mongoose": "~4.11.3",
"morgan": "~1.8.1",
"multer": "~1.3.0",
"multer-s3": "^2.7.0",
"nodemailer": "~4.0.1",
"owasp-password-strength-test": "~1.3.0",
"passport": "~0.3.2",