feat: split rdbms into individual blocks and two challengeTypes (#44978)

* feat: split english rdbms into individual blocks

fix: stuff

fix: remove from partiallyComplete array on submit

fix: add suggestion

Update client/i18n/locales/english/translations.json

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

Update client/i18n/locales/english/intro.json

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

Update client/i18n/locales/english/intro.json

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

Update client/i18n/locales/english/intro.json

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

Update client/src/templates/Challenges/codeally/show.tsx

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

Update client/src/templates/Challenges/codeally/show.tsx

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

Update client/src/templates/Challenges/codeally/show.tsx

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

Update client/src/templates/Challenges/codeally/show.tsx

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

Update client/src/templates/Challenges/codeally/show.tsx

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

Update client/src/templates/Challenges/codeally/show.tsx

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

Update client/src/templates/Challenges/codeally/show.tsx

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

Update client/src/templates/Challenges/codeally/show.tsx

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

* fix: prettier

* fix: style suggestion

* Apply suggestions from code review

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
Tom
2022-02-11 09:39:27 -06:00
committed by GitHub
parent 226a8248b7
commit 57cf47dad4
267 changed files with 3832 additions and 2229 deletions

View File

@ -995,6 +995,25 @@ export default function initializeUser(User) {
});
};
User.prototype.getPartiallyCompletedChallenges$ =
function getPartiallyCompletedChallenges$() {
if (
Array.isArray(this.partiallyCompletedChallenges) &&
this.partiallyCompletedChallenges.length
) {
return Observable.of(this.partiallyCompletedChallenges);
}
const id = this.getId();
const filter = {
where: { id },
fields: { partiallyCompletedChallenges: true }
};
return this.constructor.findOne$(filter).map(user => {
this.partiallyCompletedChallenges = user.partiallyCompletedChallenges;
return user.partiallyCompletedChallenges;
});
};
User.getMessages = messages => Promise.resolve(messages);
User.remoteMethod('getMessages', {

View File

@ -236,6 +236,15 @@
],
"default": []
},
"partiallyCompletedChallenges": {
"type": [
{
"completedDate": "number",
"id": "string"
}
],
"default": []
},
"portfolio": {
"type": "array",
"default": []

View File

@ -20,3 +20,6 @@ export const fixCompletedChallengeItem = obj =>
'files',
'isManuallyApproved'
]);
export const fixPartiallyCompletedChallengeItem = obj =>
pick(obj, ['id', 'completedDate']);

View File

@ -13,7 +13,10 @@ import isNumeric from 'validator/lib/isNumeric';
import isURL from 'validator/lib/isURL';
import { environment, deploymentEnv } from '../../../../config/env.json';
import { fixCompletedChallengeItem } from '../../common/utils';
import {
fixCompletedChallengeItem,
fixPartiallyCompletedChallengeItem
} from '../../common/utils';
import { getChallenges } from '../utils/get-curriculum';
import { ifNoUserSend } from '../utils/middleware';
import {
@ -134,6 +137,10 @@ export function buildUserUpdate(
)
};
updateData.$pull = {
partiallyCompletedChallenges: { id: challengeId }
};
if (
timezone &&
timezone !== 'UTC' &&
@ -283,6 +290,27 @@ function projectCompleted(req, res, next) {
});
}
// CodeRoad cert project
if (completedChallenge.challengeType === 13) {
const { partiallyCompletedChallenges = [], completedChallenges = [] } =
user;
const isPartiallyCompleted = partiallyCompletedChallenges.some(
challenge => challenge.id === completedChallenge.id
);
const isCompleted = completedChallenges.some(
challenge => challenge.id === completedChallenge.id
);
if (!isPartiallyCompleted && !isCompleted) {
return res.status(403).json({
type: 'error',
message: 'You have to complete the project before you can submit a URL.'
});
}
}
return user
.getCompletedChallenges$()
.flatMap(() => {
@ -345,6 +373,10 @@ function backendChallengeCompleted(req, res, next) {
.subscribe(() => {}, next);
}
const codeRoadChallenges = getChallenges().filter(
({ challengeType }) => challengeType === 12 || challengeType === 13
);
function createCoderoadChallengeCompleted(app) {
/* Example request coming from CodeRoad:
* req.body: { tutorialId: 'freeCodeCamp/learn-bash-by-building-a-boilerplate:v1.0.0' }
@ -374,18 +406,14 @@ function createCoderoadChallengeCompleted(app) {
return res.send('Tutorial not hosted on freeCodeCamp GitHub account');
}
const codeRoadChallenges = getChallenges().filter(
challenge => challenge.challengeType === 12
);
// validate tutorial name is in codeRoadChallenges object
const tutorialInfo = codeRoadChallenges.find(tutorial =>
tutorial.url?.includes(tutorialRepoName)
const challenge = codeRoadChallenges.find(challenge =>
challenge.url?.includes(tutorialRepoName)
);
if (!tutorialInfo) return res.send('Tutorial name is not valid');
if (!challenge) return res.send('Tutorial name is not valid');
const tutorialMongoId = tutorialInfo?.id;
const { id: challengeId, challengeType } = challenge;
try {
// check if webhook token is in database
@ -406,12 +434,43 @@ function createCoderoadChallengeCompleted(app) {
// submit challenge
const completedDate = Date.now();
const { completedChallenges = [], partiallyCompletedChallenges = [] } =
user;
const userUpdateInfo = buildUserUpdate(user, tutorialMongoId, {
id: tutorialMongoId,
completedDate
});
let userUpdateInfo = {};
const isCompleted = completedChallenges.some(
challenge => challenge.id === challengeId
);
// if CodeRoad cert project and not in completedChallenges,
// add to partiallyCompletedChallenges
if (challengeType === 13 && !isCompleted) {
const finalChallenge = {
id: challengeId,
completedDate
};
userUpdateInfo.updateData = {};
userUpdateInfo.updateData.$set = {
partiallyCompletedChallenges: uniqBy(
[
finalChallenge,
...partiallyCompletedChallenges.map(
fixPartiallyCompletedChallengeItem
)
],
'id'
)
};
// else, add to or update completedChallenges
} else {
userUpdateInfo = buildUserUpdate(user, challengeId, {
id: challengeId,
completedDate
});
}
const updatedUser = await user.updateAttributes(
userUpdateInfo?.updateData
);

View File

@ -4,7 +4,10 @@ import { body } from 'express-validator';
import { pick } from 'lodash';
import { Observable } from 'rx';
import { fixCompletedChallengeItem } from '../../common/utils';
import {
fixCompletedChallengeItem,
fixPartiallyCompletedChallengeItem
} from '../../common/utils';
import { removeCookies } from '../utils/getSetAccessToken';
import { ifNoUser401, ifNoUserRedirectHome } from '../utils/middleware';
import {
@ -99,11 +102,18 @@ function createReadSessionUser(app) {
queryUser &&
Observable.forkJoin(
queryUser.getCompletedChallenges$(),
queryUser.getPartiallyCompletedChallenges$(),
queryUser.getPoints$(),
Donation.getCurrentActiveDonationCount$(),
(completedChallenges, progressTimestamps, activeDonations) => ({
(
completedChallenges,
partiallyCompletedChallenges,
progressTimestamps,
activeDonations
) => ({
activeDonations,
completedChallenges,
partiallyCompletedChallenges,
progress: getProgress(progressTimestamps, queryUser.timezone)
})
);
@ -111,16 +121,26 @@ function createReadSessionUser(app) {
() => !queryUser,
Observable.of({ user: {}, result: '' }),
Observable.defer(() => source)
.map(({ activeDonations, completedChallenges, progress }) => ({
user: {
...queryUser.toJSON(),
...progress,
completedChallenges: completedChallenges.map(
fixCompletedChallengeItem
)
},
sessionMeta: { activeDonations }
}))
.map(
({
activeDonations,
completedChallenges,
partiallyCompletedChallenges,
progress
}) => ({
user: {
...queryUser.toJSON(),
...progress,
completedChallenges: completedChallenges.map(
fixCompletedChallengeItem
),
partiallyCompletedChallenges: partiallyCompletedChallenges.map(
fixPartiallyCompletedChallengeItem
)
},
sessionMeta: { activeDonations }
})
)
.map(({ user, sessionMeta }) => ({
user: {
[user.username]: {

View File

@ -32,6 +32,7 @@ export const publicUserProps = [
'linkedin',
'location',
'name',
'partiallyCompletedChallenges',
'points',
'portfolio',
'profileUI',