diff --git a/backend/src/controllers/timetable/deleteTimetable.ts b/backend/src/controllers/timetable/deleteTimetable.ts index 1de51a97..314e27e7 100644 --- a/backend/src/controllers/timetable/deleteTimetable.ts +++ b/backend/src/controllers/timetable/deleteTimetable.ts @@ -2,11 +2,11 @@ import "dotenv/config"; import { Request, Response } from "express"; import { z } from "zod"; import { timetableIDType } from "../../../../lib/src/index.js"; -import { env } from "../../config/server.js"; import { Timetable, User } from "../../entity/entities.js"; import { validate } from "../../middleware/zodValidateRequest.js"; import { timetableRepository } from "../../repositories/timetableRepository.js"; import { userRepository } from "../../repositories/userRepository.js"; +import { removeTimetable } from "../../utils/search.js"; import sqids, { validSqid } from "../../utils/sqids.js"; const dataSchema = z.object({ @@ -74,27 +74,8 @@ export const deleteTimetable = async (req: Request, res: Response) => { res.status(500).json({ message: "Internal Server Error" }); } try { - const searchServiceURL = `${env.SEARCH_SERVICE_URL}/timetable/remove`; - - const res = await fetch(searchServiceURL, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ id: req.params.id }), - }); - if (!res.ok) { - const resJson = await res.json(); - logger.error( - "Error while removing timetable from search service: ", - resJson.error, - ); - } + await removeTimetable(timetable.id, logger); } catch (err: any) { - logger.error( - "Error while removing timetable from search service: ", - err.message, - ); return res.status(500).json({ message: "Internal Server Error" }); } return res.json({ message: "timetable deleted" }); diff --git a/backend/src/controllers/timetable/editTimetableMetadata.ts b/backend/src/controllers/timetable/editTimetableMetadata.ts index f49a4921..28001258 100644 --- a/backend/src/controllers/timetable/editTimetableMetadata.ts +++ b/backend/src/controllers/timetable/editTimetableMetadata.ts @@ -6,11 +6,11 @@ import { namedNonEmptyStringType, timetableIDType, } from "../../../../lib/src/index.js"; -import { env } from "../../config/server.js"; import { Timetable, User } from "../../entity/entities.js"; import { validate } from "../../middleware/zodValidateRequest.js"; import { timetableRepository } from "../../repositories/timetableRepository.js"; import { userRepository } from "../../repositories/userRepository.js"; +import { addTimetable, removeTimetable } from "../../utils/search.js"; import sqids, { validSqid } from "../../utils/sqids.js"; const dataSchema = z.object({ @@ -117,59 +117,15 @@ export const editTimetableMetadata = async (req: Request, res: Response) => { return res.status(500).json({ message: "Internal Server Error" }); } - if (isDraft === false && isPrivate === false) { - try { - const searchServiceURL = `${env.SEARCH_SERVICE_URL}/timetable/add`; - const updatedTimetableStringID = { - ...updatedTimetable, - id: req.params.id, - }; - const res = await fetch(searchServiceURL, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(updatedTimetableStringID), - }); - const resJson = await res.json(); - if (!res.ok) { - logger.error( - "Error while adding timetable to search service: ", - resJson.error, - ); - } - } catch (err: any) { - logger.error( - "Error while adding timetable to search service: ", - err.message, - ); - return res.status(500).json({ message: "Internal Server Error" }); - } - } else { - try { - const searchServiceURL = `${env.SEARCH_SERVICE_URL}/timetable/remove`; - - const res = await fetch(searchServiceURL, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ id: req.params.id }), - }); - if (!res.ok) { - const resJson = await res.json(); - logger.error( - "Error while removing timetable from search service: ", - resJson.error, - ); - } - } catch (err: any) { - logger.error( - "Error while removing timetable from search service: ", - err.message, - ); - return res.status(500).json({ message: "Internal Server Error" }); + // update search service + try { + if (isDraft === false && isPrivate === false) { + await addTimetable(updatedTimetable, req.session?.email ?? "", logger); + } else { + await removeTimetable(timetable.id, logger); } + } catch (err: any) { + return res.status(500).json({ message: "Internal Server Error" }); } return res.json({ message: "timetable edited" }); diff --git a/backend/src/controllers/timetable/updateChangedTimetable.ts b/backend/src/controllers/timetable/updateChangedTimetable.ts index db9173f5..7be1fa9e 100644 --- a/backend/src/controllers/timetable/updateChangedTimetable.ts +++ b/backend/src/controllers/timetable/updateChangedTimetable.ts @@ -15,7 +15,12 @@ import { checkForClassHoursClash, checkForExamHoursClash, } from "../../utils/checkForClashes.js"; -import sqids from "../../utils/sqids.js"; +import { + addCourse, + addTimetable, + removeCourse, + removeTimetable, +} from "../../utils/search.js"; import { addExamTimings, removeSection } from "../../utils/updateSection.js"; import { updateSectionWarnings } from "../../utils/updateWarnings.js"; @@ -234,112 +239,28 @@ export const updateChangedTimetable = async (req: Request, res: Response) => { // update course in search service try { - const searchServiceURL = `${env.SEARCH_SERVICE_URL}/course/remove`; - const res = await fetch(searchServiceURL, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ id: course.id }), - }); - if (!res.ok) { - const resJson = await res.json(); - logger.error( - "Error while removing course from search service: ", - resJson.error, - ); - } + await removeCourse(course.id, logger); + await addCourse(course, logger); } catch (err: any) { - logger.error( - "Error while removing course from search service: ", - err.message, - ); - return res.status(500).json({ message: "Internal Server Error" }); - } - - try { - const searchServiceURL = `${env.SEARCH_SERVICE_URL}/course/add`; - const res = await fetch(searchServiceURL, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(course), - }); - if (!res.ok) { - const resJson = await res.json(); - logger.error( - "Error while adding course to search service: ", - resJson.error, - ); - } - } catch (err: any) { - logger.error( - "Error while adding course to search service: ", - err.message, - ); return res.status(500).json({ message: "Internal Server Error" }); } // update timetables in search service for (const timetable of timetables) { try { - const searchServiceURL = `${env.SEARCH_SERVICE_URL}/timetable/remove`; - const encodedId = sqids.encode([timetable.id]); - const res = await fetch(searchServiceURL, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ id: encodedId }), - }); - - if (!res.ok) { - const resJson = await res.json(); - logger.error( - "Error while removing timetable from search service: ", - resJson.error, - ); - } + await removeTimetable(timetable.id, logger); } catch (err: any) { - logger.error( - "Error while removing timetable from search service: ", - err.message, - ); return res.status(500).json({ message: "Internal Server Error" }); } if (!timetable.draft && !timetable.private) { const timetableWithSections = await timetableRepository .createQueryBuilder("timetable") .leftJoinAndSelect("timetable.sections", "section") - .where("timetable.id=:id", { id: timetable.id }) - .getOne(); - const encodedId = sqids.encode([timetable.id]); - const timetableWithSectionsString = { - ...timetableWithSections, - id: encodedId, - }; + .where("timetable.id = :id", { id: timetable.id }) + .getOneOrFail(); try { - const searchServiceURL = `${env.SEARCH_SERVICE_URL}/timetable/add`; - const res = await fetch(searchServiceURL, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(timetableWithSectionsString), - }); - if (!res.ok) { - const resJson = await res.json(); - logger.error( - "Error while adding timetable to search service: ", - resJson.error, - ); - } + await addTimetable(timetableWithSections, null, logger); } catch (err: any) { - logger.error( - "Error while adding timetable to search service: ", - err.message, - ); return res.status(500).json({ message: "Internal Server Error" }); } } diff --git a/backend/src/ingestJSON.ts b/backend/src/ingestJSON.ts index 8ceb6815..84b24db4 100644 --- a/backend/src/ingestJSON.ts +++ b/backend/src/ingestJSON.ts @@ -1,8 +1,12 @@ import { QueryRunner } from "typeorm"; import { sectionTypeEnum } from "../../lib/src/index.js"; -import { env } from "./config/server.js"; import { Course, Section, Timetable } from "./entity/entities.js"; -import sqids from "./utils/sqids.js"; +import { + addCourse, + addTimetable, + removeCourse, + removeTimetable, +} from "./utils/search.js"; interface ExamJSON { midsem: string | null; @@ -336,128 +340,35 @@ export const ingestJSON = async ( .getManyAndCount(); console.log(`${oldCourseCount} old courses found`); for (const { id } of oldCourseIds) { - try { - const searchServiceURL = `${env.SEARCH_SERVICE_URL}/course/remove`; - const res = await fetch(searchServiceURL, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ id }), - }); - if (!res.ok) { - const resJson = await res.json(); - console.log( - `error while removing course ${id} from search service: ${resJson.error}`, - ); - } - } catch (err: any) { - console.log( - `error while removing course ${id} from search service: ${err.message}`, - ); - } + await removeCourse(id, console); } console.log("removed old courses from search service!"); console.log("adding updated courses into search service..."); - const [updatedCourseIds, updatedCourseCount] = await queryRunner.manager + const [updatedCourses, updatedCourseCount] = await queryRunner.manager .createQueryBuilder() - .select("course.id") + .select("course") .from(Course, "course") + .leftJoinAndSelect("course.sections", "section") .getManyAndCount(); console.log(`${updatedCourseCount} courses found`); - for (const { id } of updatedCourseIds) { - const course = await queryRunner.manager - .createQueryBuilder() - .select("course") - .from(Course, "course") - .leftJoinAndSelect("course.sections", "section") - .where("course.id = :id", { id: id }) - .getOne(); - try { - const searchServiceURL = `${env.SEARCH_SERVICE_URL}/course/add`; - const res = await fetch(searchServiceURL, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(course), - }); - if (!res.ok) { - const resJson = await res.json(); - console.log( - `error while adding course ${id} to search service: ${resJson.error}`, - ); - } - } catch (err: any) { - console.log( - `error while adding course ${id} to search service: ${err.message}`, - ); - } + for (const course of updatedCourses) { + await addCourse(course, console); } console.log("added updated courses to search service!"); console.log("updating timetables in search service..."); const [timetableIds, timetableCount] = await queryRunner.manager .createQueryBuilder() - .select("timetable.id") + .select("timetable") .from(Timetable, "timetable") + .leftJoinAndSelect("timetable.sections", "section") .where("timetable.archived = :archived", { archived: true }) .getManyAndCount(); console.log(`${timetableCount} timetables are to be updated`); - for (const { id } of timetableIds) { - const encodedId = sqids.encode([id]); - try { - const searchServiceURL = `${env.SEARCH_SERVICE_URL}/timetable/remove`; - const res = await fetch(searchServiceURL, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ id: encodedId }), - }); - if (!res.ok) { - const resJson = await res.json(); - console.log( - `error while removing timetable ${id} from search service: ${resJson.error}`, - ); - } - } catch (err: any) { - console.log( - `error while removing timetable ${id} from search service: ${err.message}`, - ); - } - const timetable = await queryRunner.manager - .createQueryBuilder() - .select("timetable") - .from(Timetable, "timetable") - .leftJoinAndSelect("timetable.sections", "section") - .where("timetable.id = :id", { id }) - .getOne(); - const timetableWithSqid = { - ...timetable, - id: encodedId, - }; - try { - const searchServiceURL = `${env.SEARCH_SERVICE_URL}/timetable/add`; - const res = await fetch(searchServiceURL, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(timetableWithSqid), - }); - if (!res.ok) { - const resJson = await res.json(); - console.log( - `error while adding timetable ${id} to search service: ${resJson.error}`, - ); - } - } catch (err: any) { - console.log( - `error while adding timetable ${id} to search service: ${err.message}`, - ); - } + for (const timetable of timetableIds) { + await removeTimetable(timetable.id, console); + await addTimetable(timetable, null, console); } console.log("updated timetables in search service!"); diff --git a/backend/src/utils/search.ts b/backend/src/utils/search.ts new file mode 100644 index 00000000..ae1cca73 --- /dev/null +++ b/backend/src/utils/search.ts @@ -0,0 +1,136 @@ +import "dotenv/config"; +import { Logger } from "pino"; +import { env } from "../config/server.js"; +import { Course, Timetable } from "../entity/entities.js"; +import { userRepository } from "../repositories/userRepository.js"; +import sqids from "./sqids.js"; + +export const addTimetable = async ( + timetable: Timetable, + authorEmail: string | null, + logger: Logger | Console, +) => { + try { + const searchServiceURL = `${env.SEARCH_SERVICE_URL}/timetable/add`; + const encodedId = sqids.encode([timetable.id]); + const email = + authorEmail ?? + ( + await userRepository + .createQueryBuilder("user") + .where("user.id = :id", { id: timetable.authorId }) + .getOneOrFail() + ).email; + const authorId = email.slice(0, 9); + const modifiedTimetable = { + ...timetable, + id: encodedId, + authorId, + }; + const res = await fetch(searchServiceURL, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(modifiedTimetable), + }); + if (!res.ok) { + const resJson = await res.json(); + logger.error( + `Error while adding timetable ${timetable.id} to search service: ${resJson.error}`, + ); + } + } catch (err: any) { + logger.error( + `Error while adding timetable ${timetable.id} to search service: ${ + "message" in err ? err.message : err + }`, + ); + throw err; + } +}; + +export const removeTimetable = async ( + timetableId: number, + logger: Logger | Console, +) => { + try { + const searchServiceURL = `${env.SEARCH_SERVICE_URL}/timetable/remove`; + const encodedId = sqids.encode([timetableId]); + const res = await fetch(searchServiceURL, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ id: encodedId }), + }); + if (!res.ok) { + const resJson = await res.json(); + logger.error( + `Error while removing timetable ${timetableId} from search service: ${resJson.error}`, + ); + } + } catch (err: any) { + logger.error( + `Error while removing timetable ${timetableId} from search service: ${ + "message" in err ? err.message : err + }`, + ); + throw err; + } +}; + +export const addCourse = async (course: Course, logger: Logger | Console) => { + try { + const searchServiceURL = `${env.SEARCH_SERVICE_URL}/course/add`; + const res = await fetch(searchServiceURL, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(course), + }); + if (!res.ok) { + const resJson = await res.json(); + logger.error( + `Error while adding course ${course.id} to search service: ${resJson.error}`, + ); + } + } catch (err: any) { + logger.error( + `Error while adding course ${course.id} to search service: ${ + "message" in err ? err.message : err + }`, + ); + throw err; + } +}; + +export const removeCourse = async ( + courseId: string, + logger: Logger | Console, +) => { + try { + const searchServiceURL = `${env.SEARCH_SERVICE_URL}/course/remove`; + const res = await fetch(searchServiceURL, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ id: courseId }), + }); + if (!res.ok) { + const resJson = await res.json(); + logger.error( + `Error while removing course ${courseId} from search service: ${resJson.error}`, + ); + } + } catch (err: any) { + logger.error( + `Error while removing course ${courseId} from search service: ${ + "message" in err ? err.message : err + }`, + ); + throw err; + } +}; diff --git a/biome.json b/biome.json index 41160249..627bce22 100644 --- a/biome.json +++ b/biome.json @@ -7,6 +7,9 @@ "enabled": true, "rules": { "recommended": true, + "nursery": { + "noUnusedImports": "error" + }, "suspicious": { "noExplicitAny": "off" } diff --git a/frontend/src/SearchResults.tsx b/frontend/src/SearchResults.tsx index 2b60ee9e..efd011d2 100644 --- a/frontend/src/SearchResults.tsx +++ b/frontend/src/SearchResults.tsx @@ -1,5 +1,5 @@ import { queryOptions, useQuery } from "@tanstack/react-query"; -import { ErrorComponent, Route, notFound } from "@tanstack/react-router"; +import { ErrorComponent, Route } from "@tanstack/react-router"; import axios, { AxiosError } from "axios"; import { z } from "zod"; import { timetableWithSectionsType } from "../../lib/src";