2015-10-01 20:55:55 -07:00
|
|
|
import dedent from 'dedent';
|
2016-01-19 21:11:20 -05:00
|
|
|
import moment from 'moment-timezone';
|
2015-08-20 09:40:03 -07:00
|
|
|
import debugFactory from 'debug';
|
2018-02-19 20:32:14 +00:00
|
|
|
import { curry } from 'lodash';
|
2015-08-07 13:31:48 -07:00
|
|
|
|
2015-10-05 20:00:25 -07:00
|
|
|
import {
|
2015-12-09 12:28:19 -08:00
|
|
|
frontEndChallengeId,
|
2017-12-21 01:15:23 +00:00
|
|
|
backEndChallengeId,
|
|
|
|
respWebDesignId,
|
|
|
|
frontEndLibsId,
|
|
|
|
jsAlgoDataStructId,
|
|
|
|
dataVisId,
|
2018-02-16 23:18:53 +00:00
|
|
|
dataVis2018Id,
|
2017-12-21 01:15:23 +00:00
|
|
|
apisMicroservicesId,
|
|
|
|
infosecQaId
|
2015-10-05 20:00:25 -07:00
|
|
|
} from '../utils/constantStrings.json';
|
2016-01-11 15:58:37 -08:00
|
|
|
import certTypes from '../utils/certTypes.json';
|
2018-02-16 23:18:53 +00:00
|
|
|
import superBlockCertTypeMap from '../utils/superBlockCertTypeMap';
|
2016-06-23 20:05:30 -07:00
|
|
|
import {
|
|
|
|
ifNoUser401,
|
2016-12-15 02:54:59 +05:30
|
|
|
ifNoUserRedirectTo,
|
|
|
|
ifNotVerifiedRedirectToSettings
|
2016-06-23 20:05:30 -07:00
|
|
|
} from '../utils/middleware';
|
2015-10-02 11:47:36 -07:00
|
|
|
import { observeQuery } from '../utils/rx';
|
2015-08-07 13:31:48 -07:00
|
|
|
|
2016-01-27 11:34:44 -08:00
|
|
|
const debug = debugFactory('fcc:boot:user');
|
2015-10-01 20:55:55 -07:00
|
|
|
const sendNonUserToMap = ifNoUserRedirectTo('/map');
|
2018-02-19 20:32:14 +00:00
|
|
|
const sendNonUserToMapWithMessage = curry(ifNoUserRedirectTo, 2)('/map');
|
2016-01-11 15:58:37 -08:00
|
|
|
const certIds = {
|
|
|
|
[certTypes.frontEnd]: frontEndChallengeId,
|
2017-12-21 01:15:23 +00:00
|
|
|
[certTypes.backEnd]: backEndChallengeId,
|
|
|
|
[certTypes.respWebDesign]: respWebDesignId,
|
|
|
|
[certTypes.frontEndLibs]: frontEndLibsId,
|
|
|
|
[certTypes.jsAlgoDataStruct]: jsAlgoDataStructId,
|
|
|
|
[certTypes.dataVis]: dataVisId,
|
2018-02-16 23:18:53 +00:00
|
|
|
[certTypes.dataVis2018]: dataVis2018Id,
|
2017-12-21 01:15:23 +00:00
|
|
|
[certTypes.apisMicroservices]: apisMicroservicesId,
|
|
|
|
[certTypes.infosecQa]: infosecQaId
|
2016-01-11 15:58:37 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
const certViews = {
|
|
|
|
[certTypes.frontEnd]: 'certificate/front-end.jade',
|
|
|
|
[certTypes.backEnd]: 'certificate/back-end.jade',
|
2017-12-21 01:15:23 +00:00
|
|
|
[certTypes.fullStack]: 'certificate/full-stack.jade',
|
|
|
|
[certTypes.respWebDesign]: 'certificate/responsive-web-design.jade',
|
|
|
|
[certTypes.frontEndLibs]: 'certificate/front-end-libraries.jade',
|
|
|
|
[certTypes.jsAlgoDataStruct]:
|
|
|
|
'certificate/javascript-algorithms-and-data-structures.jade',
|
|
|
|
[certTypes.dataVis]: 'certificate/data-visualization.jade',
|
2018-02-16 23:18:53 +00:00
|
|
|
[certTypes.dataVis2018]: 'certificate/data-visualization-2018.jade',
|
2017-12-21 01:15:23 +00:00
|
|
|
[certTypes.apisMicroservices]: 'certificate/apis-and-microservices.jade',
|
|
|
|
[certTypes.infosecQa]:
|
|
|
|
'certificate/information-security-and-quality-assurance.jade'
|
2016-01-11 15:58:37 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
const certText = {
|
2016-04-03 09:53:40 +02:00
|
|
|
[certTypes.frontEnd]: 'Front End certified',
|
2016-01-11 15:58:37 -08:00
|
|
|
[certTypes.backEnd]: 'Back End Certified',
|
2017-12-21 01:15:23 +00:00
|
|
|
[certTypes.fullStack]: 'Full Stack Certified',
|
|
|
|
[certTypes.respWebDesign]: 'Responsive Web Design Certified',
|
|
|
|
[certTypes.frontEndLibs]: 'Front End Libraries Certified',
|
|
|
|
[certTypes.jsAlgoDataStruct]:
|
|
|
|
'JavaScript Algorithms and Data Structures Certified',
|
|
|
|
[certTypes.dataVis]: 'Data Visualization Certified',
|
2018-02-16 23:18:53 +00:00
|
|
|
[certTypes.dataVis2018]: 'Data Visualization Certified',
|
2017-12-21 01:15:23 +00:00
|
|
|
[certTypes.apisMicroservices]: 'APIs and Microservices Certified',
|
|
|
|
[certTypes.infosecQa]: 'Information Security and Quality Assurance Certified'
|
2016-01-11 15:58:37 -08:00
|
|
|
};
|
2015-08-19 13:05:53 -07:00
|
|
|
|
2015-06-03 16:19:23 -07:00
|
|
|
module.exports = function(app) {
|
2016-06-17 12:35:10 -07:00
|
|
|
const router = app.loopback.Router();
|
|
|
|
const api = app.loopback.Router();
|
2017-12-26 13:20:03 -08:00
|
|
|
const { Email, User } = app.models;
|
2016-12-17 01:44:06 +05:30
|
|
|
|
2015-10-02 11:47:36 -07:00
|
|
|
function findUserByUsername$(username, fields) {
|
|
|
|
return observeQuery(
|
|
|
|
User,
|
|
|
|
'findOne',
|
|
|
|
{
|
|
|
|
where: { username },
|
|
|
|
fields
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2013-11-14 02:29:55 -05:00
|
|
|
|
2016-06-17 12:35:10 -07:00
|
|
|
api.post(
|
2015-08-20 09:40:03 -07:00
|
|
|
'/account/delete',
|
|
|
|
ifNoUser401,
|
|
|
|
postDeleteAccount
|
|
|
|
);
|
2016-06-17 12:35:10 -07:00
|
|
|
api.get(
|
2015-10-01 20:55:55 -07:00
|
|
|
'/account',
|
|
|
|
sendNonUserToMap,
|
|
|
|
getAccount
|
|
|
|
);
|
2016-09-20 12:43:34 -05:00
|
|
|
api.post(
|
2018-02-16 23:18:53 +00:00
|
|
|
'/account/reset-progress',
|
2016-09-20 12:43:34 -05:00
|
|
|
ifNoUser401,
|
|
|
|
postResetProgress
|
|
|
|
);
|
2016-09-28 21:26:17 +07:00
|
|
|
api.get(
|
|
|
|
'/account/unlink/:social',
|
|
|
|
sendNonUserToMap,
|
|
|
|
getUnlinkSocial
|
|
|
|
);
|
|
|
|
|
2015-10-02 11:47:36 -07:00
|
|
|
// Ensure these are the last routes!
|
2016-06-17 12:35:10 -07:00
|
|
|
api.get(
|
2018-02-16 23:18:53 +00:00
|
|
|
'/c/:username/:cert',
|
|
|
|
showCert
|
2015-10-02 11:47:36 -07:00
|
|
|
);
|
|
|
|
|
2016-12-15 02:54:59 +05:30
|
|
|
router.get(
|
2018-02-19 20:32:14 +00:00
|
|
|
'/user/:username/report-user/',
|
|
|
|
sendNonUserToMapWithMessage('You must be signed in to report a user'),
|
2016-12-15 02:54:59 +05:30
|
|
|
ifNotVerifiedRedirectToSettings,
|
|
|
|
getReportUserProfile
|
|
|
|
);
|
|
|
|
|
|
|
|
api.post(
|
2018-02-19 20:32:14 +00:00
|
|
|
'/user/:username/report-user/',
|
2016-12-15 02:54:59 +05:30
|
|
|
ifNoUser401,
|
|
|
|
postReportUserProfile
|
|
|
|
);
|
2015-06-03 16:19:23 -07:00
|
|
|
|
2016-06-17 12:35:10 -07:00
|
|
|
app.use('/:lang', router);
|
2016-07-15 14:32:42 -07:00
|
|
|
app.use(api);
|
2015-06-03 16:31:42 -07:00
|
|
|
|
2015-07-22 23:27:18 -07:00
|
|
|
function getAccount(req, res) {
|
2015-10-01 20:55:55 -07:00
|
|
|
const { username } = req.user;
|
|
|
|
return res.redirect('/' + username);
|
2015-06-03 16:19:23 -07:00
|
|
|
}
|
2015-01-24 04:14:41 -05:00
|
|
|
|
2016-09-28 21:26:17 +07:00
|
|
|
function getUnlinkSocial(req, res, next) {
|
|
|
|
const { user } = req;
|
|
|
|
const { username } = user;
|
|
|
|
|
|
|
|
let social = req.params.social;
|
|
|
|
if (!social) {
|
2018-01-12 14:16:33 -08:00
|
|
|
req.flash('danger', 'No social account found');
|
2016-09-28 21:26:17 +07:00
|
|
|
return res.redirect('/' + username);
|
|
|
|
}
|
|
|
|
|
|
|
|
social = social.toLowerCase();
|
|
|
|
const validSocialAccounts = ['twitter', 'linkedin'];
|
|
|
|
if (validSocialAccounts.indexOf(social) === -1) {
|
2018-01-12 14:16:33 -08:00
|
|
|
req.flash('danger', 'Invalid social account');
|
2016-09-28 21:26:17 +07:00
|
|
|
return res.redirect('/' + username);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!user[social]) {
|
2018-01-12 14:16:33 -08:00
|
|
|
req.flash('danger', `No ${social} account associated`);
|
2016-09-28 21:26:17 +07:00
|
|
|
return res.redirect('/' + username);
|
|
|
|
}
|
|
|
|
|
|
|
|
const query = {
|
|
|
|
where: {
|
|
|
|
provider: social
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return user.identities(query, function(err, identities) {
|
|
|
|
if (err) { return next(err); }
|
|
|
|
|
|
|
|
// assumed user identity is unique by provider
|
|
|
|
let identity = identities.shift();
|
|
|
|
if (!identity) {
|
2018-01-12 14:16:33 -08:00
|
|
|
req.flash('danger', 'No social account found');
|
2016-09-28 21:26:17 +07:00
|
|
|
return res.redirect('/' + username);
|
|
|
|
}
|
|
|
|
|
|
|
|
return identity.destroy(function(err) {
|
|
|
|
if (err) { return next(err); }
|
|
|
|
|
|
|
|
const updateData = { [social]: null };
|
|
|
|
|
|
|
|
return user.update$(updateData)
|
|
|
|
.subscribe(() => {
|
|
|
|
debug(`${social} has been unlinked successfully`);
|
|
|
|
|
2018-01-12 14:16:33 -08:00
|
|
|
req.flash('info', `You've successfully unlinked your ${social}.`);
|
2016-09-28 21:26:17 +07:00
|
|
|
return res.redirect('/' + username);
|
|
|
|
}, next);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-02-16 23:18:53 +00:00
|
|
|
function showCert(req, res, next) {
|
|
|
|
let { username, cert } = req.params;
|
|
|
|
username = username.toLowerCase();
|
|
|
|
const certType = superBlockCertTypeMap[cert];
|
2016-02-09 14:33:25 -08:00
|
|
|
const certId = certIds[certType];
|
2016-02-11 13:46:11 -08:00
|
|
|
return findUserByUsername$(username, {
|
2016-01-31 16:35:15 -08:00
|
|
|
isCheater: true,
|
|
|
|
isLocked: true,
|
2015-10-02 11:47:36 -07:00
|
|
|
isFrontEndCert: true,
|
2015-12-09 12:28:19 -08:00
|
|
|
isBackEndCert: true,
|
2016-01-11 15:58:37 -08:00
|
|
|
isFullStackCert: true,
|
2017-12-21 01:15:23 +00:00
|
|
|
isRespWebDesignCert: true,
|
|
|
|
isFrontEndLibsCert: true,
|
|
|
|
isJsAlgoDataStructCert: true,
|
|
|
|
isDataVisCert: true,
|
2018-02-16 23:18:53 +00:00
|
|
|
is2018DataVisCert: true,
|
2017-12-21 01:15:23 +00:00
|
|
|
isApisMicroservicesCert: true,
|
|
|
|
isInfosecQaCert: true,
|
2015-10-08 09:02:35 -07:00
|
|
|
isHonest: true,
|
2015-10-02 11:47:36 -07:00
|
|
|
username: true,
|
2016-02-09 14:33:25 -08:00
|
|
|
name: true,
|
2016-02-11 13:46:11 -08:00
|
|
|
challengeMap: true
|
2015-10-02 11:47:36 -07:00
|
|
|
})
|
|
|
|
.subscribe(
|
2016-02-11 13:46:11 -08:00
|
|
|
user => {
|
2018-02-16 23:18:53 +00:00
|
|
|
const profile = `/${user.username}`;
|
2015-10-02 11:47:36 -07:00
|
|
|
if (!user) {
|
2018-01-12 14:16:33 -08:00
|
|
|
req.flash(
|
|
|
|
'danger',
|
|
|
|
`We couldn't find a user with the username ${username}`
|
|
|
|
);
|
2015-10-02 11:47:36 -07:00
|
|
|
return res.redirect('/');
|
|
|
|
}
|
2018-02-16 23:18:53 +00:00
|
|
|
|
|
|
|
if (!user.name) {
|
2018-01-12 14:16:33 -08:00
|
|
|
req.flash(
|
|
|
|
'danger',
|
|
|
|
dedent`
|
2018-02-16 23:18:53 +00:00
|
|
|
This user needs to add their name to their account
|
2016-01-04 02:01:38 -06:00
|
|
|
in order for others to be able to view their certificate.
|
2015-10-05 22:42:42 -07:00
|
|
|
`
|
2018-01-12 14:16:33 -08:00
|
|
|
);
|
2018-02-16 23:18:53 +00:00
|
|
|
return res.redirect(profile);
|
2015-10-05 22:42:42 -07:00
|
|
|
}
|
2016-01-24 15:28:15 -08:00
|
|
|
|
|
|
|
if (user.isCheater) {
|
|
|
|
return res.redirect(`/${user.username}`);
|
|
|
|
}
|
|
|
|
|
2015-10-05 20:00:25 -07:00
|
|
|
if (user.isLocked) {
|
2018-01-12 14:16:33 -08:00
|
|
|
req.flash(
|
|
|
|
'danger',
|
|
|
|
dedent`
|
2016-01-10 19:41:50 -06:00
|
|
|
${username} has chosen to make their profile
|
|
|
|
private. They will need to make their profile public
|
2016-01-04 02:01:38 -06:00
|
|
|
in order for others to be able to view their certificate.
|
2015-10-05 20:00:25 -07:00
|
|
|
`
|
2018-01-12 14:16:33 -08:00
|
|
|
);
|
2018-02-16 23:18:53 +00:00
|
|
|
return res.redirect('/');
|
2015-10-05 20:00:25 -07:00
|
|
|
}
|
2018-02-16 23:18:53 +00:00
|
|
|
|
2015-10-05 20:00:25 -07:00
|
|
|
if (!user.isHonest) {
|
2018-01-12 14:16:33 -08:00
|
|
|
req.flash(
|
|
|
|
'danger',
|
2018-02-16 23:18:53 +00:00
|
|
|
dedent`
|
2016-01-04 02:01:38 -06:00
|
|
|
${username} has not yet agreed to our Academic Honesty Pledge.
|
2015-10-05 20:00:25 -07:00
|
|
|
`
|
2018-01-12 14:16:33 -08:00
|
|
|
);
|
2018-02-16 23:18:53 +00:00
|
|
|
return res.redirect(profile);
|
2015-10-05 20:00:25 -07:00
|
|
|
}
|
|
|
|
|
2016-01-11 15:58:37 -08:00
|
|
|
if (user[certType]) {
|
2016-02-11 13:46:11 -08:00
|
|
|
const { challengeMap = {} } = user;
|
|
|
|
const { completedDate = new Date() } = challengeMap[certId] || {};
|
2015-10-02 11:47:36 -07:00
|
|
|
|
|
|
|
return res.render(
|
2016-01-11 15:58:37 -08:00
|
|
|
certViews[certType],
|
2015-10-02 11:47:36 -07:00
|
|
|
{
|
|
|
|
username: user.username,
|
2016-05-30 22:06:58 -04:00
|
|
|
date: moment(new Date(completedDate)).format('MMMM D, YYYY'),
|
2015-10-02 11:47:36 -07:00
|
|
|
name: user.name
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2018-01-12 14:16:33 -08:00
|
|
|
req.flash(
|
|
|
|
'danger',
|
|
|
|
`Looks like user ${username} is not ${certText[certType]}`
|
|
|
|
);
|
2018-02-16 23:18:53 +00:00
|
|
|
return res.redirect(profile);
|
2015-10-02 11:47:36 -07:00
|
|
|
},
|
|
|
|
next
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2015-07-22 23:27:18 -07:00
|
|
|
function postDeleteAccount(req, res, next) {
|
2015-06-03 16:19:23 -07:00
|
|
|
User.destroyById(req.user.id, function(err) {
|
2015-03-21 13:42:02 +09:00
|
|
|
if (err) { return next(err); }
|
2015-06-03 16:19:23 -07:00
|
|
|
req.logout();
|
2018-02-16 23:18:53 +00:00
|
|
|
req.flash('success', 'You have successfully deleted your account.');
|
|
|
|
return res.status(200).end();
|
2016-09-20 12:43:34 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function postResetProgress(req, res, next) {
|
|
|
|
User.findById(req.user.id, function(err, user) {
|
|
|
|
if (err) { return next(err); }
|
2018-02-16 23:18:53 +00:00
|
|
|
return user.update$({
|
2016-09-20 12:43:34 -05:00
|
|
|
progressTimestamps: [{
|
|
|
|
timestamp: Date.now()
|
|
|
|
}],
|
|
|
|
currentChallengeId: '',
|
2018-02-16 23:18:53 +00:00
|
|
|
isRespWebDesignCert: false,
|
|
|
|
is2018DataVisCert: false,
|
|
|
|
isFrontEndLibsCert: false,
|
|
|
|
isJsAlgoDataStructCert: false,
|
|
|
|
isApisMicroservicesCert: false,
|
|
|
|
isInfosecQaCert: false,
|
|
|
|
is2018FullStackCert: false,
|
|
|
|
isFrontEndCert: false,
|
2016-09-20 12:43:34 -05:00
|
|
|
isBackEndCert: false,
|
|
|
|
isDataVisCert: false,
|
2018-02-16 23:18:53 +00:00
|
|
|
isFullStackCert: false,
|
|
|
|
challengeMap: {}
|
|
|
|
})
|
|
|
|
.subscribe(
|
|
|
|
() => {
|
|
|
|
req.flash('success', 'You have successfully reset your progress.');
|
|
|
|
return res.status(200).end();
|
|
|
|
},
|
|
|
|
next
|
|
|
|
);
|
2016-09-20 12:43:34 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-12-15 02:54:59 +05:30
|
|
|
function getReportUserProfile(req, res) {
|
|
|
|
const username = req.params.username.toLowerCase();
|
|
|
|
return res.render('account/report-profile', {
|
|
|
|
title: 'Report User',
|
|
|
|
username
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function postReportUserProfile(req, res, next) {
|
|
|
|
const { user } = req;
|
|
|
|
const { username } = req.params;
|
|
|
|
const report = req.sanitize('reportDescription').trimTags();
|
|
|
|
|
|
|
|
if (!username || !report || report === '') {
|
2018-01-12 14:16:33 -08:00
|
|
|
req.flash(
|
|
|
|
'danger',
|
|
|
|
'Oops, something is not right please re-check your submission.'
|
|
|
|
);
|
2016-12-15 02:54:59 +05:30
|
|
|
return next();
|
|
|
|
}
|
|
|
|
|
|
|
|
return Email.send$({
|
|
|
|
type: 'email',
|
2017-08-26 00:07:44 +02:00
|
|
|
to: 'team@freecodecamp.org',
|
2016-12-15 02:54:59 +05:30
|
|
|
cc: user.email,
|
2017-08-26 00:07:44 +02:00
|
|
|
from: 'team@freecodecamp.org',
|
2016-12-15 02:54:59 +05:30
|
|
|
subject: 'Abuse Report : Reporting ' + username + '\'s profile.',
|
|
|
|
text: dedent(`
|
|
|
|
Hello Team,\n
|
|
|
|
This is to report the profile of ${username}.\n
|
|
|
|
Report Details:\n
|
|
|
|
${report}\n\n
|
|
|
|
Reported by:
|
|
|
|
Username: ${user.username}
|
|
|
|
Name: ${user.name}
|
|
|
|
Email: ${user.email}\n
|
|
|
|
Thanks and regards,
|
|
|
|
${user.name}
|
|
|
|
`)
|
|
|
|
}, err => {
|
|
|
|
if (err) {
|
|
|
|
err.redirectTo = '/' + username;
|
|
|
|
return next(err);
|
|
|
|
}
|
|
|
|
|
2018-01-12 14:16:33 -08:00
|
|
|
req.flash(
|
|
|
|
'info',
|
|
|
|
`A report was sent to the team with ${user.email} in copy.`
|
|
|
|
);
|
2016-12-15 02:54:59 +05:30
|
|
|
return res.redirect('/');
|
|
|
|
});
|
|
|
|
}
|
2016-12-16 10:35:38 +05:30
|
|
|
|
2015-06-03 16:19:23 -07:00
|
|
|
};
|