Skip to content

Commit

Permalink
Feature/uni 261 filter button (#306)
Browse files Browse the repository at this point in the history
* filter modal and tried to implement the api

* deleted file

* made the filter modal look nicer

* rearranged buttons and repsonsiveness of it

* finished the frontend of the filter button

* chore: 271 (added sitemap) & 287 (setup linters) (#294)

* Chore/uni 271 sitemap xml (#293)

* finished code to generate the sitemap (using temp data currently)

* fixed branch name

* added api to get a list of all courses and updated swagger.json to include this route

* changed name of all courses api

* fixed the inconsistent names when logging info on /courses/all

* generated sitemap in both root and frontend folder

* removed sitemap.ts from root (was moved to frontend so we could generate the xml)

* moved sitemap to public folder

---------

Co-authored-by: lightAndTangy <[email protected]>

* chore: UNI-287 Set up linter and formatter (#291)

* install prettier and add configs, uninstall eslint-plugin-prettier for backend

* move eslint to dev dependencies in frontend

* install and configure prettier and eslint-config-prettier

* install eslint for migration package

* install and configure prettier for migration package

* add lint command to package.json in migration

---------

Co-authored-by: Adrian Balbalosa <[email protected]>

* help fix pls xD

* attempt to make backend work

* save curr progress

* query works with hardcoded terms

* working draft

* trying to fix sql query (remove unsafe)

* combined search and filters together

* deleted search functions

* commented out all the search functions

* fixed canberra bug

* merge linting gh actions (#303)

chore: UNI-292 linting GitHub actions (#295)

* add ci for migration

* add 'npm run format' command to all three packages

* remove all instances of any type being used in migration

* remove logger in migration service

Co-authored-by: Adrian Balbalosa <[email protected]>

* uncommented search and fixed responsiveness and added semesters

* removed console logs

* fixed any type

* UNI-304: changed layout screen height

* UNI-304: course titles set to not break

* working filter function

* added dark mode

* added hover over buttons

* fixed type

* fix filter object type

* bugfix: load more courses & login button missing (#307)

* Bugfix/uni 304 load courses (#305)

* UNI-304: changed layout screen height

* UNI-304: course titles set to not break

---------

Co-authored-by: dylan <birds@DylanW-PC>
Co-authored-by: Dylan <[email protected]>

---------

* fixed not offered issue

---------
  • Loading branch information
Christin322 authored Jun 28, 2024
1 parent d492211 commit 5313c45
Show file tree
Hide file tree
Showing 6 changed files with 543 additions and 40 deletions.
24 changes: 24 additions & 0 deletions backend/src/controllers/course.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,30 @@ export class CourseController implements IController {
}
},
)
.get(
"/course/filter/:terms/:faculties/:searchTerm",
async (req: Request, res: Response, next: NextFunction) => {
this.logger.debug(`Received request in GET /course/filter`);
try {
const { terms, faculties, searchTerm } = req.params;

const result = await this.courseService.filterCourse(
terms,
faculties,
searchTerm,
);

return res.status(200).json(result);
} catch (err: any) {
this.logger.warn(
`An error occurred when trying to GET /course/filter ${formatError(
err,
)}`,
);
return next(err);
}
},
)
.delete(
"/cached/flush",
async (
Expand Down
161 changes: 159 additions & 2 deletions backend/src/repositories/course.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
CourseCodeSchema,
CourseSchema,
} from "../api/schemas/course.schema";
import e from "express";
import { Console } from "console";

export class CourseRepository {
constructor(private readonly prisma: PrismaClient) {}
Expand Down Expand Up @@ -184,8 +186,8 @@ export class CourseRepository {
LEFT JOIN reviews r ON c.course_code = r.course_code
WHERE c.course_code ILIKE ${searchQuery} OR c.title ILIKE ${searchQuery}
GROUP BY c.course_code
ORDER BY
CASE
ORDER BY
CASE
WHEN c.course_code ILIKE ${searchQuery} THEN 1
WHEN c.title ILIKE ${searchQuery} THEN 2
ELSE 3
Expand All @@ -196,4 +198,159 @@ export class CourseRepository {
const courses = rawCourses.map((course) => CourseSchema.parse(course));
return courses;
}

async filterCourse(
terms: string,
faculties: string,
searchTerm: string,
): Promise<Course[]> {
// default filters (all options)
let searchQuery = `%`;
let termFilters = ["0", "1", "2", "3", "-1", "-2"];
let facultyFilters = [
"%arts%",
"%business%",
"%engineering%",
"%law%",
"%medicine%",
"%science%",
"%unsw canberra%",
];

if (searchTerm !== "_") {
searchQuery = `%${searchTerm}%`;
}

// there are selected terms
if (terms !== "_") {
// 0&1&2 => ["0", "1", "2"];
termFilters = terms.split("&");
}

// there are selected faculties
if (faculties !== "_") {
// ['arts', 'law'] => `'%arts%', '%law%'`
facultyFilters = faculties.split("&").map((faculty) => `%${faculty}%`);
const index = facultyFilters.indexOf("%UNSW_Canberra%");
if (index !== -1) {
facultyFilters[index] = "%unsw canberra%";
}
}

const rawCourses = (await this.prisma.$queryRaw`
SELECT
c.course_code AS "courseCode",
c.archived,
c.attributes,
c.calendar,
c.campus,
c.description,
c.enrolment_rules AS "enrolmentRules",
c.equivalents,
c.exclusions,
c.faculty,
c.field_of_education AS "fieldOfEducation",
c.gen_ed AS "genEd",
c.level,
c.school,
c.study_level AS "studyLevel",
c.terms,
c.title,
c.uoc,
AVG(r.overall_rating) AS "overallRating",
AVG(r.manageability) AS "manageability",
AVG(r.usefulness) AS "usefulness",
AVG(r.enjoyability) AS "enjoyability",
CAST(COUNT(r.review_id) AS INT) AS "reviewCount"
FROM courses c
LEFT JOIN reviews r ON c.course_code = r.course_code
WHERE (c.course_code ILIKE ${searchQuery} OR c.title ILIKE ${searchQuery}) AND
c.terms && ${termFilters}::integer[] AND
c.faculty ILIKE ANY(${facultyFilters})
GROUP BY c.course_code
ORDER BY "reviewCount" DESC;
`) as any[];
const courses = rawCourses.map((course) => CourseSchema.parse(course));
return courses;
}

async filterNotOfferedCourses(
terms: string,
faculties: string,
searchTerm: string,
): Promise<Course[]> {
// default filters (all options)
let searchQuery = `%`;
let termFilters: number[] = [];
let facultyFilters = [
"%arts%",
"%business%",
"%engineering%",
"%law%",
"%medicine%",
"%science%",
"%unsw canberra%",
];

if (searchTerm !== "_") {
searchQuery = `%${searchTerm}%`;
}

// there are selected terms
if (terms !== "_") {
// 0&1&2 => ["0", "1", "2"];

termFilters = terms
.split("&")
.filter((term) => term !== "None")
.map((term) => parseInt(term, 10));
}

// there are selected faculties
if (faculties !== "_") {
// ['arts', 'law'] => `'%arts%', '%law%'`
facultyFilters = faculties.split("&").map((faculty) => `%${faculty}%`);
const index = facultyFilters.indexOf("%UNSW_Canberra%");
if (index !== -1) {
facultyFilters[index] = "%unsw canberra%";
}
}

const rawCourses = (await this.prisma.$queryRaw`
SELECT
c.course_code AS "courseCode",
c.archived,
c.attributes,
c.calendar,
c.campus,
c.description,
c.enrolment_rules AS "enrolmentRules",
c.equivalents,
c.exclusions,
c.faculty,
c.field_of_education AS "fieldOfEducation",
c.gen_ed AS "genEd",
c.level,
c.school,
c.study_level AS "studyLevel",
c.terms,
c.title,
c.uoc,
AVG(r.overall_rating) AS "overallRating",
AVG(r.manageability) AS "manageability",
AVG(r.usefulness) AS "usefulness",
AVG(r.enjoyability) AS "enjoyability",
CAST(COUNT(r.review_id) AS INT) AS "reviewCount"
FROM courses c
LEFT JOIN reviews r ON c.course_code = r.course_code
WHERE (c.course_code ILIKE ${searchQuery} OR c.title ILIKE ${searchQuery}) AND
(c.terms = ARRAY[]::integer[] OR c.terms && ${termFilters}::integer[]) AND
c.faculty ILIKE ANY(${facultyFilters})
GROUP BY c.course_code
ORDER BY "reviewCount" DESC;
`) as any[];
const courses = rawCourses.map((course) => CourseSchema.parse(course));

return courses;
}
}
42 changes: 42 additions & 0 deletions backend/src/services/course.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,48 @@ export class CourseService {
return { courses };
}

async filterCourse(
terms: string,
faculties: string,
searchTerm: string,
): Promise<CoursesSuccessResponse | undefined> {
let courses = await this.redis.get<Course[]>(
`filterCourses:${terms}&${faculties}&${searchTerm}`,
);
if (!courses) {
this.logger.info(
`Cache miss on filterCourses:${terms}&${faculties}&${searchTerm}`,
);

if (terms.includes("None")) {
// filters for not offered courses
courses = await this.courseRepository.filterNotOfferedCourses(
terms,
faculties,
searchTerm,
);
} else {
courses = await this.courseRepository.filterCourse(
terms,
faculties,
searchTerm,
);
}

await this.redis.set(
`filterCourses:${terms}&${faculties}&${searchTerm}`,
courses,
);
} else {
this.logger.info(
`Cache hit on filterCourses:${terms}&${faculties}&${searchTerm}`,
);
}

this.logger.info(`Found ${courses.length} courses.`);
return { courses };
}

async flushKey(zid: string, key: string) {
const userInfo = await this.userRepository.getUser(zid);
if (!userInfo) {
Expand Down
Loading

0 comments on commit 5313c45

Please sign in to comment.