Skip to content

Commit

Permalink
Merge pull request #27 from andela/feature/167164985/edit-user-profile
Browse files Browse the repository at this point in the history
#167164985 Edit User Profile
  • Loading branch information
joel-ace committed Aug 15, 2019
2 parents 4f73fd9 + 0e41a62 commit 1a45bce
Show file tree
Hide file tree
Showing 11 changed files with 324 additions and 16 deletions.
63 changes: 59 additions & 4 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -381,16 +381,56 @@ paths:
error:
type: string
example: no comment found with the provided id
'/profiles':
patch:
tags:
- Users
summary: Updates a user profile
security:
- ApiKeyAuth: []
requestBody:
description: fields containing user data
content:
application/json:
schema:
$ref: '#/components/schemas/UserProfileInput'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/UserProfileInput'
required: false
responses:
200:
description: profile successfully updated
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Profile'
400:
description: invalid request
content:
application/json:
schema:
$ref: '#/components/schemas/StandardServerResponse'
404:
description: user not found
content:
application/json:
schema:
$ref: '#/components/schemas/StandardServerResponse'
409:
description: email already exist
content:
application/json:
schema:
$ref: '#/components/schemas/StandardServerResponse'
500:
description: server error
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: error occured
components:
schemas:
UserRegisterInput:
Expand Down Expand Up @@ -441,6 +481,21 @@ components:
genre:
type: string
example: Action
UserProfileInput:
type: object
properties:
firstName:
type: string
example: john
lastName:
type: string
example: doe
email:
type: string
example: johndoe@email.com
avatarUrl:
type: string
example: ''
AuthResponse:
properties:
token:
Expand Down
32 changes: 30 additions & 2 deletions src/controllers/userController.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,33 @@ const getProfile = async (request, response) => {
});
};

const userController = { getProfile, signUp, login };
export default userController;
/**
* @description updates the profile of a user
* @param {object} request express request object
* @param {object} response express response object
* @param {object} next express next argument
* @returns {json} json
*/
const editProfile = async (request, response) => {
const { body, user } = request;
const updatedUser = await User.update({ ...body }, {
where: { id: user.id },
returning: true,
raw: true
});
if (updatedUser[0]) {
const {
password, createdAt, updatedAt, ...profile
} = updatedUser[1][0];
return responseMessage(response, 200, {
message: 'profile successfully updated',
data: { profile }
});
}
const errorField = Object.keys(body).toString().replace(',', ', ');
return responseMessage(response, 400, { error: `the ${errorField} field(s) entered are invalid` });
};

export default {
getProfile, editProfile, signUp, login
};
17 changes: 17 additions & 0 deletions src/helpers/emptyBody.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import responseMessage from './responseMessage';

/**
* @description checks for empty body requests
* @param {object} request express request object
* @param {onject} response express response object
* @param {object} next express next object
* @returns {json} json
*/
export default (request, response, next) => {
const { body } = request;
if (!Object.keys(body).length) {
responseMessage(response, 400, { error: 'empty request body' });
} else {
next();
}
};
2 changes: 2 additions & 0 deletions src/helpers/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import authHelper from './authHelper';
import { successResponse, errorResponse } from './responseHelper';
import emptyBody from './emptyBody';
import emailMessages from './emailMessages';
import responseMessage from './responseMessage';
import validators from './validator';
Expand All @@ -10,6 +11,7 @@ export default {
authHelper,
successResponse,
errorResponse,
emptyBody,
forgotPasswordMessage,
responseMessage,
validators
Expand Down
45 changes: 44 additions & 1 deletion src/helpers/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,46 @@ const isValidPassword = () => check('password').isLength({ min: 8 })
.isAlphanumeric()
.withMessage('password should contain only numbers and alphabets');

/**
* @param {String} field
* @returns {Object} - Express-validator
*/
const isValidProfileName = field => check(field)
.optional()
.trim()
.custom((value) => {
if (!/^[a-z]{1,}[\s]{0,1}[-']{0,1}[a-z]+$/i.test(value)) {
return false;
}
return true;
})
.escape()
.withMessage(`the ${field} can only contain alphabets, a space, apostrophe(') and a dash(-)`)
.not()
.isEmpty()
.withMessage(`${field} is a required field`);

/**
* @returns {Object} - Express-validator
*/
const isValidProfilePassword = () => check('password').isLength({ min: 8 })
.optional()
.withMessage('password cannot be updated here')
.isLength({ min: 0, max: 0 })
.withMessage('password cannot be updated here');

/**
* @returns {Object} - Express-validator
*/
const isValidAvatarUrl = () => check('avatarUrl')
.optional()
.trim()
.not()
.isEmpty()
.withMessage('avatarUrl cannot be blank')
.isURL()
.withMessage('avatarUrl must be a valid URL string');

/**
* @param {String} field
* @returns {Object} - Express-validator
Expand Down Expand Up @@ -109,5 +149,8 @@ export default {
isValidGenre,
isNotEmptySlug,
isValidComment,
isValidId
isValidId,
isValidProfileName,
isValidProfilePassword,
isValidAvatarUrl
};
1 change: 0 additions & 1 deletion src/middlewares/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import novelValidator from './novelValidator';
import commentValidator from './commentValidator';
import verifyToken from './verifyToken';


export default {
errorHandler,
userValidator,
Expand Down
14 changes: 12 additions & 2 deletions src/middlewares/userValidator.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import helpers from '../helpers';
import errorHandler from './errorHandler';

const { validators } = helpers;
const { validators, emptyBody } = helpers;
const {
isValidEmail, isValidName, isValidId, isValidPassword
isValidEmail, isValidName, isValidId, isValidPassword,
isValidProfileName, isValidProfilePassword,
isValidAvatarUrl
} = validators;

const { validatorError } = errorHandler;
Expand All @@ -24,6 +26,14 @@ const userValidator = {
isValidId('userId'),
validatorError
],
editProfileValidator: [
isValidProfileName('firstName'),
isValidProfileName('lastName'),
isValidAvatarUrl(),
isValidProfilePassword(),
validatorError,
emptyBody
],
forgotPassword: [
isValidEmail(),
validatorError
Expand Down
18 changes: 13 additions & 5 deletions src/routes/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,28 @@ import userController from '../controllers/userController';

const {
userValidator: {
signUpValidator, loginValidator, profileValidator
signUpValidator, loginValidator, profileValidator, editProfileValidator
},
verifyToken
} = middlewares;
const { signUp, login, getProfile } = userController;
const {
signUp, login, getProfile, editProfile
} = userController;
const user = express.Router();

const USER_URL = '/users';
const PROFILE_URL = '/profiles';

// Route to sign up a user
user.post('/users', signUpValidator, signUp);
user.post(`${USER_URL}`, signUpValidator, signUp);

// Route to login a user
user.post('/users/login', loginValidator, login);
user.post(`${USER_URL}/login`, loginValidator, login);

// Route to get user profile by userId
user.get('/profiles/:userId', verifyToken, profileValidator, getProfile);
user.get(`${PROFILE_URL}/:userId`, verifyToken, profileValidator, getProfile);

// Route to edit a user profile
user.patch(`${PROFILE_URL}`, verifyToken, editProfileValidator, editProfile);

export default user;
17 changes: 17 additions & 0 deletions src/services/findFollower.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import models from '../database/models';

const { Follower } = models;

/**
* @description Finds a follower following a user
* @param {string} followeeId
* @param {string} followerId
* @returns {object} a user object
*/

const findFollower = async (followeeId, followerId) => {
const follower = await Follower.findOne({ where: { followeeId, followerId } });
return follower;
};

export default findFollower;
25 changes: 25 additions & 0 deletions tests/mockData/userMock.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,30 @@ export default {
validProfileLogin: {
email: 'jamesbond@gmail.com',
password: 'jamesbond'
},
validProfile: {
firstName: 'james',
lastName: 'bonder',
avatarUrl: 'https://gravatar.com/avatar/aed61baf1e9256ed7d70e2cbbfcba9aa?s=400&d=robohash&r=x',
bio: 'I like acting movies only',
phoneNo: '2347032123007',
},
invalidProfile1: {
password: 'password',
firstName: 'james',
lastName: 'bonder',
avatarUrl: 'https://gravatar.com/avatar/aed61baf1e9256ed7d70e2cbbfcba9aa?s=400&d=robohash&r=x',
bio: 'I like acting movies only',
phoneNo: '2347032123007',
},
invalidProfile2: {
firstName: '1232'
},
invalidProfile3: {
title: '1232',
description: 'just testing'
},
invalidProfileToken: {
expiredToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwiaWF0IjoxNTY0ODA0NDQ3LCJleHAiOjE1NjQ3NzkyNDd9.778heTKCI1eFw47SVT2rZLcBoBnTB4rD5wDacVzT3Kw'
}
};
Loading

0 comments on commit 1a45bce

Please sign in to comment.