mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
breaking(api): remove submission time from exam env (#57365)
This commit is contained in:
@@ -45,7 +45,8 @@ export const config: EnvConfig = {
|
||||
numberOfCorrectAnswers: 1,
|
||||
numberOfIncorrectAnswers: 1
|
||||
}
|
||||
]
|
||||
],
|
||||
retakeTimeInMS: 24 * 60 * 60 * 1000
|
||||
};
|
||||
|
||||
export const questionSets: EnvQuestionSet[] = [
|
||||
@@ -292,8 +293,7 @@ export const examAttempt: EnvExamAttempt = {
|
||||
}
|
||||
],
|
||||
startTimeInMS: Date.now(),
|
||||
userId: defaultUserId,
|
||||
submissionTimeInMS: null
|
||||
userId: defaultUserId
|
||||
};
|
||||
|
||||
export const examAttemptSansSubmissionTimeInMS: Static<
|
||||
|
||||
+10
-13
@@ -223,15 +223,17 @@ type EnvAnswer {
|
||||
/// Configuration for an exam in the Exam Environment App
|
||||
type EnvConfig {
|
||||
/// Human-readable exam name
|
||||
name String
|
||||
name String
|
||||
/// Notes given about exam
|
||||
note String
|
||||
note String
|
||||
/// Category configuration for question selection
|
||||
tags EnvTagConfig[]
|
||||
tags EnvTagConfig[]
|
||||
/// Total time allocated for exam in milliseconds
|
||||
totalTimeInMS Int
|
||||
totalTimeInMS Int
|
||||
/// Configuration for sets of questions
|
||||
questionSets EnvQuestionSetConfig[]
|
||||
questionSets EnvQuestionSetConfig[]
|
||||
/// Duration after exam completion before a retake is allowed in milliseconds
|
||||
retakeTimeInMS Int
|
||||
}
|
||||
|
||||
/// Configuration for a set of questions in the Exam Environment App
|
||||
@@ -267,14 +269,10 @@ model EnvExamAttempt {
|
||||
/// Foreign key to generated exam id
|
||||
generatedExamId String @db.ObjectId
|
||||
|
||||
questionSets EnvQuestionSetAttempt[]
|
||||
questionSets EnvQuestionSetAttempt[]
|
||||
/// Time exam was started as milliseconds since epoch
|
||||
startTimeInMS Int
|
||||
/// Time exam was submitted as milliseconds since epoch
|
||||
///
|
||||
/// As attempt might not be submitted (disconnection or quit), field is optional
|
||||
submissionTimeInMS Int?
|
||||
needsRetake Boolean
|
||||
startTimeInMS Int
|
||||
needsRetake Boolean
|
||||
|
||||
// Relations
|
||||
user user @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
@@ -301,7 +299,6 @@ type EnvMultipleChoiceQuestionAttempt {
|
||||
/// A generated exam for the Exam Environment App
|
||||
///
|
||||
/// This is the user-facing information for an exam.
|
||||
/// TODO: Add userId?
|
||||
model EnvGeneratedExam {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
/// Foreign key to exam
|
||||
|
||||
@@ -435,8 +435,6 @@ describe('/exam-environment/', () => {
|
||||
24 * 60 * 60 * 1000 -
|
||||
mock.exam.config.totalTimeInMS -
|
||||
1 * 60 * 60 * 1000;
|
||||
submittedAttempt.submissionTimeInMS =
|
||||
Date.now() - mock.exam.config.totalTimeInMS - 24 * 60 * 60 * 1000;
|
||||
await fastifyTestInstance.prisma.envExamAttempt.create({
|
||||
data: submittedAttempt
|
||||
});
|
||||
@@ -492,7 +490,6 @@ describe('/exam-environment/', () => {
|
||||
generatedExamId: generatedExam!.id,
|
||||
questionSets: [],
|
||||
needsRetake: false,
|
||||
submissionTimeInMS: null,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
startTimeInMS: expect.any(Number)
|
||||
});
|
||||
@@ -579,7 +576,8 @@ describe('/exam-environment/', () => {
|
||||
config: {
|
||||
name: mock.exam.config.name,
|
||||
note: mock.exam.config.note,
|
||||
totalTimeInMS: mock.exam.config.totalTimeInMS
|
||||
totalTimeInMS: mock.exam.config.totalTimeInMS,
|
||||
retakeTimeInMS: mock.exam.config.retakeTimeInMS
|
||||
},
|
||||
id: mock.examId
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import * as schemas from '../schemas';
|
||||
import { mapErr, syncMapErr, UpdateReqType } from '../../utils';
|
||||
import { JWT_SECRET } from '../../utils/env';
|
||||
import {
|
||||
checkAttemptAgainstGeneratedExam,
|
||||
checkPrerequisites,
|
||||
constructUserExam,
|
||||
userAttemptToDatabaseAttemptQuestionSets,
|
||||
@@ -209,16 +208,13 @@ async function postExamGeneratedExamHandler(
|
||||
: null;
|
||||
|
||||
if (lastAttempt) {
|
||||
const attemptIsExpired =
|
||||
lastAttempt.startTimeInMS + exam.config.totalTimeInMS < Date.now();
|
||||
if (attemptIsExpired) {
|
||||
// If exam is not submitted, use exam start time + time allocated for exam
|
||||
const effectiveSubmissionTime =
|
||||
lastAttempt.submissionTimeInMS ??
|
||||
lastAttempt.startTimeInMS + exam.config.totalTimeInMS;
|
||||
const twentyFourHoursAgo = Date.now() - 24 * 60 * 60 * 1000;
|
||||
const examExpirationTime =
|
||||
lastAttempt.startTimeInMS + exam.config.totalTimeInMS;
|
||||
if (examExpirationTime < Date.now()) {
|
||||
const retakeAllowed =
|
||||
examExpirationTime + exam.config.retakeTimeInMS < Date.now();
|
||||
|
||||
if (effectiveSubmissionTime > twentyFourHoursAgo) {
|
||||
if (!retakeAllowed) {
|
||||
void reply.code(429);
|
||||
// TODO: Consider sending last completed time
|
||||
return reply.send(
|
||||
@@ -429,20 +425,6 @@ async function postExamAttemptHandler(
|
||||
latest.startTimeInMS > current.startTimeInMS ? latest : current
|
||||
);
|
||||
|
||||
// TODO: Currently, submission time is set when all questions have been answered.
|
||||
// This might not necessarily be fully submitted. So, provided there is time
|
||||
// left on the clock, the attempt should still be updated, even if the submission
|
||||
// time is set.
|
||||
// The submission time just needs to be updated.
|
||||
// if (latestAttempt.submissionTimeInMS !== null) {
|
||||
// void reply.code(403);
|
||||
// return reply.send(
|
||||
// ERRORS.FCC_EINVAL_EXAM_ENVIRONMENT_EXAM_ATTEMPT(
|
||||
// 'Attempt has already been submitted.'
|
||||
// )
|
||||
// );
|
||||
// }
|
||||
|
||||
const maybeExam = await mapErr(
|
||||
this.prisma.envExam.findUnique({
|
||||
where: {
|
||||
@@ -515,12 +497,6 @@ async function postExamAttemptHandler(
|
||||
validateAttempt(generatedExam, databaseAttemptQuestionSets)
|
||||
);
|
||||
|
||||
// If all questions have been answered, add submission time
|
||||
const allQuestionsAnswered = checkAttemptAgainstGeneratedExam(
|
||||
databaseAttemptQuestionSets,
|
||||
generatedExam
|
||||
);
|
||||
|
||||
// Update attempt in database
|
||||
const maybeUpdatedAttempt = await mapErr(
|
||||
this.prisma.envExamAttempt.update({
|
||||
@@ -528,8 +504,6 @@ async function postExamAttemptHandler(
|
||||
id: latestAttempt.id
|
||||
},
|
||||
data: {
|
||||
// NOTE: submission time is set to null, because it just depends on whether all questions have been answered.
|
||||
submissionTimeInMS: allQuestionsAnswered ? Date.now() : null,
|
||||
questionSets: databaseAttemptQuestionSets,
|
||||
// If attempt is not valid, immediately flag attempt as needing retake
|
||||
// TODO: If `needsRetake`, prevent further submissions?
|
||||
@@ -592,7 +566,8 @@ async function getExams(
|
||||
config: {
|
||||
name: exam.config.name,
|
||||
note: exam.config.note,
|
||||
totalTimeInMS: exam.config.totalTimeInMS
|
||||
totalTimeInMS: exam.config.totalTimeInMS,
|
||||
retakeTimeInMS: exam.config.retakeTimeInMS
|
||||
},
|
||||
canTake: isExamPrerequisitesMet
|
||||
};
|
||||
|
||||
@@ -12,7 +12,8 @@ export const examEnvironmentExams = {
|
||||
config: Type.Object({
|
||||
name: Type.String(),
|
||||
note: Type.String(),
|
||||
totalTimeInMS: Type.Number()
|
||||
totalTimeInMS: Type.Number(),
|
||||
retakeTimeInMS: Type.Number()
|
||||
}),
|
||||
canTake: Type.Boolean()
|
||||
})
|
||||
|
||||
@@ -101,7 +101,8 @@ export function constructUserExam(
|
||||
const config = {
|
||||
totalTimeInMS: exam.config.totalTimeInMS,
|
||||
name: exam.config.name,
|
||||
note: exam.config.note
|
||||
note: exam.config.note,
|
||||
retakeTimeInMS: exam.config.retakeTimeInMS
|
||||
};
|
||||
|
||||
const userExam: UserExam = {
|
||||
|
||||
Reference in New Issue
Block a user