diff --git a/common/app/routes/Settings/components/Cert-Settings.jsx b/common/app/routes/Settings/components/Cert-Settings.jsx
index f2214250ca..40af0960e3 100644
--- a/common/app/routes/Settings/components/Cert-Settings.jsx
+++ b/common/app/routes/Settings/components/Cert-Settings.jsx
@@ -1,6 +1,6 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
-import _ from 'lodash';
+import { values as _values, isString, findIndex } from 'lodash';
import { createSelector } from 'reselect';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
@@ -12,11 +12,17 @@ import JSAlgoAndDSForm from './JSAlgoAndDSForm.jsx';
import SectionHeader from './SectionHeader.jsx';
import { projectsSelector } from '../../../entities';
import { claimCert, updateUserBackend } from '../redux';
-import { fetchChallenges, userSelector, hardGoTo } from '../../../redux';
+import {
+ fetchChallenges,
+ userSelector,
+ hardGoTo,
+ createErrorObservable
+} from '../../../redux';
import {
buildUserProjectsMap,
jsProjectSuperBlock
} from '../utils/buildUserProjectsMap';
+import legacyProjects from '../utils/legacyProjectData';
const mapStateToProps = createSelector(
userSelector,
@@ -30,12 +36,15 @@ const mapStateToProps = createSelector(
isJsAlgoDataStructCert,
isApisMicroservicesCert,
isInfosecQaCert,
+ isFrontEndCert,
+ isBackEndCert,
+ isDataVisCert,
username
},
projects
) => ({
projects,
- userProjects: projects
+ userProjects: projects.concat(legacyProjects)
.map(block => buildUserProjectsMap(block, challengeMap))
.reduce((projects, current) => ({
...projects,
@@ -49,7 +58,10 @@ const mapStateToProps = createSelector(
'Front End Libraries Projects': isFrontEndLibsCert,
'Data Visualization Projects': is2018DataVisCert,
'API and Microservice Projects': isApisMicroservicesCert,
- 'Information Security and Quality Assurance Projects': isInfosecQaCert
+ 'Information Security and Quality Assurance Projects': isInfosecQaCert,
+ 'Legacy Front End Projects': isFrontEndCert,
+ 'Legacy Back End Projects': isBackEndCert,
+ 'Legacy Data Visualization Projects': isDataVisCert
},
username
})
@@ -58,6 +70,7 @@ const mapStateToProps = createSelector(
function mapDispatchToProps(dispatch) {
return bindActionCreators({
claimCert,
+ createError: createErrorObservable,
fetchChallenges,
hardGoTo,
updateUserBackend
@@ -67,6 +80,7 @@ function mapDispatchToProps(dispatch) {
const propTypes = {
blockNameIsCertMap: PropTypes.objectOf(PropTypes.bool),
claimCert: PropTypes.func.isRequired,
+ createError: PropTypes.func.isRequired,
fetchChallenges: PropTypes.func.isRequired,
hardGoTo: PropTypes.func.isRequired,
projects: PropTypes.arrayOf(
@@ -88,11 +102,15 @@ const propTypes = {
username: PropTypes.string
};
+const compareSuperBlockWith = id => p => p.superBlock === id;
+
class CertificationSettings extends PureComponent {
constructor(props) {
super(props);
+ this.buildProjectForms = this.buildProjectForms.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
+ this.isProjectSectionCompleted = this.isProjectSectionCompleted.bind(this);
}
componentDidMount() {
@@ -102,23 +120,154 @@ class CertificationSettings extends PureComponent {
}
}
+ buildProjectForms({
+ projectBlockName,
+ challenges,
+ superBlock
+ }) {
+ const {
+ blockNameIsCertMap,
+ claimCert,
+ hardGoTo,
+ userProjects,
+ username
+ } = this.props;
+ const isCertClaimed = blockNameIsCertMap[projectBlockName];
+ if (superBlock === jsProjectSuperBlock) {
+ return (
+
+ );
+ }
+ const options = challenges
+ .reduce((options, current) => {
+ options.types[current] = 'url';
+ return options;
+ }, { types: {} });
+
+ options.types.id = 'hidden';
+ options.placeholder = false;
+
+ const userValues = userProjects[superBlock] || {};
+
+ if (!userValues.id) {
+ userValues.id = superBlock;
+ }
+
+ const initialValues = challenges
+ .reduce((accu, current) => ({
+ ...accu,
+ [current]: ''
+ }), {});
+
+ const completedProjects = _values(userValues)
+ .filter(Boolean)
+ .filter(isString)
+ // minus 1 to account for the id
+ .length - 1;
+
+ const fullForm = completedProjects === challenges.length;
+ return (
+
+ { projectBlockName }
+
+ {
+ isCertClaimed ?
+ :
+ null
+ }
+
+
+ );
+ }
+
+ isProjectSectionCompleted(values) {
+ const { id } = values;
+ const { projects } = this.props;
+ const whereSuperBlockIsId = compareSuperBlockWith(id);
+
+ let pIndex = findIndex(projects, whereSuperBlockIsId);
+ let projectChallenges = [];
+
+ if (pIndex === -1) {
+ // submitted projects might be in a legacy certificate
+ pIndex = findIndex(legacyProjects, whereSuperBlockIsId);
+ projectChallenges = legacyProjects[pIndex].challenges;
+ if (pIndex === -1) {
+ // the submitted projects do not belong to current/legacy certificates
+ return this.props.createError(
+ new Error(
+ 'Submitted projects do not belong to either current or ' +
+ 'legacy certificates'
+ )
+ );
+ }
+ } else {
+ projectChallenges = projects[pIndex].challenges;
+ }
+ const valuesSaved = _values(this.props.userProjects[id])
+ .filter(Boolean)
+ .filter(isString);
+ // minus 1 due to the form id being in values
+ return (valuesSaved.length - 1) === projectChallenges.length;
+ }
+
handleSubmit(values) {
const { id } = values;
- const fullForm = _.values(values)
- .filter(Boolean)
- .filter(_.isString)
- // 5 projects + 1 id prop
- .length === 6;
- const valuesSaved = _.values(this.props.userProjects[id])
- .filter(Boolean)
- .filter(_.isString)
- .length === 6;
- if (fullForm && valuesSaved) {
+ if (this.isProjectSectionCompleted(values)) {
return this.props.claimCert(id);
}
const { projects } = this.props;
- const pIndex = _.findIndex(projects, p => p.superBlock === id);
- values.nameToIdMap = projects[pIndex].challengeNameIdMap;
+ const whereSuperBlockIsId = compareSuperBlockWith(id);
+
+ let pIndex = findIndex(projects, whereSuperBlockIsId);
+ let projectNameIdMap = {};
+
+ if (pIndex === -1) {
+ // submitted projects might be in a legacy certificate
+ pIndex = findIndex(legacyProjects, whereSuperBlockIsId);
+ projectNameIdMap = legacyProjects[pIndex].challengeNameIdMap;
+ if (pIndex === -1) {
+ // the submitted projects do not belong to current/legacy certificates
+ return this.props.createError(
+ new Error(
+ 'Submitted projects do not belong to either current or ' +
+ 'legacy certificates'
+ )
+ );
+ }
+ } else {
+ projectNameIdMap = projects[pIndex].challengeNameIdMap;
+ }
+ values.nameToIdMap = projectNameIdMap;
return this.props.updateUserBackend({
projects: {
[id]: values
@@ -128,12 +277,7 @@ class CertificationSettings extends PureComponent {
render() {
const {
- blockNameIsCertMap,
- claimCert,
- hardGoTo,
- projects,
- userProjects,
- username
+ projects
} = this.props;
if (!projects.length) {
return null;
@@ -150,88 +294,14 @@ class CertificationSettings extends PureComponent {
you can claim it.
- {
- projects.map(({
- projectBlockName,
- challenges,
- superBlock
- }) => {
- const isCertClaimed = blockNameIsCertMap[projectBlockName];
- if (superBlock === jsProjectSuperBlock) {
- return (
-
- );
- }
- const options = challenges
- .reduce((options, current) => {
- options.types[current] = 'url';
- return options;
- }, { types: {} });
-
- options.types.id = 'hidden';
- options.placeholder = false;
-
- const userValues = userProjects[superBlock] || {};
-
- if (!userValues.id) {
- userValues.id = superBlock;
- }
-
- const initialValues = challenges
- .reduce((accu, current) => ({
- ...accu,
- [current]: ''
- }), {});
-
- const completedProjects = _.values(userValues)
- .filter(Boolean)
- .filter(_.isString)
- // minus 1 to account for the id
- .length - 1;
-
- const fullForm = completedProjects === challenges.length;
- return (
-
- { projectBlockName }
-
- {
- isCertClaimed ?
- :
- null
- }
-
-
- );
- })
+ {
+ projects.map(this.buildProjectForms)
+ }
+
+ Legacy Certificate Settings
+
+ {
+ legacyProjects.map(this.buildProjectForms)
}
);
diff --git a/common/app/routes/Settings/utils/legacyProjectData.js b/common/app/routes/Settings/utils/legacyProjectData.js
new file mode 100644
index 0000000000..ed70bee2d7
--- /dev/null
+++ b/common/app/routes/Settings/utils/legacyProjectData.js
@@ -0,0 +1,95 @@
+const legacyFrontEndProjects = {
+ challengeNameIdMap: {
+ 'build-a-personal-portfolio-webpage': 'bd7158d8c242eddfaeb5bd13',
+ 'build-a-random-quote-machine': 'bd7158d8c442eddfaeb5bd13',
+ 'build-a-pomodoro-clock': 'bd7158d8c442eddfaeb5bd0f',
+ 'build-a-javascript-calculator': 'bd7158d8c442eddfaeb5bd17',
+ 'show-the-local-weather': 'bd7158d8c442eddfaeb5bd10',
+ 'use-the-twitchtv-json-api': 'bd7158d8c442eddfaeb5bd1f',
+ 'stylize-stories-on-camper-news': 'bd7158d8c442eddfaeb5bd18',
+ 'build-a-wikipedia-viewer': 'bd7158d8c442eddfaeb5bd19',
+ 'build-a-tic-tac-toe-game': 'bd7158d8c442eedfaeb5bd1c',
+ 'build-a-simon-game': 'bd7158d8c442eddfaeb5bd1c'
+ },
+ challenges: [
+ 'Build a Personal Portfolio Webpage',
+ 'Build a Random Quote Machine',
+ 'Build a Pomodoro Clock',
+ 'Build a JavaScript Calculator',
+ 'Show the Local Weather',
+ 'Use the Twitchtv JSON API',
+ 'Stylize Stories on Camper News',
+ 'Build a Wikipedia Viewer',
+ 'Build a Tic Tac Toe Game',
+ 'Build a Simon Game'
+ ],
+ projectBlockName: 'Legacy Front End Projects',
+ superBlock: 'legacy-front-end'
+};
+
+const legacyBackEndProjects = {
+ challengeNameIdMap: {
+ 'timestamp-microservice': 'bd7158d8c443edefaeb5bdef',
+ 'request-header-parser-microservice': 'bd7158d8c443edefaeb5bdff',
+ 'url-shortener-microservice': 'bd7158d8c443edefaeb5bd0e',
+ 'image-search-abstraction-layer': 'bd7158d8c443edefaeb5bdee',
+ 'file-metadata-microservice': 'bd7158d8c443edefaeb5bd0f',
+ 'build-a-voting-app': 'bd7158d8c443eddfaeb5bdef',
+ 'build-a-nightlife-coordination-app': 'bd7158d8c443eddfaeb5bdff',
+ 'chart-the-stock-market': 'bd7158d8c443eddfaeb5bd0e',
+ 'manage-a-book-trading-club': 'bd7158d8c443eddfaeb5bd0f',
+ 'build-a-pinterest-clone': 'bd7158d8c443eddfaeb5bdee'
+ },
+ challenges: [
+ 'Timestamp Microservice',
+ 'Request Header Parser Microservice',
+ 'URL Shortener Microservice',
+ 'Image Search Abstraction Layer',
+ 'File Metadata Microservice',
+ 'Build a Voting App',
+ 'Build a Nightlife Coordination App',
+ 'Chart the Stock Market',
+ 'Manage a Book Trading Club',
+ 'Build a Pinterest Clone'
+ ],
+ projectBlockName: 'Legacy Back End Projects',
+ superBlock: 'legacy-back-end'
+};
+
+const legacyDataVisProjects = {
+ challengeNameIdMap: {
+ 'build-a-markdown-previewer': 'bd7157d8c242eddfaeb5bd13',
+ 'build-a-camper-leaderboard': 'bd7156d8c242eddfaeb5bd13',
+ 'build-a-recipe-box': 'bd7155d8c242eddfaeb5bd13',
+ 'build-the-game-of-life': 'bd7154d8c242eddfaeb5bd13',
+ 'build-a-roguelike-dungeon-crawler-game': 'bd7153d8c242eddfaeb5bd13',
+ 'visualize-data-with-a-bar-chart': 'bd7168d8c242eddfaeb5bd13',
+ 'visualize-data-with-a-scatterplot-graph': 'bd7178d8c242eddfaeb5bd13',
+ 'visualize-data-with-a-heat-map': 'bd7188d8c242eddfaeb5bd13',
+ 'show-national-contiguity-with-a-force-directed-graph':
+ 'bd7198d8c242eddfaeb5bd13',
+ 'map-data-across-the-globe': 'bd7108d8c242eddfaeb5bd13'
+ },
+ challenges: [
+ 'Build a Markdown Previewer',
+ 'Build a Camper Leaderboard',
+ 'Build a Recipe Box',
+ 'Build the Game of Life',
+ 'Build a Roguelike Dungeon Crawler Game',
+ 'Visualize Data with a Bar Chart',
+ 'Visualize Data with a Scatterplot Graph',
+ 'Visualize Data with a Heat Map',
+ 'Show National Contiguity with a Force Directed Graph',
+ 'Map Data Across the Globe'
+ ],
+ projectBlockName: 'Legacy Data Visualization Projects',
+ superBlock: 'legacy-data-visualization'
+};
+
+const legacyProjects = [
+ legacyFrontEndProjects,
+ legacyBackEndProjects,
+ legacyDataVisProjects
+];
+
+export default legacyProjects;
diff --git a/seed/challenges/09-certificates/legacy-back-end-certificate.json b/seed/challenges/09-certificates/legacy-back-end-certificate.json
new file mode 100644
index 0000000000..8c09367c6c
--- /dev/null
+++ b/seed/challenges/09-certificates/legacy-back-end-certificate.json
@@ -0,0 +1,57 @@
+{
+ "name": "Legacy Back End Certificate",
+ "order": 1,
+ "isPrivate": true,
+ "challenges": [
+ {
+ "id": "660add10cb82ac38a17513be",
+ "title": "Legacy Back End Certificate",
+ "challengeType": 7,
+ "description": [],
+ "challengeSeed": [],
+ "isPrivate": true,
+ "tests": [
+ {
+ "id": "bd7158d8c443edefaeb5bdef",
+ "title": "Timestamp Microservice"
+ },
+ {
+ "id": "bd7158d8c443edefaeb5bdff",
+ "title": "Request Header Parser Microservice"
+ },
+ {
+ "id": "bd7158d8c443edefaeb5bd0e",
+ "title": "URL Shortener Microservice"
+ },
+ {
+ "id": "bd7158d8c443edefaeb5bdee",
+ "title": "Image Search Abstraction Layer"
+ },
+ {
+ "id": "bd7158d8c443edefaeb5bd0f",
+ "title": "File Metadata Microservice"
+ },
+ {
+ "id": "bd7158d8c443eddfaeb5bdef",
+ "title": "Build a Voting App"
+ },
+ {
+ "id": "bd7158d8c443eddfaeb5bdff",
+ "title": "Build a Nightlife Coordination App"
+ },
+ {
+ "id": "bd7158d8c443eddfaeb5bd0e",
+ "title": "Chart the Stock Market"
+ },
+ {
+ "id": "bd7158d8c443eddfaeb5bd0f",
+ "title": "Manage a Book Trading Club"
+ },
+ {
+ "id": "bd7158d8c443eddfaeb5bdee",
+ "title": "Build a Pinterest Clone"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/seed/challenges/09-certificates/legacy-data-visualization-certificate.json b/seed/challenges/09-certificates/legacy-data-visualization-certificate.json
new file mode 100644
index 0000000000..673c901d27
--- /dev/null
+++ b/seed/challenges/09-certificates/legacy-data-visualization-certificate.json
@@ -0,0 +1,57 @@
+{
+ "name": "Legacy Data Visualization Certificate",
+ "order": 1,
+ "isPrivate": true,
+ "challenges": [
+ {
+ "id": "561add10cb82ac39a17513bc",
+ "title": "Legacy Data Visualization Certificate",
+ "challengeType": 7,
+ "description": [],
+ "challengeSeed": [],
+ "isPrivate": true,
+ "tests": [
+ {
+ "id": "bd7157d8c242eddfaeb5bd13",
+ "title": "Build a Markdown Previewer"
+ },
+ {
+ "id": "bd7156d8c242eddfaeb5bd13",
+ "title": "Build a Camper Leaderboard"
+ },
+ {
+ "id": "bd7155d8c242eddfaeb5bd13",
+ "title": "Build a Recipe Box"
+ },
+ {
+ "id": "bd7154d8c242eddfaeb5bd13",
+ "title": "Build the Game of Life"
+ },
+ {
+ "id": "bd7153d8c242eddfaeb5bd13",
+ "title": "Build a Roguelike Dungeon Crawler Game"
+ },
+ {
+ "id": "bd7168d8c242eddfaeb5bd13",
+ "title": "Visualize Data with a Bar Chart"
+ },
+ {
+ "id": "bd7178d8c242eddfaeb5bd13",
+ "title": "Visualize Data with a Scatterplot Graph"
+ },
+ {
+ "id": "bd7188d8c242eddfaeb5bd13",
+ "title": "Visualize Data with a Heat Map"
+ },
+ {
+ "id": "bd7198d8c242eddfaeb5bd13",
+ "title": "Show National Contiguity with a Force Directed Graph"
+ },
+ {
+ "id": "bd7108d8c242eddfaeb5bd13",
+ "title": "Map Data Across the Globe"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/seed/challenges/09-certificates/legacy-front-end-certificate.json b/seed/challenges/09-certificates/legacy-front-end-certificate.json
new file mode 100644
index 0000000000..da0a9d1f2e
--- /dev/null
+++ b/seed/challenges/09-certificates/legacy-front-end-certificate.json
@@ -0,0 +1,57 @@
+{
+ "name": "Legacy Front End Certificate",
+ "order": 1,
+ "isPrivate": true,
+ "challenges": [
+ {
+ "id": "561add10cb82ac38a17513be",
+ "title": "Legacy Front End Certificate",
+ "challengeType": 7,
+ "description": [],
+ "challengeSeed": [],
+ "isPrivate": true,
+ "tests": [
+ {
+ "id": "bd7158d8c242eddfaeb5bd13",
+ "title": "Build a Personal Portfolio Webpage"
+ },
+ {
+ "id": "bd7158d8c442eddfaeb5bd13",
+ "title": "Build a Random Quote Machine"
+ },
+ {
+ "id": "bd7158d8c442eddfaeb5bd0f",
+ "title": "Build a Pomodoro Clock"
+ },
+ {
+ "id": "bd7158d8c442eddfaeb5bd17",
+ "title": "Build a JavaScript Calculator"
+ },
+ {
+ "id": "bd7158d8c442eddfaeb5bd10",
+ "title": "Show the Local Weather"
+ },
+ {
+ "id": "bd7158d8c442eddfaeb5bd1f",
+ "title": "Use the Twitch.tv JSON API"
+ },
+ {
+ "id": "bd7158d8c442eddfaeb5bd18",
+ "title": "Stylize Stories on Camper News"
+ },
+ {
+ "id": "bd7158d8c442eddfaeb5bd19",
+ "title": "Build a Wikipedia Viewer"
+ },
+ {
+ "id": "bd7158d8c442eedfaeb5bd1c",
+ "title": "Build a Tic Tac Toe Game"
+ },
+ {
+ "id": "bd7158d8c442eddfaeb5bd1c",
+ "title": "Build a Simon Game"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/server/boot/certificate.js b/server/boot/certificate.js
index ce4162f614..11243e2aa6 100644
--- a/server/boot/certificate.js
+++ b/server/boot/certificate.js
@@ -1,5 +1,6 @@
import _ from 'lodash';
import loopback from 'loopback';
+import moment from 'moment-timezone';
import path from 'path';
import dedent from 'dedent';
import { Observable } from 'rx';
@@ -13,26 +14,23 @@ import {
import { observeQuery } from '../utils/rx';
import {
- // legacy
- frontEndChallengeId,
- backEndChallengeId,
- dataVisId,
+ legacyFrontEndChallengeId,
+ legacyBackEndChallengeId,
+ legacyDataVisId,
- // modern
respWebDesignId,
frontEndLibsId,
- dataVis2018Id,
jsAlgoDataStructId,
+ dataVis2018Id,
apisMicroservicesId,
infosecQaId
} from '../utils/constantStrings.json';
+import certTypes from '../utils/certTypes.json';
+import superBlockCertTypeMap from '../utils/superBlockCertTypeMap';
import {
completeCommitment$
} from '../utils/commit';
-import certTypes from '../utils/certTypes.json';
-import superBlockCertTypeMap from '../utils/superBlockCertTypeMap';
-
const log = debug('fcc:certification');
const renderCertifedEmail = loopback.template(path.join(
__dirname,
@@ -46,6 +44,48 @@ function isCertified(ids, challengeMap = {}) {
return _.every(ids, ({ id }) => _.has(challengeMap, id));
}
+const certIds = {
+ [certTypes.frontEnd]: legacyFrontEndChallengeId,
+ [certTypes.backEnd]: legacyBackEndChallengeId,
+ [certTypes.dataVis]: legacyDataVisId,
+ [certTypes.respWebDesign]: respWebDesignId,
+ [certTypes.frontEndLibs]: frontEndLibsId,
+ [certTypes.jsAlgoDataStruct]: jsAlgoDataStructId,
+ [certTypes.dataVis2018]: dataVis2018Id,
+ [certTypes.apisMicroservices]: apisMicroservicesId,
+ [certTypes.infosecQa]: infosecQaId
+};
+
+const certViews = {
+ [certTypes.frontEnd]: 'certificate/legacy/front-end.jade',
+ [certTypes.backEnd]: 'certificate/legacy/back-end.jade',
+ [certTypes.dataVis]: 'certificate/legacy/data-visualization.jade',
+ [certTypes.fullStack]: 'certificate/legacy/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.dataVis2018]: 'certificate/data-visualization.jade',
+ [certTypes.apisMicroservices]: 'certificate/apis-and-microservices.jade',
+ [certTypes.infosecQa]:
+ 'certificate/information-security-and-quality-assurance.jade'
+};
+
+const certText = {
+ [certTypes.frontEnd]: 'Legacy Front End certified',
+ [certTypes.backEnd]: 'Legacy Back End Certified',
+ [certTypes.dataVis]: 'Legacy Data Visualization Certified',
+ [certTypes.fullStack]: 'Legacy Full Stack Certified',
+ [certTypes.respWebDesign]: 'Responsive Web Design Certified',
+ [certTypes.frontEndLibs]: 'Front End Libraries Certified',
+ [certTypes.jsAlgoDataStruct]:
+ 'JavaScript Algorithms and Data Structures Certified',
+ [certTypes.dataVis2018]: 'Data Visualization Certified',
+ [certTypes.apisMicroservices]: 'APIs and Microservices Certified',
+ [certTypes.infosecQa]: 'Information Security and Quality Assurance Certified'
+};
+
function getIdsForCert$(id, Challenge) {
return observeQuery(
Challenge,
@@ -117,13 +157,24 @@ function sendCertifiedEmail(
export default function certificate(app) {
const router = app.loopback.Router();
- const { Email, Challenge } = app.models;
+ const { Email, Challenge, User } = app.models;
+
+ function findUserByUsername$(username, fields) {
+ return observeQuery(
+ User,
+ 'findOne',
+ {
+ where: { username },
+ fields
+ }
+ );
+ }
const certTypeIds = {
// legacy
- [certTypes.frontEnd]: getIdsForCert$(frontEndChallengeId, Challenge),
- [certTypes.backEnd]: getIdsForCert$(backEndChallengeId, Challenge),
- [certTypes.dataVis]: getIdsForCert$(dataVisId, Challenge),
+ [certTypes.frontEnd]: getIdsForCert$(legacyFrontEndChallengeId, Challenge),
+ [certTypes.backEnd]: getIdsForCert$(legacyBackEndChallengeId, Challenge),
+ [certTypes.dataVis]: getIdsForCert$(legacyDataVisId, Challenge),
// modern
[certTypes.respWebDesign]: getIdsForCert$(respWebDesignId, Challenge),
@@ -145,6 +196,10 @@ export default function certificate(app) {
ifNoSuperBlock404,
verifyCert
);
+ router.get(
+ '/c/:username/:cert',
+ showCert
+ );
app.use(router);
@@ -172,16 +227,12 @@ export default function certificate(app) {
function verifyCert(req, res, next) {
const { body: { superBlock }, user } = req;
-
+ log(superBlock);
let certType = superBlockCertTypeMap[superBlock];
log(certType);
- if (certType === 'isDataVisCert') {
- certType = 'is2018DataVisCert';
- log(certType);
- }
return user.getChallengeMap$()
- .flatMap(() => certTypeIds[certType])
- .flatMap(challenge => {
+ .flatMap(() => certTypeIds[certType])
+ .flatMap(challenge => {
const {
id,
tests,
@@ -251,4 +302,101 @@ export default function certificate(app) {
}
return res.status(404).end();
}
+
+ function showCert(req, res, next) {
+ let { username, cert } = req.params;
+ username = username.toLowerCase();
+ const certType = superBlockCertTypeMap[cert];
+ const certId = certIds[certType];
+ return findUserByUsername$(
+ username,
+ {
+ isCheater: true,
+ isLocked: true,
+ isFrontEndCert: true,
+ isBackEndCert: true,
+ isFullStackCert: true,
+ isRespWebDesignCert: true,
+ isFrontEndLibsCert: true,
+ isJsAlgoDataStructCert: true,
+ isDataVisCert: true,
+ is2018DataVisCert: true,
+ isApisMicroservicesCert: true,
+ isInfosecQaCert: true,
+ isHonest: true,
+ username: true,
+ name: true,
+ challengeMap: true
+ }
+ )
+ .subscribe(
+ user => {
+ const profile = `/${user.username}`;
+ if (!user) {
+ req.flash(
+ 'danger',
+ `We couldn't find a user with the username ${username}`
+ );
+ return res.redirect('/');
+ }
+
+ if (!user.name) {
+ req.flash(
+ 'danger',
+ dedent`
+ This user needs to add their name to their account
+ in order for others to be able to view their certificate.
+ `
+ );
+ return res.redirect(profile);
+ }
+
+ if (user.isCheater) {
+ return res.redirect(`/${user.username}`);
+ }
+
+ if (user.isLocked) {
+ req.flash(
+ 'danger',
+ dedent`
+ ${username} has chosen to make their profile
+ private. They will need to make their profile public
+ in order for others to be able to view their certificate.
+ `
+ );
+ return res.redirect('/');
+ }
+
+ if (!user.isHonest) {
+ req.flash(
+ 'danger',
+ dedent`
+ ${username} has not yet agreed to our Academic Honesty Pledge.
+ `
+ );
+ return res.redirect(profile);
+ }
+
+ if (user[certType]) {
+ const { challengeMap = {} } = user;
+ const { completedDate = new Date() } = challengeMap[certId] || {};
+
+ return res.render(
+ certViews[certType],
+ {
+ username: user.username,
+ date: moment(new Date(completedDate)).format('MMMM D, YYYY'),
+ name: user.name
+ }
+ );
+ }
+ req.flash(
+ 'danger',
+ `Looks like user ${username} is not ${certText[certType]}`
+ );
+ return res.redirect(profile);
+ },
+ next
+ );
+ }
}
diff --git a/server/boot/user.js b/server/boot/user.js
index add4f91cb5..1e901c31a7 100644
--- a/server/boot/user.js
+++ b/server/boot/user.js
@@ -1,88 +1,22 @@
import dedent from 'dedent';
-import moment from 'moment-timezone';
import debugFactory from 'debug';
import { curry } from 'lodash';
-import {
- frontEndChallengeId,
- backEndChallengeId,
- respWebDesignId,
- frontEndLibsId,
- jsAlgoDataStructId,
- dataVisId,
- dataVis2018Id,
- apisMicroservicesId,
- infosecQaId
-} from '../utils/constantStrings.json';
-import certTypes from '../utils/certTypes.json';
-import superBlockCertTypeMap from '../utils/superBlockCertTypeMap';
import {
ifNoUser401,
ifNoUserRedirectTo,
ifNotVerifiedRedirectToSettings
} from '../utils/middleware';
-import { observeQuery } from '../utils/rx';
const debug = debugFactory('fcc:boot:user');
const sendNonUserToMap = ifNoUserRedirectTo('/map');
const sendNonUserToMapWithMessage = curry(ifNoUserRedirectTo, 2)('/map');
-const certIds = {
- [certTypes.frontEnd]: frontEndChallengeId,
- [certTypes.backEnd]: backEndChallengeId,
- [certTypes.respWebDesign]: respWebDesignId,
- [certTypes.frontEndLibs]: frontEndLibsId,
- [certTypes.jsAlgoDataStruct]: jsAlgoDataStructId,
- [certTypes.dataVis]: dataVisId,
- [certTypes.dataVis2018]: dataVis2018Id,
- [certTypes.apisMicroservices]: apisMicroservicesId,
- [certTypes.infosecQa]: infosecQaId
-};
-
-const certViews = {
- [certTypes.frontEnd]: 'certificate/front-end.jade',
- [certTypes.backEnd]: 'certificate/back-end.jade',
- [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',
- [certTypes.dataVis2018]: 'certificate/data-visualization-2018.jade',
- [certTypes.apisMicroservices]: 'certificate/apis-and-microservices.jade',
- [certTypes.infosecQa]:
- 'certificate/information-security-and-quality-assurance.jade'
-};
-
-const certText = {
- [certTypes.frontEnd]: 'Front End certified',
- [certTypes.backEnd]: 'Back End Certified',
- [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',
- [certTypes.dataVis2018]: 'Data Visualization Certified',
- [certTypes.apisMicroservices]: 'APIs and Microservices Certified',
- [certTypes.infosecQa]: 'Information Security and Quality Assurance Certified'
-};
module.exports = function(app) {
const router = app.loopback.Router();
const api = app.loopback.Router();
const { Email, User } = app.models;
- function findUserByUsername$(username, fields) {
- return observeQuery(
- User,
- 'findOne',
- {
- where: { username },
- fields
- }
- );
- }
-
api.post(
'/account/delete',
ifNoUser401,
@@ -105,11 +39,6 @@ module.exports = function(app) {
);
// Ensure these are the last routes!
- api.get(
- '/c/:username/:cert',
- showCert
- );
-
router.get(
'/user/:username/report-user/',
sendNonUserToMapWithMessage('You must be signed in to report a user'),
@@ -185,100 +114,6 @@ module.exports = function(app) {
});
}
- function showCert(req, res, next) {
- let { username, cert } = req.params;
- username = username.toLowerCase();
- const certType = superBlockCertTypeMap[cert];
- const certId = certIds[certType];
- return findUserByUsername$(username, {
- isCheater: true,
- isLocked: true,
- isFrontEndCert: true,
- isBackEndCert: true,
- isFullStackCert: true,
- isRespWebDesignCert: true,
- isFrontEndLibsCert: true,
- isJsAlgoDataStructCert: true,
- isDataVisCert: true,
- is2018DataVisCert: true,
- isApisMicroservicesCert: true,
- isInfosecQaCert: true,
- isHonest: true,
- username: true,
- name: true,
- challengeMap: true
- })
- .subscribe(
- user => {
- const profile = `/${user.username}`;
- if (!user) {
- req.flash(
- 'danger',
- `We couldn't find a user with the username ${username}`
- );
- return res.redirect('/');
- }
-
- if (!user.name) {
- req.flash(
- 'danger',
- dedent`
- This user needs to add their name to their account
- in order for others to be able to view their certificate.
- `
- );
- return res.redirect(profile);
- }
-
- if (user.isCheater) {
- return res.redirect(`/${user.username}`);
- }
-
- if (user.isLocked) {
- req.flash(
- 'danger',
- dedent`
- ${username} has chosen to make their profile
- private. They will need to make their profile public
- in order for others to be able to view their certificate.
- `
- );
- return res.redirect('/');
- }
-
- if (!user.isHonest) {
- req.flash(
- 'danger',
- dedent`
- ${username} has not yet agreed to our Academic Honesty Pledge.
- `
- );
- return res.redirect(profile);
- }
-
- if (user[certType]) {
- const { challengeMap = {} } = user;
- const { completedDate = new Date() } = challengeMap[certId] || {};
-
- return res.render(
- certViews[certType],
- {
- username: user.username,
- date: moment(new Date(completedDate)).format('MMMM D, YYYY'),
- name: user.name
- }
- );
- }
- req.flash(
- 'danger',
- `Looks like user ${username} is not ${certText[certType]}`
- );
- return res.redirect(profile);
- },
- next
- );
- }
-
function postDeleteAccount(req, res, next) {
User.destroyById(req.user.id, function(err) {
if (err) { return next(err); }
diff --git a/server/utils/constantStrings.json b/server/utils/constantStrings.json
index 78d72a5a01..74303fe654 100644
--- a/server/utils/constantStrings.json
+++ b/server/utils/constantStrings.json
@@ -1,9 +1,9 @@
{
"gitHubUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1521.3 Safari/537.36",
- "frontEndChallengeId": "561add10cb82ac38a17513be",
- "backEndChallengeId": "660add10cb82ac38a17513be",
- "dataVisId": "561add10cb82ac39a17513bc",
+ "legacyFrontEndChallengeId": "561add10cb82ac38a17513be",
+ "legacyBackEndChallengeId": "660add10cb82ac38a17513be",
+ "legacyDataVisId": "561add10cb82ac39a17513bc",
"respWebDesignId": "561add10cb82ac38a17513bc",
"frontEndLibsId": "561acd10cb82ac38a17513bc",
diff --git a/server/utils/publicUserProps.js b/server/utils/publicUserProps.js
index 964cad0ab9..36a153234d 100644
--- a/server/utils/publicUserProps.js
+++ b/server/utils/publicUserProps.js
@@ -15,6 +15,7 @@ export const publicUserProps = [
'isApisMicroservicesCert',
'isBackEndCert',
'isCheater',
+ 'is2018DataVisCert',
'isDataVisCert',
'isFrontEndCert',
'isFullStackCert',
diff --git a/server/utils/superBlockCertTypeMap.js b/server/utils/superBlockCertTypeMap.js
index 49c91e1aed..9d62df67be 100644
--- a/server/utils/superBlockCertTypeMap.js
+++ b/server/utils/superBlockCertTypeMap.js
@@ -2,16 +2,16 @@ import certTypes from './certTypes.json';
const superBlockCertTypeMap = {
// legacy
- 'front-end': certTypes.frontEnd,
- 'back-end': certTypes.backEnd,
- 'data-visualization': certTypes.dataVis,
- 'full-stack': certTypes.fullStack,
+ 'legacy-front-end': certTypes.frontEnd,
+ 'legacy-back-end': certTypes.backEnd,
+ 'legacy-data-visualization': certTypes.dataVis,
+ 'legacy-full-stack': certTypes.fullStack,
// modern
'responsive-web-design': certTypes.respWebDesign,
'javascript-algorithms-and-data-structures': certTypes.jsAlgoDataStruct,
'front-end-libraries': certTypes.frontEndLibs,
- 'data-visualization-2018': certTypes.dataVis2018,
+ 'data-visualization': certTypes.dataVis2018,
'apis-and-microservices': certTypes.apisMicroservices,
'information-security-and-quality-assurance': certTypes.infosecQa
};
diff --git a/server/views/certificate/advanced-front-end.jade b/server/views/certificate/advanced-front-end.jade
index 78f781f4b4..01a76199f1 100644
--- a/server/views/certificate/advanced-front-end.jade
+++ b/server/views/certificate/advanced-front-end.jade
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
- p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/advanced-front-end-certification
+ p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/advanced-front-end
\ No newline at end of file
diff --git a/server/views/certificate/apis-and-microservices.jade b/server/views/certificate/apis-and-microservices.jade
index 6a37a5722a..8fcdd5e371 100644
--- a/server/views/certificate/apis-and-microservices.jade
+++ b/server/views/certificate/apis-and-microservices.jade
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
- p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/apis-and-microservices-certification
+ p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/apis-and-microservices
diff --git a/server/views/certificate/data-visualization.jade b/server/views/certificate/data-visualization.jade
index 0782d408aa..c0570958e1 100644
--- a/server/views/certificate/data-visualization.jade
+++ b/server/views/certificate/data-visualization.jade
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
- p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/data-visualization-certification
+ p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/data-visualization
diff --git a/server/views/certificate/front-end-libraries.jade b/server/views/certificate/front-end-libraries.jade
index f5d4206fd2..62a4e99e7d 100644
--- a/server/views/certificate/front-end-libraries.jade
+++ b/server/views/certificate/front-end-libraries.jade
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
- p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/front-end-libraries-certification
+ p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/front-end-libraries
diff --git a/server/views/certificate/information-security-and-quality-assurance.jade b/server/views/certificate/information-security-and-quality-assurance.jade
index 0a71cf3b97..9215bc4902 100644
--- a/server/views/certificate/information-security-and-quality-assurance.jade
+++ b/server/views/certificate/information-security-and-quality-assurance.jade
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
- p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/information-security-and-quality-assurance-certification
+ p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/information-security-and-quality-assurance
diff --git a/server/views/certificate/javascript-algorithms-and-data-structures.jade b/server/views/certificate/javascript-algorithms-and-data-structures.jade
index ebe72a1f44..2c821113e4 100644
--- a/server/views/certificate/javascript-algorithms-and-data-structures.jade
+++ b/server/views/certificate/javascript-algorithms-and-data-structures.jade
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
- p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/javascript-algorithms-and-data-structures-certification
+ p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/javascript-algorithms-and-data-structures
diff --git a/server/views/certificate/back-end.jade b/server/views/certificate/legacy/back-end.jade
similarity index 95%
rename from server/views/certificate/back-end.jade
rename to server/views/certificate/legacy/back-end.jade
index e94d39a4ad..56c3a29266 100644
--- a/server/views/certificate/back-end.jade
+++ b/server/views/certificate/legacy/back-end.jade
@@ -1,6 +1,6 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
-include styles
+include ../styles
.certificate-wrapper.container
.row
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
- p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/back-end-certification
+ p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/legacy-back-end
diff --git a/server/views/certificate/legacy/data-visualization.jade b/server/views/certificate/legacy/data-visualization.jade
new file mode 100644
index 0000000000..640bf815d8
--- /dev/null
+++ b/server/views/certificate/legacy/data-visualization.jade
@@ -0,0 +1,32 @@
+meta(name='viewport', content='width=device-width, initial-scale=1')
+link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
+include ../styles
+
+.certificate-wrapper.container
+ .row
+ header
+ .col-md-5.col-sm-12
+ .logo
+ img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
+ .col-md-7.col-sm-12
+ .issue-date Issued
+ strong #{date}
+
+ section.information
+ .information-container
+ h3 This certifies that
+ h1
+ strong= name
+ h3 has successfully completed freeCodeCamp's
+ h1
+ strong Data Visualization Projects
+ h4 1 of 3 legacy freeCodeCamp certificates, representing approximately 400 hours of coursework
+
+ footer
+ .row.signatures
+ img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
+ p
+ strong Quincy Larson
+ p Executive Director, freeCodeCamp.org
+ .row
+ p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/legacy-data-visualization
diff --git a/server/views/certificate/front-end.jade b/server/views/certificate/legacy/front-end.jade
similarity index 94%
rename from server/views/certificate/front-end.jade
rename to server/views/certificate/legacy/front-end.jade
index 1179721bb0..d68947129e 100644
--- a/server/views/certificate/front-end.jade
+++ b/server/views/certificate/legacy/front-end.jade
@@ -1,6 +1,6 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
-include styles
+include ../styles
.certificate-wrapper.container
.row
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
- p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/front-end-certification
+ p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/legacy-front-end
diff --git a/server/views/certificate/full-stack.jade b/server/views/certificate/legacy/full-stack.jade
similarity index 80%
rename from server/views/certificate/full-stack.jade
rename to server/views/certificate/legacy/full-stack.jade
index 30782f0137..c8f4f233a4 100644
--- a/server/views/certificate/full-stack.jade
+++ b/server/views/certificate/legacy/full-stack.jade
@@ -1,6 +1,6 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
-include styles
+include ../styles
.certificate-wrapper.container
.row
@@ -19,8 +19,8 @@ include styles
strong= name
h3 has successfully completed freeCodeCamp's
h1
- strong Full Stack Development Projects
- h4 1 of 3 legacy freeCodeCamp certificates, representing approximately 400 hours of coursework
+ strong Legacy Full Stack Development Program
+ h4 All three of the legacy freeCodeCamp certificates, representing approximately 12000 hours of coursework
footer
.row.signatures
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
- p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/full-stack-certification
+ p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/legacy-full-stack
\ No newline at end of file
diff --git a/server/views/certificate/responsive-web-design.jade b/server/views/certificate/responsive-web-design.jade
index 2873106ba0..cc4eb37871 100644
--- a/server/views/certificate/responsive-web-design.jade
+++ b/server/views/certificate/responsive-web-design.jade
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
- p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/responsive-web-design-certification
+ p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/responsive-web-design