Skip to content

Commit ab79531

Browse files
thomasyzy7kevin-lannAustin-Xminhhaitran08MasahisaSekita
authored
Release/1.5 (#138)
Co-authored-by: Kevin Lan <[email protected]> Co-authored-by: Austin-X <[email protected]> Co-authored-by: minhhaitran08 <[email protected]> Co-authored-by: Masahisa Sekita <[email protected]> Co-authored-by: Austin-X <[email protected]> Co-authored-by: dawangk <[email protected]> Co-authored-by: kevin-lann <[email protected]> Co-authored-by: MasahisaSekita <[email protected]> Co-authored-by: Dmitriy Prokopchuk <[email protected]>
1 parent 21f2214 commit ab79531

37 files changed

+1320
-517
lines changed

.github/workflows/workflow.yml

Lines changed: 0 additions & 22 deletions
This file was deleted.

course-matrix/backend/__tests__/timetablesController.test.ts

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -320,27 +320,6 @@ describe("PUT /api/timetables/:id", () => {
320320
beforeEach(() => {
321321
jest.clearAllMocks();
322322
});
323-
test("should return error code 400 and message 'New timetable title or semester or updated favorite status is required when updating a timetable' if request body is empty", async () => {
324-
// Make sure the test user is authenticated
325-
const user_id = "testuser04-f84fd0da-d775-4424-ad88-d9675282453c";
326-
const timetableData = {};
327-
328-
// Mock authHandler to simulate the user being logged in
329-
(
330-
authHandler as jest.MockedFunction<typeof authHandler>
331-
).mockImplementationOnce(mockAuthHandler(user_id));
332-
333-
const response = await request(app)
334-
.put("/api/timetables/1")
335-
.send(timetableData);
336-
337-
// Check that the `update` method was called
338-
expect(response.statusCode).toBe(400);
339-
expect(response.body).toEqual({
340-
error:
341-
"New timetable title or semester or updated favorite status or email notifications enabled is required when updating a timetable",
342-
});
343-
});
344323

345324
test("should update the timetable successfully", async () => {
346325
// Make sure the test user is authenticated
@@ -361,7 +340,7 @@ describe("PUT /api/timetables/:id", () => {
361340

362341
// Check that the `update` method was called
363342
expect(response.statusCode).toBe(200);
364-
expect(response.body).toEqual({
343+
expect(response.body).toMatchObject({
365344
timetable_title: "Updated Title",
366345
semester: "Spring 2025",
367346
});

course-matrix/backend/src/constants/availableFunctions.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ export const availableFunctions: AvailableFunctions = {
4141
try {
4242
// Retrieve user_id
4343
const user_id = (req as any).user.id;
44-
4544
// Retrieve user timetable item based on user_id
4645
let timeTableQuery = supabase
4746
.schema("timetable")
@@ -63,7 +62,11 @@ export const availableFunctions: AvailableFunctions = {
6362
};
6463
}
6564

66-
return { status: 200, data: timetableData };
65+
return {
66+
status: 200,
67+
timetableCount: timetableData.length,
68+
data: timetableData,
69+
};
6770
} catch (error) {
6871
console.log(error);
6972
return { status: 400, error: error };
@@ -113,6 +116,7 @@ export const availableFunctions: AvailableFunctions = {
113116
}
114117

115118
let updateData: any = {};
119+
updateData.updated_at = new Date().toISOString();
116120
if (timetable_title) updateData.timetable_title = timetable_title;
117121
if (semester) updateData.semester = semester;
118122

@@ -198,13 +202,33 @@ export const availableFunctions: AvailableFunctions = {
198202
try {
199203
// Extract event details and course information from the request
200204
const { name, semester, courses, restrictions } = args;
205+
// Get user id from session authentication to insert in the user_id col
206+
const user_id = (req as any).user.id;
207+
201208
if (name.length > 50) {
202209
return {
203210
status: 400,
204211
error: "timetable title is over 50 characters long",
205212
};
206213
}
207214

215+
// Timetables cannot exceed the size of 25.
216+
const { count: timetable_count, error: timetableCountError } =
217+
await supabase
218+
.schema("timetable")
219+
.from("timetables")
220+
.select("*", { count: "exact", head: true })
221+
.eq("user_id", user_id);
222+
223+
console.log(timetable_count);
224+
225+
if ((timetable_count ?? 0) >= 25) {
226+
return {
227+
status: 400,
228+
error: "You have exceeded the limit of 25 timetables",
229+
};
230+
}
231+
208232
const courseOfferingsList: OfferingList[] = [];
209233
const validCourseOfferingsList: GroupedOfferingList[] = [];
210234
const maxdays = getMaxDays(restrictions);
@@ -277,9 +301,6 @@ export const availableFunctions: AvailableFunctions = {
277301

278302
// ------ CREATE FLOW ------
279303

280-
// Get user id from session authentication to insert in the user_id col
281-
const user_id = (req as any).user.id;
282-
283304
// Retrieve timetable title
284305
const schedule = trim(validSchedules)[0];
285306
if (!name || !semester) {

course-matrix/backend/src/controllers/aiController.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,6 @@ export const chat = asyncHandler(async (req: Request, res: Response) => {
244244
- Allowing natural language queries about courses, offerings, and academic programs
245245
- Providing personalized recommendations based on degree requirements and course availability
246246
- Creating, reading, updating, and deleting user timetables based on natural language
247-
248247
## Your Capabilities
249248
- Create new timetables based on provided courses and restrictions
250249
- Update timetable names and semesters
@@ -266,11 +265,12 @@ export const chat = asyncHandler(async (req: Request, res: Response) => {
266265
}/dashboard/timetable?edit=[[TIMETABLE_ID]] , where TIMETABLE_ID is the id of the respective timetable.
267266
- If the user provides a course code of length 6 like CSCA08, then assume they mean CSCA08H3 (H3 appended)
268267
- If the user wants to create a timetable:
269-
1. First call getCourses to get course information on the requested courses,
270-
2. If the user provided a semester, then call getOfferings with the provided courses and semester to ensure the courses are actually offered in the semester.
268+
1. First call getTimetables to refetch most recent info on timetable names + timetableCount. DO NOT assume timetable names and # of timetables is same since last query.
269+
2. Then call getCourses to get course information on the requested courses,
270+
3. If the user provided a semester, then call getOfferings with the provided courses and semester to ensure the courses are actually offered in the semester.
271271
a) If a course is NOT returned by getOFferings, then list it under "Excluded courses" with "reason: not offered in [provided semester]"
272272
b) If no courses have offerings, then do not generate the timetable.
273-
3. Lastly, call generateTimetable with the provided information.
273+
4. Lastly, call generateTimetable with the provided information.
274274
- Do not make up fake courses or offerings.
275275
- If a user asks about a course that you do not know of, acknowledge this.
276276
- You can only edit title of the timetable, nothing else. If a user tries to edit something else, acknowledge this limitation.
@@ -280,12 +280,14 @@ export const chat = asyncHandler(async (req: Request, res: Response) => {
280280
- After a deletion has been cancelled, /timetable confirm will do nothing. If the user wants to delete again after cancelling, they must specify so.
281281
- Do not create multiple timetables for a single user query. Each user query can create at most 1 timetable
282282
- If you try to update or create a timetable but you get an error saying a timetable with the same name already exists, then ask the user to rename
283+
- It is possible for users to delete timetables / update them manually. For this reason, always refetch getTimtables before creation, to get the latest names and timetable count. Do not assume the timetables are the same since the last query.
284+
- If a user asks for the timetable count, always refetch getTimetables. Assume this count could have changed between user queries.
283285
`,
284286
messages,
285287
tools: {
286288
getTimetables: tool({
287289
description:
288-
"Get all the timetables of the currently logged in user.",
290+
"Get all the timetables of the currently logged in user AND the number of timetables of the currently logged in user",
289291
parameters: z.object({}),
290292
execute: async (args) => {
291293
return await availableFunctions.getTimetables(args, req);

course-matrix/backend/src/controllers/timetablesController.ts

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,21 @@ export default {
3131
.status(400)
3232
.json({ error: "Timetable Title cannot be over 50 characters long" });
3333
}
34+
// Timetables cannot exceed the size of 25.
35+
const { count: timetable_count, error: timetableCountError } =
36+
await supabase
37+
.schema("timetable")
38+
.from("timetables")
39+
.select("*", { count: "exact", head: true })
40+
.eq("user_id", user_id);
41+
42+
console.log(timetable_count);
43+
44+
if ((timetable_count ?? 0) >= 25) {
45+
return res
46+
.status(400)
47+
.json({ error: "You have exceeded the limit of 25 timetables" });
48+
}
3449

3550
// Check if a timetable with the same title already exist for this user
3651
const { data: existingTimetable, error: existingTimetableError } =
@@ -163,17 +178,6 @@ export default {
163178
favorite,
164179
email_notifications_enabled,
165180
} = req.body;
166-
if (
167-
!timetable_title &&
168-
!semester &&
169-
favorite === undefined &&
170-
email_notifications_enabled === undefined
171-
) {
172-
return res.status(400).json({
173-
error:
174-
"New timetable title or semester or updated favorite status or email notifications enabled is required when updating a timetable",
175-
});
176-
}
177181

178182
// Timetables cannot be longer than 50 characters.
179183
if (timetable_title && timetable_title.length > 50) {
@@ -184,7 +188,6 @@ export default {
184188

185189
//Retrieve the authenticated user
186190
const user_id = (req as any).user.id;
187-
188191
//Retrieve users allowed to access the timetable
189192
const { data: timetableUserData, error: timetableUserError } =
190193
await supabase
@@ -194,15 +197,13 @@ export default {
194197
.eq("user_id", user_id)
195198
.eq("id", id)
196199
.maybeSingle();
197-
198200
if (timetableUserError)
199201
return res.status(400).json({ error: timetableUserError.message });
200202

201203
//Validate timetable validity:
202204
if (!timetableUserData || timetableUserData.length === 0) {
203205
return res.status(404).json({ error: "Calendar id not found" });
204206
}
205-
206207
// Check if another timetable with the same title already exist for this user
207208
const { data: existingTimetable, error: existingTimetableError } =
208209
await supabase
@@ -213,7 +214,6 @@ export default {
213214
.eq("timetable_title", timetable_title)
214215
.neq("id", id)
215216
.maybeSingle();
216-
217217
if (existingTimetableError) {
218218
return res.status(400).json({ error: existingTimetableError.message });
219219
}
@@ -223,8 +223,8 @@ export default {
223223
.status(400)
224224
.json({ error: "Another timetable with this title already exists" });
225225
}
226-
227226
let updateData: any = {};
227+
updateData.updated_at = new Date().toISOString();
228228
if (timetable_title) updateData.timetable_title = timetable_title;
229229
if (semester) updateData.semester = semester;
230230
if (favorite !== undefined) updateData.favorite = favorite;
@@ -240,13 +240,11 @@ export default {
240240
.eq("id", id)
241241
.select()
242242
.single();
243-
244243
const { data: timetableData, error: timetableError } =
245244
await updateTimetableQuery;
246245

247246
if (timetableError)
248247
return res.status(400).json({ error: timetableError.message });
249-
250248
// If no records were updated due to non-existence timetable or it doesn't belong to the user.
251249
if (!timetableData || timetableData.length === 0) {
252250
return res.status(404).json({
@@ -255,6 +253,7 @@ export default {
255253
}
256254
return res.status(200).json(timetableData);
257255
} catch (error) {
256+
console.error(error);
258257
return res.status(500).send({ error });
259258
}
260259
}),
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { describe, expect, it, test } from "@jest/globals";
2+
3+
import { convertTimestampToLocaleTime } from "../src/utils/convert-timestamp-to-locale-time";
4+
5+
describe("convertTimestampToLocaleTime", () => {
6+
test("should convert a valid timestamp string to a locale time string", () => {
7+
const timestamp = "2025-03-28T12:00:00Z";
8+
const result = convertTimestampToLocaleTime(timestamp);
9+
expect(typeof result).toBe("string");
10+
expect(result.length).toBeGreaterThan(0); // Ensures it returns a non-empty string
11+
});
12+
13+
test("should convert a valid numeric timestamp to a locale time string", () => {
14+
const timestamp = 1711622400000; // Equivalent to 2025-03-28T12:00:00Z
15+
// in milliseconds
16+
const result = convertTimestampToLocaleTime(timestamp);
17+
expect(typeof result).toBe("string");
18+
expect(result.length).toBeGreaterThan(0);
19+
});
20+
21+
test("convert to locale time date is different", () => {
22+
const timestamp = "2025-03-28 02:33:02.589Z";
23+
const result = convertTimestampToLocaleTime(timestamp);
24+
expect(typeof result).toBe("string");
25+
expect(result.length).toBeGreaterThan(0);
26+
});
27+
28+
test("should return 'Invalid Date' for an invalid timestamp", () => {
29+
const timestamp = "invalid";
30+
const result = convertTimestampToLocaleTime(timestamp);
31+
expect(result).toBe("Invalid Date");
32+
});
33+
});

course-matrix/frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8" />
5-
<link rel="icon" type="image/png" href="/img/logo.png" />
5+
<link rel="icon" type="image/png" href="/img/course-matrix-logo.png" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<title>Course Matrix </title>
88
</head>

course-matrix/frontend/package-lock.json

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

course-matrix/frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"@radix-ui/react-separator": "^1.1.2",
3131
"@radix-ui/react-slot": "^1.1.2",
3232
"@radix-ui/react-switch": "^1.1.3",
33+
"@radix-ui/react-toast": "^1.2.6",
3334
"@radix-ui/react-tooltip": "^1.1.8",
3435
"@reduxjs/toolkit": "^2.5.1",
3536
"@schedule-x/drag-and-drop": "^2.21.1",
14.1 KB
Loading

0 commit comments

Comments
 (0)