Skip to content

Commit

Permalink
feature(profile-update): implement feedback changes and rebase off de…
Browse files Browse the repository at this point in the history
…velop
  • Loading branch information
fxola committed Jul 8, 2019
2 parents 7606bda + 4d59526 commit 1cc9b13
Show file tree
Hide file tree
Showing 20 changed files with 393 additions and 34 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ node_modules

# Optional npm cache directory
.npm
uploads

# Optional REPL history
.node_repl_history
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"description": "A Social platform for the creative at heart",
"main": "index.js",
"scripts": {
"test": "NODE_ENV=test npm run migration-rollback && npm run db-migration && nyc mocha src/**/*.spec.js --require @babel/register --exit || true",
"test": "NODE_ENV=test npm run migration-rollback && npm run db-migration && nyc mocha src/**/*.spec.js --require @babel/register --exit --timeout 100000|| true",
"test-watch": "nodemon --exec \"npm test\"",
"start": "nodemon --exec babel-node src/index.js",
"build": "npm run db-migration && npm run clean && npm run babel-build",
"clean": "rm -rf dist",
Expand All @@ -26,6 +27,7 @@
"body-parser": "^1.18.3",
"chai": "^4.2.0",
"chai-http": "^4.3.0",
"cloudinary": "^1.14.0",
"cors": "^2.8.4",
"cross-env": "^5.2.0",
"dotenv": "^6.2.0",
Expand All @@ -37,6 +39,7 @@
"lint-staged": "^9.0.2",
"mocha": "^6.1.4",
"morgan": "^1.9.1",
"multer": "^1.4.1",
"pg": "^7.11.0",
"pg-hstore": "^2.3.3",
"sequelize": "^5.8.12",
Expand Down
43 changes: 43 additions & 0 deletions src/controllers/auth.controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
passwordResetServices
} from '../services/auth.service';
import Helper from '../services/helper';
import upload from '../helpers/image.helper';

export default {
/**
Expand Down Expand Up @@ -124,5 +125,47 @@ export default {
message: 'password reset was successful'
}
});
},

/**
*
* @description Handles the Logic for Updating a User profile
* Route: PUT: /users/profileupdate
* @param {object} request
* @param {object} response
* @param {function} next
* @returns {object}JSON API Response
*/
async profileUpdate(request, response, next) {
try {
let imagePath;
const previousImage = request.foundUser.image;
let imageUniqueName;
let uploadedImage;
if (request.file) {
imagePath = request.file.path;
imageUniqueName = request.file.originalname;
const imageResponse = await upload(imagePath, imageUniqueName);
uploadedImage = imageResponse.secure_url;
}

const fields = {
...request.body,
image: uploadedImage || previousImage
};
const updatedUser = await request.foundUser.update(fields);

return Helper.successResponse(response, 200, {
firstName: updatedUser.dataValues.firstName,
lastName: updatedUser.dataValues.lastName,
bio: updatedUser.dataValues.bio,
userName: updatedUser.dataValues.userName,
twitterHandle: updatedUser.dataValues.twitterHandle,
facebookHandle: updatedUser.dataValues.facebookHandle,
image: updatedUser.dataValues.image
});
} catch (error) {
next(error);
}
}
};
11 changes: 11 additions & 0 deletions src/db/migrations/add-bio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn('Users', 'bio', {
type: Sequelize.TEXT,
allowNull: true
});
},
down: queryInterface => {
return queryInterface.removeColumn('Users', 'bio');
}
};
11 changes: 11 additions & 0 deletions src/db/migrations/add-facebook-handle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn('Users', 'facebookHandle', {
type: Sequelize.STRING,
allowNull: true
});
},
down: queryInterface => {
return queryInterface.removeColumn('Users', 'facebookHandle');
}
};
11 changes: 11 additions & 0 deletions src/db/migrations/add-twitter-handle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn('Users', 'twitterHandle', {
type: Sequelize.STRING,
allowNull: true
});
},
down: queryInterface => {
return queryInterface.removeColumn('Users', 'twitterHandle');
}
};
12 changes: 12 additions & 0 deletions src/db/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,18 @@ export default (sequelize, DataTypes) => {
type: DataTypes.STRING,
allowNull: true,
defaultValue: 'author'
},
bio: {
type: DataTypes.TEXT,
allowNull: true
},
twitterHandle: {
type: DataTypes.STRING,
allowNull: true
},
facebookHandle: {
type: DataTypes.STRING,
allowNull: true
}
});

Expand Down
28 changes: 28 additions & 0 deletions src/helpers/image.helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import cloudinary from 'cloudinary';
import fs from 'fs';

cloudinary.config({
cloud_name: process.env.CLOUDINARY_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET
});
/**
* Handles the upload of image to cloudinary
*
* @param {string} path
* @param {string} uniqueFilename
* @returns {object} uploaded image response
*/
const upload = async (path, uniqueFilename) => {
return cloudinary.v2.uploader.upload(
path,
{ public_id: `avatar/${uniqueFilename}`, tags: `avatar` }, // directory and tags are optional
image => {
// remove file from server
fs.unlinkSync(path);
// return image details
return image;
}
);
};
export default upload;
14 changes: 6 additions & 8 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import Routes from './routes/v1';
const app = express();

// Normal express config defaults
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.urlencoded({ extended: true }));
app.use('/uploads', express.static('uploads'));
app.use(bodyParser.json());
app.use(logger('dev'));
Routes(app);
Expand All @@ -23,24 +24,21 @@ app.get('/', (request, response) => {
});
});

app.use((req, res, next) => {
app.use((request, response, next) => {
const error = new Error('You are trying to access a wrong Route');
error.status = 404;
next(error);
});

app.use((error, req, res, next) => {
res.status(error.status || 500);
res.json({
app.use((error, request, response, next) => {
response.status(error.status || 500);
response.json({
status: error.status || 500,
error: error.name,
message: error.message
});
next();
});

const PORT = process.env.PORT || 3000;

// finally, let's start our server...
app.listen(PORT, () => {
console.log(`Listening on port: ${PORT}`);
Expand Down
31 changes: 31 additions & 0 deletions src/middlewares/imageUpload.middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import multer from 'multer';

const fileFilter = (request, file, callback) => {
const error = new Error('Only JPG/PNG images are allowed');
error.status = 422;
// accept only jpg or png images
if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') {
callback(null, true);
} else {
callback(error, false);
}
};

const storage = multer.diskStorage({
destination: (request, file, callback) => {
callback(null, 'uploads/');
},
filename: (request, file, callback) => {
callback(null, new Date().toISOString() + file.originalname);
}
});

const upload = multer({
storage,
fileFilter,
limits: {
fileSize: 1024 * 1024 * 5
}
});

export default upload;
37 changes: 37 additions & 0 deletions src/middlewares/profileUpdateCheck.middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { findByUserName, findUserById } from '../services/auth.service';
import Helper from '../services/helper';
/**
*
* @description Handles neccessary checks on profile update
* @param {object} request
* @param {object} response
* @param {function} next
* @returns {(function|Object)} Function next() or an error Object
*/
const profileChecks = async (request, response, next) => {
try {
const userInstance = await findUserById(request.user.id);
if (!userInstance) {
return Helper.failResponse(response, 404, {
message: 'User account does not exist'
});
}

if (request.body.userName) {
const userNameExists = await findByUserName(request.body.userName);
const { id } = userNameExists;
if (userNameExists && id !== request.user.id) {
return Helper.failResponse(response, 409, {
message: 'Username has already been taken'
});
}
}
request.foundUser = userInstance;

return next();
} catch (error) {
return Helper.errorResponse(response, 500, error);
}
};

export default { profileChecks };
15 changes: 14 additions & 1 deletion src/routes/v1/auth.route.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ import express from 'express';
import authenticationValidator from '../../validators/user.validator';
import authenticationController from '../../controllers/auth.controllers';
import authorization from '../../middlewares/auth.middleware';
import profileUpdateCheck from '../../middlewares/profileUpdateCheck.middleware';
import upload from '../../middlewares/imageUpload.middleware';

const { validator, checkValidationResult } = authenticationValidator;
const { verifyToken } = authorization;
const { profileChecks } = profileUpdateCheck;
const {
signUp,
login,
forgotPassword,
passwordReset
passwordReset,
profileUpdate
} = authenticationController;

const router = express.Router();
Expand All @@ -29,4 +34,12 @@ router
passwordReset
);

router.put(
'/profileupdate',
verifyToken,
upload.single('image'),
profileChecks,
profileUpdate
);

export default router;
4 changes: 2 additions & 2 deletions src/routes/v1/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import auth from './auth.route';
import users from './user.route';

export default app => {
app.use('/api/v1/users', auth);
app.use('/api/v1/users/', users);
app.use(`${process.env.API_VERSION}/users`, auth);
app.use(`${process.env.API_VERSION}/users/`, users);
};
Loading

0 comments on commit 1cc9b13

Please sign in to comment.