fix(settings): fix-up user objects for solutions (#17556)
This commit is contained in:
committed by
mrugesh mohapatra
parent
e10a9fcda0
commit
f54b7c07f5
@ -64,7 +64,7 @@ class Timeline extends PureComponent {
|
|||||||
|
|
||||||
renderCompletion(completed) {
|
renderCompletion(completed) {
|
||||||
const { idToNameMap } = this.props;
|
const { idToNameMap } = this.props;
|
||||||
const { id, completedDate, solution, files } = completed;
|
const { id, completedDate } = completed;
|
||||||
const challengeDashedName = idToNameMap[id];
|
const challengeDashedName = idToNameMap[id];
|
||||||
return (
|
return (
|
||||||
<tr key={ id }>
|
<tr key={ id }>
|
||||||
@ -80,28 +80,7 @@ class Timeline extends PureComponent {
|
|||||||
}
|
}
|
||||||
</time>
|
</time>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td/>
|
||||||
{/* eslint-disable no-nested-ternary */
|
|
||||||
files ? (
|
|
||||||
<Button
|
|
||||||
block={ true }
|
|
||||||
bsStyle='primary'
|
|
||||||
onClick={ () => this.viewSolution(id) }
|
|
||||||
>
|
|
||||||
View Solution
|
|
||||||
</Button>
|
|
||||||
) : solution ? (
|
|
||||||
<Button
|
|
||||||
block={ true }
|
|
||||||
bsStyle='primary'
|
|
||||||
href={solution}
|
|
||||||
target='_blank'
|
|
||||||
>
|
|
||||||
View Solution
|
|
||||||
</Button>
|
|
||||||
) : ''
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -142,7 +121,7 @@ class Timeline extends PureComponent {
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Challenge</th>
|
<th>Challenge</th>
|
||||||
<th className='text-center'>First Completed</th>
|
<th className='text-center'>Completed</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -100,12 +100,15 @@ const propTypes = {
|
|||||||
superBlock: PropTypes.string,
|
superBlock: PropTypes.string,
|
||||||
updateUserBackend: PropTypes.func.isRequired,
|
updateUserBackend: PropTypes.func.isRequired,
|
||||||
userProjects: PropTypes.objectOf(
|
userProjects: PropTypes.objectOf(
|
||||||
PropTypes.objectOf(PropTypes.oneOfType(
|
PropTypes.oneOfType(
|
||||||
[
|
[
|
||||||
|
// this is really messy, it should be addressed
|
||||||
|
// in completedChallenges migration to unify to one type
|
||||||
PropTypes.string,
|
PropTypes.string,
|
||||||
|
PropTypes.arrayOf(PropTypes.object),
|
||||||
PropTypes.object
|
PropTypes.object
|
||||||
]
|
]
|
||||||
))
|
)
|
||||||
),
|
),
|
||||||
username: PropTypes.string
|
username: PropTypes.string
|
||||||
};
|
};
|
||||||
@ -113,7 +116,6 @@ const propTypes = {
|
|||||||
class CertificationSettings extends PureComponent {
|
class CertificationSettings extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.buildProjectForms = this.buildProjectForms.bind(this);
|
this.buildProjectForms = this.buildProjectForms.bind(this);
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,12 @@ const jsFormPropTypes = {
|
|||||||
claimCert: PropTypes.func.isRequired,
|
claimCert: PropTypes.func.isRequired,
|
||||||
hardGoTo: PropTypes.func.isRequired,
|
hardGoTo: PropTypes.func.isRequired,
|
||||||
isCertClaimed: PropTypes.bool,
|
isCertClaimed: PropTypes.bool,
|
||||||
jsProjects: PropTypes.objectOf(PropTypes.object),
|
jsProjects: PropTypes.objectOf(
|
||||||
|
PropTypes.oneOfType(
|
||||||
|
PropTypes.arrayOf(PropTypes.object),
|
||||||
|
PropTypes.string
|
||||||
|
)
|
||||||
|
),
|
||||||
projectBlockName: PropTypes.string,
|
projectBlockName: PropTypes.string,
|
||||||
superBlock: PropTypes.string,
|
superBlock: PropTypes.string,
|
||||||
username: PropTypes.string
|
username: PropTypes.string
|
||||||
|
@ -11,35 +11,59 @@ const prismLang = {
|
|||||||
html: 'markup'
|
html: 'markup'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getContentString(file) {
|
||||||
|
return file.trim() || '// We do not have the solution to this challenge';
|
||||||
|
}
|
||||||
|
|
||||||
function SolutionViewer({ files }) {
|
function SolutionViewer({ files }) {
|
||||||
|
const solutions = files && Array.isArray(files) ?
|
||||||
|
files.map(file => (
|
||||||
|
<Panel
|
||||||
|
bsStyle='primary'
|
||||||
|
className='solution-viewer'
|
||||||
|
header={ file.ext.toUpperCase() }
|
||||||
|
key={ file.ext }
|
||||||
|
>
|
||||||
|
<pre>
|
||||||
|
<code
|
||||||
|
className={ `language-${prismLang[file.ext]}` }
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: Prism.highlight(
|
||||||
|
file.contents.trim(),
|
||||||
|
Prism.languages[prismLang[file.ext]]
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</pre>
|
||||||
|
</Panel>
|
||||||
|
)) : (
|
||||||
|
<Panel
|
||||||
|
bsStyle='primary'
|
||||||
|
className='solution-viewer'
|
||||||
|
header='JS'
|
||||||
|
key={ files.slice(0, 10) }
|
||||||
|
>
|
||||||
|
<pre>
|
||||||
|
<code
|
||||||
|
className='language-markup'
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: Prism.highlight(
|
||||||
|
getContentString(files),
|
||||||
|
Prism.languages.js
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</pre>
|
||||||
|
</Panel>
|
||||||
|
)
|
||||||
|
;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<link href='/css/prism.css' rel='stylesheet' />
|
<link href='/css/prism.css' rel='stylesheet' />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
{
|
{
|
||||||
Object.keys(files)
|
solutions
|
||||||
.map(key => files[key])
|
|
||||||
.map(file => (
|
|
||||||
<Panel
|
|
||||||
bsStyle='primary'
|
|
||||||
className='solution-viewer'
|
|
||||||
header={ file.ext.toUpperCase() }
|
|
||||||
key={ file.ext }
|
|
||||||
>
|
|
||||||
<pre>
|
|
||||||
<code
|
|
||||||
className={ `language-${prismLang[file.ext]}` }
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: Prism.highlight(
|
|
||||||
file.contents.trim(),
|
|
||||||
Prism.languages[prismLang[file.ext]]
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</pre>
|
|
||||||
</Panel>
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -47,7 +71,10 @@ function SolutionViewer({ files }) {
|
|||||||
|
|
||||||
SolutionViewer.displayName = 'SolutionViewer';
|
SolutionViewer.displayName = 'SolutionViewer';
|
||||||
SolutionViewer.propTypes = {
|
SolutionViewer.propTypes = {
|
||||||
files: PropTypes.object
|
files: PropTypes.oneOfType(
|
||||||
|
PropTypes.arrayOf(PropTypes.objectOf(PropTypes.string)),
|
||||||
|
PropTypes.string
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SolutionViewer;
|
export default SolutionViewer;
|
||||||
|
@ -21,7 +21,7 @@ export function buildUserProjectsMap(projectBlock, completedChallenges) {
|
|||||||
if (completed) {
|
if (completed) {
|
||||||
solution = 'solution' in completed ?
|
solution = 'solution' in completed ?
|
||||||
completed.solution :
|
completed.solution :
|
||||||
completed.files;
|
completed.files || '';
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...solutions,
|
...solutions,
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* Any ref to fixCompletedChallengesItem should be removed post
|
||||||
|
* a db migration to fix all completedChallenges
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
import { Observable } from 'rx';
|
import { Observable } from 'rx';
|
||||||
import uuid from 'uuid/v4';
|
import uuid from 'uuid/v4';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
@ -10,6 +17,7 @@ import _ from 'lodash';
|
|||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
|
|
||||||
|
import { fixCompletedChallengeItem } from '../utils';
|
||||||
import { themes } from '../utils/themes';
|
import { themes } from '../utils/themes';
|
||||||
import { saveUser, observeMethod } from '../../server/utils/rx.js';
|
import { saveUser, observeMethod } from '../../server/utils/rx.js';
|
||||||
import { blacklistedUsernames } from '../../server/utils/constants.js';
|
import { blacklistedUsernames } from '../../server/utils/constants.js';
|
||||||
@ -24,7 +32,7 @@ import {
|
|||||||
publicUserProps
|
publicUserProps
|
||||||
} from '../../server/utils/publicUserProps';
|
} from '../../server/utils/publicUserProps';
|
||||||
|
|
||||||
const debug = debugFactory('fcc:models:user');
|
const log = debugFactory('fcc:models:user');
|
||||||
const BROWNIEPOINTS_TIMEOUT = [1, 'hour'];
|
const BROWNIEPOINTS_TIMEOUT = [1, 'hour'];
|
||||||
|
|
||||||
const createEmailError = redirectTo => wrapHandledError(
|
const createEmailError = redirectTo => wrapHandledError(
|
||||||
@ -47,7 +55,9 @@ function buildCompletedChallengesUpdate(completedChallenges, project) {
|
|||||||
const key = Object.keys(project)[0];
|
const key = Object.keys(project)[0];
|
||||||
const solutions = project[key];
|
const solutions = project[key];
|
||||||
const solutionKeys = Object.keys(solutions);
|
const solutionKeys = Object.keys(solutions);
|
||||||
const currentCompletedChallenges = [ ...completedChallenges ];
|
const currentCompletedChallenges = [
|
||||||
|
...completedChallenges.map(fixCompletedChallengeItem)
|
||||||
|
];
|
||||||
const currentCompletedProjects = currentCompletedChallenges
|
const currentCompletedProjects = currentCompletedChallenges
|
||||||
.filter(({id}) => solutionKeys.includes(id));
|
.filter(({id}) => solutionKeys.includes(id));
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@ -59,7 +69,7 @@ function buildCompletedChallengesUpdate(completedChallenges, project) {
|
|||||||
const isCurrentlyCompleted = indexOfCurrentId !== -1;
|
const isCurrentlyCompleted = indexOfCurrentId !== -1;
|
||||||
if (isCurrentlyCompleted) {
|
if (isCurrentlyCompleted) {
|
||||||
update[indexOfCurrentId] = {
|
update[indexOfCurrentId] = {
|
||||||
..._.find(update, ({id}) => id === currentId).__data,
|
..._.find(update, ({id}) => id === currentId),
|
||||||
solution: solutions[currentId]
|
solution: solutions[currentId]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -298,7 +308,7 @@ module.exports = function(User) {
|
|||||||
User.observe('before delete', function(ctx, next) {
|
User.observe('before delete', function(ctx, next) {
|
||||||
const UserIdentity = User.app.models.UserIdentity;
|
const UserIdentity = User.app.models.UserIdentity;
|
||||||
const UserCredential = User.app.models.UserCredential;
|
const UserCredential = User.app.models.UserCredential;
|
||||||
debug('removing user', ctx.where);
|
log('removing user', ctx.where);
|
||||||
var id = ctx.where && ctx.where.id ? ctx.where.id : null;
|
var id = ctx.where && ctx.where.id ? ctx.where.id : null;
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return next();
|
return next();
|
||||||
@ -315,20 +325,20 @@ module.exports = function(User) {
|
|||||||
)
|
)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
function(data) {
|
function(data) {
|
||||||
debug('deleted', data);
|
log('deleted', data);
|
||||||
},
|
},
|
||||||
function(err) {
|
function(err) {
|
||||||
debug('error deleting user %s stuff', id, err);
|
log('error deleting user %s stuff', id, err);
|
||||||
next(err);
|
next(err);
|
||||||
},
|
},
|
||||||
function() {
|
function() {
|
||||||
debug('user stuff deleted for user %s', id);
|
log('user stuff deleted for user %s', id);
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
debug('setting up user hooks');
|
log('setting up user hooks');
|
||||||
// overwrite lb confirm
|
// overwrite lb confirm
|
||||||
User.confirm = function(uid, token, redirectTo) {
|
User.confirm = function(uid, token, redirectTo) {
|
||||||
return this.findById(uid)
|
return this.findById(uid)
|
||||||
@ -366,6 +376,17 @@ module.exports = function(User) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function manualReload() {
|
||||||
|
this.reload((err, instance) => {
|
||||||
|
if (err) {
|
||||||
|
throw Error('failed to reload user instance');
|
||||||
|
}
|
||||||
|
Object.assign(this, instance);
|
||||||
|
log('user reloaded from db');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
User.prototype.manualReload = manualReload;
|
||||||
|
|
||||||
User.prototype.loginByRequest = function loginByRequest(req, res) {
|
User.prototype.loginByRequest = function loginByRequest(req, res) {
|
||||||
const {
|
const {
|
||||||
query: {
|
query: {
|
||||||
@ -423,7 +444,7 @@ module.exports = function(User) {
|
|||||||
if (!username && (!email || !isEmail(email))) {
|
if (!username && (!email || !isEmail(email))) {
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
debug('checking existence');
|
log('checking existence');
|
||||||
|
|
||||||
// check to see if username is on blacklist
|
// check to see if username is on blacklist
|
||||||
if (username && blacklistedUsernames.indexOf(username) !== -1) {
|
if (username && blacklistedUsernames.indexOf(username) !== -1) {
|
||||||
@ -436,7 +457,7 @@ module.exports = function(User) {
|
|||||||
} else {
|
} else {
|
||||||
where.email = email ? email.toLowerCase() : email;
|
where.email = email ? email.toLowerCase() : email;
|
||||||
}
|
}
|
||||||
debug('where', where);
|
log('where', where);
|
||||||
return User.count(where)
|
return User.count(where)
|
||||||
.then(count => count > 0);
|
.then(count => count > 0);
|
||||||
};
|
};
|
||||||
@ -531,10 +552,7 @@ module.exports = function(User) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.do(() => {
|
.do(() => this.manualReload());
|
||||||
this.isDonating = true;
|
|
||||||
this.donationEmails = [ ...this.donationEmails, donation.email ];
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
User.prototype.getEncodedEmail = function getEncodedEmail(email) {
|
User.prototype.getEncodedEmail = function getEncodedEmail(email) {
|
||||||
@ -677,9 +695,7 @@ module.exports = function(User) {
|
|||||||
this.requestAuthEmail(false, newEmail),
|
this.requestAuthEmail(false, newEmail),
|
||||||
(_, message) => message
|
(_, message) => message
|
||||||
)
|
)
|
||||||
.do(() => {
|
.doOnNext(() => this.manualReload());
|
||||||
Object.assign(this, updateConfig);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -715,15 +731,11 @@ module.exports = function(User) {
|
|||||||
Observable.from(updates)
|
Observable.from(updates)
|
||||||
.flatMap(({ flag, newValue }) => {
|
.flatMap(({ flag, newValue }) => {
|
||||||
return Observable.fromPromise(User.doesExist(null, this.email))
|
return Observable.fromPromise(User.doesExist(null, this.email))
|
||||||
.flatMap(() => {
|
.flatMap(() => this.update$({ [flag]: newValue }));
|
||||||
return this.update$({ [flag]: newValue })
|
|
||||||
.do(() => {
|
|
||||||
this[flag] = newValue;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
.doOnNext(() => this.manualReload())
|
||||||
.map(() => dedent`
|
.map(() => dedent`
|
||||||
We have successfully updated your account.
|
We have successfully updated your account.
|
||||||
`);
|
`);
|
||||||
@ -748,9 +760,7 @@ module.exports = function(User) {
|
|||||||
updatedPortfolio[pIndex] = { ...portfolioItem };
|
updatedPortfolio[pIndex] = { ...portfolioItem };
|
||||||
}
|
}
|
||||||
return this.update$({ portfolio: updatedPortfolio })
|
return this.update$({ portfolio: updatedPortfolio })
|
||||||
.do(() => {
|
.do(() => this.manualReload())
|
||||||
this.portfolio = updatedPortfolio;
|
|
||||||
})
|
|
||||||
.map(() => dedent`
|
.map(() => dedent`
|
||||||
Your portfolio has been updated.
|
Your portfolio has been updated.
|
||||||
`);
|
`);
|
||||||
@ -779,7 +789,7 @@ module.exports = function(User) {
|
|||||||
}
|
}
|
||||||
return this.update$(updateData);
|
return this.update$(updateData);
|
||||||
})
|
})
|
||||||
.do(() => Object.assign(this, updateData))
|
.doOnNext(() => this.manualReload() )
|
||||||
.map(() => dedent`
|
.map(() => dedent`
|
||||||
Your projects have been updated.
|
Your projects have been updated.
|
||||||
`);
|
`);
|
||||||
@ -795,7 +805,7 @@ module.exports = function(User) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return this.update$(update)
|
return this.update$(update)
|
||||||
.do(() => Object.assign(this, update))
|
.doOnNext(() => this.manualReload())
|
||||||
.map(() => dedent`
|
.map(() => dedent`
|
||||||
Your privacy settings have been updated.
|
Your privacy settings have been updated.
|
||||||
`);
|
`);
|
||||||
@ -824,9 +834,7 @@ module.exports = function(User) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this.update$({ username: newUsername })
|
return this.update$({ username: newUsername })
|
||||||
.do(() => {
|
.do(() => this.manualReload())
|
||||||
this.username = newUsername;
|
|
||||||
})
|
|
||||||
.map(() => dedent`
|
.map(() => dedent`
|
||||||
Your username has been updated successfully.
|
Your username has been updated successfully.
|
||||||
`);
|
`);
|
||||||
@ -955,7 +963,7 @@ module.exports = function(User) {
|
|||||||
},
|
},
|
||||||
(e) => cb(e, null, dev ? { giver, receiver, data } : null),
|
(e) => cb(e, null, dev ? { giver, receiver, data } : null),
|
||||||
() => {
|
() => {
|
||||||
debug('brownie points assigned completed');
|
log('brownie points assigned completed');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -1014,7 +1022,9 @@ module.exports = function(User) {
|
|||||||
);
|
);
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
}
|
}
|
||||||
return this.update$({ theme }).toPromise();
|
return this.update$({ theme })
|
||||||
|
.doOnNext(() => this.manualReload())
|
||||||
|
.toPromise();
|
||||||
};
|
};
|
||||||
|
|
||||||
// deprecated. remove once live
|
// deprecated. remove once live
|
||||||
|
@ -167,25 +167,37 @@
|
|||||||
"description": "Camper is information security and quality assurance certified",
|
"description": "Camper is information security and quality assurance certified",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"completedChallengeCount": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "generated per request, not held in db"
|
|
||||||
},
|
|
||||||
"completedCertCount": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "generated per request, not held in db"
|
|
||||||
},
|
|
||||||
"completedProjectCount": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "generated per request, not held in db"
|
|
||||||
},
|
|
||||||
"completedChallenges": {
|
"completedChallenges": {
|
||||||
"type": [
|
"type": [
|
||||||
{
|
{
|
||||||
"completedDate": "number",
|
"completedDate": "number",
|
||||||
"id": "string",
|
"id": "string",
|
||||||
"solution": "string",
|
"solution": "string",
|
||||||
"challengeType": "number"
|
"githubLink": "string",
|
||||||
|
"challengeType": "number",
|
||||||
|
"files": {
|
||||||
|
"type": [
|
||||||
|
{
|
||||||
|
"contents": {
|
||||||
|
"type": "string",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"ext": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"default": []
|
"default": []
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
export function dashify(str) {
|
export function dashify(str) {
|
||||||
return ('' + str)
|
return ('' + str)
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
@ -8,3 +10,8 @@ export function dashify(str) {
|
|||||||
// todo: unify with server/utils/index.js:dasherize
|
// todo: unify with server/utils/index.js:dasherize
|
||||||
const dasherize = dashify;
|
const dasherize = dashify;
|
||||||
export { dasherize };
|
export { dasherize };
|
||||||
|
|
||||||
|
export const fixCompletedChallengeItem = obj => pick(
|
||||||
|
obj,
|
||||||
|
[ 'id', 'completedDate', 'solution', 'githubLink', 'challengeType', 'files' ]
|
||||||
|
);
|
||||||
|
@ -355,8 +355,6 @@ export default function certificate(app) {
|
|||||||
)
|
)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
user => {
|
user => {
|
||||||
const { isLocked, showCerts } = user.profileUI;
|
|
||||||
const profile = `/portfolio/${user.username}`;
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
req.flash(
|
req.flash(
|
||||||
'danger',
|
'danger',
|
||||||
@ -364,6 +362,8 @@ export default function certificate(app) {
|
|||||||
);
|
);
|
||||||
return res.redirect('/');
|
return res.redirect('/');
|
||||||
}
|
}
|
||||||
|
const { isLocked, showCerts } = user.profileUI;
|
||||||
|
const profile = `/portfolio/${user.username}`;
|
||||||
|
|
||||||
if (!user.name) {
|
if (!user.name) {
|
||||||
req.flash(
|
req.flash(
|
||||||
@ -415,7 +415,7 @@ export default function certificate(app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (user[certType]) {
|
if (user[certType]) {
|
||||||
const { completedChallenges = {} } = user;
|
const { completedChallenges = [] } = user;
|
||||||
const { completedDate = new Date() } = _.find(
|
const { completedDate = new Date() } = _.find(
|
||||||
completedChallenges, ({ id }) => certId === id
|
completedChallenges, ({ id }) => certId === id
|
||||||
) || {};
|
) || {};
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* Any ref to fixCompletedChallengesItem should be removed post
|
||||||
|
* a db migration to fix all completedChallenges
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import accepts from 'accepts';
|
import accepts from 'accepts';
|
||||||
@ -8,17 +15,49 @@ import { getChallengeById, cachedMap } from '../utils/map';
|
|||||||
import { dasherize } from '../utils';
|
import { dasherize } from '../utils';
|
||||||
|
|
||||||
import pathMigrations from '../resources/pathMigration.json';
|
import pathMigrations from '../resources/pathMigration.json';
|
||||||
|
import { fixCompletedChallengeItem } from '../../common/utils';
|
||||||
|
|
||||||
const log = debug('fcc:boot:challenges');
|
const log = debug('fcc:boot:challenges');
|
||||||
|
|
||||||
const learnURL = 'https://learn.freecodecamp.org';
|
const learnURL = 'https://learn.freecodecamp.org';
|
||||||
|
|
||||||
|
const jsProjects = [
|
||||||
|
'aaa48de84e1ecc7c742e1124',
|
||||||
|
'a7f4d8f2483413a6ce226cac',
|
||||||
|
'56533eb9ac21ba0edf2244e2',
|
||||||
|
'aff0395860f5d3034dc0bfc9',
|
||||||
|
'aa2e6f85cab2ab736c9a9b24'
|
||||||
|
];
|
||||||
|
|
||||||
function buildUserUpdate(
|
function buildUserUpdate(
|
||||||
user,
|
user,
|
||||||
challengeId,
|
challengeId,
|
||||||
completedChallenge,
|
_completedChallenge,
|
||||||
timezone
|
timezone
|
||||||
) {
|
) {
|
||||||
|
const { files } = _completedChallenge;
|
||||||
|
let completedChallenge = {};
|
||||||
|
|
||||||
|
if (jsProjects.includes(challengeId)) {
|
||||||
|
completedChallenge = {
|
||||||
|
..._completedChallenge,
|
||||||
|
files: Object.keys(files)
|
||||||
|
.map(key => files[key])
|
||||||
|
.map(file => _.pick(
|
||||||
|
file,
|
||||||
|
[
|
||||||
|
'contents',
|
||||||
|
'key',
|
||||||
|
'index',
|
||||||
|
'name',
|
||||||
|
'path',
|
||||||
|
'ext'
|
||||||
|
]
|
||||||
|
))
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
completedChallenge = _.omit(_completedChallenge, ['files']);
|
||||||
|
}
|
||||||
let finalChallenge;
|
let finalChallenge;
|
||||||
const updateData = {};
|
const updateData = {};
|
||||||
const { timezone: userTimezone, completedChallenges = [] } = user;
|
const { timezone: userTimezone, completedChallenges = [] } = user;
|
||||||
@ -46,7 +85,7 @@ function buildUserUpdate(
|
|||||||
|
|
||||||
updateData.$set = {
|
updateData.$set = {
|
||||||
completedChallenges: _.uniqBy(
|
completedChallenges: _.uniqBy(
|
||||||
[finalChallenge, ...completedChallenges],
|
[finalChallenge, ...completedChallenges.map(fixCompletedChallengeItem)],
|
||||||
'id'
|
'id'
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
@ -167,6 +206,7 @@ export default function(app) {
|
|||||||
const points = alreadyCompleted ? user.points : user.points + 1;
|
const points = alreadyCompleted ? user.points : user.points + 1;
|
||||||
|
|
||||||
return user.update$(updateData)
|
return user.update$(updateData)
|
||||||
|
.doOnNext(() => user.manualReload())
|
||||||
.doOnNext(({ count }) => log('%s documents updated', count))
|
.doOnNext(({ count }) => log('%s documents updated', count))
|
||||||
.map(() => {
|
.map(() => {
|
||||||
if (type === 'json') {
|
if (type === 'json') {
|
||||||
@ -199,7 +239,7 @@ export default function(app) {
|
|||||||
return req.user.getCompletedChallenges$()
|
return req.user.getCompletedChallenges$()
|
||||||
.flatMap(() => {
|
.flatMap(() => {
|
||||||
const completedDate = Date.now();
|
const completedDate = Date.now();
|
||||||
const { id, solution, timezone } = req.body;
|
const { id, solution, timezone, files } = req.body;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
alreadyCompleted,
|
alreadyCompleted,
|
||||||
@ -207,7 +247,7 @@ export default function(app) {
|
|||||||
} = buildUserUpdate(
|
} = buildUserUpdate(
|
||||||
req.user,
|
req.user,
|
||||||
id,
|
id,
|
||||||
{ id, solution, completedDate },
|
{ id, solution, completedDate, files },
|
||||||
timezone
|
timezone
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -250,7 +290,7 @@ export default function(app) {
|
|||||||
|
|
||||||
const completedChallenge = _.pick(
|
const completedChallenge = _.pick(
|
||||||
body,
|
body,
|
||||||
[ 'id', 'solution', 'githubLink', 'challengeType' ]
|
[ 'id', 'solution', 'githubLink', 'challengeType', 'files' ]
|
||||||
);
|
);
|
||||||
completedChallenge.completedDate = Date.now();
|
completedChallenge.completedDate = Date.now();
|
||||||
|
|
||||||
@ -278,6 +318,7 @@ export default function(app) {
|
|||||||
} = buildUserUpdate(user, completedChallenge.id, completedChallenge);
|
} = buildUserUpdate(user, completedChallenge.id, completedChallenge);
|
||||||
|
|
||||||
return user.update$(updateData)
|
return user.update$(updateData)
|
||||||
|
.doOnNext(() => user.manualReload())
|
||||||
.doOnNext(({ count }) => log('%s documents updated', count))
|
.doOnNext(({ count }) => log('%s documents updated', count))
|
||||||
.doOnNext(() => {
|
.doOnNext(() => {
|
||||||
if (type === 'json') {
|
if (type === 'json') {
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* Any ref to fixCompletedChallengesItem should be removed post
|
||||||
|
* a db migration to fix all completedChallenges
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
import { Observable } from 'rx';
|
import { Observable } from 'rx';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
@ -6,6 +13,7 @@ import {
|
|||||||
normaliseUserFields,
|
normaliseUserFields,
|
||||||
userPropsForSession
|
userPropsForSession
|
||||||
} from '../utils/publicUserProps';
|
} from '../utils/publicUserProps';
|
||||||
|
import { fixCompletedChallengeItem } from '../../common/utils';
|
||||||
|
|
||||||
export default function userServices() {
|
export default function userServices() {
|
||||||
return {
|
return {
|
||||||
@ -32,7 +40,9 @@ export default function userServices() {
|
|||||||
.map(({ completedChallenges, progress }) => ({
|
.map(({ completedChallenges, progress }) => ({
|
||||||
...queryUser.toJSON(),
|
...queryUser.toJSON(),
|
||||||
...progress,
|
...progress,
|
||||||
completedChallenges
|
completedChallenges: completedChallenges.map(
|
||||||
|
fixCompletedChallengeItem
|
||||||
|
)
|
||||||
}))
|
}))
|
||||||
.map(
|
.map(
|
||||||
user => ({
|
user => ({
|
||||||
|
Reference in New Issue
Block a user