Skip to content

Commit c9eb037

Browse files
Merge pull request #726 from freeCodeCamp/main
Create a new pull request by comparing changes across two branches
2 parents 54e56df + 7b82835 commit c9eb037

File tree

2,766 files changed

+160120
-61608
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

2,766 files changed

+160120
-61608
lines changed

.github/workflows/e2e-playwright.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,16 @@ jobs:
157157
pnpm run start-ci &
158158
sleep 10
159159
npx playwright test --project="${{ matrix.browsers }}" --grep-invert 'third-party-donation.spec.ts'
160-
161160
- uses: actions/upload-artifact@v4
162161
if: ${{ !cancelled() }}
163162
with:
164163
name: playwright-report-${{ matrix.browsers }}
165164
path: playwright/reporter
166165
retention-days: 30
166+
- name: Upload screenshots
167+
if: failure()
168+
uses: actions/upload-artifact@v4
169+
with:
170+
name: screenshots-${{ matrix.browsers }}
171+
path: playwright/test-results
172+
retention-days: 14

LICENSE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
BSD 3-Clause License
22

3-
Copyright (c) 2024, freeCodeCamp.
3+
Copyright (c) 2025, freeCodeCamp.
44
All rights reserved.
55

66
Redistribution and use in source and binary forms, with or without

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,9 @@ The general platform status for all our applications is available at [`status.fr
263263

264264
### License
265265

266-
Copyright © 2024 freeCodeCamp.org
266+
Copyright © 2025 freeCodeCamp.org
267267

268268
The content of this repository is bound by the following licenses:
269269

270270
- The computer software is licensed under the [BSD-3-Clause](LICENSE.md) license.
271-
- The learning resources in the [`/curriculum`](/curriculum) directory including their subdirectories thereon are copyright © 2024 freeCodeCamp.org
271+
- The learning resources in the [`/curriculum`](/curriculum) directory including their subdirectories thereon are copyright © 2025 freeCodeCamp.org

api/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ cd ../.. # back to the root of the repo
2525
pnpm seed
2626
```
2727

28+
### Troubleshooting
29+
30+
If you have any issues connecting to the database (e.g. MongoServerError: not primary), try removing the volume and recreating the containers.
31+
32+
```bash
33+
cd tools
34+
docker compose down -v
35+
docker compose up -d
36+
```
37+
2838
## Login in development/testing
2939

3040
During development and testing, the api exposes the endpoint GET auth/dev-callback. Calling this will log you in as the user with the email `[email protected]` by setting the session cookie for that user.

api/__mocks__/env-exam.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ export const config: EnvConfig = {
4545
numberOfCorrectAnswers: 1,
4646
numberOfIncorrectAnswers: 1
4747
}
48-
]
48+
],
49+
retakeTimeInMS: 24 * 60 * 60 * 1000
4950
};
5051

5152
export const questionSets: EnvQuestionSet[] = [
@@ -292,8 +293,7 @@ export const examAttempt: EnvExamAttempt = {
292293
}
293294
],
294295
startTimeInMS: Date.now(),
295-
userId: defaultUserId,
296-
submissionTimeInMS: null
296+
userId: defaultUserId
297297
};
298298

299299
export const examAttemptSansSubmissionTimeInMS: Static<

api/prisma/schema.prisma

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,20 @@ type SavedChallenge {
6969
lastSavedDate Float
7070
}
7171

72+
type QuizAttempt {
73+
challengeId String
74+
quizId String
75+
timestamp Float
76+
}
77+
7278
/// Corresponds to the `user` collection.
7379
model user {
7480
id String @id @default(auto()) @map("_id") @db.ObjectId
7581
about String
7682
acceptedPrivacyTerms Boolean
7783
completedChallenges CompletedChallenge[]
7884
completedExams CompletedExam[] // Undefined
85+
quizAttempts QuizAttempt[] // Undefined
7986
currentChallengeId String?
8087
donationEmails String[] // Undefined | String[] (only possible for built in Types like String)
8188
email String
@@ -108,7 +115,7 @@ model user {
108115
is2018DataVisCert Boolean? // Undefined
109116
is2018FullStackCert Boolean? // Undefined
110117
isCollegeAlgebraPyCertV8 Boolean? // Undefined
111-
isUpcomingPythonCertV8 Boolean? // Undefined
118+
// isUpcomingPythonCertV8 Boolean? // Undefined. It is in the db but has never been used.
112119
keyboardShortcuts Boolean? // Undefined
113120
linkedin String? // Null | Undefined
114121
location String? // Null
@@ -223,15 +230,17 @@ type EnvAnswer {
223230
/// Configuration for an exam in the Exam Environment App
224231
type EnvConfig {
225232
/// Human-readable exam name
226-
name String
233+
name String
227234
/// Notes given about exam
228-
note String
235+
note String
229236
/// Category configuration for question selection
230-
tags EnvTagConfig[]
237+
tags EnvTagConfig[]
231238
/// Total time allocated for exam in milliseconds
232-
totalTimeInMS Int
239+
totalTimeInMS Int
233240
/// Configuration for sets of questions
234-
questionSets EnvQuestionSetConfig[]
241+
questionSets EnvQuestionSetConfig[]
242+
/// Duration after exam completion before a retake is allowed in milliseconds
243+
retakeTimeInMS Int
235244
}
236245

237246
/// Configuration for a set of questions in the Exam Environment App
@@ -267,14 +276,10 @@ model EnvExamAttempt {
267276
/// Foreign key to generated exam id
268277
generatedExamId String @db.ObjectId
269278
270-
questionSets EnvQuestionSetAttempt[]
279+
questionSets EnvQuestionSetAttempt[]
271280
/// Time exam was started as milliseconds since epoch
272-
startTimeInMS Int
273-
/// Time exam was submitted as milliseconds since epoch
274-
///
275-
/// As attempt might not be submitted (disconnection or quit), field is optional
276-
submissionTimeInMS Int?
277-
needsRetake Boolean
281+
startTimeInMS Int
282+
needsRetake Boolean
278283
279284
// Relations
280285
user user @relation(fields: [userId], references: [id], onDelete: Cascade)
@@ -301,7 +306,6 @@ type EnvMultipleChoiceQuestionAttempt {
301306
/// A generated exam for the Exam Environment App
302307
///
303308
/// This is the user-facing information for an exam.
304-
/// TODO: Add userId?
305309
model EnvGeneratedExam {
306310
id String @id @default(auto()) @map("_id") @db.ObjectId
307311
/// Foreign key to exam

api/src/app.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ import {
3838
FCC_ENABLE_DEV_LOGIN_MODE,
3939
FCC_ENABLE_SWAGGER_UI,
4040
FCC_ENABLE_SHADOW_CAPTURE,
41-
FCC_ENABLE_EXAM_ENVIRONMENT
41+
FCC_ENABLE_EXAM_ENVIRONMENT,
42+
FCC_ENABLE_SENTRY_ROUTES
4243
} from './utils/env';
4344
import { isObjectID } from './utils/validation';
4445
import {
@@ -198,6 +199,10 @@ export const build = async (
198199
void fastify.register(examEnvironmentOpenRoutes);
199200
}
200201

202+
if (FCC_ENABLE_SENTRY_ROUTES) {
203+
void fastify.register(publicRoutes.sentryRoutes);
204+
}
205+
201206
void fastify.register(publicRoutes.chargeStripeRoute);
202207
void fastify.register(publicRoutes.signoutRoute);
203208
void fastify.register(publicRoutes.emailSubscribtionRoutes);

api/src/exam-environment/generate/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ const prisma = new PrismaClient({
2424
/// TODO:
2525
/// 1. Deprecate all previous generated exams for a given exam id?
2626
async function main() {
27+
console.info('Connecting to cluster...');
2728
await prisma.$connect();
29+
console.info('Connected.');
2830

2931
const exam = await prisma.envExam.findUnique({
3032
where: {
@@ -38,17 +40,22 @@ async function main() {
3840

3941
let numberOfExamsGenerated = 0;
4042

43+
console.info(
44+
`Exam with _id ${ENV_EXAM_ID} found. Generating ${NUMBER_OF_EXAMS_TO_GENERATE} exams...`
45+
);
4146
while (numberOfExamsGenerated < NUMBER_OF_EXAMS_TO_GENERATE) {
4247
try {
4348
const generatedExam = generateExam(exam);
4449
await prisma.envGeneratedExam.create({
4550
data: generatedExam
4651
});
4752
numberOfExamsGenerated++;
53+
console.info(`Generated ${numberOfExamsGenerated} exams`);
4854
} catch (e) {
4955
console.log(e);
5056
}
5157
}
58+
console.log(`Finished generating exams.`);
5259
}
5360

5461
void main();

api/src/exam-environment/routes/exam-environment.test.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { Static } from '@fastify/type-provider-typebox';
2+
import jwt from 'jsonwebtoken';
3+
24
import {
35
createSuperRequest,
46
defaultUserId,
@@ -11,6 +13,7 @@ import {
1113
} from '../schemas';
1214
import * as mock from '../../../__mocks__/env-exam';
1315
import { constructUserExam } from '../utils/exam';
16+
import { JWT_SECRET } from '../../utils/env';
1417

1518
jest.mock('../../utils/env', () => {
1619
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
@@ -435,8 +438,6 @@ describe('/exam-environment/', () => {
435438
24 * 60 * 60 * 1000 -
436439
mock.exam.config.totalTimeInMS -
437440
1 * 60 * 60 * 1000;
438-
submittedAttempt.submissionTimeInMS =
439-
Date.now() - mock.exam.config.totalTimeInMS - 24 * 60 * 60 * 1000;
440441
await fastifyTestInstance.prisma.envExamAttempt.create({
441442
data: submittedAttempt
442443
});
@@ -492,7 +493,6 @@ describe('/exam-environment/', () => {
492493
generatedExamId: generatedExam!.id,
493494
questionSets: [],
494495
needsRetake: false,
495-
submissionTimeInMS: null,
496496
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
497497
startTimeInMS: expect.any(Number)
498498
});
@@ -579,7 +579,8 @@ describe('/exam-environment/', () => {
579579
config: {
580580
name: mock.exam.config.name,
581581
note: mock.exam.config.note,
582-
totalTimeInMS: mock.exam.config.totalTimeInMS
582+
totalTimeInMS: mock.exam.config.totalTimeInMS,
583+
retakeTimeInMS: mock.exam.config.retakeTimeInMS
583584
},
584585
id: mock.examId
585586
}
@@ -641,7 +642,7 @@ describe('/exam-environment/', () => {
641642
});
642643

643644
describe('GET /exam-environment/token-meta', () => {
644-
it('should allow a valid request', async () => {
645+
it('should reject invalid tokens', async () => {
645646
const res = await superGet('/exam-environment/token-meta').set(
646647
'exam-environment-authorization-token',
647648
'invalid-token'
@@ -654,6 +655,24 @@ describe('/exam-environment/', () => {
654655
}
655656
});
656657
});
658+
659+
it('should tell the requester if the token does not exist', async () => {
660+
const validToken = jwt.sign(
661+
{ examEnvironmentAuthorizationToken: 'does-not-exist' },
662+
JWT_SECRET
663+
);
664+
const res = await superGet('/exam-environment/token-meta').set(
665+
'exam-environment-authorization-token',
666+
validToken
667+
);
668+
669+
expect(res).toMatchObject({
670+
status: 418,
671+
body: {
672+
code: 'FCC_EINVAL_EXAM_ENVIRONMENT_AUTHORIZATION_TOKEN'
673+
}
674+
});
675+
});
657676
});
658677

659678
describe('GET /exam-environment/exams', () => {

0 commit comments

Comments
 (0)