Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
FACEBOOK_APP_ID=
FACEBOOK_APP_SECRET=
FACEBOOK_REDIRECT_URL=

PROJECT_ID=
CLIENT_EMAIL=
PRIVATE_KEY=
Expand Down
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"import/no-unresolved": [2, { "commonjs": true }],
"import/extensions": ["error", "as-needed"],
"no-shadow": ["error", { "allow": ["req", "res", "err"] }],
"import/named": 0,
"valid-jsdoc": ["error", {
"requireReturn": true,
"requireReturnType": true,
Expand Down
3 changes: 3 additions & 0 deletions config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ module.exports = {
database: process.env.TEST_DB_DATABASE,
host: process.env.TEST_DB_HOST,
dialect: 'postgres',
dialectOptions: {
ssl: true
},
operatorsAliases: false,

},
Expand Down
15 changes: 15 additions & 0 deletions config/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import dotenv from 'dotenv';

dotenv.config();

const config = {
secret: process.env.SECRET,
jwtSecret: process.env.JWT_TOKEN_SECRET,
cloudinary: {
cloud_name: process.env.CLOUD_NAME,
api_key: process.env.API_KEY,
api_secret: process.env.API_SECRET,
}
};

export default config;
6 changes: 3 additions & 3 deletions config/passport.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ const passportConfig = (app) => {
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: '/api/users/login/google/redirect',
callbackURL: `${process.env.BACKEND_HOST_URL}/api/users/login/google/redirect`,
}, AuthController.strategyCallback));

passport.use(new FacebookStrategy({
clientID: process.env.FACEBOOK_APP_ID,
clientSecret: process.env.FACEBOOK_APP_SECRET,
callbackURL: process.envFACEBOOK_REDIRECT_URL,
profileFields: ['id', 'displayName', 'photos', 'email'],
callbackURL: `${process.env.BACKEND_HOST_URL}/api/users/login/facebook/redirect`,
profileFields: ['id', 'name', 'photos', 'email'],
}, AuthController.strategyCallback));
};

Expand Down
4 changes: 3 additions & 1 deletion config/serviceAccount.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
const key = process.env.NODE_ENV === 'production' ? JSON.parse(process.env.PRIVATE_KEY) : process.env.PRIVATE_KEY.replace(/\\n/g, '\n');

export default {
type: 'service_account',
project_id: process.env.PROJECT_ID,
private_key_id: process.env.PRIVATE_KEY_ID,
private_key: process.env.PRIVATE_KEY.replace(/\\n/g, '\n'),
private_key: key,
client_email: process.env.CLIENT_EMAIL,
client_id: process.env.CLIENT_ID,
auth_uri: process.env.AUTH_URI,
Expand Down
157 changes: 112 additions & 45 deletions controllers/ArticleController.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Op } from 'sequelize';
import cloudinary from '../config/cloudinary';
import Utilities from '../helpers/utilities';
import { Article, User, Like, Payment } from '../models';
import {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexpected line break after this opening brace object-curly-newline

Article, User, Like, Payment
} from '../models';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexpected line break before this closing brace object-curly-newline

import createArticleHelper from '../helpers/createArticleHelper';
import editResponse from '../helpers/editArticleHelper';

/**
* Article class for users
Expand Down Expand Up @@ -47,35 +49,105 @@ class ArticleController {
* @returns {object} - the found article from database or error if not found
*/
static getArticle(req, res, next) {
const { articleObject } = req;
if (articleObject.isPaidFor === true) {
return Payment.find({
where: {
[Op.and]: [
{ userId: req.userId },
{ articleId: articleObject.id }
]
const { articleObject, userId } = req;
const articleBody = `${articleObject.body.substring(0, 500)}...`;
const notPaid = {
article: {
title: articleObject.title,
slug: articleObject.slug,
body: articleBody,
tagList: articleObject.tagList,
categorylist: articleObject.categorylist,
imageUrl: articleObject.imageUrl,
isPaidFor: articleObject.isPaidFor,
price: articleObject.price,
readTime: articleObject.readTime,
createdAt: articleObject.createdAt,
author: articleObject.author,
likes: articleObject.likes,
errors: {
body: ['You need to purchase this article to read it']
}
})
.then((payment) => {
if (payment) {
return res.status(200).json({
article: articleObject,
});
}
};
if (articleObject.isPaidFor === true) {
if (userId) {
return Payment.find({
where: {
[Op.and]: [
{ userId: req.userId },
{ articleId: articleObject.id }
]
}
return res.status(400).json({
errors: {
body: ['You need to purchase this article to read it']
}
});
})
.catch(next);
.then((payment) => {
if (payment || articleObject.author.id === userId) {
return res.status(200).json({
article: articleObject,
});
}
return res.status(200).json(notPaid);
})
.catch(next);
}
return res.status(200).json(notPaid);
}
return res.status(200).json({
article: articleObject,
});
}

/**
* get an article using slug as query parameter
* @param {object} req - request object
* @param {object} res - response object
* @param {function} next - to handle errors
* @returns {object} - the found article from database or error if not found
*/
static getAllUserArticles(req, res, next) {
const { username } = req.params;
const { page, limit } = req;
let offset = null;

if (page || limit) {
// calculate offset
offset = limit * (page - 1);
}
return User.findOne({ where: { username, } })
.then(user => Article.findAll({
where: {
userId: user.id,
},
include: [{
model: User,
as: 'author',
attributes: { exclude: ['id', 'email', 'hashedPassword', 'createdAt', 'updatedAt'] }
},
{
model: Like,
as: 'likes',
}],
offset,
limit,
}))
.then((articles) => {
if (articles.length === 0) {
const message = page ? 'articles limit exceeded'
: 'Sorry, no articles created';
return res.status(200).json({
message,
articles,
articlesCount: articles.length
});
}
return res.status(200).json({
articles,
articlesCount: articles.length
});
})
.catch(next);
}

/**
* get all articles created and use the query
* if provided to implement pagination
Expand All @@ -90,14 +162,20 @@ class ArticleController {
if (req.query.author || req.query.tag || req.query.title || req.query.category) return next();

if (page || limit) {
// calculate offset
// calculate offset
offset = limit * (page - 1);
}
return Article
.findAll({
order: [['id', 'DESC']],
include: [{
model: User,
attributes: { exclude: ['id', 'email', 'hashedPassword', 'createdAt', 'updatedAt'] }
as: 'author',
attributes: { exclude: ['id', 'email', 'hashedPassword', 'createdAt', 'updatedAt'] },
},
{
model: Like,
as: 'likes',
}],
offset,
limit,
Expand Down Expand Up @@ -133,31 +211,20 @@ class ArticleController {
* successful requests, or error object for
* requests that fail
*/
static editArticle(req, res, next) {
static editArticle(req, res) {
const {
title, description, body, isPaidFor, price
title, description, body, isPaidFor, price, imageData, imageUrl, tagList, categorylist,
} = req.body.article;
const { count } = req;
const { slug } = req.params;
return Article.update({
title,
description,
body,
isPaidFor,
price,
updatedCount: Utilities.increaseCount(count)
}, {
where: {
slug,
},
returning: true,
plain: true
})
.then(result => res.status(200).json({
success: true,
article: result[1]
}))
.catch(next);
const articleObject = {
title, description, body, isPaidFor, price, count, slug, tagList, categorylist,
};
if (imageData) {
return cloudinary.v2.uploader.upload(imageData, { tags: 'basic_sample' })
.then(image => editResponse(res, articleObject, image.url));
}
editResponse(res, articleObject, imageUrl);
}

/**
Expand Down
8 changes: 6 additions & 2 deletions controllers/CommentsController.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,17 @@ export default class CommentsController {
*/
static getComments(req, res, next) {
Comment.findAll({
order: [['id', 'ASC']],
include: [{
model: User,
attributes: { exclude: ['id', 'email', 'hashedPassword', 'createdAt', 'updatedAt'] }
attributes: { exclude: ['id', 'email', 'hashedPassword', 'createdAt', 'updatedAt'] },
}, {
model: Reply,
include: [{
model: User,
attributes: { exclude: ['id', 'email', 'hashedPassword', 'createdAt', 'updatedAt', 'lastname', 'bio'] },
}]
}],
}, {
where: {
articleId: req.articleObject.id,
}
Expand Down
4 changes: 2 additions & 2 deletions controllers/PaymentController.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ export default class PaymentController {
* @returns {link} redirects to create payment
*/
static makePayment(req, res, next) {
this.getStripe().customers.create({
PaymentController.getStripe().customers.create({
email: req.body.email,
source: req.body.stripeToken
})
.then(customer => this.getStripe().charges.create({
.then(customer => PaymentController.getStripe().charges.create({
amount: req.body.amount * 100,
description: `Payment for ${req.params.slug}`,
currency: 'usd',
Expand Down
14 changes: 8 additions & 6 deletions controllers/UsersController.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import jwt from 'jsonwebtoken';
import { Op } from 'sequelize';
import { User, Follow } from '../models';
import utils from '../helpers/utilities';
import sendVerificationEmail from '../helpers/sendmail';
import { sendMailVerify } from '../helpers/sendEmail';

/**
* Class representing users
Expand All @@ -18,23 +18,24 @@ export default class UsersController {
*/
static registerUser(req, res, next) {
const { email, username, password } = req.body.user;
const userEmail = email.toLowerCase();
bcrypt.hash(password, 10, (err, hash) => {
if (err) {
return next(err);
}
User.find({
where: {
[Op.or]: [{ email }, { username }],
[Op.or]: [{ email: userEmail }, { username }],
},
}).then((user) => {
if (!user) {
User.create({
email,
email: userEmail,
username,
hashedPassword: hash,
}).then((registeredUser) => {
const token = utils.signToken({ id: registeredUser.id });
sendVerificationEmail.sendEmail(registeredUser);
sendMailVerify(registeredUser);
res.status(200).json(utils.userToJson(registeredUser, token));
}).catch(next);
} else {
Expand All @@ -59,8 +60,9 @@ export default class UsersController {
*/
static login(req, res, next) {
const { email, password } = req.body.user;
const userEmail = email.toLowerCase();
User.find({
where: { email }
where: { email: userEmail }
})
.then((foundUser) => {
if (foundUser) {
Expand Down Expand Up @@ -191,7 +193,7 @@ export default class UsersController {
return res.status(200).json({ message: 'The user has been verified' });
} catch (err) {
return res.status(400).json({
errors: { body: ['Your verification link has expired or invalid'] }
errors: { body: ['Your verification link has expired or is invalid'] }
});
}
}
Expand Down
7 changes: 3 additions & 4 deletions controllers/authController.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,11 @@ export default class AuthController {
username: req.user.username,
image: req.user.image,
};
if (req.user.created) {
return res.status(201).json({ success: true, user });
}
return res.status(200).json({ success: true, user });

return res.redirect(`${process.env.FRONTEND_HOST_URL}?username=${user.username}&&token=${user.token}`);
}


/**
* @description - model query function
* @static
Expand Down
Loading