Skip to content

Commit

Permalink
Merge branch 'main' into jiale/bugfix
Browse files Browse the repository at this point in the history
# Conflicts:
#	backend/matching-service/src/matchingService.ts
  • Loading branch information
Singa-pirate committed Nov 9, 2024
2 parents 4b21a85 + 2c1b0d0 commit dac3f96
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 33 deletions.
29 changes: 15 additions & 14 deletions backend/matching-service/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@ generator client {
}

model MatchRecord {
recordId Int @id @default(autoincrement())
userId String // ID of the user waiting for a match
topic String // Preferred topic for the match
difficulty String // Preferred difficulty level for the match
socketId String // Socket Id for communication
matched Boolean @default(false) // Whether a match has been made
matchedUserId String? // Optional field to store matched user's ID
isPending Boolean @default(false) // Whether a match is currently pending confirmation
isConfirmed Boolean @default(false) // Whether a match is confirmed by the user
isArchived Boolean @default(false) // Whether a match record has been archived
createdAt DateTime @default(now()) // Timestamp for the match attempt
roomNumber String // Matched room number
questionId Int? // Question ID for the match
recordId Int @id @default(autoincrement())
userId String // ID of the user waiting for a match
topic String // Preferred topic for the match
difficulty String // Preferred difficulty level for the match
socketId String // Socket Id for communication
matched Boolean @default(false) // Whether a match has been made
matchedUserId String? // Optional field to store matched user's ID
isPending Boolean @default(false) // Whether a match is currently pending confirmation
isConfirmed Boolean @default(false) // Whether a match is confirmed by the user
isArchived Boolean @default(false) // Whether a match record has been archived
createdAt DateTime @default(now()) // Timestamp for the match attempt
constraintsRelaxed Boolean @default(false)
roomNumber String // Matched room number
questionId Int? // Question ID for the match
}

model SessionHistory {
Expand All @@ -34,5 +35,5 @@ model SessionHistory {
userTwoId String
isUserOneActive Boolean @default(true)
isUserTwoActive Boolean @default(true)
createdAt DateTime @default(now()) // Timestamp for the start of session
createdAt DateTime @default(now()) // Timestamp for the start of session
}
224 changes: 209 additions & 15 deletions backend/matching-service/src/matchingService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const prisma = new PrismaClient();

const DELAY_TIME = 30000;
const CONFIRM_DELAY_TIME = 10000;
const RELAX_CONSTRAINT_DELAY = 10000;

export async function handleMatchingRequest(userRequest: any, socketId: string) {
userRequest.socketId = socketId;
Expand Down Expand Up @@ -38,7 +39,129 @@ function sendConfirmDelayedTimeoutMessage(recordId: string) {
console.log("Sent delayed message for confirm timeout for recordId: ", recordId);
}

export async function handleUserRequest(userRequest: any) {
function sendRelaxConstraintsMessage(userRequest: any) {
userRequest["type"] = "relax_constraints";
sendDelayedMessage(userRequest, RELAX_CONSTRAINT_DELAY);
console.log("Scheduled to relax constraints for user:", userRequest.userId);
}

export async function handleUserRequestWithRelaxedConstraints(userRequest: any) {
const { userId, topic, difficulty } = userRequest;

const user = await prisma.matchRecord.findFirst({
where: { userId, isPending: false, isArchived: false },
});

if (user) {
await prisma.matchRecord.update({
where: { recordId: user.recordId },
data: { constraintsRelaxed: true },
});
} else {
console.error(`No match record found for user ${userId} during relaxed constraints matching`);
return;
}

const excludedUserIds = [userId];

let existingMatch = null;

existingMatch = await prisma.matchRecord.findFirst({
where: {
topic,
difficulty,
matched: false,
isArchived: false,
userId: {
notIn: excludedUserIds,
},
},
});

if (existingMatch === null) {
existingMatch = await prisma.matchRecord.findFirst({
where: {
topic,
matched: false,
isArchived: false,
userId: {
notIn: excludedUserIds,
},
},
});
}

if (existingMatch !== null && existingMatch.constraintsRelaxed) {
// Proceed with matching logic
const roomNumber = uuidv4();
const question = await fetchRandomQuestion(difficulty, topic);

if (!question) {
io.to(userRequest.socketId).emit("question_error", {
message: "No Question found for the selected topic",
});
io.to(existingMatch.socketId).emit("question_error", {
message: "No Question found for the selected topic",
});
await prisma.matchRecord.delete({
where: { recordId: existingMatch.recordId },
});
return;
}

// Match found, update both records to mark as isPending
await prisma.matchRecord.update({
where: { recordId: existingMatch.recordId },
data: {
matched: true,
matchedUserId: userId,
isPending: true,
roomNumber,
questionId: question.questionId as number,
},
});

// Update current user's match record
const currentUserRecord = await prisma.matchRecord.findFirst({
where: { userId, isPending: false, isArchived: false },
});

if (currentUserRecord) {
await prisma.matchRecord.update({
where: { recordId: currentUserRecord.recordId },
data: {
matched: true,
matchedUserId: existingMatch.userId,
isPending: true,
roomNumber,
questionId: question.questionId as number,
},
});
} else {
console.error(`No match record found for user ${userId} during relaxed constraints matching`);
return;
}

// Update both clients about the successful match
io.to(currentUserRecord.socketId).emit("matched", {
matchedWith: existingMatch.userId,
roomNumber,
questionId: question.questionId,
});
io.to(existingMatch.socketId).emit("matched", {
matchedWith: userId,
roomNumber,
questionId: question.questionId,
});

sendConfirmDelayedTimeoutMessage(currentUserRecord.recordId.toString());
sendConfirmDelayedTimeoutMessage(existingMatch.recordId.toString());
} else {
console.log(`No match found for ${userId} after relaxing constraints, waiting for future matches`);
}
}

export async function handleUserRequestWithoutRelaxedConstraints(userRequest: any) {
const { userId, topic, difficulty, socketId } = userRequest;

// Check if user already has a match record
Expand All @@ -65,37 +188,85 @@ export async function handleUserRequest(userRequest: any) {
return;
}

// Check if there is an existing match
const existingMatch = await prisma.matchRecord.findFirst({
// Get list of previous matches
const previousMatches = await prisma.sessionHistory.findMany({
where: {
OR: [{ userOneId: userId }, { userTwoId: userId }],
},
select: {
userOneId: true,
userTwoId: true,
},
});

const previousUserIds = previousMatches.flatMap((match) => {
if (match.userOneId === userId) {
return [match.userTwoId];
} else {
return [match.userOneId];
}
});

// Build list of userIds to exclude
const excludedUserIds = [userId, ...previousUserIds];

let existingMatch = null;

// First attempt: same topic and difficulty, excluding previous matches
existingMatch = await prisma.matchRecord.findFirst({
where: {
topic,
difficulty,
matched: false,
isArchived: false,
NOT: { userId },
userId: {
notIn: excludedUserIds,
},
},
});

if (existingMatch === null) {
// Second attempt: same topic, any difficulty, excluding previous matches
existingMatch = await prisma.matchRecord.findFirst({
where: {
topic,
matched: false,
isArchived: false,
userId: {
notIn: excludedUserIds,
},
},
});
}

if (existingMatch !== null) {
// Proceed with matching logic
const roomNumber = uuidv4();
const question = await fetchRandomQuestion(difficulty, topic);

if (!question) {
io.to(socketId).emit("question_error", {
message: "No Question found for the selected topic and difficulty",
message: "No Question found for the selected topic",
});
io.to(existingMatch.socketId).emit("question_error", {
message: "No Question found for the selected topic and difficulty",
message: "No Question found for the selected topic",
});
await prisma.matchRecord.delete({
where: { recordId: existingMatch.recordId },
});
return;
}

// Match found, update both records to mark as isPending
await prisma.matchRecord.update({
where: { recordId: existingMatch.recordId },
data: { matched: true, matchedUserId: userId, isPending: true },
data: {
matched: true,
matchedUserId: userId,
isPending: true,
roomNumber,
questionId: question.questionId as number,
},
});
const current = await prisma.matchRecord.create({
data: {
Expand All @@ -107,11 +278,10 @@ export async function handleUserRequest(userRequest: any) {
matchedUserId: existingMatch.userId,
isPending: true,
roomNumber,
questionId: question?.questionId as number,
questionId: question.questionId as number,
},
});

// Update both clients about the successful match
io.to(socketId).emit("matched", {
matchedWith: existingMatch.userId,
});
Expand All @@ -123,6 +293,32 @@ export async function handleUserRequest(userRequest: any) {
sendConfirmDelayedTimeoutMessage(current.recordId.toString());
sendConfirmDelayedTimeoutMessage(existingMatch.recordId.toString());
} else {
// No match found
// Add user to match record and schedule constraint relaxation
console.log(`No match found for ${userId}, added to record and scheduling constraint relaxation`);
await addOrUpdateMatchRecord(userRequest);
sendRelaxConstraintsMessage(userRequest);
}
}

async function addOrUpdateMatchRecord(userRequest: any) {
const { userId, topic, difficulty, socketId } = userRequest;

// Check if a matchRecord already exists for the user
const existingRecord = await prisma.matchRecord.findFirst({
where: { userId, isArchived: false, isPending: false, matched: false },
});

if (existingRecord) {
// Update the existing record's socketId if necessary
if (existingRecord.socketId !== socketId) {
await prisma.matchRecord.update({
where: { recordId: existingRecord.recordId },
data: { socketId },
});
}
} else {
// Create a new matchRecord
const roomNumber = uuidv4();
await prisma.matchRecord.create({
data: {
Expand All @@ -134,8 +330,6 @@ export async function handleUserRequest(userRequest: any) {
roomNumber,
},
});

console.log(`No match found for ${userId}, added to record`);
}
}

Expand Down Expand Up @@ -259,7 +453,7 @@ export async function handleMatchingDecline(userRequest: any) {
data: { isArchived: true },
});

// user decline, match failed regardlessly
// user decline, match failed regardless
console.log(`User ${userId} declined match`);
io.to(matchedRecord.socketId).emit("other_declined", "Match not confirmed. Please try again.");
await prisma.matchRecord.update({
Expand Down Expand Up @@ -294,10 +488,10 @@ export async function handleTimeout(userRequest: any) {
export async function handleConfirmTimeout(recordId: string) {
const recordIdInt = Number(recordId);
const result = await prisma.matchRecord.findUnique({
where: { recordId: recordIdInt, isArchived: false },
where: { recordId: recordIdInt },
});
console.log(`Timeout: Confirm timeout for recordId ${recordId}`);
if (result !== null) {
if (result !== null && !result.isArchived) {
if (result.isConfirmed === false) {
console.log(`Timeout: Match not confirmed for recordId ${recordId} with userId ${result.userId}`);
} else {
Expand All @@ -317,7 +511,7 @@ export async function handleDisconnected(socketId: string) {
const result = await prisma.matchRecord.findMany({
where: { socketId },
});
if (result !== null) {
if (result && result.length > 0) {
await prisma.matchRecord.updateMany({
where: { socketId },
data: { isArchived: true },
Expand Down
10 changes: 6 additions & 4 deletions backend/matching-service/src/rabbitmq.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import amqp from "amqplib";
import {
handleUserRequest,
handleUserRequestWithRelaxedConstraints,
handleTimeout,
handleConfirmTimeout,
handleUserRequestWithoutRelaxedConstraints,
} from "./matchingService";

const RABBITMQ_URL = process.env.RABBITMQ_URL || "amqp://localhost";
Expand All @@ -29,7 +30,7 @@ export async function setupRabbitMQ() {
if (msg !== null) {
const userRequest = JSON.parse(msg.content.toString());
console.log("Received from queue:", userRequest);
await handleUserRequest(userRequest);
await handleUserRequestWithoutRelaxedConstraints(userRequest);
rabbitMQChannel.ack(msg);
}
});
Expand All @@ -43,8 +44,9 @@ export async function setupRabbitMQ() {
await handleTimeout(userRequest);
} else if (userRequest.type === "confirm_timeout") {
await handleConfirmTimeout(userRequest.recordId);
} else if (userRequest.type === "relax_constraints") {
await handleUserRequestWithRelaxedConstraints(userRequest);
}

rabbitMQChannel.ack(msg);
}
});
Expand All @@ -66,6 +68,6 @@ export function sendDelayedMessage(message: any, delay: number) {
Buffer.from(JSON.stringify(message)),
{
headers: { "x-delay": delay },
},
}
);
}

0 comments on commit dac3f96

Please sign in to comment.