feat: save submission to db (#64450)

This commit is contained in:
Shaun Hamilton
2025-12-12 09:31:25 +02:00
committed by GitHub
parent 0a5f192370
commit 33b1967a25
62 changed files with 113 additions and 60 deletions
+1 -2
View File
@@ -2,7 +2,7 @@ import type { CompletedChallenge } from '@prisma/client';
import validator from 'validator'; import validator from 'validator';
import type { FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox'; import type { FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox';
import { getChallenges } from '../../utils/get-challenges.js'; import { challenges, getChallenges } from '../../utils/get-challenges.js';
import { import {
Certification, Certification,
certSlugTypeMap, certSlugTypeMap,
@@ -216,7 +216,6 @@ export const protectedCertificateRoutes: FastifyPluginCallbackTypebox = (
_options, _options,
done done
) => { ) => {
const challenges = getChallenges();
const certLookup = createCertLookup(challenges); const certLookup = createCertLookup(challenges);
// TODO(POST_MVP): Response should not include updated user. If a client wants the updated user, it should make a separate request // TODO(POST_MVP): Response should not include updated user. If a client wants the updated user, it should make a separate request
+1 -3
View File
@@ -22,7 +22,7 @@ import {
formatCoderoadChallengeCompletedValidation, formatCoderoadChallengeCompletedValidation,
formatProjectCompletedValidation formatProjectCompletedValidation
} from '../../utils/error-formatting.js'; } from '../../utils/error-formatting.js';
import { getChallenges } from '../../utils/get-challenges.js'; import { challenges } from '../../utils/get-challenges.js';
import { ProgressTimestamp, getPoints } from '../../utils/progress.js'; import { ProgressTimestamp, getPoints } from '../../utils/progress.js';
import { import {
validateExamFromDbSchema, validateExamFromDbSchema,
@@ -57,8 +57,6 @@ const userChallengeSelect = {
savedChallenges: true savedChallenges: true
}; };
const challenges = getChallenges();
/** /**
* Plugin for the challenge submission endpoints. * Plugin for the challenge submission endpoints.
* *
+5 -9
View File
@@ -2,7 +2,7 @@ import type { ExamResults, user, Prisma } from '@prisma/client';
import { FastifyInstance } from 'fastify'; import { FastifyInstance } from 'fastify';
import { omit, pick } from 'lodash-es'; import { omit, pick } from 'lodash-es';
import { challengeTypes } from '../../../shared/config/challenge-types.js'; import { challengeTypes } from '../../../shared/config/challenge-types.js';
import { getChallenges } from './get-challenges.js'; import { challenges, savableChallenges } from './get-challenges.js';
import { normalizeDate } from './normalize.js'; import { normalizeDate } from './normalize.js';
export const jsCertProjectIds = [ export const jsCertProjectIds = [
@@ -13,15 +13,15 @@ export const jsCertProjectIds = [
'aa2e6f85cab2ab736c9a9b24' 'aa2e6f85cab2ab736c9a9b24'
]; ];
export const multifileCertProjectIds = getChallenges() export const multifileCertProjectIds = challenges
.filter(c => c.challengeType === challengeTypes.multifileCertProject) .filter(c => c.challengeType === challengeTypes.multifileCertProject)
.map(c => c.id); .map(c => c.id);
export const multifilePythonCertProjectIds = getChallenges() export const multifilePythonCertProjectIds = challenges
.filter(c => c.challengeType === challengeTypes.multifilePythonCertProject) .filter(c => c.challengeType === challengeTypes.multifilePythonCertProject)
.map(c => c.id); .map(c => c.id);
export const msTrophyChallenges = getChallenges() export const msTrophyChallenges = challenges
.filter(challenge => challenge.challengeType === challengeTypes.msTrophy) .filter(challenge => challenge.challengeType === challengeTypes.msTrophy)
.map(({ id, msTrophyId }) => ({ id, msTrophyId })); .map(({ id, msTrophyId }) => ({ id, msTrophyId }));
@@ -133,11 +133,7 @@ export async function updateUserChallengeData(
_completedChallenge; _completedChallenge;
let completedChallenge: CompletedChallenge; let completedChallenge: CompletedChallenge;
if ( if (savableChallenges.has(challengeId)) {
jsCertProjectIds.includes(challengeId) ||
multifileCertProjectIds.includes(challengeId) ||
multifilePythonCertProjectIds.includes(challengeId)
) {
completedChallenge = { completedChallenge = {
..._completedChallenge, ..._completedChallenge,
files: files?.map( files: files?.map(
+11
View File
@@ -21,6 +21,7 @@ interface Block {
challengeType: number; challengeType: number;
url?: string; url?: string;
msTrophyId?: string; msTrophyId?: string;
saveSubmissionToDB?: boolean;
}[]; }[];
} }
@@ -51,3 +52,13 @@ export function getChallenges(): Block['challenges'] {
return [...acc, ...challengesForBlock.flat()]; return [...acc, ...challengesForBlock.flat()];
}, []); }, []);
} }
export const challenges = getChallenges();
export const savableChallenges = challenges.reduce((acc, curr) => {
if (curr.saveSubmissionToDB) {
acc.add(curr.id);
}
return acc;
}, new Set<string>());
+2
View File
@@ -103,6 +103,7 @@ exports.createPages = async function createPages({
history history
fileKey fileKey
} }
saveSubmissionToDB
solutions { solutions {
contents contents
ext ext
@@ -334,6 +335,7 @@ exports.createSchemaCustomization = ({ actions }) => {
questions: [Question] questions: [Question]
quizzes: [Quiz] quizzes: [Quiz]
required: [RequiredResource] required: [RequiredResource]
saveSubmissionToDB: Boolean
scene: Scene scene: Scene
solutions: [[FileContents]] solutions: [[FileContents]]
suborder: Int suborder: Int
+3
View File
@@ -246,6 +246,7 @@ export type ChallengeNode = {
quizzes: Quiz[]; quizzes: Quiz[];
assignments: string[]; assignments: string[];
required: Required[]; required: Required[];
saveSubmissionToDB?: boolean;
scene: FullScene; scene: FullScene;
solutions: { solutions: {
[T: string]: FileKeyChallenge; [T: string]: FileKeyChallenge;
@@ -309,6 +310,7 @@ export type DailyCodingChallengeNode = {
notes: string; notes: string;
videoUrl?: string; videoUrl?: string;
translationPending: false; translationPending: false;
saveSubmissionToDB?: boolean;
}; };
}; };
@@ -534,6 +536,7 @@ export type ChallengeMeta = {
helpCategory: string; helpCategory: string;
disableLoopProtectTests: boolean; disableLoopProtectTests: boolean;
disableLoopProtectPreview: boolean; disableLoopProtectPreview: boolean;
saveSubmissionToDB?: boolean;
} & NavigationPaths; } & NavigationPaths;
export type NavigationPaths = { export type NavigationPaths = {
+4 -3
View File
@@ -1,6 +1,5 @@
import { call, put, select, takeEvery } from 'redux-saga/effects'; import { call, put, select, takeEvery } from 'redux-saga/effects';
import { canSaveToDB } from '../../../shared-dist/config/challenge-types';
import { createFlashMessage } from '../components/Flash/redux'; import { createFlashMessage } from '../components/Flash/redux';
import { FlashMessages } from '../components/Flash/redux/flash-messages'; import { FlashMessages } from '../components/Flash/redux/flash-messages';
import { import {
@@ -19,7 +18,9 @@ import { saveChallengeComplete } from './actions';
import { savedChallengesSelector } from './selectors'; import { savedChallengesSelector } from './selectors';
function* saveChallengeSaga() { function* saveChallengeSaga() {
const { id, challengeType } = yield select(challengeMetaSelector); const { id, challengeType, saveSubmissionToDB } = yield select(
challengeMetaSelector
);
const { challengeFiles } = yield select(challengeDataSelector); const { challengeFiles } = yield select(challengeDataSelector);
const savedChallenges = yield select(savedChallengesSelector); const savedChallenges = yield select(savedChallengesSelector);
const savedChallenge = savedChallenges.find(challenge => challenge.id === id); const savedChallenge = savedChallenges.find(challenge => challenge.id === id);
@@ -34,7 +35,7 @@ function* saveChallengeSaga() {
); );
} }
if (canSaveToDB(challengeType)) { if (saveSubmissionToDB) {
const body = standardizeRequestBody({ id, challengeFiles, challengeType }); const body = standardizeRequestBody({ id, challengeFiles, challengeType });
const bodySizeInBytes = getStringSizeInBytes(body); const bodySizeInBytes = getStringSizeInBytes(body);
@@ -34,10 +34,7 @@ import type {
} from '../../../redux/prop-types'; } from '../../../redux/prop-types';
import { editorToneOptions } from '../../../utils/tone/editor-config'; import { editorToneOptions } from '../../../utils/tone/editor-config';
import { editorNotes } from '../../../utils/tone/editor-notes'; import { editorNotes } from '../../../utils/tone/editor-notes';
import { import { challengeTypes } from '../../../../../shared-dist/config/challenge-types';
canSaveToDB,
challengeTypes
} from '../../../../../shared-dist/config/challenge-types';
import { import {
executeChallenge, executeChallenge,
saveEditorContent, saveEditorContent,
@@ -106,6 +103,7 @@ export interface EditorProps {
resizeProps: ResizeProps; resizeProps: ResizeProps;
saveChallenge: () => void; saveChallenge: () => void;
saveEditorContent: () => void; saveEditorContent: () => void;
saveSubmissionToDB?: boolean;
setEditorFocusability: (isFocusable: boolean) => void; setEditorFocusability: (isFocusable: boolean) => void;
submitChallenge: () => void; submitChallenge: () => void;
stopResetting: () => void; stopResetting: () => void;
@@ -154,7 +152,10 @@ const mapStateToProps = createSelector(
( (
attempts: number, attempts: number,
canFocus: boolean, canFocus: boolean,
{ challengeType }: { challengeType: number }, {
challengeType,
saveSubmissionToDB
}: { challengeType: number; saveSubmissionToDB?: boolean },
open, open,
previewOpen: boolean, previewOpen: boolean,
isResetting: boolean, isResetting: boolean,
@@ -166,6 +167,7 @@ const mapStateToProps = createSelector(
attempts, attempts,
canFocus: open ? false : canFocus, canFocus: open ? false : canFocus,
challengeType, challengeType,
saveSubmissionToDB,
previewOpen, previewOpen,
isResetting, isResetting,
isSignedIn, isSignedIn,
@@ -615,7 +617,7 @@ const Editor = (props: EditorProps): JSX.Element => {
monaco.KeyMod.WinCtrl | monaco.KeyCode.KeyS monaco.KeyMod.WinCtrl | monaco.KeyCode.KeyS
], ],
run: run:
canSaveToDB(props.challengeType) && props.isSignedIn props.saveSubmissionToDB && props.isSignedIn
? // save to database ? // save to database
props.saveChallenge props.saveChallenge
: // save to local storage : // save to local storage
@@ -212,7 +212,8 @@ function ShowClassic({
usesMultifileEditor, usesMultifileEditor,
notes, notes,
videoUrl, videoUrl,
translationPending translationPending,
saveSubmissionToDB
} }
} }
}, },
@@ -551,7 +552,10 @@ function ShowClassic({
superBlock={superBlock} superBlock={superBlock}
/> />
<VideoModal videoUrl={videoUrl} /> <VideoModal videoUrl={videoUrl} />
<ResetModal challengeType={challengeType} challengeTitle={title} /> <ResetModal
saveSubmissionToDB={saveSubmissionToDB}
challengeTitle={title}
/>
<ProjectPreviewModal <ProjectPreviewModal
challengeData={challengeData} challengeData={challengeData}
closeText={t('buttons.start-coding')} closeText={t('buttons.start-coding')}
@@ -613,6 +617,7 @@ export const query = graphql`
editableRegionBoundaries editableRegionBoundaries
history history
} }
saveSubmissionToDB
tests { tests {
text text
testString testString
@@ -8,12 +8,11 @@ import { Button, Modal } from '@freecodecamp/ui';
import { closeModal, resetChallenge } from '../redux/actions'; import { closeModal, resetChallenge } from '../redux/actions';
import { isResetModalOpenSelector } from '../redux/selectors'; import { isResetModalOpenSelector } from '../redux/selectors';
import callGA from '../../../analytics/call-ga'; import callGA from '../../../analytics/call-ga';
import { canSaveToDB } from '../../../../../shared-dist/config/challenge-types';
interface ResetModalProps { interface ResetModalProps {
close: () => void; close: () => void;
isOpen: boolean; isOpen: boolean;
challengeType: number; saveSubmissionToDB?: boolean;
reset: () => void; reset: () => void;
challengeTitle: string; challengeTitle: string;
} }
@@ -41,7 +40,7 @@ function withActions(...fns: Array<() => void>) {
function ResetModal({ function ResetModal({
reset, reset,
close, close,
challengeType, saveSubmissionToDB,
isOpen, isOpen,
challengeTitle challengeTitle
}: ResetModalProps): JSX.Element { }: ResetModalProps): JSX.Element {
@@ -56,7 +55,7 @@ function ResetModal({
</Modal.Header> </Modal.Header>
<Modal.Body alignment='center'> <Modal.Body alignment='center'>
<p> <p>
{canSaveToDB(challengeType) {saveSubmissionToDB
? t('learn.revert-warn') ? t('learn.revert-warn')
: t('learn.reset-warn', { : t('learn.reset-warn', {
title: challengeTitle title: challengeTitle
@@ -73,7 +72,7 @@ function ResetModal({
variant='danger' variant='danger'
onClick={withActions(reset, close)} onClick={withActions(reset, close)}
> >
{canSaveToDB(challengeType) {saveSubmissionToDB
? t('buttons.revert-to-saved-code') ? t('buttons.revert-to-saved-code')
: t('buttons.reset-lesson')} : t('buttons.reset-lesson')}
</Button> </Button>
@@ -7,7 +7,6 @@ import { connect } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux'; import { bindActionCreators, Dispatch } from 'redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { canSaveToDB } from '../../../../../shared-dist/config/challenge-types';
import { openModal, executeChallenge } from '../redux/actions'; import { openModal, executeChallenge } from '../redux/actions';
import { challengeMetaSelector } from '../redux/selectors'; import { challengeMetaSelector } from '../redux/selectors';
import { saveChallenge } from '../../../redux/actions'; import { saveChallenge } from '../../../redux/actions';
@@ -18,11 +17,8 @@ import './tool-panel.css';
const mapStateToProps = createSelector( const mapStateToProps = createSelector(
challengeMetaSelector, challengeMetaSelector,
isSignedInSelector, isSignedInSelector,
( ({ saveSubmissionToDB }: { saveSubmissionToDB?: boolean }, isSignedIn) => ({
{ challengeType }: { challengeId: string; challengeType: number }, saveSubmissionToDB,
isSignedIn
) => ({
challengeType,
isSignedIn isSignedIn
}) })
); );
@@ -39,7 +35,7 @@ const mapDispatchToProps = (dispatch: Dispatch) =>
); );
interface ToolPanelProps { interface ToolPanelProps {
challengeType: number; saveSubmissionToDB?: boolean;
executeChallenge: (options?: { showCompletionModal: boolean }) => void; executeChallenge: (options?: { showCompletionModal: boolean }) => void;
saveChallenge: () => void; saveChallenge: () => void;
isMobile?: boolean; isMobile?: boolean;
@@ -52,7 +48,7 @@ interface ToolPanelProps {
} }
function ToolPanel({ function ToolPanel({
challengeType, saveSubmissionToDB,
executeChallenge, executeChallenge,
saveChallenge, saveChallenge,
isMobile, isMobile,
@@ -76,7 +72,7 @@ function ToolPanel({
<Button block={true} variant='primary' onClick={handleRunTests}> <Button block={true} variant='primary' onClick={handleRunTests}>
{isMobile ? t('buttons.run') : t('buttons.run-test')} {isMobile ? t('buttons.run') : t('buttons.run-test')}
</Button> </Button>
{isSignedIn && canSaveToDB(challengeType) && ( {isSignedIn && saveSubmissionToDB && (
<> <>
<Spacer size='xxs' /> <Spacer size='xxs' />
<Button block={true} variant='primary' onClick={saveChallenge}> <Button block={true} variant='primary' onClick={saveChallenge}>
@@ -87,9 +83,9 @@ function ToolPanel({
<Spacer size='xxs' /> <Spacer size='xxs' />
<Button block={true} variant='primary' onClick={openResetModal}> <Button block={true} variant='primary' onClick={openResetModal}>
{isMobile {isMobile
? t(canSaveToDB(challengeType) ? 'buttons.revert' : 'buttons.reset') ? t(saveSubmissionToDB ? 'buttons.revert' : 'buttons.reset')
: t( : t(
canSaveToDB(challengeType) saveSubmissionToDB
? 'buttons.revert-to-saved-code' ? 'buttons.revert-to-saved-code'
: 'buttons.reset-lesson' : 'buttons.reset-lesson'
)} )}
@@ -16,7 +16,6 @@ import {
msTrophyVerified msTrophyVerified
} from '../../../utils/error-messages'; } from '../../../utils/error-messages';
import { import {
canSaveToDB,
challengeTypes, challengeTypes,
getIsDailyCodingChallenge, getIsDailyCodingChallenge,
getDailyCodingChallengeLanguage, getDailyCodingChallengeLanguage,
@@ -119,7 +118,8 @@ function submitModern(type, state) {
} }
if (type === actionTypes.submitChallenge) { if (type === actionTypes.submitChallenge) {
const { id, block, challengeType } = challengeMetaSelector(state); const { id, challengeType, saveSubmissionToDB } =
challengeMetaSelector(state);
let update; let update;
@@ -140,10 +140,7 @@ function submitModern(type, state) {
const challengeFiles = challengeFilesSelector(state); const challengeFiles = challengeFilesSelector(state);
let body; let body;
if ( if (saveSubmissionToDB) {
block === 'javascript-algorithms-and-data-structures-projects' ||
canSaveToDB(challengeType)
) {
body = standardizeRequestBody({ id, challengeType, challengeFiles }); body = standardizeRequestBody({ id, challengeType, challengeFiles });
} else { } else {
body = { body = {
@@ -14,10 +14,7 @@ import {
takeLatest takeLatest
} from 'redux-saga/effects'; } from 'redux-saga/effects';
import { import { challengeTypes } from '../../../../../shared-dist/config/challenge-types';
canSaveToDB,
challengeTypes
} from '../../../../../shared-dist/config/challenge-types';
import { createFlashMessage } from '../../../components/Flash/redux'; import { createFlashMessage } from '../../../components/Flash/redux';
import { FlashMessages } from '../../../components/Flash/redux/flash-messages'; import { FlashMessages } from '../../../components/Flash/redux/flash-messages';
import { import {
@@ -77,11 +74,13 @@ const LOGS_TO_IGNORE = [
// when 'run tests' is clicked, do this first // when 'run tests' is clicked, do this first
function* executeCancellableChallengeSaga(payload) { function* executeCancellableChallengeSaga(payload) {
const { challengeType, id } = yield select(challengeMetaSelector); const { challengeType, id, saveSubmissionToDB } = yield select(
challengeMetaSelector
);
const { challengeFiles } = yield select(challengeDataSelector); const { challengeFiles } = yield select(challengeDataSelector);
// if canSaveToDB, see if body/code size is submittable // if canSaveToDB, see if body/code size is submittable
if (canSaveToDB(challengeType)) { if (saveSubmissionToDB) {
const body = standardizeRequestBody({ id, challengeFiles, challengeType }); const body = standardizeRequestBody({ id, challengeFiles, challengeType });
const bodySizeInBytes = getStringSizeInBytes(body); const bodySizeInBytes = getStringSizeInBytes(body);
@@ -25,7 +25,8 @@ const initialState = {
isLastChallengeInBlock: false, isLastChallengeInBlock: false,
nextChallengePath: '/', nextChallengePath: '/',
prevChallengePath: '/', prevChallengePath: '/',
challengeType: -1 challengeType: -1,
saveSubmissionToDB: false
}, },
challengeTests: [], challengeTests: [],
consoleOut: [], consoleOut: [],
@@ -100,7 +100,8 @@ exports.createChallengePages = function (
template, template,
challengeType, challengeType,
id, id,
isLastChallengeInBlock isLastChallengeInBlock,
saveSubmissionToDB
} = node.challenge; } = node.challenge;
createPage({ createPage({
@@ -123,7 +124,8 @@ exports.createChallengePages = function (
isLastChallengeInBlock: isLastChallengeInBlock, isLastChallengeInBlock: isLastChallengeInBlock,
nextChallengePath: idToNextPathCurrentCurriculum[node.id], nextChallengePath: idToNextPathCurrentCurriculum[node.id],
prevChallengePath: idToPrevPathCurrentCurriculum[node.id], prevChallengePath: idToPrevPathCurrentCurriculum[node.id],
id id,
saveSubmissionToDB
}, },
projectPreview: getProjectPreviewConfig( projectPreview: getProjectPreviewConfig(
node.challenge, node.challenge,
@@ -2,6 +2,7 @@
id: 5e44413e903586ffb414c94e id: 5e44413e903586ffb414c94e
title: Build a Budget App Project title: Build a Budget App Project
challengeType: 23 challengeType: 23
saveSubmissionToDB: true
forumTopicId: 462361 forumTopicId: 462361
dashedName: build-a-budget-app-project dashedName: build-a-budget-app-project
--- ---
@@ -2,6 +2,7 @@
id: 657bdcc3a322aae1eac38392 id: 657bdcc3a322aae1eac38392
title: Build a Cash Register title: Build a Cash Register
challengeType: 14 challengeType: 14
saveSubmissionToDB: true
forumTopicId: 16012 forumTopicId: 16012
dashedName: build-a-cash-register dashedName: build-a-cash-register
--- ---
@@ -2,6 +2,7 @@
id: 657bdc55a322aae1eac3838f id: 657bdc55a322aae1eac3838f
title: Build a Palindrome Checker title: Build a Palindrome Checker
challengeType: 14 challengeType: 14
saveSubmissionToDB: true
forumTopicId: 16004 forumTopicId: 16004
dashedName: build-a-palindrome-checker dashedName: build-a-palindrome-checker
--- ---
@@ -2,6 +2,7 @@
id: bd7158d8c242eddfaeb5bd13 id: bd7158d8c242eddfaeb5bd13
title: Build a Personal Portfolio Webpage title: Build a Personal Portfolio Webpage
challengeType: 14 challengeType: 14
saveSubmissionToDB: true
forumTopicId: 301143 forumTopicId: 301143
dashedName: build-a-personal-portfolio-webpage dashedName: build-a-personal-portfolio-webpage
--- ---
@@ -2,6 +2,7 @@
id: 5e444147903586ffb414c94f id: 5e444147903586ffb414c94f
title: Build a Polygon Area Calculator Project title: Build a Polygon Area Calculator Project
challengeType: 23 challengeType: 23
saveSubmissionToDB: true
forumTopicId: 462363 forumTopicId: 462363
dashedName: build-a-polygon-area-calculator-project dashedName: build-a-polygon-area-calculator-project
--- ---
@@ -2,6 +2,7 @@
id: 5e44414f903586ffb414c950 id: 5e44414f903586ffb414c950
title: Build a Probability Calculator Project title: Build a Probability Calculator Project
challengeType: 23 challengeType: 23
saveSubmissionToDB: true
forumTopicId: 462364 forumTopicId: 462364
dashedName: build-a-probability-calculator-project dashedName: build-a-probability-calculator-project
--- ---
@@ -2,6 +2,7 @@
id: 587d78af367417b2b2512b04 id: 587d78af367417b2b2512b04
title: Build a Product Landing Page title: Build a Product Landing Page
challengeType: 14 challengeType: 14
saveSubmissionToDB: true
forumTopicId: 301144 forumTopicId: 301144
dashedName: build-a-product-landing-page dashedName: build-a-product-landing-page
--- ---
@@ -2,6 +2,7 @@
id: 657bdc8ba322aae1eac38390 id: 657bdc8ba322aae1eac38390
title: Build a Roman Numeral Converter title: Build a Roman Numeral Converter
challengeType: 14 challengeType: 14
saveSubmissionToDB: true
forumTopicId: 16044 forumTopicId: 16044
dashedName: build-a-roman-numeral-converter dashedName: build-a-roman-numeral-converter
--- ---
@@ -2,6 +2,7 @@
id: 587d78af367417b2b2512b03 id: 587d78af367417b2b2512b03
title: Build a Survey Form title: Build a Survey Form
challengeType: 14 challengeType: 14
saveSubmissionToDB: true
forumTopicId: 301145 forumTopicId: 301145
dashedName: build-a-survey-form dashedName: build-a-survey-form
--- ---
@@ -2,6 +2,7 @@
id: 587d78b0367417b2b2512b05 id: 587d78b0367417b2b2512b05
title: Build a Technical Documentation Page title: Build a Technical Documentation Page
challengeType: 14 challengeType: 14
saveSubmissionToDB: true
forumTopicId: 301146 forumTopicId: 301146
dashedName: build-a-technical-documentation-page dashedName: build-a-technical-documentation-page
--- ---
@@ -2,6 +2,7 @@
id: 657bdcb9a322aae1eac38391 id: 657bdcb9a322aae1eac38391
title: Build a Telephone Number Validator title: Build a Telephone Number Validator
challengeType: 14 challengeType: 14
saveSubmissionToDB: true
forumTopicId: 16090 forumTopicId: 16090
dashedName: build-a-telephone-number-validator dashedName: build-a-telephone-number-validator
--- ---
@@ -2,6 +2,7 @@
id: 5e444136903586ffb414c94d id: 5e444136903586ffb414c94d
title: Build a Time Calculator Project title: Build a Time Calculator Project
challengeType: 23 challengeType: 23
saveSubmissionToDB: true
forumTopicId: 462360 forumTopicId: 462360
dashedName: build-a-time-calculator-project dashedName: build-a-time-calculator-project
--- ---
@@ -2,6 +2,7 @@
id: bd7158d8c442eddfaeb5bd18 id: bd7158d8c442eddfaeb5bd18
title: Build a Tribute Page title: Build a Tribute Page
challengeType: 14 challengeType: 14
saveSubmissionToDB: true
forumTopicId: 301147 forumTopicId: 301147
dashedName: build-a-tribute-page dashedName: build-a-tribute-page
--- ---
@@ -2,6 +2,7 @@
id: 5e44412c903586ffb414c94c id: 5e44412c903586ffb414c94c
title: Build an Arithmetic Formatter Project title: Build an Arithmetic Formatter Project
challengeType: 23 challengeType: 23
saveSubmissionToDB: true
forumTopicId: 462359 forumTopicId: 462359
dashedName: build-an-arithmetic-formatter-project dashedName: build-an-arithmetic-formatter-project
--- ---
@@ -2,6 +2,7 @@
id: 6555c1d3e11a1574434cf8b5 id: 6555c1d3e11a1574434cf8b5
title: Build an RPG Creature Search App title: Build an RPG Creature Search App
challengeType: 14 challengeType: 14
saveSubmissionToDB: true
forumTopicId: 16003 forumTopicId: 16003
dashedName: build-an-rpg-creature-search-app dashedName: build-an-rpg-creature-search-app
--- ---
@@ -2,6 +2,7 @@
id: 56533eb9ac21ba0edf2244e2 id: 56533eb9ac21ba0edf2244e2
title: Caesars Cipher title: Caesars Cipher
challengeType: 5 challengeType: 5
saveSubmissionToDB: true
forumTopicId: 16003 forumTopicId: 16003
dashedName: caesars-cipher dashedName: caesars-cipher
--- ---
@@ -2,6 +2,7 @@
id: a7f4d8f2483413a6ce226cac id: a7f4d8f2483413a6ce226cac
title: Roman Numeral Converter title: Roman Numeral Converter
challengeType: 5 challengeType: 5
saveSubmissionToDB: true
forumTopicId: 16044 forumTopicId: 16044
dashedName: roman-numeral-converter dashedName: roman-numeral-converter
--- ---
@@ -2,6 +2,7 @@
id: aa2e6f85cab2ab736c9a9b24 id: aa2e6f85cab2ab736c9a9b24
title: Cash Register title: Cash Register
challengeType: 5 challengeType: 5
saveSubmissionToDB: true
forumTopicId: 16012 forumTopicId: 16012
dashedName: cash-register dashedName: cash-register
--- ---
@@ -2,6 +2,7 @@
id: aaa48de84e1ecc7c742e1124 id: aaa48de84e1ecc7c742e1124
title: Palindrome Checker title: Palindrome Checker
challengeType: 5 challengeType: 5
saveSubmissionToDB: true
forumTopicId: 16004 forumTopicId: 16004
dashedName: palindrome-checker dashedName: palindrome-checker
--- ---
@@ -2,6 +2,7 @@
id: aff0395860f5d3034dc0bfc9 id: aff0395860f5d3034dc0bfc9
title: Telephone Number Validator title: Telephone Number Validator
challengeType: 5 challengeType: 5
saveSubmissionToDB: true
forumTopicId: 16090 forumTopicId: 16090
dashedName: telephone-number-validator dashedName: telephone-number-validator
--- ---
@@ -3,6 +3,7 @@ id: 6718d2d59337c822ecb697ff
title: Build a Bank Account Management Program title: Build a Bank Account Management Program
challengeType: 26 challengeType: 26
dashedName: build-a-bank-account-management-program dashedName: build-a-bank-account-management-program
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -4,6 +4,7 @@ title: Build a Book Inventory App
challengeType: 25 challengeType: 25
dashedName: build-a-book-inventory-app dashedName: build-a-book-inventory-app
demoType: onClick demoType: onClick
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -3,6 +3,7 @@ id: 5e44413e903586ffb414c94e
title: Build a Budget App title: Build a Budget App
challengeType: 27 challengeType: 27
dashedName: build-a-budget-app dashedName: build-a-budget-app
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -4,6 +4,7 @@ title: Build a Celestial Bodies Database
challengeType: 13 challengeType: 13
url: freeCodeCamp/learn-celestial-bodies-database url: freeCodeCamp/learn-celestial-bodies-database
dashedName: lab-celestial-bodies-database dashedName: lab-celestial-bodies-database
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -4,6 +4,7 @@ title: Build a Currency Converter
challengeType: 25 challengeType: 25
dashedName: build-a-currency-converter dashedName: build-a-currency-converter
demoType: onClick demoType: onClick
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -4,6 +4,7 @@ title: Build a Drum Machine
challengeType: 25 challengeType: 25
dashedName: build-drum-machine dashedName: build-drum-machine
demoType: onClick demoType: onClick
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -3,6 +3,7 @@ id: 67ed03ac474c48692f41749e
title: Build a Hash Table title: Build a Hash Table
challengeType: 27 challengeType: 27
dashedName: build-a-hash-table dashedName: build-a-hash-table
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -4,6 +4,7 @@ title: Build a Markdown to HTML Converter
challengeType: 25 challengeType: 25
dashedName: build-a-markdown-to-html-converter dashedName: build-a-markdown-to-html-converter
demoType: onClick demoType: onClick
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -4,6 +4,7 @@ title: Build a Number Guessing Game
challengeType: 13 challengeType: 13
url: freeCodeCamp/learn-number-guessing-game url: freeCodeCamp/learn-number-guessing-game
dashedName: lab-number-guessing-game dashedName: lab-number-guessing-game
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -4,6 +4,7 @@ title: Build a Page of Playing Cards
challengeType: 25 challengeType: 25
dashedName: build-a-page-of-playing-cards dashedName: build-a-page-of-playing-cards
demoType: onClick demoType: onClick
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -4,6 +4,7 @@ title: Build a Palindrome Checker
challengeType: 25 challengeType: 25
dashedName: build-a-palindrome-checker dashedName: build-a-palindrome-checker
demoType: onClick demoType: onClick
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -4,6 +4,7 @@ title: Build a Periodic Table Database
challengeType: 13 challengeType: 13
url: freeCodeCamp/learn-periodic-table-database url: freeCodeCamp/learn-periodic-table-database
dashedName: lab-periodic-table-database dashedName: lab-periodic-table-database
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -4,6 +4,7 @@ title: Build a Personal Portfolio
challengeType: 25 challengeType: 25
dashedName: build-a-personal-portfolio dashedName: build-a-personal-portfolio
demoType: onClick demoType: onClick
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -3,6 +3,7 @@ id: 5e444147903586ffb414c94f
title: Build a Polygon Area Calculator title: Build a Polygon Area Calculator
challengeType: 27 challengeType: 27
dashedName: build-a-polygon-area-calculator dashedName: build-a-polygon-area-calculator
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -4,6 +4,7 @@ title: Build a Product Landing Page
challengeType: 25 challengeType: 25
dashedName: build-a-product-landing-page dashedName: build-a-product-landing-page
demoType: onClick demoType: onClick
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -4,6 +4,7 @@ title: Build a Salon Appointment Scheduler
challengeType: 13 challengeType: 13
url: freeCodeCamp/learn-salon-appointment-scheduler url: freeCodeCamp/learn-salon-appointment-scheduler
dashedName: lab-salon-appointment-scheduler dashedName: lab-salon-appointment-scheduler
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -4,6 +4,7 @@ title: Build a Survey Form
challengeType: 25 challengeType: 25
dashedName: build-a-survey-form dashedName: build-a-survey-form
demoType: onClick demoType: onClick
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -4,6 +4,7 @@ title: Build a Technical Documentation Page
challengeType: 25 challengeType: 25
demoType: onClick demoType: onClick
dashedName: build-a-technical-documentation-page dashedName: build-a-technical-documentation-page
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -4,6 +4,7 @@ title: Build a Tic-Tac-Toe Game
challengeType: 25 challengeType: 25
dashedName: build-a-tic-tac-toe-game dashedName: build-a-tic-tac-toe-game
demoType: onClick demoType: onClick
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -2,6 +2,7 @@
id: 68773ee26f332a80bc0295db id: 68773ee26f332a80bc0295db
title: Implement the Tower of Hanoi Algorithm title: Implement the Tower of Hanoi Algorithm
challengeType: 23 challengeType: 23
saveSubmissionToDB: true
dashedName: implement-the-tower-of-hanoi-algorithm dashedName: implement-the-tower-of-hanoi-algorithm
--- ---
@@ -4,6 +4,7 @@ title: Build a Tribute Page
challengeType: 25 challengeType: 25
demoType: onClick demoType: onClick
dashedName: build-a-tribute-page dashedName: build-a-tribute-page
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -3,6 +3,7 @@ id: 684aaf9ec670c68d20efd0d0
title: Build a User Configuration Manager title: Build a User Configuration Manager
challengeType: 27 challengeType: 27
dashedName: build-a-user-configuration-manager dashedName: build-a-user-configuration-manager
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -3,6 +3,7 @@ id: 673b567e3ba535dda140d278
title: Build a Voting System title: Build a Voting System
challengeType: 26 challengeType: 26
dashedName: build-a-voting-system dashedName: build-a-voting-system
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -4,6 +4,7 @@ title: Build a Weather App
challengeType: 25 challengeType: 25
dashedName: lab-weather-app dashedName: lab-weather-app
demoType: onClick demoType: onClick
saveSubmissionToDB: true
--- ---
# --description-- # --description--
@@ -4,6 +4,7 @@ title: Build a World Cup Database
challengeType: 13 challengeType: 13
url: freeCodeCamp/learn-world-cup-database url: freeCodeCamp/learn-world-cup-database
dashedName: lab-world-cup-database dashedName: lab-world-cup-database
saveSubmissionToDB: true
--- ---
# --description-- # --description--
+1
View File
@@ -315,6 +315,7 @@ const schema = Joi.object().keys({
then: Joi.array().items(Joi.string()).required(), then: Joi.array().items(Joi.string()).required(),
otherwise: Joi.array().items(Joi.string()) otherwise: Joi.array().items(Joi.string())
}), }),
saveSubmissionToDB: Joi.bool(),
scene: Joi.object().keys({ scene: Joi.object().keys({
setup: setupJoi.required(), setup: setupJoi.required(),
commands: Joi.array() commands: Joi.array()
-4
View File
@@ -170,10 +170,6 @@ export const submitTypes = {
[review]: 'tests' [review]: 'tests'
}; };
export const canSaveToDB = (challengeType: number): boolean =>
challengeType === challengeTypes.multifileCertProject ||
challengeType === challengeTypes.multifilePythonCertProject;
const dailyCodingChallengeTypes = [ const dailyCodingChallengeTypes = [
challengeTypes.dailyChallengeJs, challengeTypes.dailyChallengeJs,
challengeTypes.dailyChallengePy challengeTypes.dailyChallengePy