Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Session history implementation #36

Merged
merged 10 commits into from
Nov 8, 2024
2 changes: 2 additions & 0 deletions backend/collaboration-service/.env.sample
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
LOG_LEVEL=20
USER_SERVICE_URL=http://localhost:3001
MATCHING_SERVICE_URL=http://localhost:3003

# In Docker
LOG_LEVEL=20
USER_SERVICE_URL=http://user:3001
MATCHING_SERVICE_URL=http://matching:3003
3 changes: 2 additions & 1 deletion backend/collaboration-service/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
class User:
users: dict[str, "User"] = {}

def __init__(self, username: str, sid: str):
def __init__(self, user_id, username: str, sid: str):
self.user_id = user_id
self.username = username
self.cursor_position = 0
self.sid = sid
Expand Down
29 changes: 23 additions & 6 deletions backend/collaboration-service/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
import logging
import os
import dotenv
import requests

dotenv.load_dotenv()
MATCHING_SERVICE_URL = os.environ.get('MATCHING_SERVICE_URL')
if not MATCHING_SERVICE_URL:
raise ValueError('MATCHING_SERVICE_URL environment variable not set')

from events import Events
from models import Room, User
Expand All @@ -26,9 +30,11 @@ async def connect(sid, environ):
logging.debug(f"connect {token=}")
break
if token:
username = authenticate(token)
if username:
User(username, sid)
user = authenticate(token)
user_id = user.get('id', None)
username = user.get('username', None)
if user_id and username:
User(user_id, username, sid)
logging.info(f"User {username} authenticated and connected with sid {sid}")
else:
unauthenticated_sids.add(sid)
Expand Down Expand Up @@ -62,8 +68,13 @@ async def join_request(sid, data):
logging.error(f"After join_request, user.room is None for sid {sid}")
else:
logging.debug(f"User {sid} joined room {room.id}")

data = {
"user_id": user.user_id,
"room_details": room.details()
}

await sio.emit(Events.JOIN_REQUEST, room.details(), room=room_id)
await sio.emit(Events.JOIN_REQUEST, data, room=room_id)


@sio.on(Events.CODE_UPDATED)
Expand Down Expand Up @@ -167,12 +178,18 @@ async def disconnect(sid):
if room is None:
logging.error(f"User {sid} has no room during disconnect")
return

room_still_exists = room.remove_user(user)

if room_still_exists:
try:
await sio.emit(Events.USER_LEFT, user.details(), room=room.id)
logging.debug(f"Emitted USER_LEFT to room {room.id}")
except Exception as e:
logging.error(f"Failed to emit USER_LEFT for room {room.id}: {e}")
logging.error(f"Failed to emit USER_LEFT for room {room.id}: {e}")

response = requests.put(f"{MATCHING_SERVICE_URL}/leave-session", json={"data": {"roomId": room.id, "userId": user.user_id} })
if response.status_code != 200:
logging.error(f"Error communicating with matching service: {response.content}")
else:
logging.info(f"Requested matching service to mark user {user.user_id} as having left room {room.id}")
2 changes: 1 addition & 1 deletion backend/collaboration-service/user_verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ def authenticate(authorization_header) -> str | None:
response = requests.get(f"{USER_SERVICE_URL}/auth/verify-token", headers={'authorization': authorization_header})
if response.status_code != 200:
return None
return response.json()["data"]['username']
return response.json()["data"]
14 changes: 14 additions & 0 deletions backend/matching-service/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,17 @@ model MatchRecord {
roomNumber String // Matched room number
questionId Int? // Question ID for the match
}

model SessionHistory {
sessionId Int @id @default(autoincrement())
roomNumber String
questionId Int
submission String?
language String?
isOngoing Boolean @default(true)
userOneId String
userTwoId String
isUserOneActive Boolean @default(true)
isUserTwoActive Boolean @default(true)
createdAt DateTime @default(now()) // Timestamp for the start of session
}
98 changes: 29 additions & 69 deletions backend/matching-service/src/matchingService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ const prisma = new PrismaClient();
const DELAY_TIME = 30000;
const CONFIRM_DELAY_TIME = 10000;

export async function handleMatchingRequest(
userRequest: any,
socketId: string,
) {
export async function handleMatchingRequest(userRequest: any, socketId: string) {
userRequest.socketId = socketId;

addUserToQueue(userRequest);
Expand All @@ -37,12 +34,9 @@ function sendConfirmDelayedTimeoutMessage(recordId: string) {
recordId: recordId,
type: "confirm_timeout",
},
CONFIRM_DELAY_TIME,
);
console.log(
"Sent delayed message for confirm timeout for recordId: ",
recordId,
CONFIRM_DELAY_TIME
);
console.log("Sent delayed message for confirm timeout for recordId: ", recordId);
}

export async function handleUserRequest(userRequest: any) {
Expand All @@ -59,7 +53,7 @@ export async function handleUserRequest(userRequest: any) {
console.log("Duplicate socket detected. New socket will be used.");
io.to(pastSocketId).emit(
"duplicate socket",
"New connection detected for the same user. Please close the current page",
"New connection detected for the same user. Please close the current page"
);

// Update socket ID upon potential reconnection
Expand Down Expand Up @@ -167,20 +161,14 @@ export async function handleMatchingConfirm(userRequest: any) {
where: { recordId: matchedRecord.recordId },
data: { isArchived: true },
});
io.to(matchedRecord.socketId).emit(
"other_declined",
"Match not confirmed. Please try again.",
);
io.to(matchedRecord.socketId).emit("other_declined", "Match not confirmed. Please try again.");
}
if (userRecord !== null) {
await prisma.matchRecord.update({
where: { recordId: userRecord.recordId },
data: { isArchived: true },
});
io.to(userRecord.socketId).emit(
"other_declined",
"Match not confirmed. Please try again.",
);
io.to(userRecord.socketId).emit("other_declined", "Match not confirmed. Please try again.");
}
return;
}
Expand Down Expand Up @@ -208,24 +196,22 @@ export async function handleMatchingConfirm(userRequest: any) {
where: { recordId: userRecord.recordId },
data: { isArchived: true },
});
await prisma.sessionHistory.create({
data: {
roomNumber: matchedRecord.roomNumber,
questionId: matchedRecord.questionId ?? userRecord.questionId ?? 0,
isOngoing: true,
userOneId: userRecord.userId,
userTwoId: matchedRecord.userId,
},
});

io.to(userRecord.socketId).emit(
"matching_success",
"Match confirmed. Proceeding to collaboration service.",
);
io.to(matchedRecord.socketId).emit(
"matching_success",
"Match confirmed. Proceeding to collaboration service.",
);
io.to(userRecord.socketId).emit("matching_success", "Match confirmed. Proceeding to collaboration service.");
io.to(matchedRecord.socketId).emit("matching_success", "Match confirmed. Proceeding to collaboration service.");
// TODO: add further logic here to proceed to collaboration service
} else {
console.log(
`User ${userId} confirmed match, waiting for other user to confirm`,
);
io.to(matchedRecord.socketId).emit(
"other_accepted",
"Other user confirmed match. Please confirm.",
);
console.log(`User ${userId} confirmed match, waiting for other user to confirm`);
io.to(matchedRecord.socketId).emit("other_accepted", "Other user confirmed match. Please confirm.");
}
}

Expand All @@ -246,28 +232,16 @@ export async function handleMatchingDecline(userRequest: any) {
where: { recordId: matchedRecord.recordId },
data: { isArchived: true },
});
io.to(matchedRecord.socketId).emit(
"other_declined",
"Match not confirmed. Please try again.",
);
io.to(matchedRecord.socketId).emit(
"matching_fail",
"Match not confirmed. Please try again.",
);
io.to(matchedRecord.socketId).emit("other_declined", "Match not confirmed. Please try again.");
io.to(matchedRecord.socketId).emit("matching_fail", "Match not confirmed. Please try again.");
}
if (userRecord !== null) {
await prisma.matchRecord.update({
where: { recordId: userRecord.recordId },
data: { isArchived: true },
});
io.to(userRecord.socketId).emit(
"other_declined",
"Match not confirmed. Please try again.",
);
io.to(userRecord.socketId).emit(
"matching_fail",
"Match not confirmed. Please try again.",
);
io.to(userRecord.socketId).emit("other_declined", "Match not confirmed. Please try again.");
io.to(userRecord.socketId).emit("matching_fail", "Match not confirmed. Please try again.");
}

return;
Expand All @@ -280,23 +254,14 @@ export async function handleMatchingDecline(userRequest: any) {

// user decline, match failed regardlessly
console.log(`User ${userId} declined match`);
io.to(matchedRecord.socketId).emit(
"other_declined",
"Match not confirmed. Please try again.",
);
io.to(matchedRecord.socketId).emit("other_declined", "Match not confirmed. Please try again.");
await prisma.matchRecord.update({
where: { recordId: matchedRecord.recordId },
data: { isArchived: true },
});

io.to(userRecord.socketId).emit(
"matching_fail",
"Match not confirmed. Please try again.",
);
io.to(matchedRecord.socketId).emit(
"matching_fail",
"Match not confirmed. Please try again.",
);
io.to(userRecord.socketId).emit("matching_fail", "Match not confirmed. Please try again.");
io.to(matchedRecord.socketId).emit("matching_fail", "Match not confirmed. Please try again.");
}

export async function handleTimeout(userRequest: any) {
Expand Down Expand Up @@ -327,18 +292,13 @@ export async function handleConfirmTimeout(recordId: string) {
console.log(`Timeout: Confirm timeout for recordId ${recordId}`);
if (result !== null) {
if (result.isConfirmed === false) {
console.log(
`Timeout: Match not confirmed for recordId ${recordId} with userId ${result.userId}`,
);
console.log(`Timeout: Match not confirmed for recordId ${recordId} with userId ${result.userId}`);
} else {
console.log(
`Timeout: Match confirmed for recordId ${recordId} with userId ${result.userId} but other user did not confirm`,
`Timeout: Match confirmed for recordId ${recordId} with userId ${result.userId} but other user did not confirm`
);
}
io.to(result.socketId).emit(
"matching_fail",
"Match not confirmed. Please try again.",
);
io.to(result.socketId).emit("matching_fail", "Match not confirmed. Please try again.");
await prisma.matchRecord.update({
where: { recordId: recordIdInt },
data: { isArchived: true },
Expand Down
Loading
Loading