Skip to content

Commit

Permalink
Update Matching Algo
Browse files Browse the repository at this point in the history
  • Loading branch information
dedsecrattle committed Nov 9, 2024
1 parent 207dd6a commit 2c1b0d0
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 55 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
}
208 changes: 171 additions & 37 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,
Expand Down Expand Up @@ -44,7 +45,139 @@ function sendConfirmDelayedTimeoutMessage(recordId: string) {
);
}

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 Down Expand Up @@ -122,35 +255,6 @@ export async function handleUserRequest(userRequest: any) {
});
}

if (existingMatch === null) {
// Third attempt: same topic and difficulty, including previous matches
existingMatch = await prisma.matchRecord.findFirst({
where: {
topic,
difficulty,
matched: false,
isArchived: false,
userId: {
not: userId,
},
},
});
}

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

if (existingMatch !== null) {
// Proceed with matching logic
const roomNumber = uuidv4();
Expand All @@ -172,7 +276,13 @@ export async function handleUserRequest(userRequest: any) {
// 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 @@ -188,7 +298,6 @@ export async function handleUserRequest(userRequest: any) {
},
});

// Update both clients about the successful match
io.to(socketId).emit("matched", {
matchedWith: existingMatch.userId,
roomNumber,
Expand All @@ -204,7 +313,34 @@ export async function handleUserRequest(userRequest: any) {
sendConfirmDelayedTimeoutMessage(current.recordId.toString());
sendConfirmDelayedTimeoutMessage(existingMatch.recordId.toString());
} else {
// No match found, add user to matchRecord
// 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 @@ -216,8 +352,6 @@ export async function handleUserRequest(userRequest: any) {
roomNumber,
},
});

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

Expand Down Expand Up @@ -358,7 +492,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",
Expand Down Expand Up @@ -430,7 +564,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 2c1b0d0

Please sign in to comment.