Skip to content

Commit 4d6f6ec

Browse files
authored
Merge pull request #101 from CS3219-AY2425S1/nephelite-patch-3
Implement Saving on Leave Room
2 parents 4ff8f4a + 9b16f1a commit 4d6f6ec

File tree

13 files changed

+84
-41
lines changed

13 files changed

+84
-41
lines changed

backend/matching-service/.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ RELAXATION_INTERVAL=3000
1818
MATCH_TIMEOUT=30000
1919
CLEANUP_INTERVAL=75000
2020

21-
JWT_ACCESS_TOKEN_SECRET=<your-jwt-accecss-token>
21+
JWT_ACCESS_TOKEN_SECRET=d2636f0c0ce9119c4aca178220a3a5a7bba0e5f6dffa982f8095f5b566162029
2222

2323
# Lifespan of a room (1 day in milliseconds)
2424
ROOM_LIFESPAN=86400000

backend/question-service/.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ PORT=3002
77
# Initialize data from questions.json
88
POPULATE_DB=true
99

10-
JWT_ACCESS_TOKEN_SECRET=<your-jwt-accecss-token>
10+
JWT_ACCESS_TOKEN_SECRET=d2636f0c0ce9119c4aca178220a3a5a7bba0e5f6dffa982f8095f5b566162029
1111

1212
# Kafka configuration
1313
KAFKA_HOST=localhost

backend/question-service/controllers/historyController.ts

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ export const getUserHistoryEntries = async (req: any, res: Response) => {
2323
key: entry._id,
2424
roomId: entry.roomId,
2525
attemptStartedAt: entry.attemptStartedAt.getTime(),
26-
attemptCompletedAt: entry.attemptCompletedAt.getTime(),
26+
lastAttemptSubmittedAt: entry.lastAttemptSubmittedAt.getTime(),
2727
title: entry.question.title,
2828
difficulty: entry.question.difficulty,
2929
topics: entry.question.categories.map((cat: any) => cat.name),
30-
attemptCodes: entry.attemptCodes,
30+
attemptCodes: entry.attemptCodes.filter((attemptCode) => attemptCode && attemptCode !== ""),
3131
};
3232
});
3333
res.status(200).json(historyViewModels);
@@ -40,38 +40,45 @@ export const createOrUpdateUserHistoryEntry = async (req: any, res: Response) =>
4040
try {
4141
const userId = req.userId;
4242

43-
const { questionId, roomId, attemptStartedAt, attemptCompletedAt, collaboratorId, attemptCode } = req.body;
43+
const { questionId, roomId, attemptStartedAt, attemptCompletedAt, collaboratorId, attemptCode, isInitial } = req.body;
4444

4545
if (!roomId) {
4646
return res.status(400).json({ error: "roomId is required" });
4747
}
4848

4949
const existingEntry = await historyEntryModel.findOne({ userId, roomId });
5050

51-
if (existingEntry) {
51+
if (!isInitial && existingEntry && attemptCode && attemptCode !== "") {
5252
existingEntry.question = questionId;
5353
existingEntry.attemptStartedAt = attemptStartedAt;
54-
existingEntry.attemptCompletedAt = attemptCompletedAt;
54+
existingEntry.lastAttemptSubmittedAt = attemptCompletedAt;
5555
existingEntry.collaboratorId = collaboratorId;
5656
existingEntry.attemptCodes.push(attemptCode);
5757

5858
const updatedEntry = await existingEntry.save();
5959

6060
return res.status(200).json(updatedEntry);
61+
} else if (!existingEntry) {
62+
try {
63+
const newHistoryEntry = new historyEntryModel({
64+
userId,
65+
question: questionId,
66+
roomId,
67+
attemptStartedAt,
68+
attemptCompletedAt,
69+
collaboratorId,
70+
attemptCodes: isInitial ? [attemptCode] : [],
71+
});
72+
73+
const savedEntry = await newHistoryEntry.save();
74+
75+
return res.status(201).json(savedEntry);
76+
} catch {
77+
return res.status(200).json("Attempt already exists.");
78+
}
6179
} else {
62-
const newHistoryEntry = new historyEntryModel({
63-
userId,
64-
question: questionId,
65-
roomId,
66-
attemptStartedAt,
67-
attemptCompletedAt,
68-
collaboratorId,
69-
attemptCodes: [attemptCode],
70-
});
71-
72-
const savedEntry = await newHistoryEntry.save();
73-
74-
return res.status(201).json(savedEntry);
80+
return res.status(200).json("Attempt already exists.");
81+
// To reach here, this must be initial creation and there's an existing entry. No need to create new attempt.
7582
}
7683
} catch (error) {
7784
return res.status(500).json({ error: getErrorMessage(error) });

backend/question-service/models/HistoryEntry.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export interface HistoryEntry extends mongoose.Document {
77
question: Question;
88
roomId: string; // Note: This should not be used to retrieve the room from matching service! This is only here to serve as a uniqueness check for updating attempt information!
99
attemptStartedAt: Date;
10-
attemptCompletedAt: Date;
10+
lastAttemptSubmittedAt: Date;
1111
collaboratorId: string;
1212
attemptCodes: string[];
1313
}
@@ -17,10 +17,12 @@ const historyEntrySchema: Schema = new Schema<HistoryEntry>({
1717
question: { type: Schema.Types.ObjectId, ref: 'question', required: true },
1818
roomId: { type: String, required: true, unique: false },
1919
attemptStartedAt: { type: Date, required: true, default: Date.now() },
20-
attemptCompletedAt: { type: Date, required: true, default: Date.now() },
20+
lastAttemptSubmittedAt: { type: Date, required: true, default: Date.now() },
2121
collaboratorId: { type: String, required: true },
22-
attemptCodes: [{ type: String, required: true }],
22+
attemptCodes: [{ type: String }],
2323
});
2424

25+
historyEntrySchema.index({ userId: 1, roomId: 1 }, { unique: true });
26+
2527
const historyEntryModel = mongoose.model<HistoryEntry>('historyEntry', historyEntrySchema);
2628
export default historyEntryModel;

backend/user-service/.env.example

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ DB_CLOUD_URI=<your-cloud-mongodb-uri>
1111
ENV=production
1212

1313
# Secrets for creating JWT signatures
14-
JWT_ACCESS_TOKEN_SECRET=<your-jwt-access-token-secret>
15-
JWT_REFRESH_TOKEN_SECRET=<your-jwt-refresh-token-secret>
16-
JWT_RESET_TOKEN_SECRET=<your-jwt-reset-token-secret>
14+
JWT_ACCESS_TOKEN_SECRET=d2636f0c0ce9119c4aca178220a3a5a7bba0e5f6dffa982f8095f5b566162029
15+
JWT_REFRESH_TOKEN_SECRET=65863c16dc76d23f06668c12b9223f93cb25f4f2c9f0919ba3330000abaa9253
16+
JWT_RESET_TOKEN_SECRET=70468fdf0966eaf0769cbd17f3348cf3debd9a47ac8b85dbee9aff3ebd5d074c
1717

1818
# If using mongoDB containerization, set to true. Else set to false (i.e local testing)
1919
DB_REQUIRE_AUTH=true

frontend/src/data/repositories/HistoryRemoteDataSource.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ export class HistoryRemoteDataSource extends BaseApi {
1313
return await this.protectedGet<HistoryEntry[]>("/");
1414
}
1515

16-
async createOrUpdateUserHistory(questionId: string, roomId: string, attemptStartedAt: string, attemptCompletedAt: string, collaboratorId: string, attemptCode: string): Promise<void> {
17-
return await this.protectedPost<void>("/", { questionId, roomId, attemptStartedAt, attemptCompletedAt, collaboratorId, attemptCode });
16+
async createOrUpdateUserHistory(questionId: string, roomId: string, attemptStartedAt: string, attemptCompletedAt: string, collaboratorId: string, attemptCode: string, isInitial: boolean): Promise<void> {
17+
return await this.protectedPost<void>("/", { questionId, roomId, attemptStartedAt, attemptCompletedAt, collaboratorId, attemptCode, isInitial });
1818
}
1919

2020
async deleteSelectedHistories(selectedHistoryIds: string[]): Promise<void> {

frontend/src/data/repositories/HistoryRepositoryImpl.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ export class HistoryRepositoryImpl {
88
return this.dataSource.getAllUserHistories();
99
}
1010

11-
async createOrUpdateUserHistory(questionId: string, roomId: string, attemptStartedAt: string, attemptCompletedAt: string, collaboratorId: string, attemptCode: string): Promise<void> {
12-
this.dataSource.createOrUpdateUserHistory(questionId, roomId, attemptStartedAt, attemptCompletedAt, collaboratorId, attemptCode)
11+
async createOrUpdateUserHistory(questionId: string, roomId: string, attemptStartedAt: string, attemptCompletedAt: string, collaboratorId: string, attemptCode: string, isInitial: boolean): Promise<void> {
12+
this.dataSource.createOrUpdateUserHistory(questionId, roomId, attemptStartedAt, attemptCompletedAt, collaboratorId, attemptCode, isInitial)
1313
}
1414

1515
async deleteSelectedUserHistories(selectedHistoryIds: string[]): Promise<void> {

frontend/src/domain/entities/HistoryEntry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export interface HistoryEntry {
33
key: string;
44
roomId: string;
55
attemptStartedAt: string;
6-
attemptCompletedAt: string;
6+
lastAttemptSubmittedAt: string;
77
title: string;
88
difficulty: 'Easy' | 'Medium' | 'Hard';
99
topics: string[];

frontend/src/domain/repositories/IHistoryRepository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { HistoryEntry } from "domain/entities/HistoryEntry";
22

33
export interface IHistoryRepository {
44
getAllUserHistories(): Promise<HistoryEntry[]>;
5-
createOrUpdateUserHistory(questionId: string, roomId: string, attemptStartedAt: string, attemptCompletedAt: string, collaboratorId: string, attemptCode: string): Promise<void>;
5+
createOrUpdateUserHistory(questionId: string, roomId: string, attemptStartedAt: string, attemptCompletedAt: string, collaboratorId: string, attemptCode: string, isInitial: boolean): Promise<void>;
66
deleteSelectedUserHistories(selectedHistoryIds: string[]): Promise<void>;
77
deleteAllUserHistories(): Promise<void>;
88
}

frontend/src/domain/usecases/HistoryUseCases.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,37 @@ export class HistoryUseCases {
1414
return allHistories;
1515
}
1616

17+
/**
18+
* Creates a new User History Entry.
19+
* (Note that just roomId isn't enough because the collaborator will also have a very similar History Entry)
20+
* @returns Promise resolving when the history has been successfully created.
21+
*/
22+
async createUserHistory(questionId: string, roomId: string, attemptStartedAt: string, attemptCompletedAt: string, collaboratorId: string, attemptCode: string): Promise<void> {
23+
if (!questionId || questionId.trim() === ""
24+
|| !roomId || roomId.trim() === ""
25+
|| !attemptStartedAt || attemptStartedAt.trim() === ""
26+
|| !attemptCompletedAt || attemptCompletedAt.trim() === ""
27+
|| !collaboratorId || collaboratorId.trim() === "") {
28+
throw new Error("Missing Attempt Details");
29+
}
30+
await this.historyRepository.createOrUpdateUserHistory(questionId, roomId, attemptStartedAt, attemptCompletedAt, collaboratorId, attemptCode, true);
31+
}
32+
1733
/**
1834
* Creates a new User History Entry. If there already exists a History Entry with the same userId (obtained in
1935
* backend via JWT token and not passed here) and roomId, update the existing one instead.
2036
* (Note that just roomId isn't enough because the collaborator will also have a very similar History Entry)
2137
* @returns Promise resolving when the history has been successfully created.
2238
*/
23-
async createOrUpdateUserHistory(questionId: string, roomId: string, attemptStartedAt: string, attemptCompletedAt: string, collaboratorId: string, attemptCode: string): Promise<void> {
39+
async updateUserHistory(questionId: string, roomId: string, attemptStartedAt: string, attemptCompletedAt: string, collaboratorId: string, attemptCode: string): Promise<void> {
2440
if (!questionId || questionId.trim() === ""
2541
|| !roomId || roomId.trim() === ""
2642
|| !attemptStartedAt || attemptStartedAt.trim() === ""
2743
|| !attemptCompletedAt || attemptCompletedAt.trim() === ""
2844
|| !collaboratorId || collaboratorId.trim() === "") {
2945
throw new Error("Missing Attempt Details");
3046
}
31-
await this.historyRepository.createOrUpdateUserHistory(questionId, roomId, attemptStartedAt, attemptCompletedAt, collaboratorId, attemptCode);
47+
await this.historyRepository.createOrUpdateUserHistory(questionId, roomId, attemptStartedAt, attemptCompletedAt, collaboratorId, attemptCode, false);
3248
}
3349

3450
/**

0 commit comments

Comments
 (0)