From 19aa2b4acdc92ce05910c0f688a5bb9ae7d4bc56 Mon Sep 17 00:00:00 2001 From: Mrugesh Mohapatra Date: Wed, 25 Feb 2026 23:18:08 +0530 Subject: [PATCH] fix: replace dead v1 curriculum REST API with GraphQL The v1 REST API at freecodecamp.org/curriculum-data/v1/ now returns 404 HTML, crashing every page that loads curriculum data. Replace with the GraphQL API at curriculum-db.freecodecamp.org. - Add util/curriculum/fetchCurriculum.js (GraphQL client with cache) - Rewire getAllSuperblockTitlesAndDashedNames to use GraphQL - Rewire getSuperblockTitlesInClassroomByIndex to use GraphQL - Update classes page and all 3 dashboard pages --- pages/classes/index.js | 8 +- pages/dashboard/[id].js | 18 +- pages/dashboard/v2/[id].js | 8 +- .../v2/details/[id]/[studentEmail].js | 8 +- util/api_proccesor.js | 418 ------------------ util/curriculum/fetchCurriculum.js | 80 ++++ .../getAllSuperblockTitlesAndDashedNames.js | 29 +- ...TitlesAndDashedNamesSuperblockJSONArray.js | 16 - .../getSuperblockTitlesInClassroomByIndex.js | 8 +- util/legacy/getDashedNamesURLs.js | 33 -- util/legacy/getNonDashedNamesURLs.js | 28 -- util/legacy/getSuperBlockJsons.js | 36 -- 12 files changed, 105 insertions(+), 585 deletions(-) delete mode 100644 util/api_proccesor.js create mode 100644 util/curriculum/fetchCurriculum.js delete mode 100644 util/curriculum/getAllTitlesAndDashedNamesSuperblockJSONArray.js delete mode 100644 util/legacy/getDashedNamesURLs.js delete mode 100644 util/legacy/getNonDashedNamesURLs.js delete mode 100644 util/legacy/getSuperBlockJsons.js diff --git a/pages/classes/index.js b/pages/classes/index.js index bb4bf41d3..1a7ced307 100644 --- a/pages/classes/index.js +++ b/pages/classes/index.js @@ -8,6 +8,7 @@ import { ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import { useState } from 'react'; import redirectUser from '../../util/redirectUser.js'; +import { getAvailableSuperblocks } from '../../util/curriculum/fetchCurriculum'; export async function getServerSideProps(ctx) { // Dynamic import to prevent Prisma from being bundled for client @@ -45,12 +46,9 @@ export async function getServerSideProps(ctx) { }) ); - const superblocksres = await fetch( - 'https://www.freecodecamp.org/curriculum-data/v1/available-superblocks.json' - ); - const superblocksreq = await superblocksres.json(); + const superblocks = await getAvailableSuperblocks(); const blocks = []; - superblocksreq['superblocks'].map((x, i) => + superblocks.map((x, i) => blocks.push({ value: i, label: x.dashedName, displayName: x.title }) ); return { diff --git a/pages/dashboard/[id].js b/pages/dashboard/[id].js index 95e003192..91eaa3a2a 100644 --- a/pages/dashboard/[id].js +++ b/pages/dashboard/[id].js @@ -8,10 +8,10 @@ import { createSuperblockDashboardObject } from '../../util/dashboard/createSupe import { fetchStudentData } from '../../util/student/fetchStudentData'; import redirectUser from '../../util/redirectUser.js'; -// NOTE: These functions are deprecated for v9 curriculum (no individual REST API JSON files) -import { getDashedNamesURLs } from '../../util/legacy/getDashedNamesURLs'; -import { getNonDashedNamesURLs } from '../../util/legacy/getNonDashedNamesURLs'; -import { getSuperBlockJsons } from '../../util/legacy/getSuperBlockJsons'; +import { + getAvailableSuperblocks, + getSuperblocksByIndices +} from '../../util/curriculum/fetchCurriculum'; export async function getServerSideProps(context) { // Dynamic import to prevent Prisma from being bundled for client @@ -49,14 +49,14 @@ export async function getServerSideProps(context) { fccCertifications: true } }); - let superblockURLS = await getDashedNamesURLs( - certificationNumbers.fccCertifications + const allSuperblocks = await getAvailableSuperblocks(); + let nonDashedNames = certificationNumbers.fccCertifications.map( + i => allSuperblocks[i]?.title ); - let nonDashedNames = await getNonDashedNamesURLs( + + let superBlockJsons = await getSuperblocksByIndices( certificationNumbers.fccCertifications ); - - let superBlockJsons = await getSuperBlockJsons(superblockURLS); let dashboardObjs = await createSuperblockDashboardObject(superBlockJsons); let currStudentData = await fetchStudentData(); diff --git a/pages/dashboard/v2/[id].js b/pages/dashboard/v2/[id].js index 0de21d81b..2d87b8f80 100644 --- a/pages/dashboard/v2/[id].js +++ b/pages/dashboard/v2/[id].js @@ -11,9 +11,7 @@ import { fetchStudentData } from '../../../util/student/fetchStudentData'; import { checkIfStudentHasProgressDataForSuperblocksSelectedByTeacher } from '../../../util/student/checkIfStudentHasProgressDataForSuperblocksSelectedByTeacher'; import redirectUser from '../../../util/redirectUser.js'; -// NOTE: These functions are deprecated for v9 curriculum (no individual REST API JSON files) -import { getDashedNamesURLs } from '../../../util/legacy/getDashedNamesURLs'; -import { getSuperBlockJsons } from '../../../util/legacy/getSuperBlockJsons'; +import { getSuperblocksByIndices } from '../../../util/curriculum/fetchCurriculum'; export async function getServerSideProps(context) { // Dynamic import to prevent Prisma from being bundled for client @@ -57,11 +55,9 @@ export async function getServerSideProps(context) { } }); - let superblockURLS = await getDashedNamesURLs( + let superBlockJsons = await getSuperblocksByIndices( certificationNumbers.fccCertifications ); - - let superBlockJsons = await getSuperBlockJsons(superblockURLS); // this is an array of urls let dashboardObjs = await createSuperblockDashboardObject(superBlockJsons); let totalChallenges = getTotalChallengesForSuperblocks(dashboardObjs); diff --git a/pages/dashboard/v2/details/[id]/[studentEmail].js b/pages/dashboard/v2/details/[id]/[studentEmail].js index 61f9ef0cb..e2fcc89a1 100644 --- a/pages/dashboard/v2/details/[id]/[studentEmail].js +++ b/pages/dashboard/v2/details/[id]/[studentEmail].js @@ -11,9 +11,7 @@ import redirectUser from '../../../../../util/redirectUser.js'; import styles from '../../../../../components/DetailsCSS.module.css'; import DetailsDashboard from '../../../../../components/DetailsDashboard'; -// NOTE: These functions are deprecated for v9 curriculum (no individual REST API JSON files) -import { getDashedNamesURLs } from '../../../../../util/legacy/getDashedNamesURLs'; -import { getSuperBlockJsons } from '../../../../../util/legacy/getSuperBlockJsons'; +import { getSuperblocksByIndices } from '../../../../../util/curriculum/fetchCurriculum'; export async function getServerSideProps(context) { // Dynamic import to prevent Prisma from being bundled for client @@ -72,11 +70,9 @@ export async function getServerSideProps(context) { certificationNumbers.fccCertifications ); - let superblockURLS = await getDashedNamesURLs( + let superBlockJsons = await getSuperblocksByIndices( certificationNumbers.fccCertifications ); - - let superBlockJsons = await getSuperBlockJsons(superblockURLS); // this is an array of urls let superblocksDetailsJSONArray = await createSuperblockDashboardObject( superBlockJsons ); diff --git a/util/api_proccesor.js b/util/api_proccesor.js deleted file mode 100644 index ed9a1c538..000000000 --- a/util/api_proccesor.js +++ /dev/null @@ -1,418 +0,0 @@ -export const FCC_BASE_URL = 'https://www.freecodecamp.org/curriculum-data/v1/'; -export const AVAILABLE_SUPER_BLOCKS = - FCC_BASE_URL + 'available-superblocks.json'; - -/** ============ getAllTitlesAndDashedNamesSuperblockJSONArray() ============ */ -export async function getAllTitlesAndDashedNamesSuperblockJSONArray() { - // calls this API https://www.freecodecamp.org/curriculum-data/v1/available-superblocks.json - const superblocksres = await fetch(AVAILABLE_SUPER_BLOCKS); - - // the response of this structure is [ superblocks: [ {}, {}, ...etc] ] - const curriculumData = await superblocksres.json(); - - // which is why we return curriculumData.superblocks - return curriculumData.superblocks; -} - -/** ============ getAllSuperblockTitlesAndDashedNames() ============ */ -export async function getAllSuperblockTitlesAndDashedNames() { - let superblockTitleAndDashedNameJSONArray = - await getAllTitlesAndDashedNamesSuperblockJSONArray(); - - let superblockDashedNameToTitleArrayMapping = []; - superblockTitleAndDashedNameJSONArray.forEach( - superblockDashedNameAndTitleObject => { - let superblockDashedNameToTitleArray = { - superblockDashedName: '', - superblockReadableTitle: '' - }; - let superblockDashedName = superblockDashedNameAndTitleObject.dashedName; - let superblockTitle = superblockDashedNameAndTitleObject.title; - superblockDashedNameToTitleArray.superblockDashedName = - superblockDashedName; - superblockDashedNameToTitleArray.superblockReadableTitle = - superblockTitle; - superblockDashedNameToTitleArrayMapping.push( - superblockDashedNameToTitleArray - ); - } - ); - return superblockDashedNameToTitleArrayMapping; -} - -/** ============ getSuperblockTitlesInClassroomByIndex(fccCertificationsArrayOfIndicies) ============ */ -// The reason we use an array of indicies is because that is how the data is stored in the Classroom table after class creation, see ClassInviteTable.js and modal.js component for more context. -export async function getSuperblockTitlesInClassroomByIndex( - fccCertificationsArrayOfIndicies -) { - let allSuperblockTitles = await getAllSuperblockTitlesAndDashedNames(); - - return fccCertificationsArrayOfIndicies.map( - x => allSuperblockTitles[x].superblockReadableTitle - ); -} - -/** ============ checkIfStudentHasProgressDataForSuperblock(studentJSON, superblockDashboardObj) ============ */ -// Since we are using hard-coded mock data at the moment, this check allows to anticipate the -// correct response, however, when the student API data goes live, it will be assumed that it will on -// provide student data on the specified superblocks selected by the teacher -export function checkIfStudentHasProgressDataForSuperblocksSelectedByTeacher( - studentJSON, - superblockDashboardObj -) { - // Returns a boolean matrix which checks to see enrollment in at least 1 superblock (at least 1 because in the GlobalDashboard component we calculate the cumulative progress) - - let superblockTitlesSelectedByTeacher = []; - - superblockDashboardObj.forEach(superblockObj => { - superblockTitlesSelectedByTeacher.push(superblockObj[0].superblock); - }); - - let studentResponseDataHasSuperblockBooleanArray = []; - studentJSON.forEach(studentDetails => { - let individualStudentEnrollmentStatus = []; - studentDetails.certifications.forEach(certObj => { - let studentIsEnrolledSuperblock = false; - if (superblockTitlesSelectedByTeacher.includes(Object.keys(certObj)[0])) { - studentIsEnrolledSuperblock = true; - } - individualStudentEnrollmentStatus.push(studentIsEnrolledSuperblock); - }); - studentResponseDataHasSuperblockBooleanArray.push( - individualStudentEnrollmentStatus - ); - }); - - return studentResponseDataHasSuperblockBooleanArray; -} - -/** ============ sortSuperBlocks(superblock) ============ */ -/** - * This function returns a 2D array for each block within blocks - * block[0] is the name of the course - * block[1] is a dictionary {desc, challenges} - * Example Usage: - * sortSuperBlocks("2022/responsive-web-design.json", "https://www.freecodecamp.org/curriculum-data/v1/2022/responsive-web-design.json") - * - */ -export function sortSuperBlocks(superblock) { - let sortedBlock = superblock.sort((a, b) => a['order'] - b['order']); - return sortedBlock; -} - -/** ============ getDashedNamesURLs(fccCertifications) ============ */ -/* - * [Parameters] an array of indices as a parameter. - * Those indices correspond to an index in an array of objects containing superblock data at a JSON endpoint (https://www.freecodecamp.org/curriculum-data/v1/available-superblocks.json) - * The array of indices is stored in Prisma as fccCertificates (see const certificationNumbers in [id].js). - * - * [Returns] an array of URL endpoints where JSON for superblocks is accessed. - * - * Example usage: - * getDashedNamesURLs([0, 2, 3]) - * - * - * Example output: - * [ - * 'https://www.freecodecamp.org/curriculum-data/v1/2022/responsive-web-design.json', - * 'https://www.freecodecamp.org/curriculum-data/v1/responsive-web-design.json', - * 'https://www.freecodecamp.org/curriculum-data/v1/back-end-development-and-apis.json' - * ] - * - * */ -export async function getDashedNamesURLs(fccCertifications) { - const superblocksres = await fetch(AVAILABLE_SUPER_BLOCKS); - - const curriculumData = await superblocksres.json(); - - return fccCertifications.map( - x => FCC_BASE_URL + curriculumData['superblocks'][x]['dashedName'] + '.json' - ); -} - -/** ============ getNonDashedNamesURLs([0,1,2) ============ */ -/** - * The parameter relates to the index found at the following API response - * https://www.freecodecamp.org/curriculum-data/v1/available-superblocks.json - * - * Context: The way we know which superblocks are assigned in the classroom - * is by storing the indicies in our DB (Prisma to access/write) - * [see the Classroom table, then the fccCertifications column] - * if you would like more context see the following file(s): - * pages/classes/index.js and take a look at the Modal component - * (components/modal.js), and also take a look at the - * ClassInviteTable component (component/ClassInviteTable). - * You can also search the codebase for the folling string to get more context - * on the relation on the indicies stored in Prisma (unded the - * fccCertifications column): "Select certifications:" - */ -export async function getNonDashedNamesURLs(fccCertificationsIndex) { - const superblocksres = await fetch(AVAILABLE_SUPER_BLOCKS); - - const curriculumData = await superblocksres.json(); - - return fccCertificationsIndex.map( - x => curriculumData['superblocks'][x]['title'] - ); -} - -/** ============ getSuperBlockJsons(superblockURLS) ============ */ -/* - * [Parameters] an array of URLs as a parameter, where the URLs are the json endpoint URLs that contain information about the superblock/certificate. - * - * [Returns] an array of objects containing superblock/certificate information. - * The objects have 1 key: the superblock/certificate URL (dashed/or undashed URL name) and the value of the objects - * is the corresponding information associated with the superblock/certificate. The values contain two arrays 'intro' and 'blocks'. - * - * Example usage: - * getSuperBlockJsons([ - * 'https://www.freecodecamp.org/curriculum-data/v1/2022/responsive-web-design.json', - * 'https://www.freecodecamp.org/curriculum-data/v1/javascript-algorithms-and-data-structures.json' - * ]) - * - * - * Example output: - * [ - * { - * '2022/responsive-web-design': { intro: [Array], blocks: [Object] } - * }, - * { - * 'javascript-algorithms-and-data-structures': { intro: [Array], blocks: [Object] } - * } - * ] - * - * */ -export async function getSuperBlockJsons(superblockURLS) { - let responses = await Promise.all( - superblockURLS.map(async currUrl => { - let currResponse = await fetch(currUrl); - let superblockJSON = currResponse.json(); - return superblockJSON; - }) - ); - return responses; -} - -/** ============ createSuperblockDashboardObject(superblock) ============ */ -/* - * [Parameters] an array of objects containing superblock/certificate information as a parameter. - * - * [Returns] a 2d array of objects, where the array length is 1, and array[0] is length N, where array[0][N] are objects - * with block (not superblock) data. - * - * Example usage: - * createDasboardObject([ - * { - * '2022/responsive-web-design': { intro: [Array], blocks: [Object] } - * }, - * { - * 'javascript-algorithms-and-data-structures': { intro: [Array], blocks: [Object] } - * } - *]) - * - * - * - * Example output: - * [ - * [ - * { - * name: 'Learn HTML by Building a Cat Photo App', - * selector: 'learn-html-by-building-a-cat-photo-app', - * dashedName: 'learn-html-by-building-a-cat-photo-app', - * allChallenges: [Array], - * order: 0 - * }, - * { - * name: 'Learn Basic CSS by Building a Cafe Menu', - * selector: 'learn-basic-css-by-building-a-cafe-menu', - * dashedName: 'learn-basic-css-by-building-a-cafe-menu', - * allChallenges: [Array], - * order: 1 - * } - * ] - * ] - * - */ -export async function createSuperblockDashboardObject(superblock) { - let superblockDashedNamesAndTitlesArray = - await getAllSuperblockTitlesAndDashedNames(); - - let sortedBlocks = superblock.map(currBlock => { - let certification = Object.keys(currBlock).map(certificationName => { - let superblockDashedNameAndTitle = - superblockDashedNamesAndTitlesArray.find( - superblockDashedNameAndTitleJSON => - superblockDashedNameAndTitleJSON['superblockDashedName'] === - certificationName - ); - - let blockInfo = Object.entries( - currBlock[certificationName]['blocks'] - ).map(([course]) => { - /* -The following object is necessary in order to sort our courses/superblocks correctly in order to pass them into our dashtabs.js component - - -Layout: -blockInfo: This is an array of objects that will be passed into our sorting function. - - -name: This is the human readable name of the course -selector: this is for our dashtabs component to have a unique selector for each dynamically generated tab -allChallenges: As the name implies, this holds all of our challenges (inside of the current block) in correct order -The last bit is the order of the current block inside of the certification, not the challenges that exist inside of this block -*/ - let currCourseBlock = { - superblock: superblockDashedNameAndTitle.superblockDashedName, - superblockReadableTitle: - superblockDashedNameAndTitle.superblockReadableTitle, - blockName: - currBlock[certificationName]['blocks'][course]['challenges'][ - 'name' - ], - /* -This selector is changed inside of components/dashtabs.js -If you are having issues with the selector, you should probably check there. -*/ - selector: course, - dashedName: course, - allChallenges: - currBlock[certificationName]['blocks'][course]['challenges'][ - 'challengeOrder' - ], - order: - currBlock[certificationName]['blocks'][course]['challenges'][ - 'order' - ] - }; - return currCourseBlock; - }); - sortSuperBlocks(blockInfo); - return blockInfo; - }); - return certification; - }); - // Since we return new arrays at every map, we have to flatten our 3D array down to 2D. - return sortedBlocks.flat(1); -} - -/** ============ fetchStudentData() ============ */ -export async function fetchStudentData() { - let data = await fetch(process.env.MOCK_USER_DATA_URL); - return data.json(); -} - -/** ============ getIndividualStudentData(studentEmail) ============ */ -// Uses for the details page -export async function getIndividualStudentData(studentEmail) { - let studentData = await fetchStudentData(); - let individualStudentObj = {}; - studentData.forEach(individualStudentDetailsObj => { - if (individualStudentDetailsObj.email === studentEmail) { - individualStudentObj = individualStudentDetailsObj; - } - }); - - return individualStudentObj; -} - -/** ============ getTotalChallengesForSuperblocks(superblockDasboardObj) ============ */ -export function getTotalChallengesForSuperblocks(superblockDasboardObj) { - let totalChallengesInSuperblock = 0; - superblockDasboardObj.forEach(blockObjArray => { - blockObjArray.forEach(blockObj => { - totalChallengesInSuperblock += blockObj.allChallenges.length; - }); - }); - - return totalChallengesInSuperblock; -} - -/** ============ extractStudentCompletionTimestamps(studentSuperblockProgressJSONArray) ============ */ -export function extractStudentCompletionTimestamps( - studentSuperblockProgressJSONArray -) { - let completedTimestampsArray = []; - - studentSuperblockProgressJSONArray.forEach(superblockProgressJSON => { - // since the keys are dynamic we have to use Object.values(obj) - let superblockProgressJSONArray = Object.values(superblockProgressJSON)[0] - .blocks; - superblockProgressJSONArray.forEach(blockProgressJSON => { - let blockKey = Object.keys(blockProgressJSON)[0]; - let allCompletedChallengesArrayWithTimestamps = - blockProgressJSON[blockKey].completedChallenges; - allCompletedChallengesArrayWithTimestamps.forEach(completionDetails => { - completedTimestampsArray.push(completionDetails.completedDate); - }); - }); - }); - return completedTimestampsArray; -} - -/** ============ extractFilteredCompletionTimestamps(studentSuperblockProgressJSONArray, selectedSuperblocks) ============ */ -export function extractFilteredCompletionTimestamps( - studentSuperblockProgressJSONArray, - selectedSuperblocks -) { - let completedTimestampsArray = []; - - studentSuperblockProgressJSONArray.forEach(superblockProgressJSON => { - let superblockDashedName = Object.keys(superblockProgressJSON)[0]; - - // Only include selected superblocks - if (!selectedSuperblocks.includes(superblockDashedName)) { - return; - } - - let superblockProgressJSONArray = Object.values(superblockProgressJSON)[0] - .blocks; - superblockProgressJSONArray.forEach(blockProgressJSON => { - let blockKey = Object.keys(blockProgressJSON)[0]; - let allCompletedChallengesArrayWithTimestamps = - blockProgressJSON[blockKey].completedChallenges; - - allCompletedChallengesArrayWithTimestamps.forEach(completionDetails => { - completedTimestampsArray.push(completionDetails.completedDate); - }); - }); - }); - - return completedTimestampsArray; -} - -/** ============ getStudentProgressInSuperblock(studentSuperblocksJSON, specificSuperblockDashedName) ============ */ -export function getStudentProgressInSuperblock( - studentSuperblocksJSON, - specificSuperblockDashedName -) { - let blockProgressDetails = []; - - studentSuperblocksJSON.certifications.forEach(superblockProgressJSON => { - // the keys are dynamic which is why we have to use Object.keys(obj) - let superblockDashedName = Object.keys(superblockProgressJSON)[0]; - if (specificSuperblockDashedName === superblockDashedName) { - blockProgressDetails = Object.values(superblockProgressJSON)[0].blocks; - } - }); - - return blockProgressDetails; -} - -/** ============ getStudentTotalChallengesCompletedInBlock(studentProgressInBlock,blockName) ============ */ -export function getStudentTotalChallengesCompletedInBlock( - studentProgressInBlock, - blockName -) { - let totalChallengesCompletedInBlock = 0; - studentProgressInBlock.forEach(blockProgressObj => { - let blockTitle = Object.keys(blockProgressObj)[0]; - - if (blockTitle === blockName) { - totalChallengesCompletedInBlock = - blockProgressObj[blockTitle].completedChallenges.length; - } - }); - - return totalChallengesCompletedInBlock; -} diff --git a/util/curriculum/fetchCurriculum.js b/util/curriculum/fetchCurriculum.js new file mode 100644 index 000000000..a7f4e1780 --- /dev/null +++ b/util/curriculum/fetchCurriculum.js @@ -0,0 +1,80 @@ +const GRAPHQL_ENDPOINT = 'https://curriculum-db.freecodecamp.org/graphql'; + +const QUERY = `{ + superblocks { + dashedName + name + blockObjects { + dashedName + name + order + challengeOrder { + id + title + } + } + } +}`; + +let cached = null; + +async function fetchAll() { + if (cached) return cached; + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 10000); + const res = await fetch(GRAPHQL_ENDPOINT, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query: QUERY }), + signal: controller.signal + }); + clearTimeout(timeout); + if (!res.ok) throw new Error(`GraphQL fetch failed: ${res.status}`); + const json = await res.json(); + if (json.errors || !json.data?.superblocks) { + throw new Error( + `GraphQL response error: ${JSON.stringify( + json.errors || 'missing superblocks data' + )}` + ); + } + cached = json.data.superblocks; + return cached; +} + +/** + * Returns the list of available superblocks in the same shape the v1 REST API + * returned (array of {dashedName, title}). + */ +export async function getAvailableSuperblocks() { + const superblocks = await fetchAll(); + return superblocks.map(sb => ({ dashedName: sb.dashedName, title: sb.name })); +} + +/** + * Given an array of superblock indices, returns curriculum data in the same + * shape that the legacy getDashedNamesURLs + getSuperBlockJsons chain produced. + * + * Each element: { [dashedName]: { blocks: { [blockDashed]: { challenges: { name, challengeOrder, order } } } } } + */ +export async function getSuperblocksByIndices(indices) { + const superblocks = await fetchAll(); + return indices.map(i => { + const sb = superblocks[i]; + if (!sb) return {}; + const blocks = {}; + sb.blockObjects.forEach(block => { + blocks[block.dashedName] = { + challenges: { + name: block.name, + challengeOrder: block.challengeOrder.map(c => ({ + id: c.id, + title: c.title + })), + order: block.order + } + }; + }); + return { [sb.dashedName]: { blocks } }; + }); +} diff --git a/util/curriculum/getAllSuperblockTitlesAndDashedNames.js b/util/curriculum/getAllSuperblockTitlesAndDashedNames.js index 9f937cf85..4ec4a19ef 100644 --- a/util/curriculum/getAllSuperblockTitlesAndDashedNames.js +++ b/util/curriculum/getAllSuperblockTitlesAndDashedNames.js @@ -1,30 +1,13 @@ -import { getAllTitlesAndDashedNamesSuperblockJSONArray } from './getAllTitlesAndDashedNamesSuperblockJSONArray'; +import { getAvailableSuperblocks } from './fetchCurriculum'; /** * Gets all superblock dashedNames and readable titles * @returns {Promise} Array of objects with superblockDashedName and superblockReadableTitle */ export async function getAllSuperblockTitlesAndDashedNames() { - let superblockTitleAndDashedNameJSONArray = - await getAllTitlesAndDashedNamesSuperblockJSONArray(); - - let superblockDashedNameToTitleArrayMapping = []; - superblockTitleAndDashedNameJSONArray.forEach( - superblockDashedNameAndTitleObject => { - let superblockDashedNameToTitleArray = { - superblockDashedName: '', - superblockReadableTitle: '' - }; - let superblockDashedName = superblockDashedNameAndTitleObject.dashedName; - let superblockTitle = superblockDashedNameAndTitleObject.title; - superblockDashedNameToTitleArray.superblockDashedName = - superblockDashedName; - superblockDashedNameToTitleArray.superblockReadableTitle = - superblockTitle; - superblockDashedNameToTitleArrayMapping.push( - superblockDashedNameToTitleArray - ); - } - ); - return superblockDashedNameToTitleArrayMapping; + const superblocks = await getAvailableSuperblocks(); + return superblocks.map(sb => ({ + superblockDashedName: sb.dashedName, + superblockReadableTitle: sb.title + })); } diff --git a/util/curriculum/getAllTitlesAndDashedNamesSuperblockJSONArray.js b/util/curriculum/getAllTitlesAndDashedNamesSuperblockJSONArray.js deleted file mode 100644 index 244ceb5cd..000000000 --- a/util/curriculum/getAllTitlesAndDashedNamesSuperblockJSONArray.js +++ /dev/null @@ -1,16 +0,0 @@ -import { AVAILABLE_SUPER_BLOCKS } from './constants'; - -/** - * Fetches all available superblocks from FCC v1 API - * @returns {Promise} Array of superblock objects with dashedName and title - */ -export async function getAllTitlesAndDashedNamesSuperblockJSONArray() { - // calls this API https://www.freecodecamp.org/curriculum-data/v1/available-superblocks.json - const superblocksres = await fetch(AVAILABLE_SUPER_BLOCKS); - - // the response of this structure is [ superblocks: [ {}, {}, ...etc] ] - const curriculumData = await superblocksres.json(); - - // which is why we return curriculumData.superblocks - return curriculumData.superblocks; -} diff --git a/util/curriculum/getSuperblockTitlesInClassroomByIndex.js b/util/curriculum/getSuperblockTitlesInClassroomByIndex.js index 92f3704a3..1ef787f82 100644 --- a/util/curriculum/getSuperblockTitlesInClassroomByIndex.js +++ b/util/curriculum/getSuperblockTitlesInClassroomByIndex.js @@ -1,4 +1,4 @@ -import { getAllSuperblockTitlesAndDashedNames } from './getAllSuperblockTitlesAndDashedNames'; +import { getAvailableSuperblocks } from './fetchCurriculum'; /** * Maps an array of superblock indices to their readable titles @@ -10,9 +10,7 @@ import { getAllSuperblockTitlesAndDashedNames } from './getAllSuperblockTitlesAn export async function getSuperblockTitlesInClassroomByIndex( fccCertificationsArrayOfIndicies ) { - let allSuperblockTitles = await getAllSuperblockTitlesAndDashedNames(); + const superblocks = await getAvailableSuperblocks(); - return fccCertificationsArrayOfIndicies.map( - x => allSuperblockTitles[x].superblockReadableTitle - ); + return fccCertificationsArrayOfIndicies.map(x => superblocks[x]?.title); } diff --git a/util/legacy/getDashedNamesURLs.js b/util/legacy/getDashedNamesURLs.js deleted file mode 100644 index d4b2b1467..000000000 --- a/util/legacy/getDashedNamesURLs.js +++ /dev/null @@ -1,33 +0,0 @@ -export const FCC_BASE_URL = 'https://www.freecodecamp.org/curriculum-data/v1/'; -export const AVAILABLE_SUPER_BLOCKS = - FCC_BASE_URL + 'available-superblocks.json'; - -/** - * [Parameters] an array of indices as a parameter. - * Those indices correspond to an index in an array of objects containing superblock data at a JSON endpoint (https://www.freecodecamp.org/curriculum-data/v1/available-superblocks.json) - * The array of indices is stored in Prisma as fccCertificates (see const certificationNumbers in [id].js). - * - * [Returns] an array of URL endpoints where JSON for superblocks is accessed. - * - * Example usage: - * getDashedNamesURLs([0, 2, 3]) - * - * - * Example output: - * [ - * 'https://www.freecodecamp.org/curriculum-data/v1/2022/responsive-web-design.json', - * 'https://www.freecodecamp.org/curriculum-data/v1/responsive-web-design.json', - * 'https://www.freecodecamp.org/curriculum-data/v1/back-end-development-and-apis.json' - * ] - * - * NOTE: This function is deprecated for v9 curriculum which doesn't have individual REST API JSON files. - * */ -export async function getDashedNamesURLs(fccCertifications) { - const superblocksres = await fetch(AVAILABLE_SUPER_BLOCKS); - - const curriculumData = await superblocksres.json(); - - return fccCertifications.map( - x => FCC_BASE_URL + curriculumData['superblocks'][x]['dashedName'] + '.json' - ); -} diff --git a/util/legacy/getNonDashedNamesURLs.js b/util/legacy/getNonDashedNamesURLs.js deleted file mode 100644 index 3f331730c..000000000 --- a/util/legacy/getNonDashedNamesURLs.js +++ /dev/null @@ -1,28 +0,0 @@ -export const FCC_BASE_URL = 'https://www.freecodecamp.org/curriculum-data/v1/'; -export const AVAILABLE_SUPER_BLOCKS = - FCC_BASE_URL + 'available-superblocks.json'; - -/** - * The parameter relates to the index found at the following API response - * https://www.freecodecamp.org/curriculum-data/v1/available-superblocks.json - * - * Context: The way we know which superblocks are assigned in the classroom - * is by storing the indicies in our DB (Prisma to access/write) - * [see the Classroom table, then the fccCertifications column] - * if you would like more context see the following file(s): - * pages/classes/index.js and take a look at the Modal component - * (components/modal.js), and also take a look at the - * ClassInviteTable component (component/ClassInviteTable). - * You can also search the codebase for the folling string to get more context - * on the relation on the indicies stored in Prisma (unded the - * fccCertifications column): "Select certifications:" - */ -export async function getNonDashedNamesURLs(fccCertificationsIndex) { - const superblocksres = await fetch(AVAILABLE_SUPER_BLOCKS); - - const curriculumData = await superblocksres.json(); - - return fccCertificationsIndex.map( - x => curriculumData['superblocks'][x]['title'] - ); -} diff --git a/util/legacy/getSuperBlockJsons.js b/util/legacy/getSuperBlockJsons.js deleted file mode 100644 index 72a8672c4..000000000 --- a/util/legacy/getSuperBlockJsons.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * [Parameters] an array of URLs as a parameter, where the URLs are the json endpoint URLs that contain information about the superblock/certificate. - * - * [Returns] an array of objects containing superblock/certificate information. - * The objects have 1 key: the superblock/certificate URL (dashed/or undashed URL name) and the value of the objects - * is the corresponding information associated with the superblock/certificate. The values contain two arrays 'intro' and 'blocks'. - * - * Example usage: - * getSuperBlockJsons([ - * 'https://www.freecodecamp.org/curriculum-data/v1/2022/responsive-web-design.json', - * 'https://www.freecodecamp.org/curriculum-data/v1/javascript-algorithms-and-data-structures.json' - * ]) - * - * - * Example output: - * [ - * { - * '2022/responsive-web-design': { intro: [Array], blocks: [Object] } - * }, - * { - * 'javascript-algorithms-and-data-structures': { intro: [Array], blocks: [Object] } - * } - * ] - * - * NOTE: This function is deprecated for v9 curriculum which doesn't have individual REST API JSON files. - * */ -export async function getSuperBlockJsons(superblockURLS) { - let responses = await Promise.all( - superblockURLS.map(async currUrl => { - let currResponse = await fetch(currUrl); - let superblockJSON = currResponse.json(); - return superblockJSON; - }) - ); - return responses; -}