Skip to content

Commit

Permalink
Merge 91feb9e into 7c1c0db
Browse files Browse the repository at this point in the history
  • Loading branch information
madeofhuman authored Aug 23, 2018
2 parents 7c1c0db + 91feb9e commit 91fa2d9
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 3 deletions.
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,7 @@ GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CLIENT_CALLBACK_URL=

COPYLEAKS_KEY=
COPYLEAKS_EMAIL=

SITE_ADMIN_EMAIL=
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"coverage": "nyc report --reporter=text-lcov | coveralls",
"coveralls": "nyc --reporter=lcov --reporter=text-lcov npm test",
"start": "babel-node server/index.js --presets babel-preset-env",
"dev": "nodemon server/index.js --exec babel-node --presets babel-preset-env",
"dev": "cross-env NODE_ENV=development nodemon server/index.js --exec babel-node --presets babel-preset-env",
"migrate:test": "sequelize db:migrate:undo:all --env=test && sequelize db:migrate --env=test && sequelize db:seed:all --env=test",
"migrate:dev": "sequelize db:migrate",
"unmigrate:dev": "sequelize db:migrate:undo:all",
Expand Down Expand Up @@ -52,6 +52,7 @@
"pg": "^7.4.3",
"pg-hstore": "^2.3.2",
"request": "^2.87.0",
"request-promise": "^4.2.2",
"sequelize": "^4.38.0",
"slug": "^0.9.1",
"underscore": "^1.9.1",
Expand Down
41 changes: 41 additions & 0 deletions server/helpers/copyleaks/Auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import httpClient from 'request-promise';
import { } from 'dotenv/config';

/**
* This class contains all the methods responsible for copyleaks authentication
*/
class Auth {
constructor(email, apiKey) {
this.options = {
method: 'POST',
uri: 'https://api.copyleaks.com/v1/account/login-api',
body: {
Email: email,
ApiKey: apiKey,
},
json: true,
};
this.accessToken = {};
this.login();
}

/**
* returns the access token gotten after login
*/
get accesstoken() {
return this.accessToken.access_token;
}

/**
* logs into the copyleaks api to get an access token
*/
login() {
httpClient(this.options)
.then((response) => {
this.accessToken = response;
})
.catch(error => error);
}
}

export default new Auth(process.env.COPYLEAKS_EMAIL, process.env.COPYLEAKS_KEY);
62 changes: 62 additions & 0 deletions server/helpers/copyleaks/Scan.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import httpClient from 'request-promise';
import { } from 'dotenv/config';

class Scan {
constructor(accessToken) {
this.accessToken = accessToken;
}

/**
* @description - This method uploads a text of about 6,000 characters to
* copyleaks api for plagiarism checks
* @param {String} scanText - The text to be scanned
*/
uploadScan(scanText) {
return new Promise((resolve, reject) => {
httpClient({
uri: 'https://api.copyleaks.com/v1/businesses/create-by-text',
method: 'POST',
headers: {
'content-type': 'application/x-www-form-urlencoded',
authorization: `Bearer ${this.accessToken}`,
},
body: scanText,
json: true,
})
.then((response) => {
setTimeout(() => {
resolve(response.ProcessId);
}, 20000);
})
.catch((error) => {
reject(error);
});
});
}

/**
* @description - Checks the result of a scanning process
* @param {Number} processId - The process id of a scan whose result is to be retrieved
*/
checkResult(processId) {
return new Promise((resolve, reject) => {
httpClient({
uri: `https://api.copyleaks.com/v2/businesses/${processId}/result`,
method: 'GET',
headers: {
'content-type': 'application/x-www-form-urlencoded',
authorization: `Bearer ${this.accessToken}`,
},
json: true,
})
.then((response) => {
resolve(response);
})
.catch((error) => {
reject(error);
});
});
}
}

export default Scan;
15 changes: 14 additions & 1 deletion server/helpers/emailMessages.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@ const emails = {
Sincerely,<br>
The Authors' Haven team.
`
})
}),
copyleaksCreditExhaustion: email => ({
to: email,
from: 'admin@authorshaven.com',
subject: 'Copyleaks Credit Exhaustion',
html: `
Hi,
<br><br>
Our Copyleaks credits have finished. We could either change the email again,
or maybe actually try shelling out a few bucks for the premium version. 🤷🏾‍♂️<br><br>
Sincerely,<br>
Us.
`
}),
};
export default emails;
43 changes: 43 additions & 0 deletions server/middlewares/plagiarismCheck.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { } from 'dotenv/config';
import Auth from '../helpers/copyleaks/Auth';
import Scanner from '../helpers/copyleaks/Scan';
import Mailer from '../helpers/Mailer';
import emails from '../helpers/emailMessages';

/**
* Run a plagiarism check on an article if unattributed,
* checking for a >= 50% match.
* @param {*} req the request object
* @param {*} res the response object
* @param {*} next calls the next middleware
*/
const plagiarismCheck = async (req, res, next) => {
if (process.env.NODE_ENV === 'test') return next();
if (req.body.isAttributed !== 'true') {
try {
const scanner = new Scanner(Auth.accesstoken);
const { body } = req.body;
const processId = await scanner.uploadScan(body);
const response = await scanner.checkResult(processId);
const foundIn = response.results.filter(occurrence => occurrence.totalMatchedPercents >= 50);
if (foundIn.length >= 1) {
return res.status(451).send({
status: 'fail',
message: `The article cannot be created because more than 50% of the content was found elsewhere.
Please attribute your sources and check the 'I have attributed all relevant sources' button to continue.`
});
}
next();
} catch (err) {
const msg = emails.copyleaksCreditExhaustion(process.env.SITE_ADMIN_EMAIL);
Mailer.sendMail(msg);
return res.status(599).send({
status: 'error',
message: 'The article cannot be created at the moment, please try again later.',
});
}
}
next();
};

export default plagiarismCheck;
2 changes: 2 additions & 0 deletions server/middlewares/validations/ArticleValidation.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default class ArticleValidation {
categoryId: 'required|integer|min:1|max:5',
title: 'required|string',
body: 'required|string',
isAttributed: 'required|boolean|in:true,false',
};

const validator = new Validator(req.body, articleProperties);
Expand Down Expand Up @@ -48,6 +49,7 @@ export default class ArticleValidation {
categoryId: 'integer|min:1|max:5',
title: 'string',
body: 'string',
isAttributed: 'required|boolean',
};

const validator = new Validator(req.body, articleProperties);
Expand Down
3 changes: 2 additions & 1 deletion server/routes/article.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import ArticleValidation from '../middlewares/validations/ArticleValidation';
import CommentValidation from '../middlewares/validations/CommentValidation';
import CommentController from '../controllers/CommentController';
import RatingController from '../controllers/RatingController';
import plagiarismCheck from '../middlewares/plagiarismCheck';

const articleRouter = Router();

articleRouter.post('/', isLoggedIn, ArticleValidation.validateCreateArticle, ArticleController.createArticle);
articleRouter.post('/', isLoggedIn, ArticleValidation.validateCreateArticle, plagiarismCheck, ArticleController.createArticle);

articleRouter.put('/:slug', isLoggedIn, ArticleValidation.validateUpdateArticle, ArticleController.updateArticle);

Expand Down
6 changes: 6 additions & 0 deletions server/tests/article.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ describe('Test for Article Request', () => {
title,
body,
categoryId,
isAttributed: 'true',
})
.end((err, res) => {
res.status.should.eql(201);
Expand All @@ -282,6 +283,7 @@ describe('Test for Article Request', () => {
body,
categoryId,
tags,
isAttributed: 'true',
})
.end((err, res) => {
res.status.should.eql(201);
Expand All @@ -303,6 +305,7 @@ describe('Test for Article Request', () => {
title,
body,
categoryId,
isAttributed: 'true',
})
.end((err, res) => {
res.status.should.eql(201);
Expand Down Expand Up @@ -439,6 +442,7 @@ describe('Test for Article Request', () => {
title,
body,
categoryId,
isAttributed: 'true',
})
.end((err, res) => {
res.status.should.eql(404);
Expand All @@ -455,6 +459,7 @@ describe('Test for Article Request', () => {
.set('x-access-token', anotherToken)
.send({
title: 'A beautiful sunday morning',
isAttributed: 'true',
})
.end((err, res) => {
res.status.should.eql(403);
Expand All @@ -471,6 +476,7 @@ describe('Test for Article Request', () => {
.set('x-access-token', token)
.send({
title: 'A beautiful sunday morning',
isAttributed: 'true',
})
.end((err, res) => {
res.status.should.eql(200);
Expand Down

0 comments on commit 91fa2d9

Please sign in to comment.