Skip to content

Commit c917346

Browse files
authored
Merge pull request #178 from CS3219-AY2425S1/feat/matching/backend-integration
Frontend-Backend matching integration
2 parents c13d229 + 3518f5b commit c917346

File tree

15 files changed

+554
-151
lines changed

15 files changed

+554
-151
lines changed

frontend/components/matching/find-match.tsx

Lines changed: 147 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,20 @@ import { MatchForm } from "@/components/matching/matching-form";
44
import { SearchProgress } from "@/components/matching/search-progress";
55
import { SelectionSummary } from "@/components/matching/selection-summary";
66
import { useToast } from "@/components/hooks/use-toast";
7+
import { useAuth } from "@/app/auth/auth-context";
8+
import { joinMatchQueue } from "@/lib/join-match-queue";
9+
import { leaveMatchQueue } from "@/lib/leave-match-queue";
10+
import { subscribeMatch } from "@/lib/subscribe-match";
711

812
export default function FindMatch() {
913
const [selectedDifficulty, setSelectedDifficulty] = useState<string>("");
1014
const [selectedTopic, setSelectedTopic] = useState<string>("");
1115
const [isSearching, setIsSearching] = useState<boolean>(false);
1216
const [waitTime, setWaitTime] = useState<number>(0);
1317
const { toast } = useToast();
18+
const auth = useAuth();
19+
20+
const waitTimeout = 60000;
1421

1522
useEffect(() => {
1623
let interval: NodeJS.Timeout | undefined;
@@ -21,24 +28,155 @@ export default function FindMatch() {
2128
} else {
2229
setWaitTime(0);
2330
}
24-
return () => clearInterval(interval);
31+
32+
return () => {
33+
clearInterval(interval);
34+
};
2535
}, [isSearching]);
2636

27-
const handleSearch = () => {
28-
if (selectedDifficulty && selectedTopic) {
29-
setIsSearching(true);
30-
} else {
37+
const handleSearch = async () => {
38+
if (!selectedDifficulty || !selectedTopic) {
3139
toast({
3240
title: "Invalid Selection",
3341
description: "Please select both a difficulty level and a topic",
3442
variant: "destructive",
3543
});
44+
return;
45+
}
46+
47+
if (!auth || !auth.token) {
48+
toast({
49+
title: "Access denied",
50+
description: "No authentication token found",
51+
variant: "destructive",
52+
});
53+
return;
54+
}
55+
56+
if (!auth.user) {
57+
toast({
58+
title: "Access denied",
59+
description: "Not logged in",
60+
variant: "destructive",
61+
});
62+
return;
63+
}
64+
65+
const response = await joinMatchQueue(
66+
auth.token,
67+
auth?.user?.id,
68+
selectedTopic,
69+
selectedDifficulty
70+
);
71+
switch (response.status) {
72+
case 201:
73+
toast({
74+
title: "Matched",
75+
description: "Successfully matched",
76+
variant: "success",
77+
});
78+
return;
79+
case 202:
80+
case 304:
81+
setIsSearching(true);
82+
const ws = await subscribeMatch(
83+
auth?.user.id,
84+
selectedTopic,
85+
selectedDifficulty
86+
);
87+
const queueTimeout = setTimeout(() => {
88+
handleCancel(true);
89+
}, waitTimeout);
90+
ws.onmessage = () => {
91+
setIsSearching(false);
92+
clearTimeout(queueTimeout);
93+
toast({
94+
title: "Matched",
95+
description: "Successfully matched",
96+
variant: "success",
97+
});
98+
ws.onclose = () => null;
99+
};
100+
ws.onclose = () => {
101+
setIsSearching(false);
102+
clearTimeout(queueTimeout);
103+
toast({
104+
title: "Matching Stopped",
105+
description: "Matching has been stopped",
106+
variant: "destructive",
107+
});
108+
};
109+
return;
110+
default:
111+
toast({
112+
title: "Unknown Error",
113+
description: "An unexpected error has occured",
114+
variant: "destructive",
115+
});
116+
return;
36117
}
37118
};
38119

39-
const handleCancel = () => {
40-
setIsSearching(false);
41-
setWaitTime(0);
120+
const handleCancel = async (timedOut: boolean) => {
121+
if (!selectedDifficulty || !selectedTopic) {
122+
toast({
123+
title: "Invalid Selection",
124+
description: "Please select both a difficulty level and a topic",
125+
variant: "destructive",
126+
});
127+
return;
128+
}
129+
130+
if (!auth || !auth.token) {
131+
toast({
132+
title: "Access denied",
133+
description: "No authentication token found",
134+
variant: "destructive",
135+
});
136+
return;
137+
}
138+
139+
if (!auth.user) {
140+
toast({
141+
title: "Access denied",
142+
description: "Not logged in",
143+
variant: "destructive",
144+
});
145+
return;
146+
}
147+
148+
const response = await leaveMatchQueue(
149+
auth.token,
150+
auth.user?.id,
151+
selectedTopic,
152+
selectedDifficulty
153+
);
154+
switch (response.status) {
155+
case 200:
156+
setIsSearching(false);
157+
setWaitTime(0);
158+
if (timedOut) {
159+
toast({
160+
title: "Timed Out",
161+
description: "Matching has been stopped",
162+
variant: "destructive",
163+
});
164+
} else {
165+
toast({
166+
title: "Matching Stopped",
167+
description: "Matching has been stopped",
168+
variant: "destructive",
169+
});
170+
}
171+
return;
172+
default:
173+
toast({
174+
title: "Unknown Error",
175+
description: "An unexpected error has occured",
176+
variant: "destructive",
177+
});
178+
return;
179+
}
42180
};
43181

44182
return (
@@ -50,7 +188,7 @@ export default function FindMatch() {
50188
setSelectedTopic={setSelectedTopic}
51189
handleSearch={handleSearch}
52190
isSearching={isSearching}
53-
handleCancel={handleCancel}
191+
handleCancel={() => handleCancel(false)}
54192
/>
55193

56194
{isSearching && <SearchProgress waitTime={waitTime} />}

frontend/lib/api-uri.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
const constructUri = (baseUri: string, port: string | undefined) =>
22
`http://${process.env.NEXT_PUBLIC_BASE_URI || baseUri}:${port}`;
33

4+
const constructWebSockUri = (baseUri: string, port: string | undefined) =>
5+
`ws://${process.env.NEXT_PUBLIC_BASE_URI || baseUri}:${port}`;
6+
47
export const userServiceUri: (baseUri: string) => string = (baseUri) =>
58
constructUri(baseUri, process.env.NEXT_PUBLIC_USER_SVC_PORT);
69
export const questionServiceUri: (baseUri: string) => string = (baseUri) =>
710
constructUri(baseUri, process.env.NEXT_PUBLIC_QUESTION_SVC_PORT);
811
export const matchingServiceUri: (baseUri: string) => string = (baseUri) =>
912
constructUri(baseUri, process.env.NEXT_PUBLIC_MATCHING_SVC_PORT);
13+
14+
export const matchingServiceWebSockUri: (baseUri: string) => string = (
15+
baseUri
16+
) => constructWebSockUri(baseUri, process.env.NEXT_PUBLIC_MATCHING_SVC_PORT);

frontend/lib/join-match-queue.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { matchingServiceUri } from "@/lib/api-uri";
2+
3+
export const joinMatchQueue = async (
4+
jwtToken: string,
5+
userId: string,
6+
category: string,
7+
complexity: string
8+
) => {
9+
const params = new URLSearchParams({
10+
topic: category,
11+
difficulty: complexity,
12+
}).toString();
13+
const response = await fetch(
14+
`${matchingServiceUri(window.location.hostname)}/match/queue/${userId}?${params}`,
15+
{
16+
method: "POST",
17+
headers: {
18+
Authorization: `Bearer ${jwtToken}`,
19+
"Content-Type": "application/json",
20+
},
21+
}
22+
);
23+
return response;
24+
};

frontend/lib/leave-match-queue.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { matchingServiceUri } from "@/lib/api-uri";
2+
3+
export const leaveMatchQueue = async (
4+
jwtToken: string,
5+
userId: string,
6+
category: string,
7+
complexity: string
8+
) => {
9+
const params = new URLSearchParams({
10+
topic: category,
11+
difficulty: complexity,
12+
}).toString();
13+
const response = await fetch(
14+
`${matchingServiceUri(window.location.hostname)}/match/queue/${userId}?${params}`,
15+
{
16+
method: "DELETE",
17+
headers: {
18+
Authorization: `Bearer ${jwtToken}`,
19+
"Content-Type": "application/json",
20+
},
21+
}
22+
);
23+
return response;
24+
};

frontend/lib/subscribe-match.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { matchingServiceWebSockUri } from "@/lib/api-uri";
2+
3+
export const subscribeMatch = async (
4+
userId: string,
5+
category: string,
6+
complexity: string
7+
) => {
8+
const params = new URLSearchParams({
9+
topic: category,
10+
difficulty: complexity,
11+
});
12+
return new WebSocket(
13+
`${matchingServiceWebSockUri(window.location.hostname)}/match/subscribe/${userId}?${params}`
14+
);
15+
};

frontend/package-lock.json

Lines changed: 71 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class NoExistingConnectionException(Exception):
2+
pass

matching-service/app/logger.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import logging
2+
import sys
3+
4+
logger = logging.getLogger(__name__)
5+
logger.setLevel(logging.DEBUG)
6+
stream_handler = logging.StreamHandler(sys.stdout)
7+
log_formatter = logging.Formatter("%(levelname)s: %(message)s")
8+
stream_handler.setFormatter(log_formatter)
9+
logger.addHandler(stream_handler)

0 commit comments

Comments
 (0)