Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 22 additions & 0 deletions .github/workflows/workflow.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Format the code

on:
push:

jobs:
format:
runs-on: ubuntu-latest
name: Format Files
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "20"
- name: Prettier
run: npx prettier --write **/*.{js,ts,tsx,json,md}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: stefanzweifel/git-auto-commit-action@v4
if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
with:
commit_message: "Auto-formatted the code using Prettier"
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ The `DATABASE_URL` variable should contain your Supabase project url and the `DA
VITE_SERVER_URL="http://localhost:8081"
```


### Running the Application

To run the application locally:
Expand Down
16 changes: 8 additions & 8 deletions course-matrix/backend/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { config } from "dotenv";
const configFile = `./.env`;
config({ path: configFile });

const {PORT, NODE_ENV, CLIENT_APP_URL, DATABASE_URL, DATABASE_KEY } =
process.env;
const { PORT, NODE_ENV, CLIENT_APP_URL, DATABASE_URL, DATABASE_KEY } =
process.env;

export default {
PORT,
env: NODE_ENV,
CLIENT_APP_URL,
DATABASE_URL,
DATABASE_KEY,
};
PORT,
env: NODE_ENV,
CLIENT_APP_URL,
DATABASE_URL,
DATABASE_KEY,
};
24 changes: 12 additions & 12 deletions course-matrix/backend/src/config/swaggerOptions.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
export const swaggerOptions = {
swaggerDefinition: {
openapi: '3.0.0',
info: {
title: 'Application API',
description: 'Application API Information',
version: "v1"
openapi: "3.0.0",
info: {
title: "Application API",
description: "Application API Information",
version: "v1",
},
servers: [
{
url: "http://localhost:8081",
},
servers: [
{
url: "http://localhost:8081"
}
],
],
},
apis: ['./src/routes/*.ts']
}
apis: ["./src/routes/*.ts"],
};
57 changes: 28 additions & 29 deletions course-matrix/backend/src/controllers/authentication.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,36 @@
import {Request, Response} from 'express';
import { Request, Response } from "express";

import {supabase} from '../db/setupDb';
import asyncHandler from '../middleware/asyncHandler';
import { supabase } from "../db/setupDb";
import asyncHandler from "../middleware/asyncHandler";

export const handleAuthCode =
asyncHandler(async (req: Request, res: Response) => {
try {
const token_hash = req.query.token_hash as string;
const type = req.query.type as string;
const next = (req.query.next as string) ?? '/';

if (!token_hash || !type) {
return res.status(400).json({error: 'Missing token or type'});
}

if (token_hash && type) {
const {data, error} = await supabase.auth.verifyOtp({
type: 'email',
token_hash,
});
export const handleAuthCode = asyncHandler(
async (req: Request, res: Response) => {
try {
const token_hash = req.query.token_hash as string;
const type = req.query.type as string;
const next = (req.query.next as string) ?? "/";

if (!token_hash || !type) {
return res.status(400).json({ error: "Missing token or type" });
}

if (token_hash && type) {
const { data, error } = await supabase.auth.verifyOtp({
type: "email",
token_hash,
});

if (!error) {
console.log('Authentication successful');
if (!error) {
console.log("Authentication successful");

return res.redirect(303, decodeURIComponent(next));
}
return res.redirect(303, decodeURIComponent(next));
}
// Redirect to an error page if verification fails or parameters are
// missing
return res.redirect(303, '/auth/auth-code-error');
} catch (error) {
return res.status(500).json({error: 'Internal Server Error'});
}
});
// Redirect to an error page if verification fails or parameters are
// missing
return res.redirect(303, "/auth/auth-code-error");
} catch (error) {
return res.status(500).json({ error: "Internal Server Error" });
}
},
);
154 changes: 84 additions & 70 deletions course-matrix/backend/src/controllers/coursesController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,81 +2,95 @@ import { Request, Response } from "express";
import asyncHandler from "../middleware/asyncHandler";
import { supabaseCourseClient } from "../db/setupDb";

const DEFAULT_COURSE_LIMIT = 1000
const DEFAULT_COURSE_LIMIT = 1000;

export default {
getCourses: asyncHandler(async (req: Request, res: Response) => {
try {
// Get the query parameters
const { limit, search, semester, breadthRequirement, creditWeight, department, yearLevel } = req.query;
getCourses: asyncHandler(async (req: Request, res: Response) => {
try {
// Get the query parameters
const {
limit,
search,
semester,
breadthRequirement,
creditWeight,
department,
yearLevel,
} = req.query;

// Query the courses, offerings tables from the database
let coursesQuery = supabaseCourseClient
.from("courses")
.select()
.limit(Number(limit || DEFAULT_COURSE_LIMIT));

if ((search as string)?.trim()) {
coursesQuery = coursesQuery.or(`code.ilike.%${search}%,name.ilike.%${search}%`);
}
let offeringsQuery = supabaseCourseClient.from("offerings").select();
// Query the courses, offerings tables from the database
let coursesQuery = supabaseCourseClient
.from("courses")
.select()
.limit(Number(limit || DEFAULT_COURSE_LIMIT));

// Get the data and errors from the queries
const { data: coursesData, error: coursesError } = await coursesQuery;
const { data: offeringsData, error: offeringsError } = await offeringsQuery;
if ((search as string)?.trim()) {
coursesQuery = coursesQuery.or(
`code.ilike.%${search}%,name.ilike.%${search}%`,
);
}
let offeringsQuery = supabaseCourseClient.from("offerings").select();

// Set the courses and offerings data
const courses = coursesData || [];
const offerings = offeringsData || [];
// Get the data and errors from the queries
const { data: coursesData, error: coursesError } = await coursesQuery;
const { data: offeringsData, error: offeringsError } =
await offeringsQuery;

// Create a map of course codes to semesters.
const courseCodesToSemestersMap: { [key: string]: string[] } = {};
offerings.forEach((offering) => {
const courseCode = offering.code;
const semester = offering.offering;
if (courseCodesToSemestersMap[courseCode]) {
courseCodesToSemestersMap[courseCode].push(semester);
} else {
courseCodesToSemestersMap[courseCode] = [semester];
}
});

// Filter the courses based on the breadth requirement, credit weight, semester, department, and year level
let filteredCourses = courses;
if (breadthRequirement) {
filteredCourses = filteredCourses.filter((course) => {
return course.breadth_requirement === breadthRequirement;
});
}
if (creditWeight) {
filteredCourses = filteredCourses.filter((course) => {
const courseCreditWeight =
course.code[course.code.length - 2] === "H" ? 0.5 : 1;
return courseCreditWeight === Number(creditWeight);
});
}
if (semester) {
filteredCourses = filteredCourses.filter((course) => {
return courseCodesToSemestersMap[course.code]?.includes(semester as string);
});
}
if (department) {
filteredCourses = filteredCourses.filter((course) => {
const courseDepartment = course.code.substring(0, 3);
return courseDepartment === department;
});
}
if (yearLevel) {
filteredCourses = filteredCourses.filter((course) => {
const courseYearLevel = course.code.charCodeAt(3) - 'A'.charCodeAt(0) + 1;
return courseYearLevel === Number(yearLevel);
});
}
// Set the courses and offerings data
const courses = coursesData || [];
const offerings = offeringsData || [];

// Return the filtered courses
return res.status(200).send(filteredCourses);
} catch (err) {
return res.status(500).send({ err });
}
}),
// Create a map of course codes to semesters.
const courseCodesToSemestersMap: { [key: string]: string[] } = {};
offerings.forEach((offering) => {
const courseCode = offering.code;
const semester = offering.offering;
if (courseCodesToSemestersMap[courseCode]) {
courseCodesToSemestersMap[courseCode].push(semester);
} else {
courseCodesToSemestersMap[courseCode] = [semester];
}
});

// Filter the courses based on the breadth requirement, credit weight, semester, department, and year level
let filteredCourses = courses;
if (breadthRequirement) {
filteredCourses = filteredCourses.filter((course) => {
return course.breadth_requirement === breadthRequirement;
});
}
if (creditWeight) {
filteredCourses = filteredCourses.filter((course) => {
const courseCreditWeight =
course.code[course.code.length - 2] === "H" ? 0.5 : 1;
return courseCreditWeight === Number(creditWeight);
});
}
if (semester) {
filteredCourses = filteredCourses.filter((course) => {
return courseCodesToSemestersMap[course.code]?.includes(
semester as string,
);
});
}
if (department) {
filteredCourses = filteredCourses.filter((course) => {
const courseDepartment = course.code.substring(0, 3);
return courseDepartment === department;
});
}
if (yearLevel) {
filteredCourses = filteredCourses.filter((course) => {
const courseYearLevel =
course.code.charCodeAt(3) - "A".charCodeAt(0) + 1;
return courseYearLevel === Number(yearLevel);
});
}

// Return the filtered courses
return res.status(200).send(filteredCourses);
} catch (err) {
return res.status(500).send({ err });
}
}),
};
16 changes: 8 additions & 8 deletions course-matrix/backend/src/controllers/departmentsController.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import {Request, Response} from 'express';
import { Request, Response } from "express";

import {supabaseCourseClient} from '../db/setupDb';
import asyncHandler from '../middleware/asyncHandler';
import { supabaseCourseClient } from "../db/setupDb";
import asyncHandler from "../middleware/asyncHandler";

export default {
getDepartments: asyncHandler(async (req: Request, res: Response) => {
try {
// Query the departments table from the database
let departmentsQuery = supabaseCourseClient.from('departments').select();
let departmentsQuery = supabaseCourseClient.from("departments").select();

// Get the data and errors from the query
const {data: departmentsData, error: departmentsError} =
await departmentsQuery;
const { data: departmentsData, error: departmentsError } =
await departmentsQuery;

// Set the departments data
const departments = departmentsData || [];
Expand All @@ -20,7 +20,7 @@ export default {
res.status(200).json(departments);
} catch (error) {
console.error(error);
res.status(500).json({message: 'Internal Server Error'});
res.status(500).json({ message: "Internal Server Error" });
}
}),
}
};
39 changes: 20 additions & 19 deletions course-matrix/backend/src/controllers/offeringsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,26 @@ import asyncHandler from "../middleware/asyncHandler";
import { supabaseCourseClient } from "../db/setupDb";

export default {
getOfferings: asyncHandler(async (req: Request, res: Response) => {
try {
const { course_code, semester } = req.query;

let offeringsQuery = supabaseCourseClient
.from("offerings")
.select()
.eq("code", course_code)
.eq("offering", semester);
getOfferings: asyncHandler(async (req: Request, res: Response) => {
try {
const { course_code, semester } = req.query;

// Get the data and errors from the query
const { data: offeringsData, error: offeringsError } = await offeringsQuery;
let offeringsQuery = supabaseCourseClient
.from("offerings")
.select()
.eq("code", course_code)
.eq("offering", semester);

const offerings = offeringsData || [];
// Get the data and errors from the query
const { data: offeringsData, error: offeringsError } =
await offeringsQuery;

res.status(200).json(offerings);
} catch (error) {
console.error(error);
res.status(500).json({ message: "Internal Server Error" });
}
}),
}
const offerings = offeringsData || [];

res.status(200).json(offerings);
} catch (error) {
console.error(error);
res.status(500).json({ message: "Internal Server Error" });
}
}),
};
Loading