feat(legacy-certs): Claim legacy certificates from the settings page
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import _ from 'lodash';
|
import { values as _values, isString, findIndex } from 'lodash';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
@ -12,11 +12,17 @@ import JSAlgoAndDSForm from './JSAlgoAndDSForm.jsx';
|
|||||||
import SectionHeader from './SectionHeader.jsx';
|
import SectionHeader from './SectionHeader.jsx';
|
||||||
import { projectsSelector } from '../../../entities';
|
import { projectsSelector } from '../../../entities';
|
||||||
import { claimCert, updateUserBackend } from '../redux';
|
import { claimCert, updateUserBackend } from '../redux';
|
||||||
import { fetchChallenges, userSelector, hardGoTo } from '../../../redux';
|
import {
|
||||||
|
fetchChallenges,
|
||||||
|
userSelector,
|
||||||
|
hardGoTo,
|
||||||
|
createErrorObservable
|
||||||
|
} from '../../../redux';
|
||||||
import {
|
import {
|
||||||
buildUserProjectsMap,
|
buildUserProjectsMap,
|
||||||
jsProjectSuperBlock
|
jsProjectSuperBlock
|
||||||
} from '../utils/buildUserProjectsMap';
|
} from '../utils/buildUserProjectsMap';
|
||||||
|
import legacyProjects from '../utils/legacyProjectData';
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
userSelector,
|
userSelector,
|
||||||
@ -30,12 +36,15 @@ const mapStateToProps = createSelector(
|
|||||||
isJsAlgoDataStructCert,
|
isJsAlgoDataStructCert,
|
||||||
isApisMicroservicesCert,
|
isApisMicroservicesCert,
|
||||||
isInfosecQaCert,
|
isInfosecQaCert,
|
||||||
|
isFrontEndCert,
|
||||||
|
isBackEndCert,
|
||||||
|
isDataVisCert,
|
||||||
username
|
username
|
||||||
},
|
},
|
||||||
projects
|
projects
|
||||||
) => ({
|
) => ({
|
||||||
projects,
|
projects,
|
||||||
userProjects: projects
|
userProjects: projects.concat(legacyProjects)
|
||||||
.map(block => buildUserProjectsMap(block, challengeMap))
|
.map(block => buildUserProjectsMap(block, challengeMap))
|
||||||
.reduce((projects, current) => ({
|
.reduce((projects, current) => ({
|
||||||
...projects,
|
...projects,
|
||||||
@ -49,7 +58,10 @@ const mapStateToProps = createSelector(
|
|||||||
'Front End Libraries Projects': isFrontEndLibsCert,
|
'Front End Libraries Projects': isFrontEndLibsCert,
|
||||||
'Data Visualization Projects': is2018DataVisCert,
|
'Data Visualization Projects': is2018DataVisCert,
|
||||||
'API and Microservice Projects': isApisMicroservicesCert,
|
'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
|
username
|
||||||
})
|
})
|
||||||
@ -58,6 +70,7 @@ const mapStateToProps = createSelector(
|
|||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return bindActionCreators({
|
return bindActionCreators({
|
||||||
claimCert,
|
claimCert,
|
||||||
|
createError: createErrorObservable,
|
||||||
fetchChallenges,
|
fetchChallenges,
|
||||||
hardGoTo,
|
hardGoTo,
|
||||||
updateUserBackend
|
updateUserBackend
|
||||||
@ -67,6 +80,7 @@ function mapDispatchToProps(dispatch) {
|
|||||||
const propTypes = {
|
const propTypes = {
|
||||||
blockNameIsCertMap: PropTypes.objectOf(PropTypes.bool),
|
blockNameIsCertMap: PropTypes.objectOf(PropTypes.bool),
|
||||||
claimCert: PropTypes.func.isRequired,
|
claimCert: PropTypes.func.isRequired,
|
||||||
|
createError: PropTypes.func.isRequired,
|
||||||
fetchChallenges: PropTypes.func.isRequired,
|
fetchChallenges: PropTypes.func.isRequired,
|
||||||
hardGoTo: PropTypes.func.isRequired,
|
hardGoTo: PropTypes.func.isRequired,
|
||||||
projects: PropTypes.arrayOf(
|
projects: PropTypes.arrayOf(
|
||||||
@ -88,11 +102,15 @@ const propTypes = {
|
|||||||
username: PropTypes.string
|
username: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const compareSuperBlockWith = id => p => p.superBlock === id;
|
||||||
|
|
||||||
class CertificationSettings extends PureComponent {
|
class CertificationSettings extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this.buildProjectForms = this.buildProjectForms.bind(this);
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
this.isProjectSectionCompleted = this.isProjectSectionCompleted.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -102,60 +120,18 @@ class CertificationSettings extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit(values) {
|
buildProjectForms({
|
||||||
const { id } = values;
|
projectBlockName,
|
||||||
const fullForm = _.values(values)
|
challenges,
|
||||||
.filter(Boolean)
|
superBlock
|
||||||
.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) {
|
|
||||||
return this.props.claimCert(id);
|
|
||||||
}
|
|
||||||
const { projects } = this.props;
|
|
||||||
const pIndex = _.findIndex(projects, p => p.superBlock === id);
|
|
||||||
values.nameToIdMap = projects[pIndex].challengeNameIdMap;
|
|
||||||
return this.props.updateUserBackend({
|
|
||||||
projects: {
|
|
||||||
[id]: values
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
const {
|
||||||
blockNameIsCertMap,
|
blockNameIsCertMap,
|
||||||
claimCert,
|
claimCert,
|
||||||
hardGoTo,
|
hardGoTo,
|
||||||
projects,
|
|
||||||
userProjects,
|
userProjects,
|
||||||
username
|
username
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if (!projects.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<SectionHeader>
|
|
||||||
Certification Settings
|
|
||||||
</SectionHeader>
|
|
||||||
<FullWidthRow>
|
|
||||||
<p>
|
|
||||||
Add links to the live demos of your projects as you finish them.
|
|
||||||
Then, once you have added all 5 projects required for a certificate,
|
|
||||||
you can claim it.
|
|
||||||
</p>
|
|
||||||
</FullWidthRow>
|
|
||||||
{
|
|
||||||
projects.map(({
|
|
||||||
projectBlockName,
|
|
||||||
challenges,
|
|
||||||
superBlock
|
|
||||||
}) => {
|
|
||||||
const isCertClaimed = blockNameIsCertMap[projectBlockName];
|
const isCertClaimed = blockNameIsCertMap[projectBlockName];
|
||||||
if (superBlock === jsProjectSuperBlock) {
|
if (superBlock === jsProjectSuperBlock) {
|
||||||
return (
|
return (
|
||||||
@ -193,9 +169,9 @@ class CertificationSettings extends PureComponent {
|
|||||||
[current]: ''
|
[current]: ''
|
||||||
}), {});
|
}), {});
|
||||||
|
|
||||||
const completedProjects = _.values(userValues)
|
const completedProjects = _values(userValues)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.filter(_.isString)
|
.filter(isString)
|
||||||
// minus 1 to account for the id
|
// minus 1 to account for the id
|
||||||
.length - 1;
|
.length - 1;
|
||||||
|
|
||||||
@ -231,7 +207,101 @@ class CertificationSettings extends PureComponent {
|
|||||||
<hr />
|
<hr />
|
||||||
</FullWidthRow>
|
</FullWidthRow>
|
||||||
);
|
);
|
||||||
})
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
if (this.isProjectSectionCompleted(values)) {
|
||||||
|
return this.props.claimCert(id);
|
||||||
|
}
|
||||||
|
const { projects } = this.props;
|
||||||
|
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
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
projects
|
||||||
|
} = this.props;
|
||||||
|
if (!projects.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SectionHeader>
|
||||||
|
Certification Settings
|
||||||
|
</SectionHeader>
|
||||||
|
<FullWidthRow>
|
||||||
|
<p>
|
||||||
|
Add links to the live demos of your projects as you finish them.
|
||||||
|
Then, once you have added all 5 projects required for a certificate,
|
||||||
|
you can claim it.
|
||||||
|
</p>
|
||||||
|
</FullWidthRow>
|
||||||
|
{
|
||||||
|
projects.map(this.buildProjectForms)
|
||||||
|
}
|
||||||
|
<SectionHeader>
|
||||||
|
Legacy Certificate Settings
|
||||||
|
</SectionHeader>
|
||||||
|
{
|
||||||
|
legacyProjects.map(this.buildProjectForms)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
95
common/app/routes/Settings/utils/legacyProjectData.js
Normal file
95
common/app/routes/Settings/utils/legacyProjectData.js
Normal file
@ -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;
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import loopback from 'loopback';
|
import loopback from 'loopback';
|
||||||
|
import moment from 'moment-timezone';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
import { Observable } from 'rx';
|
import { Observable } from 'rx';
|
||||||
@ -13,26 +14,23 @@ import {
|
|||||||
import { observeQuery } from '../utils/rx';
|
import { observeQuery } from '../utils/rx';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
// legacy
|
legacyFrontEndChallengeId,
|
||||||
frontEndChallengeId,
|
legacyBackEndChallengeId,
|
||||||
backEndChallengeId,
|
legacyDataVisId,
|
||||||
dataVisId,
|
|
||||||
|
|
||||||
// modern
|
|
||||||
respWebDesignId,
|
respWebDesignId,
|
||||||
frontEndLibsId,
|
frontEndLibsId,
|
||||||
dataVis2018Id,
|
|
||||||
jsAlgoDataStructId,
|
jsAlgoDataStructId,
|
||||||
|
dataVis2018Id,
|
||||||
apisMicroservicesId,
|
apisMicroservicesId,
|
||||||
infosecQaId
|
infosecQaId
|
||||||
} from '../utils/constantStrings.json';
|
} from '../utils/constantStrings.json';
|
||||||
|
import certTypes from '../utils/certTypes.json';
|
||||||
|
import superBlockCertTypeMap from '../utils/superBlockCertTypeMap';
|
||||||
import {
|
import {
|
||||||
completeCommitment$
|
completeCommitment$
|
||||||
} from '../utils/commit';
|
} from '../utils/commit';
|
||||||
|
|
||||||
import certTypes from '../utils/certTypes.json';
|
|
||||||
import superBlockCertTypeMap from '../utils/superBlockCertTypeMap';
|
|
||||||
|
|
||||||
const log = debug('fcc:certification');
|
const log = debug('fcc:certification');
|
||||||
const renderCertifedEmail = loopback.template(path.join(
|
const renderCertifedEmail = loopback.template(path.join(
|
||||||
__dirname,
|
__dirname,
|
||||||
@ -46,6 +44,48 @@ function isCertified(ids, challengeMap = {}) {
|
|||||||
return _.every(ids, ({ id }) => _.has(challengeMap, id));
|
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) {
|
function getIdsForCert$(id, Challenge) {
|
||||||
return observeQuery(
|
return observeQuery(
|
||||||
Challenge,
|
Challenge,
|
||||||
@ -117,13 +157,24 @@ function sendCertifiedEmail(
|
|||||||
|
|
||||||
export default function certificate(app) {
|
export default function certificate(app) {
|
||||||
const router = app.loopback.Router();
|
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 = {
|
const certTypeIds = {
|
||||||
// legacy
|
// legacy
|
||||||
[certTypes.frontEnd]: getIdsForCert$(frontEndChallengeId, Challenge),
|
[certTypes.frontEnd]: getIdsForCert$(legacyFrontEndChallengeId, Challenge),
|
||||||
[certTypes.backEnd]: getIdsForCert$(backEndChallengeId, Challenge),
|
[certTypes.backEnd]: getIdsForCert$(legacyBackEndChallengeId, Challenge),
|
||||||
[certTypes.dataVis]: getIdsForCert$(dataVisId, Challenge),
|
[certTypes.dataVis]: getIdsForCert$(legacyDataVisId, Challenge),
|
||||||
|
|
||||||
// modern
|
// modern
|
||||||
[certTypes.respWebDesign]: getIdsForCert$(respWebDesignId, Challenge),
|
[certTypes.respWebDesign]: getIdsForCert$(respWebDesignId, Challenge),
|
||||||
@ -145,6 +196,10 @@ export default function certificate(app) {
|
|||||||
ifNoSuperBlock404,
|
ifNoSuperBlock404,
|
||||||
verifyCert
|
verifyCert
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/c/:username/:cert',
|
||||||
|
showCert
|
||||||
|
);
|
||||||
|
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
@ -172,13 +227,9 @@ export default function certificate(app) {
|
|||||||
|
|
||||||
function verifyCert(req, res, next) {
|
function verifyCert(req, res, next) {
|
||||||
const { body: { superBlock }, user } = req;
|
const { body: { superBlock }, user } = req;
|
||||||
|
log(superBlock);
|
||||||
let certType = superBlockCertTypeMap[superBlock];
|
let certType = superBlockCertTypeMap[superBlock];
|
||||||
log(certType);
|
log(certType);
|
||||||
if (certType === 'isDataVisCert') {
|
|
||||||
certType = 'is2018DataVisCert';
|
|
||||||
log(certType);
|
|
||||||
}
|
|
||||||
return user.getChallengeMap$()
|
return user.getChallengeMap$()
|
||||||
.flatMap(() => certTypeIds[certType])
|
.flatMap(() => certTypeIds[certType])
|
||||||
.flatMap(challenge => {
|
.flatMap(challenge => {
|
||||||
@ -251,4 +302,101 @@ export default function certificate(app) {
|
|||||||
}
|
}
|
||||||
return res.status(404).end();
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,88 +1,22 @@
|
|||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
import moment from 'moment-timezone';
|
|
||||||
import debugFactory from 'debug';
|
import debugFactory from 'debug';
|
||||||
import { curry } from 'lodash';
|
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 {
|
import {
|
||||||
ifNoUser401,
|
ifNoUser401,
|
||||||
ifNoUserRedirectTo,
|
ifNoUserRedirectTo,
|
||||||
ifNotVerifiedRedirectToSettings
|
ifNotVerifiedRedirectToSettings
|
||||||
} from '../utils/middleware';
|
} from '../utils/middleware';
|
||||||
import { observeQuery } from '../utils/rx';
|
|
||||||
|
|
||||||
const debug = debugFactory('fcc:boot:user');
|
const debug = debugFactory('fcc:boot:user');
|
||||||
const sendNonUserToMap = ifNoUserRedirectTo('/map');
|
const sendNonUserToMap = ifNoUserRedirectTo('/map');
|
||||||
const sendNonUserToMapWithMessage = curry(ifNoUserRedirectTo, 2)('/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) {
|
module.exports = function(app) {
|
||||||
const router = app.loopback.Router();
|
const router = app.loopback.Router();
|
||||||
const api = app.loopback.Router();
|
const api = app.loopback.Router();
|
||||||
const { Email, User } = app.models;
|
const { Email, User } = app.models;
|
||||||
|
|
||||||
function findUserByUsername$(username, fields) {
|
|
||||||
return observeQuery(
|
|
||||||
User,
|
|
||||||
'findOne',
|
|
||||||
{
|
|
||||||
where: { username },
|
|
||||||
fields
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
api.post(
|
api.post(
|
||||||
'/account/delete',
|
'/account/delete',
|
||||||
ifNoUser401,
|
ifNoUser401,
|
||||||
@ -105,11 +39,6 @@ module.exports = function(app) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Ensure these are the last routes!
|
// Ensure these are the last routes!
|
||||||
api.get(
|
|
||||||
'/c/:username/:cert',
|
|
||||||
showCert
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/user/:username/report-user/',
|
'/user/:username/report-user/',
|
||||||
sendNonUserToMapWithMessage('You must be signed in to report a 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) {
|
function postDeleteAccount(req, res, next) {
|
||||||
User.destroyById(req.user.id, function(err) {
|
User.destroyById(req.user.id, function(err) {
|
||||||
if (err) { return next(err); }
|
if (err) { return next(err); }
|
||||||
|
@ -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",
|
"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",
|
"legacyFrontEndChallengeId": "561add10cb82ac38a17513be",
|
||||||
"backEndChallengeId": "660add10cb82ac38a17513be",
|
"legacyBackEndChallengeId": "660add10cb82ac38a17513be",
|
||||||
"dataVisId": "561add10cb82ac39a17513bc",
|
"legacyDataVisId": "561add10cb82ac39a17513bc",
|
||||||
|
|
||||||
"respWebDesignId": "561add10cb82ac38a17513bc",
|
"respWebDesignId": "561add10cb82ac38a17513bc",
|
||||||
"frontEndLibsId": "561acd10cb82ac38a17513bc",
|
"frontEndLibsId": "561acd10cb82ac38a17513bc",
|
||||||
|
@ -15,6 +15,7 @@ export const publicUserProps = [
|
|||||||
'isApisMicroservicesCert',
|
'isApisMicroservicesCert',
|
||||||
'isBackEndCert',
|
'isBackEndCert',
|
||||||
'isCheater',
|
'isCheater',
|
||||||
|
'is2018DataVisCert',
|
||||||
'isDataVisCert',
|
'isDataVisCert',
|
||||||
'isFrontEndCert',
|
'isFrontEndCert',
|
||||||
'isFullStackCert',
|
'isFullStackCert',
|
||||||
|
@ -2,16 +2,16 @@ import certTypes from './certTypes.json';
|
|||||||
|
|
||||||
const superBlockCertTypeMap = {
|
const superBlockCertTypeMap = {
|
||||||
// legacy
|
// legacy
|
||||||
'front-end': certTypes.frontEnd,
|
'legacy-front-end': certTypes.frontEnd,
|
||||||
'back-end': certTypes.backEnd,
|
'legacy-back-end': certTypes.backEnd,
|
||||||
'data-visualization': certTypes.dataVis,
|
'legacy-data-visualization': certTypes.dataVis,
|
||||||
'full-stack': certTypes.fullStack,
|
'legacy-full-stack': certTypes.fullStack,
|
||||||
|
|
||||||
// modern
|
// modern
|
||||||
'responsive-web-design': certTypes.respWebDesign,
|
'responsive-web-design': certTypes.respWebDesign,
|
||||||
'javascript-algorithms-and-data-structures': certTypes.jsAlgoDataStruct,
|
'javascript-algorithms-and-data-structures': certTypes.jsAlgoDataStruct,
|
||||||
'front-end-libraries': certTypes.frontEndLibs,
|
'front-end-libraries': certTypes.frontEndLibs,
|
||||||
'data-visualization-2018': certTypes.dataVis2018,
|
'data-visualization': certTypes.dataVis2018,
|
||||||
'apis-and-microservices': certTypes.apisMicroservices,
|
'apis-and-microservices': certTypes.apisMicroservices,
|
||||||
'information-security-and-quality-assurance': certTypes.infosecQa
|
'information-security-and-quality-assurance': certTypes.infosecQa
|
||||||
};
|
};
|
||||||
|
@ -29,4 +29,4 @@ include styles
|
|||||||
strong Quincy Larson
|
strong Quincy Larson
|
||||||
p Executive Director, freeCodeCamp.org
|
p Executive Director, freeCodeCamp.org
|
||||||
.row
|
.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
|
@ -29,4 +29,4 @@ include styles
|
|||||||
strong Quincy Larson
|
strong Quincy Larson
|
||||||
p Executive Director, freeCodeCamp.org
|
p Executive Director, freeCodeCamp.org
|
||||||
.row
|
.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
|
||||||
|
@ -29,4 +29,4 @@ include styles
|
|||||||
strong Quincy Larson
|
strong Quincy Larson
|
||||||
p Executive Director, freeCodeCamp.org
|
p Executive Director, freeCodeCamp.org
|
||||||
.row
|
.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
|
||||||
|
@ -29,4 +29,4 @@ include styles
|
|||||||
strong Quincy Larson
|
strong Quincy Larson
|
||||||
p Executive Director, freeCodeCamp.org
|
p Executive Director, freeCodeCamp.org
|
||||||
.row
|
.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
|
||||||
|
@ -29,4 +29,4 @@ include styles
|
|||||||
strong Quincy Larson
|
strong Quincy Larson
|
||||||
p Executive Director, freeCodeCamp.org
|
p Executive Director, freeCodeCamp.org
|
||||||
.row
|
.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
|
||||||
|
@ -29,4 +29,4 @@ include styles
|
|||||||
strong Quincy Larson
|
strong Quincy Larson
|
||||||
p Executive Director, freeCodeCamp.org
|
p Executive Director, freeCodeCamp.org
|
||||||
.row
|
.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
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
meta(name='viewport', content='width=device-width, initial-scale=1')
|
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')
|
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
|
.certificate-wrapper.container
|
||||||
.row
|
.row
|
||||||
@ -29,4 +29,4 @@ include styles
|
|||||||
strong Quincy Larson
|
strong Quincy Larson
|
||||||
p Executive Director, freeCodeCamp.org
|
p Executive Director, freeCodeCamp.org
|
||||||
.row
|
.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
|
32
server/views/certificate/legacy/data-visualization.jade
Normal file
32
server/views/certificate/legacy/data-visualization.jade
Normal file
@ -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
|
@ -1,6 +1,6 @@
|
|||||||
meta(name='viewport', content='width=device-width, initial-scale=1')
|
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')
|
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
|
.certificate-wrapper.container
|
||||||
.row
|
.row
|
||||||
@ -29,4 +29,4 @@ include styles
|
|||||||
strong Quincy Larson
|
strong Quincy Larson
|
||||||
p Executive Director, freeCodeCamp.org
|
p Executive Director, freeCodeCamp.org
|
||||||
.row
|
.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
|
@ -1,6 +1,6 @@
|
|||||||
meta(name='viewport', content='width=device-width, initial-scale=1')
|
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')
|
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
|
.certificate-wrapper.container
|
||||||
.row
|
.row
|
||||||
@ -19,8 +19,8 @@ include styles
|
|||||||
strong= name
|
strong= name
|
||||||
h3 has successfully completed freeCodeCamp's
|
h3 has successfully completed freeCodeCamp's
|
||||||
h1
|
h1
|
||||||
strong Full Stack Development Projects
|
strong Legacy Full Stack Development Program
|
||||||
h4 1 of 3 legacy freeCodeCamp certificates, representing approximately 400 hours of coursework
|
h4 All three of the legacy freeCodeCamp certificates, representing approximately 12000 hours of coursework
|
||||||
|
|
||||||
footer
|
footer
|
||||||
.row.signatures
|
.row.signatures
|
||||||
@ -29,4 +29,4 @@ include styles
|
|||||||
strong Quincy Larson
|
strong Quincy Larson
|
||||||
p Executive Director, freeCodeCamp.org
|
p Executive Director, freeCodeCamp.org
|
||||||
.row
|
.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
|
@ -29,4 +29,4 @@ include styles
|
|||||||
strong Quincy Larson
|
strong Quincy Larson
|
||||||
p Executive Director, freeCodeCamp.org
|
p Executive Director, freeCodeCamp.org
|
||||||
.row
|
.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
|
||||||
|
Reference in New Issue
Block a user