Skip to content
Open
Show file tree
Hide file tree
Changes from all 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