Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions course-matrix/backend/src/controllers/eventsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,46 @@ export default {
}
}),

/**
* Get all events given a user_id and calendar_id
*/
getSharedEvents: asyncHandler(async (req: Request, res: Response) => {
try {
const { user_id, calendar_id } = req.params;

if (!user_id) {
return res.status(400).json({ error: "user_id is required" });
}
if (!calendar_id) {
return res.status(400).json({ error: "calendar_id is required" });
}

const { data: courseEvents, error: courseError } = await supabase
.schema("timetable")
.from("course_events")
.select("*")
.eq("user_id", user_id)
.eq("calendar_id", calendar_id);

const { data: userEvents, error: userError } = await supabase
.schema("timetable")
.from("user_events")
.select("*")
.eq("user_id", user_id)
.eq("calendar_id", calendar_id);

if (courseError || userError) {
return res
.status(400)
.json({ error: courseError?.message || userError?.message });
}

return res.status(200).json({ courseEvents, userEvents });
} catch (error) {
return res.status(500).send({ error });
}
}),

/**
* Update an event
* The request should provide:
Expand Down
71 changes: 58 additions & 13 deletions course-matrix/backend/src/controllers/sharesController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,22 +136,62 @@ export default {
const { data: shareData, error: sharedError } = await supabase
.schema("timetable")
.from("shared")
.select(
"id, calendar_id, owner_id, shared_id, timetables!inner(id, user_id, timetable_title, semester, favorite)",
)
.select("id, calendar_id, owner_id, shared_id, timetables!inner(*)")
.eq("shared_id", user_id);

if (sharedError) {
return res.status(400).json({ error: sharedError.message });
}

if (!shareData || shareData.length === 0) {
return res
.status(404)
.json({ error: "No shared timetables found for this user" });
return res.status(200).json(shareData);
} catch (error) {
return res.status(500).send({ error });
}
}),

/**
* Get all restrictions from a shared timetable id
* @route GET /api/shared/restrictions/:calendar_id
*/
getSharedRestrictions: asyncHandler(async (req: Request, res: Response) => {
try {
const { user_id, calendar_id } = req.query;

if (!user_id || !calendar_id) {
return res.status(400).json({
error: "User ID and Calendar ID are required",
});
}

return res.status(200).json(shareData);
//Retrieve users allowed to access the timetable
const { data: timetableData, error: timetableError } = await supabase
.schema("timetable")
.from("timetables")
.select("*")
.eq("id", calendar_id)
.eq("user_id", user_id)
.maybeSingle();

if (timetableError)
return res.status(400).json({ error: timetableError.message });

//Validate timetable validity:
if (!timetableData || timetableData.length === 0) {
return res.status(404).json({ error: "Calendar id not found" });
}

const { data: restrictionData, error: restrictionError } = await supabase
.schema("timetable")
.from("restriction")
.select()
.eq("user_id", user_id)
.eq("calendar_id", calendar_id);

if (restrictionError) {
return res.status(400).json({ error: restrictionError.message });
}

return res.status(200).json(restrictionData);
} catch (error) {
return res.status(500).send({ error });
}
Expand Down Expand Up @@ -387,15 +427,20 @@ export default {
deleteShare: asyncHandler(async (req: Request, res: Response) => {
try {
const shared_id = (req as any).user.id;
const { id } = req.params;
const { calendar_id } = req.body;
const { calendar_id, owner_id } = req.body;

if (!calendar_id || !owner_id) {
return res.status(400).json({
error: "Calendar ID and Owner ID are required",
});
}

const { data: existingTimetable, error: existingTimetableError } =
await supabase
.schema("timetable")
.from("shared")
.select("*")
.eq("id", id)
.eq("owner_id", owner_id)
.eq("calendar_id", calendar_id)
.eq("shared_id", shared_id);

Expand All @@ -412,15 +457,15 @@ export default {
.schema("timetable")
.from("shared")
.delete()
.eq("id", id)
.eq("owner_id", owner_id)
.eq("calendar_id", calendar_id)
.eq("shared_id", shared_id);
if (deleteError) {
return res.status(400).json({ error: deleteError.message });
}

return res.status(200).json({
message: `Sharing record: ${id} of calendar: ${calendar_id} deleted successfully`,
message: `Sharing record with owner_id of ${owner_id} and calendar_id of ${calendar_id} deleted successfully`,
});
} catch (error) {
return res.status(500).send({ error });
Expand Down
32 changes: 32 additions & 0 deletions course-matrix/backend/src/controllers/userController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,3 +294,35 @@ export const updateUsername = asyncHandler(
}
},
);

/**
* @route GET
* @description Gets a user's username from their user ID
*
* This endpoint:
* - Takes 1 field, the user's id
* - Calls supabase's getUsers() function
* - Responds with the user's username if the user is found
* - Responds with an error message if the user is not found
*/
export const usernameFromUserId = asyncHandler(
async (req: Request, res: Response) => {
const { user_id } = req.query;
if (!user_id) {
return res.status(400).json({ error: "User ID is required" });
}

const { data: userData, error } = await supabase.auth.admin.getUserById(
user_id as string,
);

if (error) {
return res
.status(400)
.json({ error: "Unable to get username from email" });
} else {
const username = userData?.user?.user_metadata?.username;
return res.status(200).json(username);
}
},
);
7 changes: 7 additions & 0 deletions course-matrix/backend/src/routes/authRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
resetPassword,
accountDelete,
updateUsername,
usernameFromUserId,
} from "../controllers/userController";
import { authHandler } from "../middleware/authHandler";

Expand Down Expand Up @@ -68,3 +69,9 @@ authRouter.delete("/accountDelete", accountDelete);
* @route POST /updateUsername
*/
authRouter.post("/updateUsername", authHandler, updateUsername);

/**
* Route to get the username from the user id
* @route GET /username-from-user-id
*/
authRouter.get("/username-from-user-id", authHandler, usernameFromUserId);
34 changes: 26 additions & 8 deletions course-matrix/backend/src/routes/timetableRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ timetableRouter.get(
eventController.getEvents,
);

/**
* Route to get all events in a calendar shared with the authenticated user
* @route GET /api/timetables/events/shared
* @middleware authHandler - Middleware to check if the user is authenticated.
*/
timetableRouter.get(
"/events/shared/:user_id/:calendar_id",
authHandler,
eventController.getSharedEvents,
);

/**
* Route to update an event
* @route PUT /api/timetables/events/:id
Expand Down Expand Up @@ -149,10 +160,21 @@ timetableRouter.get(

/**
* Route to get all shared entry with authenticated user
* @route GET /api/timetables/shared
* @route GET /api/timetables/shared/me
* @middleware authHandler - Middleware to check if the user is authenticated
*/
timetableRouter.get("/shared", authHandler, sharesController.getShare);
timetableRouter.get("/shared/me", authHandler, sharesController.getShare);

/**
* Route to get all the restrictions of a timetable
* @route GET /api/timetables/shared/restrictions
* @middleware authHandler - Middleware to check if the user is authenticated
*/
timetableRouter.get(
"/shared/restrictions",
authHandler,
sharesController.getSharedRestrictions,
);

/**
* Route to delete all shared entries for a timetable as timetable's owner
Expand All @@ -167,11 +189,7 @@ timetableRouter.delete(

/**
* Route to delete a single entry for the authneticate user
* @route DELETE /api/timetables/shared/:calendar_id
* @route DELETE /api/timetables/shared/me/:calendar_id
* @middleware authHandler - Middleware to check if the user is authenticated
*/
timetableRouter.delete(
"/shared/:id",
authHandler,
sharesController.deleteShare,
);
timetableRouter.delete("/shared/me", authHandler, sharesController.deleteShare);
14 changes: 14 additions & 0 deletions course-matrix/frontend/src/api/authApiSlice.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { get } from "http";
import { apiSlice } from "./baseApiSlice";
import { AUTH_URL } from "./config";

Expand Down Expand Up @@ -70,6 +71,18 @@ export const authApiSlice = apiSlice.injectEndpoints({
credentials: "include",
}),
}),
getUsernameFromUserId: builder.query<any, string | number>({
query: (user_id) => ({
url: `${AUTH_URL}/username-from-user-id`,
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json, text/plain, */*",
},
params: { user_id },
credentials: "include",
}),
}),
}),
});

Expand All @@ -80,4 +93,5 @@ export const {
useGetSessionQuery,
useAccountDeleteMutation,
useUpdateUsernameMutation,
useGetUsernameFromUserIdQuery,
} = authApiSlice;
2 changes: 2 additions & 0 deletions course-matrix/frontend/src/api/baseApiSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const apiSlice = createApi({
"Timetable",
"Event",
"Restrictions",
"Shared",
],
endpoints: () => ({}),
refetchOnMountOrArgChange: true,
});
1 change: 1 addition & 0 deletions course-matrix/frontend/src/api/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export const DEPARTMENT_URL = `${SERVER_URL}/api/departments`;
export const OFFERINGS_URL = `${SERVER_URL}/api/offerings`;
export const TIMETABLES_URL = `${SERVER_URL}/api/timetables`;
export const EVENTS_URL = `${SERVER_URL}/api/timetables/events`;
export const SHARED_URL = `${SERVER_URL}/api/timetables/shared`;
17 changes: 17 additions & 0 deletions course-matrix/frontend/src/api/eventsApiSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,22 @@ export const eventsApiSlice = apiSlice.injectEndpoints({
}),
keepUnusedDataFor: 0,
}),
getSharedEvents: builder.query<
unknown,
{ user_id: string; calendar_id: number }
>({
query: (data) => ({
url: `${EVENTS_URL}/shared/${data.user_id}/${data.calendar_id}`,
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json, text/plain, */*",
},
providesTags: ["Event"],
credentials: "include",
}),
keepUnusedDataFor: 0,
}),
updateEvent: builder.mutation({
query: (data) => ({
url: `${EVENTS_URL}/${data.id}`,
Expand Down Expand Up @@ -62,6 +78,7 @@ export const eventsApiSlice = apiSlice.injectEndpoints({
export const {
useCreateEventMutation,
useGetEventsQuery,
useGetSharedEventsQuery,
useUpdateEventMutation,
useDeleteEventMutation,
} = eventsApiSlice;
Loading