Skip to content

Commit

Permalink
Merge 73bd9b0 into 40a6c5c
Browse files Browse the repository at this point in the history
  • Loading branch information
fejiroofficial authored Mar 8, 2019
2 parents 40a6c5c + 73bd9b0 commit f101756
Show file tree
Hide file tree
Showing 22 changed files with 644 additions and 175 deletions.
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ services:
before_script:
- psql -c 'CREATE DATABASE authorshaven;' -U postgres
- psql -c "CREATE USER authorshaven WITH PASSWORD 'secret';" -U postgres
- npm run migrate

notifications:
email: false
Expand All @@ -34,4 +33,4 @@ deploy:
staging: apollo-ah-backend-staging
master: apollo-ah-backend
run:
- "npm run migrate"

2 changes: 1 addition & 1 deletion bin/babel-hook
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ if (env === 'development') dotenv.config();
// load .env.test file for test environment
if (env === 'test') dotenv.config({ path: '.env.test' });

module.exports = require('../config/database').default
module.exports = require('../config/database').default
2 changes: 1 addition & 1 deletion bin/www.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ import { env } from '../helpers/utils';
import logger from '../helpers/logger';

const port = env('PORT', 3000);
server.listen(port, () => logger.log(`We are live on port ${port}`));
server.listen(port, () => logger.log(`Running on port ${port}`));
81 changes: 81 additions & 0 deletions controllers/profileController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import models from '../models';
import { STATUS } from '../helpers/constants';
import Response from '../helpers/responseHelper';

const { Profile } = models;

const {
CREATED,
SERVER_ERROR,
UNAUTHORIZED,
} = STATUS;

/** profile controller class */
/**
*@description Handles profile features.
*
* @class ProfileController
*/
class ProfileController {
/**
* @description It creates the user's profile.
* @function create
* @memberof profileController
* @static
* @param {Object} req - The request object.
* @param {Object} res - The response object.
* @returns {Object} - It returns the response object.
*/
static async create(req, res) {
/** id should be gotten from the token */
let {
firstname, lastname, username, gender, bio, phone, address, image,
} = req.body;

firstname = firstname ? firstname.toLowerCase().toString().replace(/\s+/g, '') : firstname;
lastname = lastname ? lastname.toLowerCase().toString().replace(/\s+/g, '') : lastname;
username = username ? username.toLowerCase().toString().replace(/\s+/g, '') : username;
gender = gender ? gender.toUpperCase().toString().replace(/\s+/g, '') : gender;
bio = bio ? bio.toLowerCase().toString().replace(/\s+/g, ' ') : bio;
phone = phone ? phone.toLowerCase().toString().replace(/\s+/g, '') : phone;
address = address ? address.toLowerCase().toString().replace(/\s+/g, ' ') : address;
image = image ? image.toLowerCase().toString().replace(/\s+/g, '') : image;

const requestForm = {
firstname, lastname, username, gender, bio, phone, address, image,
};


try {
const [profile, success] = await Profile.findOrCreate({
where: {
user_id: req.user.id,
},
defaults: requestForm,
});

if (success) {
return Response.send(res, CREATED, profile, 'Profile created successfully', true);
}

await Profile.update(
req.body,
{
where: {
user_id: req.user.id,
}
}
);

return Response.send(res, CREATED, requestForm, 'Profile updated successfully', true);
} catch (error) {
console.log(error, '---->');
if (error.message === 'insert or update on table "profiles" violates foreign key constraint "profiles_user_id_fkey"') {
return Response.send(res, UNAUTHORIZED, [], 'You have to be signed up to create a profile', false);
}
return Response.send(res, SERVER_ERROR, error.message, 'Profile update failed, try again later!', false);
}
}
}

export default ProfileController;
4 changes: 1 addition & 3 deletions database/migrations/20190304154730-create-user.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,5 @@ export default {
* @param {Sequelize} sequelize - Sequelize object.
* @return {void}
*/
down: (queryInterface, sequelize) => (
queryInterface.dropTable('users')
),
down: (queryInterface, sequelize) => queryInterface.dropTable('Users')
};
54 changes: 54 additions & 0 deletions database/migrations/20190304173339-create-profile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@


module.exports = {
up: (queryInterface, Sequelize) => queryInterface.createTable('profiles', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
firstname: {
type: Sequelize.STRING
},
lastname: {
type: Sequelize.STRING
},
username: {
type: Sequelize.STRING
},
gender: {
type: Sequelize.STRING
},
bio: {
type: Sequelize.STRING
},
image: {
type: Sequelize.STRING
},
phone: {
type: Sequelize.STRING
},
address: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
},
user_id: {
allowNull: false,
type: Sequelize.INTEGER,
onDelete: 'CASCADE',
references: {
model: 'users',
key: 'id',
}
},
}),
down: (queryInterface, Sequelize) => queryInterface.dropTable('Profiles')
};
1 change: 0 additions & 1 deletion helpers/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export const STATUS = {
NOT_FOUND: 404, // Not found, when a resource can't be found to fulfill the request
UNPROCESSED: 422, // Unprocssable entity, requests parameters contains invalid fields
SERVER_ERROR: 500,

};

export const MESSAGE = {
Expand Down
2 changes: 2 additions & 0 deletions helpers/regex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const urlRegex = /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9]\.[^\s]{2,})/;
export const phoneRegex = /^\+(?:[0-9] ?){6,14}[0-9]$/;
2 changes: 1 addition & 1 deletion helpers/responseHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default class Response {
* @memberof Response
* @returns {void}
*/
static send(response, code = STATUS.OK, data = [], message = '', status = true) {
static send(response, code, data = [], message = '', status = true) {
response.status(code).json({
code, data, message, status
});
Expand Down
7 changes: 2 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import swaggerJSDoc from 'swagger-jsdoc';
import morgan from 'morgan';
import methodOveride from 'method-override';
import logger from './helpers/logger';

// Routes
import apiRoutes from './routes';
import routes from './routes';

const isProduction = process.env.NODE_ENV === 'production';
// Create global app object
Expand Down Expand Up @@ -62,8 +60,7 @@ app.get('/', (req, res) => {
res.status(200).send({ message: 'Welcome to the API' });
});

// routes endpoint
app.use('/api/v1', apiRoutes);
app.use('/api/v1', routes);

app.get('/swagger.json', (req, res) => {
res.setHeader('Content-Type', 'application/json');
Expand Down
9 changes: 9 additions & 0 deletions middlewares/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import validateCreateProfile from './validateCreateProfile';
import authenticate from './authenticate';

const middlewares = {
validateCreateProfile,
authenticate,
};

export default middlewares;
52 changes: 52 additions & 0 deletions middlewares/validateCreateProfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { urlRegex, phoneRegex } from '../helpers/regex';
/**
* This is a validation for creating a profile
* @constant
*
* @param {String} req request object
* @param {Object} res response object
* @param {Object} err error object
*
* @returns {Object}
*
* @exports validateCreateProfile
*/

const validateCreateProfile = (req, res, next) => {
let {
firstname, lastname, username, gender, bio, phone, image,
} = req.body;

firstname = firstname ? firstname.toLowerCase().toString().replace(/\s+/g, '') : firstname;
lastname = lastname ? lastname.toLowerCase().toString().replace(/\s+/g, '') : lastname;
username = username ? username.toLowerCase().toString().replace(/\s+/g, '') : username;
gender = gender ? gender.toUpperCase().toString().replace(/\s+/g, '') : gender;
bio = bio ? bio.toLowerCase().toString().replace(/\s+/g, ' ') : bio;
phone = phone ? phone.toLowerCase().toString().replace(/\s+/g, '') : phone;
image = image ? image.toLowerCase().toString().replace(/\s+/g, '') : image;

const errors = {};

if (!firstname) errors.firstname = 'Firstname is required';
if (!lastname) errors.lastname = 'Lastname is required';
if (!username) errors.username = 'username field cannot be emnpty';
if (!gender) errors.gender = 'Gender is required';
if (gender !== 'M' && gender !== 'F') errors.genderType = 'Gender must either be M or F';
if (!bio) errors.bio = 'Please provide a brief description about yourself';
if (bio.length > 255) errors.bio = 'Bio has reached it\'s maximum limit';
if (phone && !phoneRegex.test(phone)) errors.phone = 'Phone number should have a country code and not contain alphabets e.g +234';
if (phone && !phoneRegex.test(phone)) errors.phoneLength = 'Phone number length should adhere to international standard';
if (image && !urlRegex.test(image)) errors.image = 'image URL is not valid';

if (Object.getOwnPropertyNames(errors).length) {
return res.status(400).json({
status: 400,
success: 'false',
errors,
});
}

return next();
};

export default validateCreateProfile;
58 changes: 58 additions & 0 deletions models/Profile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* A model class representing user resource
*
* @param {Sequelize} sequelize - Sequelize object
* @param {Sequelize.DataTypes} DataTypes - A convinient object holding data types
* @return {Sequelize.Model} - User model
*/

const Profile = (sequelize, DataTypes) => {
/**
* @type {Sequelize.Model}
*/
const ProfileSchema = sequelize.define('profile', {
firstname: {
type: DataTypes.STRING,
allowNull: false,
},
lastname: {
type: DataTypes.STRING,
allowNull: false,
},
username: {
type: DataTypes.STRING,
allowNull: false,
},
gender: {
type: DataTypes.STRING,
allowNull: false,
},
bio: {
type: DataTypes.STRING,
allowNull: false,
},
phone: {
type: DataTypes.STRING,
allowNull: true,
},
address: {
type: DataTypes.STRING,
allowNull: true,
},
image: {
type: DataTypes.STRING,
allowNull: true,
}
}, {});

ProfileSchema.associate = (models) => {
ProfileSchema.belongsTo(models.User, {
foreignKey: 'user_id',
targetKey: 'id',
onDelete: 'CASCADE'
});
};
return ProfileSchema;
};

export default Profile;
9 changes: 2 additions & 7 deletions models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,9 @@ const User = (sequelize, DataTypes) => {
},
});

/**
* User relationship
*
* @param {Sequelize.Model} models - Sequelize model
* @returns {void}
*/

UserSchema.associate = (models) => {
// UserSchema.hasOne(models.Profile);
UserSchema.hasOne(models.Profile, { foreignKey: 'user_id' });
};

/**
Expand Down
1 change: 1 addition & 0 deletions models/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ if (config.use_env_variable) {

const models = {
User: sequelize.import('./User.js'),
Profile: sequelize.import('./Profile.js'),
};

Object.keys(models).forEach((key) => {
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"sequelize": "sequelize",
"start": "node dist/bin/www.js",
"start:dev": "nodemon --exec npm run dev",
"test": "nyc --reporter=html --reporter=text --reporter=lcov mocha --timeout 5000"
"pretest": "NODE_ENV=test npm run migrate",
"test": "nyc --reporter=html --reporter=text --reporter=lcov mocha --timeout 10000"
},
"author": "Andela Simulations Programme",
"license": "MIT",
Expand All @@ -43,6 +44,7 @@
"faker": "^4.1.0",
"http-errors": "^1.7.2",
"jsonwebtoken": "^8.5.0",
"lodash": "^4.17.11",
"maildev": "^1.0.0-rc3",
"method-override": "^2.3.10",
"methods": "^1.1.2",
Expand Down Expand Up @@ -74,8 +76,10 @@
"@babel/core": "^7.3.4",
"@babel/node": "^7.2.2",
"@babel/preset-env": "^7.3.4",
"@babel/register": "^7.0.0",
"chai": "^4.2.0",
"chai-http": "^4.2.1",
"chai-integer": "^0.1.0",
"coveralls": "^3.0.3",
"eslint": "^5.14.1",
"eslint-config-airbnb-base": "^13.1.0",
Expand Down
Loading

0 comments on commit f101756

Please sign in to comment.