Skip to content

Commit d8d3540

Browse files
authored
fixing encoding/decoding problem (#175)
* fixing encoding/decoding problem * push learner attempt fixes * updating learner timer and attempts
1 parent dd55b68 commit d8d3540

File tree

32 files changed

+1665
-479
lines changed

32 files changed

+1665
-479
lines changed

apps/api-gateway/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"start:debug": "yarn start --debug --watch",
1818
"start:dev": "yarn start --watch",
1919
"start:prod": "node dist/main",
20-
"test": "jest",
20+
"test": "jest --detectOpenHandles --forceExit",
2121
"test:cov": "jest --coverage",
2222
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
2323
"test:e2e": "jest --config ./test/jest-e2e.json",

apps/api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"start:debug": "yarn start --debug --watch",
1818
"start:dev": "yarn start --watch",
1919
"start:prod": "node dist/main",
20-
"test": "jest",
20+
"test": "jest --detectOpenHandles --forceExit",
2121
"test:cov": "jest --coverage",
2222
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
2323
"test:e2e": "jest --config ./test/jest-e2e.json",

apps/api/src/api/assignment/attempt/attempt.service.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,7 +1523,13 @@ export class AttemptServiceV1 {
15231523
const ongoingAttempts = attempts.filter(
15241524
(sub) => !sub.submitted && (!sub.expiresAt || sub.expiresAt >= now),
15251525
);
1526+
console.log(
1527+
`Found ${ongoingAttempts.length} ongoing attempts for user ${userSession.userId} on assignment ${assignment.id}`,
1528+
);
15261529
if (ongoingAttempts.length > 0) {
1530+
console.log(
1531+
`User ${userSession.userId} has ongoing attempts for assignment ${assignment.id}`,
1532+
);
15271533
throw new UnprocessableEntityException(IN_PROGRESS_SUBMISSION_EXCEPTION);
15281534
}
15291535
const attemptsInTimeRange = attempts.filter(

apps/api/src/api/assignment/attempt/dto/assignment-attempt/create.update.assignment.attempt.request.dto.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,12 @@ export class LearnerUpdateAssignmentAttemptRequestDto {
9191
@IsArray()
9292
@IsOptional()
9393
preTranslatedQuestions?: Map<number, QuestionDto>;
94+
95+
@ApiProperty({
96+
description: "The expiration date of the assignment attempt",
97+
type: Date,
98+
required: false,
99+
})
100+
@IsOptional()
101+
expiresAt?: Date;
94102
}

apps/api/src/api/attempt/services/attempt-submission.service.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,25 @@ export class AttemptSubmissionService {
7676
assignmentId: number,
7777
userSession: UserSession,
7878
): Promise<BaseAssignmentAttemptResponseDto> {
79+
const now = new Date();
80+
81+
const existingAttempt = await this.prisma.assignmentAttempt.findFirst({
82+
where: {
83+
assignmentId,
84+
userId: userSession.userId,
85+
submitted: false,
86+
OR: [{ expiresAt: null }, { expiresAt: { gte: now } }],
87+
},
88+
orderBy: { createdAt: "desc" },
89+
});
90+
91+
if (existingAttempt) {
92+
return {
93+
id: existingAttempt.id,
94+
success: true,
95+
};
96+
}
97+
7998
const assignment = await this.assignmentRepository.findById(
8099
assignmentId,
81100
userSession,
@@ -116,7 +135,7 @@ export class AttemptSubmissionService {
116135

117136
const assignmentAttempt = await this.prisma.assignmentAttempt.create({
118137
data: {
119-
expiresAt: attemptExpiresAt,
138+
expiresAt: attemptExpiresAt ?? null,
120139
submitted: false,
121140
assignmentId,
122141
assignmentVersionId: activeVersionId,
@@ -1088,12 +1107,14 @@ export class AttemptSubmissionService {
10881107
authorAssignmentDetails,
10891108
language,
10901109
preTranslatedQuestions,
1110+
expiresAt: _ignoredExpiresAt,
10911111
...cleanedUpdateDto
10921112
} = updateDto;
10931113

10941114
return this.prisma.assignmentAttempt.update({
10951115
data: {
10961116
...cleanedUpdateDto,
1117+
submitted: true,
10971118
preferredLanguage: language ?? "en",
10981119
expiresAt: new Date(),
10991120
grade,
@@ -1148,7 +1169,7 @@ export class AttemptSubmissionService {
11481169
) {
11491170
return new Date(Date.now() + assignment.allotedTimeMinutes * 60 * 1000);
11501171
}
1151-
return undefined;
1172+
return null;
11521173
}
11531174

11541175
/**

apps/api/src/api/attempt/services/attempt-validation.service.ts

Lines changed: 56 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -34,60 +34,44 @@ export class AttemptValidationService {
3434
const now = new Date();
3535
const timeRangeStartDate = this.calculateTimeRangeStartDate(assignment);
3636

37-
const attempts = await this.prisma.assignmentAttempt.findMany({
37+
const activeAttempt = await this.prisma.assignmentAttempt.findFirst({
3838
where: {
3939
userId: userSession.userId,
4040
assignmentId: assignment.id,
41+
submitted: false,
4142
OR: [
43+
{ expiresAt: null },
4244
{
43-
submitted: false,
4445
expiresAt: {
4546
gte: now,
4647
},
4748
},
48-
{
49-
submitted: false,
50-
expiresAt: undefined,
51-
},
52-
{
53-
createdAt: {
54-
gte: timeRangeStartDate,
55-
lte: now,
56-
},
57-
},
5849
],
5950
},
6051
orderBy: { createdAt: "desc" },
6152
});
6253

63-
const lastSubmittedAttempt = await this.prisma.assignmentAttempt.findFirst({
64-
where: {
65-
userId: userSession.userId,
66-
assignmentId: assignment.id,
67-
submitted: true,
68-
},
69-
orderBy: { expiresAt: "desc" },
70-
});
71-
72-
const ongoingAttempts = attempts.filter(
73-
(sub) => !sub.submitted && (!sub.expiresAt || sub.expiresAt >= now),
74-
);
75-
76-
if (ongoingAttempts.length > 0) {
54+
if (activeAttempt) {
7755
throw new UnprocessableEntityException(IN_PROGRESS_SUBMISSION_EXCEPTION);
7856
}
7957

80-
const attemptsInTimeRange = attempts.filter(
81-
(sub) => sub.createdAt >= timeRangeStartDate && sub.createdAt <= now,
82-
);
58+
if (assignment.attemptsPerTimeRange) {
59+
const attemptsInTimeRange = await this.prisma.assignmentAttempt.count({
60+
where: {
61+
userId: userSession.userId,
62+
assignmentId: assignment.id,
63+
createdAt: {
64+
gte: timeRangeStartDate,
65+
lte: now,
66+
},
67+
},
68+
});
8369

84-
if (
85-
assignment.attemptsPerTimeRange &&
86-
attemptsInTimeRange.length >= assignment.attemptsPerTimeRange
87-
) {
88-
throw new UnprocessableEntityException(
89-
TIME_RANGE_ATTEMPTS_SUBMISSION_EXCEPTION_MESSAGE,
90-
);
70+
if (attemptsInTimeRange >= assignment.attemptsPerTimeRange) {
71+
throw new UnprocessableEntityException(
72+
TIME_RANGE_ATTEMPTS_SUBMISSION_EXCEPTION_MESSAGE,
73+
);
74+
}
9175
}
9276

9377
if (assignment.numAttempts !== null && assignment.numAttempts !== -1) {
@@ -107,22 +91,45 @@ export class AttemptValidationService {
10791

10892
if (
10993
attemptsBeforeCoolDown > 0 &&
94+
cooldownMinutes > 0 &&
11095
totalAttempts >= attemptsBeforeCoolDown
11196
) {
112-
const lastAttemptTime = new Date(
113-
lastSubmittedAttempt.expiresAt,
114-
).getTime();
115-
const cooldownMs = cooldownMinutes * 60_000;
116-
const nextEligibleTime = lastAttemptTime + cooldownMs;
117-
118-
if (now.getTime() < nextEligibleTime) {
119-
throw new HttpException(
120-
{
121-
statusCode: HttpStatus.TOO_MANY_REQUESTS,
122-
message: IN_COOLDOWN_PERIOD,
97+
const lastSubmittedAttempt =
98+
await this.prisma.assignmentAttempt.findFirst({
99+
where: {
100+
userId: userSession.userId,
101+
assignmentId: assignment.id,
102+
submitted: true,
123103
},
124-
HttpStatus.TOO_MANY_REQUESTS,
125-
);
104+
orderBy: [{ expiresAt: "desc" }, { createdAt: "desc" }],
105+
});
106+
107+
if (lastSubmittedAttempt) {
108+
const lastAttemptReference =
109+
lastSubmittedAttempt.expiresAt ?? lastSubmittedAttempt.createdAt;
110+
111+
if (lastAttemptReference) {
112+
const referenceTimestamp = new Date(lastAttemptReference).getTime();
113+
114+
if (!Number.isNaN(referenceTimestamp)) {
115+
const lastAttemptTime = Math.min(
116+
referenceTimestamp,
117+
now.getTime(),
118+
);
119+
const cooldownMs = cooldownMinutes * 60_000;
120+
const nextEligibleTime = lastAttemptTime + cooldownMs;
121+
122+
if (now.getTime() < nextEligibleTime) {
123+
throw new HttpException(
124+
{
125+
statusCode: HttpStatus.TOO_MANY_REQUESTS,
126+
message: IN_COOLDOWN_PERIOD,
127+
},
128+
HttpStatus.TOO_MANY_REQUESTS,
129+
);
130+
}
131+
}
132+
}
126133
}
127134
}
128135
}

0 commit comments

Comments
 (0)