diff --git a/peerprep/backend/matching-service/src/controllers/requestController2.ts b/peerprep/backend/matching-service/src/controllers/requestController2.ts index 31ae8e6889..3b94d09f5a 100644 --- a/peerprep/backend/matching-service/src/controllers/requestController2.ts +++ b/peerprep/backend/matching-service/src/controllers/requestController2.ts @@ -1,5 +1,5 @@ import { Request, Response } from 'express'; -import { addToQueue} from '../services/queueManager2'; +import { addToQueue, removeFromQueue, cancelSession} from '../services/queueManager2'; import ReqObj from '../models/ReqObj'; export const createRequest = async (req: Request, res: Response): Promise => { @@ -30,4 +30,32 @@ export const createRequest = async (req: Request, res: Response): Promise res.status(500).json({ message: 'Failed to add request to the queue due to an unknown error' }); } } - }; \ No newline at end of file +}; + +export const deleteRequest = async (req: Request, res: Response): Promise => { + const userId = req.params.userId as string; // Get userId from URL parameters + + try { + // Try to remove the user from the queue + console.log("Attempting to remove user from queue"); + await removeFromQueue(userId); + res.status(200).json({ message: 'Request successfully removed from the queue' }); + } catch (error) { + console.error("Error in deleteRequest:", error); + res.status(500).json({ message: 'Failed to remove request from the queue due to an unknown error' }); + } +} + +// API to delete a session +export const deleteSession = async (req: Request, res: Response): Promise => { + const sessionId = req.params.sessionId as string; // Get userId from URL parameters + + try { + // Cancel the timeout and remove session from Redis + await cancelSession(sessionId); + res.status(200).json({ message: 'Session successfully deleted' }); + } catch (error) { + console.error("Error in deleteSession:", error); + res.status(500).json({ message: 'Failed to delete session due to an unknown error' }); + } +}; \ No newline at end of file diff --git a/peerprep/backend/matching-service/src/routes/apiRoutes.ts b/peerprep/backend/matching-service/src/routes/apiRoutes.ts index e1b08c28a9..03f544b474 100644 --- a/peerprep/backend/matching-service/src/routes/apiRoutes.ts +++ b/peerprep/backend/matching-service/src/routes/apiRoutes.ts @@ -1,5 +1,5 @@ import express from 'express'; -import { createRequest } from '../controllers/requestController2'; +import { createRequest, deleteRequest, deleteSession } from '../controllers/requestController2'; import { matchStatusStream } from '../controllers/sseController'; // Import your SSE handler const router = express.Router(); @@ -10,5 +10,10 @@ router.post('/matchingrequest', createRequest); // Route for Server-Sent Events (SSE) to check match status router.get('/matchingrequest/:userId', matchStatusStream); // Change to use `:userId` as a URL parameter +// Route to delete a matching request by userId +router.delete('/matchingrequest/:userId', deleteRequest); + +// Route to delete a session by userId +router.delete('/matchingrequest/session/:sessionId', deleteSession); export default router; \ No newline at end of file diff --git a/peerprep/backend/matching-service/src/services/queueManager2.ts b/peerprep/backend/matching-service/src/services/queueManager2.ts index 52bb291d4d..1a6642861c 100644 --- a/peerprep/backend/matching-service/src/services/queueManager2.ts +++ b/peerprep/backend/matching-service/src/services/queueManager2.ts @@ -21,6 +21,16 @@ export const addToQueue = async (userId: string, topic: string, difficulty: stri } }; +export const removeFromQueue = async (userId: string) => { + try { + await redis.dequeueUser(userId); + } + catch (error) { + console.error('Error in removeFromQueue:', error); + throw new Error("Failed to remove user from the queue due to an unknown error"); + } +}; + export const findMatchInQueue = async (userId: string) => { const topicTimeoutSeconds = MATCH_TIMEOUT_SECONDS / 2; @@ -88,6 +98,17 @@ export const getStatus = async (userId: string) => { } } +// function to delete session +export const cancelSession = async (sessionId: string) => { + try { + await redis.deleteSession(sessionId); + } + catch (error) { + console.error('Error in cancelSession:', error); + throw new Error("Failed to cancel the user's session"); + } +} + /* export const getStatus = async (userId: string, topic: string, difficulty: string) => { try { diff --git a/peerprep/backend/matching-service/src/services/statusService.ts b/peerprep/backend/matching-service/src/services/statusService.ts index 41de41ebc8..1a70430e94 100644 --- a/peerprep/backend/matching-service/src/services/statusService.ts +++ b/peerprep/backend/matching-service/src/services/statusService.ts @@ -4,13 +4,18 @@ import { getStatus, findMatchInQueue } from '../services/queueManager2'; // declare function to retrieve the user's match request status from the queue export const updateStatus = async (userId: string) => { try { - return await getStatus(userId); + const status = await getStatus(userId); + if (status.includes("request pending")) { + findMatchInQueue(userId); + } + return status; } catch (error) { console.error('Error in updateStatus:', error); throw new Error("Failed to update the status of the user's match request"); - } finally { + } /*finally { // Find match await findMatchInQueue(userId); } // Add session status check here + */ }; diff --git a/peerprep/backend/matching-service/src/utils/redisUtils3.ts b/peerprep/backend/matching-service/src/utils/redisUtils3.ts index d2d323bfad..876c8443c4 100644 --- a/peerprep/backend/matching-service/src/utils/redisUtils3.ts +++ b/peerprep/backend/matching-service/src/utils/redisUtils3.ts @@ -57,6 +57,41 @@ export const enqueueUser = async ( } }; +export const dequeueUser = async (userId: string): Promise => { + const redisClient: Redis = app.locals.redisClient; + const multi = redisClient.multi(); + + try { + // Retrieve the user's topic and difficulty from Redis + const topic = await redisClient.hget("user-topic", userId); + const difficulty = await redisClient.hget("user-difficulty", userId); + + if (!topic || !difficulty) { + console.log(`User ${userId} not found in queue or missing topic/difficulty.`); + return; + } + + // Remove the user from general and topic-difficulty specific queues + multi.zrem("queue2:users", userId); + multi.zrem(`queue1:${topic}:${difficulty}`, userId); + multi.zrem(`queue2:${topic}`, userId); + + // Remove user-related entries in hash sets + multi.hdel("user-timestamp", userId); + multi.hdel("user-topic", userId); + multi.hdel("user-difficulty", userId); + + await multi.exec(); + console.log(`User ${userId} has been dequeued successfully.`); + } catch (error) { + console.error("Error in dequeueUser:", error); + if (multi) { + multi.discard(); + } + throw error; + } +}; + export const clearExpiredQueue = async () => { const redisClient: Redis = app.locals.redisClient; const expiredTime = Date.now(); @@ -309,7 +344,7 @@ const createSession = async ( /* RANDOM QUESTION STARTS HERE */ const randomQuestion = await getRandomQuestionFromQuestionService(topic, difficulty); - const randomQuestionTitle = randomQuestion?.title.replace(/ /g, "-"); + const randomQuestionTitle = randomQuestion?.title.replace(/ /g, "_"); sessionId += randomQuestionTitle console.log("UPDATED Session ID:", sessionId); @@ -548,4 +583,116 @@ export const findSessionByUser = async ( } }; +export const findSessionIdByUser = async ( + userId: string +): Promise => { + const redisClient: Redis = app.locals.redisClient; + console.log("Finding session id by user" + userId); + try { + const sessionId = await redisClient.hget(`session:userId`, userId); + if (!sessionId) { + return null; + } + return sessionId; + } catch (error) { + console.error("Error in findSessionIdByUser:", error); + throw error; + } +}; + +export const findUserIdsBySessionId = async ( + sessionId: string +): Promise<{ userId1: string; userId2: string } | null> => { + const redisClient: Redis = app.locals.redisClient; + try { + // Retrieve the session data from Redis + const sessionData = await redisClient.hget(`session:sessionId`, sessionId); + if (!sessionData) { + console.log(`No session found for sessionId ${sessionId}`); + return null; + } + + // Parse the session JSON data + const session: Session = JSON.parse(sessionData); + + // Return the userId1 and userId2 from the session + return { userId1: session.userId1, userId2: session.userId2 }; + } catch (error) { + console.error("Error in findUserIdsBySessionId:", error); + throw error; + } +}; + + + +export const deleteSession = async ( + sessionId: string +): Promise => { + const redisClient: Redis = app.locals.redisClient; + // Find users based on sessionId + const userIds = await findUserIdsBySessionId(sessionId); + const userId1 = userIds?.userId1; + const userId2 = userIds?.userId2; + if (!userId1 || !userId2) { + console.log(`No users found for sessionId ${sessionId}`); + throw new Error("No users found for sessionId"); + } + + console.log(`Deleting session ${sessionId} and user1 ${userId1} and user2 ${userId2}`); + + try { + const multi = redisClient.multi(); + + // Deleting session details by sessionId + multi.hdel("session:sessionId", sessionId); + + // Deleting the user-session mapping entries + multi.hdel("session:userId", userId1); + multi.hdel("session:userId", userId2); + + // Remove the user from general and topic-difficulty specific queues + multi.zrem("queue2:users", userId1); + multi.zrem("queue2:users", userId2); + + // Remove user-related entries in hash sets + multi.hdel("user-timestamp", userId1); + multi.hdel("user-topic", userId1); + multi.hdel("user-difficulty", userId1); + multi.hdel("user-timestamp", userId2); + multi.hdel("user-topic", userId2); + multi.hdel("user-difficulty", userId2); + + await multi.exec(); + console.log(`Session ${sessionId} and associated user entries deleted.`); + } catch (error) { + console.error("Error in deleteSession:", error); + throw error; + } +}; + +export const deleteSessionByUserId = async (userId: string): Promise => { + try { + // Step 1: Find sessionId by userId + const sessionId = await findSessionIdByUser(userId); + if (!sessionId) { + console.log(`No session found for userId ${userId}`); + return; + } + + // Step 2: Find userId1 and userId2 by sessionId + const userIds = await findUserIdsBySessionId(sessionId); + if (!userIds) { + console.log(`No user IDs found for sessionId ${sessionId}`); + return; + } + + // Step 3: Delete the session and all associated entries + await deleteSession(sessionId); + console.log(`Session and entries associated with userId ${userId} have been deleted.`); + } catch (error) { + console.error("Error in deleteSessionByUserId:", error); + throw error; + } +}; + diff --git a/peerprep/backend/matching-service/yarn.lock b/peerprep/backend/matching-service/yarn.lock index 55f5bf66d7..99282ae25b 100644 --- a/peerprep/backend/matching-service/yarn.lock +++ b/peerprep/backend/matching-service/yarn.lock @@ -37,7 +37,7 @@ resolved "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz" integrity sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg== -"@redis/client@1.6.0": +"@redis/client@^1.0.0", "@redis/client@1.6.0": version "1.6.0" resolved "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz" integrity sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg== @@ -219,12 +219,12 @@ array-flatten@1.1.1: asynckit@^0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== axios@^1.7.7: version "1.7.7" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + resolved "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz" integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== dependencies: follow-redirects "^1.15.6" @@ -305,14 +305,14 @@ chokidar@^3.5.2: optionalDependencies: fsevents "~2.3.2" -cluster-key-slot@1.1.2, cluster-key-slot@^1.1.0: +cluster-key-slot@^1.1.0, cluster-key-slot@1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz" integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== combined-stream@^1.0.8: version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" @@ -357,20 +357,27 @@ create-require@^1.1.0: resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -debug@2.6.9: - version "2.6.9" - resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== +debug@^4: + version "4.3.7" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== dependencies: - ms "2.0.0" + ms "^2.1.3" -debug@^4, debug@^4.3.4: +debug@^4.3.4: version "4.3.7" resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== dependencies: ms "^2.1.3" +debug@2.6.9: + version "2.6.9" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz" @@ -382,7 +389,7 @@ define-data-property@^1.1.4: delayed-stream@~1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== denque@^2.1.0: @@ -506,12 +513,12 @@ finalhandler@1.3.1: follow-redirects@^1.15.6: version "1.15.9" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz" integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== form-data@^4.0.0: version "4.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz" integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== dependencies: asynckit "^0.4.0" @@ -723,12 +730,17 @@ minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + ms@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -ms@2.1.3, ms@^2.1.3: +ms@2.1.3: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -801,7 +813,7 @@ proxy-addr@~2.0.7: proxy-from-env@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== pstree.remy@^1.1.8: @@ -1001,9 +1013,9 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typescript@^5.6.2: +typescript@^5.0.0, typescript@>=2.7: version "5.6.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.3.tgz#5f3449e31c9d94febb17de03cc081dd56d81db5b" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz" integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw== undefsafe@^2.0.5: @@ -1016,7 +1028,7 @@ undici-types@~6.19.2: resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz" integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== -unpipe@1.0.0, unpipe@~1.0.0: +unpipe@~1.0.0, unpipe@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== diff --git a/peerprep/frontend/src/api/matchingApi.ts b/peerprep/frontend/src/api/matchingApi.ts index 4f3fc0062e..afeaff72a2 100644 --- a/peerprep/frontend/src/api/matchingApi.ts +++ b/peerprep/frontend/src/api/matchingApi.ts @@ -124,6 +124,17 @@ export const deleteMatchingRequest = async (userId: string): Promise => { } } +// Method to delete matched session +export const deleteMatchedSession = async (sessionId: string): Promise => { + try { + // Extract Id from "matched on Session ID: 670d81daf90653ef4b9162b8-67094dcc6be97361a2e7cb1a-1730747329524-Qtest1, Topic: algorithms, Difficulty: easy" + const id = sessionId.split(": ")[1].split(",")[0]; + await axios.delete(`${API_URL}/session/${id}`); + } catch (error) { + return handleApiError(error); + } +} + // Method to save matched session export const saveMatchedSession = async (userId1: string, userId2: string, topic: string, difficulty: string, sessionId: string): Promise => { try { @@ -143,15 +154,6 @@ export const getMatchedSession = async (userId: string): Promise => { - try { - await axios.delete(`${API_URL}/session/${userId}`); - } catch (error) { - return handleApiError(error); - } -} - // Method to get all matched sessions export const getAllMatchedSessions = async (): Promise => { try { @@ -190,4 +192,4 @@ export const getAllMatchedSessionsByTopicAndDifficulty = async (topic: string, d } catch (error) { return handleApiError(error); } -} +} \ No newline at end of file diff --git a/peerprep/frontend/src/views/CollabServiceViews/CollabServiceIntegratedView.tsx b/peerprep/frontend/src/views/CollabServiceViews/CollabServiceIntegratedView.tsx index 8b3abd33d9..a1fc6385ad 100644 --- a/peerprep/frontend/src/views/CollabServiceViews/CollabServiceIntegratedView.tsx +++ b/peerprep/frontend/src/views/CollabServiceViews/CollabServiceIntegratedView.tsx @@ -19,6 +19,8 @@ import 'codemirror/mode/swift/swift'; // For Swift import { CodemirrorBinding } from 'y-codemirror'; import { WebsocketProvider } from 'y-websocket'; +import { listenToMatchStatus, deleteMatchedSession} from "../../api/matchingApi.ts"; + const CollaborationServiceIntegratedView: React.FC = () => { const { topic, difficulty, questionId, sessionId } = useParams<{ topic: string; difficulty: string; questionId: string; sessionId: string; }>(); @@ -68,8 +70,15 @@ const CollaborationServiceIntegratedView: React.FC = () => { }, [sessionId]); const handleLeaveSession = () => { - navigate('/matching'); - // Trigger deleteSession API call here + // Call the API to delete the session + try { + if (sessionId) { + deleteMatchedSession(sessionId); + navigate('/matching'); + } + } catch { + console.error('Error deleting matched session'); + } }; const handleLangChange = (e: React.ChangeEvent) => { diff --git a/peerprep/frontend/src/views/MatchingServiceViews/MatchingServiceMainView.tsx b/peerprep/frontend/src/views/MatchingServiceViews/MatchingServiceMainView.tsx index dc92cbf401..5755337f04 100644 --- a/peerprep/frontend/src/views/MatchingServiceViews/MatchingServiceMainView.tsx +++ b/peerprep/frontend/src/views/MatchingServiceViews/MatchingServiceMainView.tsx @@ -1,6 +1,6 @@ import { useNavigate, Link } from "react-router-dom"; import React, { useState, useEffect, useRef } from 'react'; -import { createMatchingRequest, listenToMatchStatus } from "../../api/matchingApi.ts"; +import { createMatchingRequest, listenToMatchStatus, deleteMatchingRequest} from "../../api/matchingApi.ts"; import { ApiError } from "../../api/matchingApi"; const MatchingServiceMainView: React.FC = () => { @@ -48,7 +48,7 @@ const MatchingServiceMainView: React.FC = () => { // Check if there is a part after the delimiter if (parts.length > 1) { - return parts[1].replace(/-/g, " "); // Return the part after "-Q" + return parts[1].replace(/_/g, " "); // Return the part after "-Q" } else { return "Empty Q" } @@ -62,6 +62,34 @@ const MatchingServiceMainView: React.FC = () => { } } + // Function to listen to match status, declared outside handleSubmit + const startListeningToMatchStatus = () => { + const stopListening = listenToMatchStatus( + userId!, + (data) => { + setStatusMessage(data.message); + if (data.message.includes("Session")) { + setStatusMessage(data.message); + setSessionId(data.message); + handleSetQuestionId(data.message); + console.log(data.message); + setMatchFound(true); // Set match status to true + stopProgressBar(); + setStatusMessage(`Match found for ${topic} and ${difficulty}!`); + setLoading(false); + stopListening(); + } + }, + (error) => { + setStatusMessage("Match timed out. Please try again."); + console.error(error); + setLoading(false); + stopProgressBar(); + stopListening(); + } + ); + }; + // Handle form submission const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -71,41 +99,22 @@ const MatchingServiceMainView: React.FC = () => { startProgressBar(); // Start the progress bar try { + // Attempt to create a matching request await createMatchingRequest(userId, topic, difficulty); - // Listen for match status updates using SSE - const stopListening = listenToMatchStatus( - userId!, - (data) => { - setStatusMessage(data.message); - if (data.message.includes("Session")) { - setStatusMessage(data.message); - setSessionId(data.message); - handleSetQuestionId(data.message); - console.log(data.message); - setMatchFound(true); // Set match status to true - stopProgressBar(); - setStatusMessage(`Match found for ${topic} and ${difficulty}!`); - setLoading(false); - stopListening(); - } - }, - (error) => { - setStatusMessage("Match timed out. Please try again."); - console.error(error); - setLoading(false); - stopProgressBar(); - stopListening(); - } - ); + // If successful, listen for match status updates + startListeningToMatchStatus(); } catch (error) { - if (error instanceof ApiError) { - setStatusMessage(error.message); // Use specific error message + // Check if the error code is 409, indicating an active session + if (error instanceof ApiError && (error.message === "You are already in an active session" || error.message === "You are already looking for a match in the matching queue")) { + // Start listening to the existing session status as if the request was successful + startListeningToMatchStatus(); } else { + // Handle other errors setStatusMessage("Error: Failed to create match request."); + setLoading(false); + stopProgressBar(); } - setLoading(false); - stopProgressBar(); } }; @@ -118,6 +127,11 @@ const MatchingServiceMainView: React.FC = () => { } }, [progress]); + // Handle delete request + const handleDeleteRequest = () => { + deleteMatchingRequest(userId!); // Fire-and-forget API request + }; + // Handle navigation to session stub const goToSession = () => { navigate('/sessionStub', { @@ -183,6 +197,21 @@ const MatchingServiceMainView: React.FC = () => {
)} + {/* Add the cancel button below the loading bar */} + {loading && !matchFound && ( + <> + {/* Cancel button to remove the matching request */} + + +)} + );