Skip to content

Commit 24fe5fe

Browse files
authored
Implemented course APIs as edge functions (#349)
1 parent 2ce70b0 commit 24fe5fe

File tree

9 files changed

+414
-199
lines changed

9 files changed

+414
-199
lines changed

server/supabase/functions/_shared/utils/auth.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { supa } from "../db.ts";
2+
import { Context } from "jsr:@hono/hono";
23
import { AuthError } from "../errors.ts";
4+
import { createMiddleware } from "jsr:@hono/hono/factory";
35

46
export async function signUpByEmailPassword(
57
email: string,
@@ -40,3 +42,33 @@ export async function validateUserFromToken(token: string) {
4042

4143
return data.user;
4244
}
45+
46+
const validateUser = async (c: Context) => {
47+
const token = c.req.header("Authorization")?.split(" ")[1];
48+
if (!token) {
49+
return c.json({ error: "Unauthorized" }, 401);
50+
}
51+
try {
52+
const user = await validateUserFromToken(token);
53+
return user;
54+
} catch {
55+
return c.json({ error: "Unauthorized" }, 401);
56+
}
57+
};
58+
59+
export type UserVariables = {
60+
user: any;
61+
};
62+
63+
export const authMiddleware = createMiddleware<{
64+
Variables: UserVariables;
65+
}>(
66+
async (
67+
c: Context<{ Variables: UserVariables }>,
68+
next: () => Promise<void>,
69+
) => {
70+
const user = await validateUser(c);
71+
c.set("user", user);
72+
await next();
73+
},
74+
);
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { supa } from '../db.ts';
2+
import { PermissionError } from '../errors.ts';
3+
import { DefaultRoles, instructorRole, Permissions } from '../../../../../shared/schema/course.ts';
4+
5+
export async function isUserMemberOfCourse(user_id: string, course_id: string): Promise<boolean> {
6+
const { data, error } = await supa.from('course_members')
7+
.select('*')
8+
.eq('user_id', user_id)
9+
.eq('course_id', course_id);
10+
11+
if (error) {
12+
throw new Error(`Failed to check if user ${user_id} is a member of course ${course_id}: ${error.message}`);
13+
}
14+
15+
return data.length > 0;
16+
}
17+
18+
export async function getRoleInCourse(user_id: string, course_id: string): Promise<DefaultRoles> {
19+
const { data: courseRoleData, error: courseRoleError } = await supa.from('course_members')
20+
.select('role_id')
21+
.eq('user_id', user_id)
22+
.eq('course_id', course_id);
23+
24+
if (courseRoleError) {
25+
throw new Error(`Failed to get role for user ${user_id} in course ${course_id}: ${courseRoleError.message}`);
26+
}
27+
28+
if (!courseRoleData || courseRoleData.length !== 1) {
29+
throw new PermissionError(`User ${user_id} is not a member of course ${course_id}`);
30+
}
31+
32+
const role_id = courseRoleData[0].role_id;
33+
const { data: roleData, error: roleError } = await supa.from('account_roles')
34+
.select('name')
35+
.eq('id', role_id);
36+
37+
if (roleError) {
38+
console.error(`Failed to get role for user ${user_id} in course ${course_id}: ${roleError.message}`);
39+
}
40+
41+
if (!roleData || roleData.length !== 1) {
42+
throw new Error(`Role ${role_id} for user ${user_id} in course ${course_id} was expected but to exist but did not`);
43+
}
44+
45+
return roleData[0].name as DefaultRoles;
46+
}
47+
48+
export async function isUserInstructorInCourse(user_id: string, course_id: string): Promise<boolean> {
49+
if (!isUserMemberOfCourse(user_id, course_id)) {
50+
return false;
51+
}
52+
53+
const role = await getRoleInCourse(user_id, course_id);
54+
return role === instructorRole;
55+
}
56+
57+
export async function getUserPermissionsInCourse(user_id: string, course_id: string): Promise<Permissions> {
58+
// TODO: This likely need to be more complicated or refactored to only get the default permissions
59+
const { data: courseRoleData, error: courseRoleError } = await supa.from('course_members')
60+
.select('role_id')
61+
.eq('user_id', user_id)
62+
.eq('course_id', course_id);
63+
64+
if (courseRoleError) {
65+
throw new Error(`Failed to get role for user ${user_id} in course ${course_id}: ${courseRoleError.message}`);
66+
}
67+
68+
if (!courseRoleData || courseRoleData.length !== 1) {
69+
throw new PermissionError(`User ${user_id} is not a member of course ${course_id}`);
70+
}
71+
72+
const role_id = courseRoleData[0].role_id;
73+
const { data: roleData, error: roleError } = await supa.from('account_roles')
74+
.select('default_permissions')
75+
.eq('id', role_id);
76+
77+
if (roleError) {
78+
console.error(`Failed to get permissions for user ${user_id} in course ${course_id}: ${roleError.message}`);
79+
}
80+
81+
if (!roleData || roleData.length !== 1) {
82+
throw new Error(`Role ${role_id} for user ${user_id} in course ${course_id} was expected but to exist but did not`);
83+
}
84+
85+
return roleData[0].default_permissions as Permissions;
86+
}

server/supabase/functions/courses/controller/add_course_member_activity.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ export async function addUserToCourse(
2424
if (joinError) {
2525
throw new Error(`Failed to add user to course: ${joinError.message}`);
2626
}
27-
28-
return true;
2927
}
3028

3129
async function getCourse(course_id: string): Promise<Course> {

server/supabase/functions/courses/controller/remove_course_member_activity.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,6 @@ async function checkIfUserInCourse(user_id: string, course_id: string) {
5353
.single();
5454

5555
if (!existingUserCourse) {
56-
throw new InputValidationError(`User ${user_id} already registered in course ${course_id}`);
56+
throw new InputValidationError(`User ${user_id} not in course ${course_id}`);
5757
}
5858
}

0 commit comments

Comments
 (0)