From 7f0e9aff8e0d522fe4fa1cbd30efcc78a67e1154 Mon Sep 17 00:00:00 2001
From: Mohamed Idrissi <70617264+mhd-hi@users.noreply.github.com>
Date: Sat, 5 Oct 2024 20:09:04 -0400
Subject: [PATCH] Add course upsertion job (#35)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Add common folder
* Add program resource
* Fix eslint rule (complexity) & fix dependencies versions
* add course, cours-instance
* add session resource
* Add prisma, scraper, program files (modules and services)
* Fix prisma & add scraper files & eslint migration
* chore: address sonarlint warning (if statement)
* Rollback eslint flat to v8 & remove entity & format imports
* add public to classes
* cleanup
* add ets api hekper files
* add ets-program logic & (WIP) upsert programs
* Remove unnecessary dtos
* (wip 🚔) Add controllers & fix course routes
* fix tests
* Fix ProgramType relation
* Add coursePrerequisite controller & service
* Move scraper folder into /common
* Move pdf folder into /common
* fix http & add prisma to app module
* fix tests (wip) & sessions route
* fix tests
* rename
* Add schema image generator
* add methods
* fix
* change "program to programType" relation
* fix ci test
* miaow miaow
* test test
* only run eslint on src folder
* Rollback to ".test.ts" only
* pdf small test
* fix logging
* fix PR & remove scraper folder
* update enum trimester
* remove file
* fix param
* Add swagger
* fix program creation parameters & some route parameters
* fix axios vulnerability
* add queues for programs and courses processing
* add bull dashboard
* add planification tests
* yummy
* Extract cheminements.txt file text
* (wip) parse programs and courses
* (wip) parsing of courses partially done
* add cours hors-programme & remove commented lines from parsing
* remove console.log
* uppercase
* add support of alternative courses (choix)
* change id to code
* Add directory parameter to FileUtil
* pdfOutputPath
* remove json
* program upsert job creating programs and program types to db
* fix progress% job
* change course code to null
* log verbose & fix course data format
* a
* test etsCourseService
* refactor upsertCourses() to use findMany() for better performance
* change prerequisite table relations
* coverage folder to root
* fix prerequisite service
* cleanup course service
* add program course resource
* add type column to ProgramCourse table
* cleanup program & courses cheminot helper
* (wip) programCourse creation almost done
* programCourse upsertion done
* Refactor logger initialization to use readonly modifier
* Add error handling
* fix sonar issues
* more fix
* add redis env variables
* fix type name
---
.env.example | 3 +
.vscode/settings.json | 2 +-
package.json | 2 +-
prisma/ERD.svg | 2 +-
.../migration.sql | 3 +
.../migration.sql | 8 +
.../migration.sql | 2 +
.../migration.sql | 24 +++
.../migration.sql | 3 +
prisma/schema.prisma | 24 ++-
src/app.module.ts | 2 +
src/common/api-helper/cheminot/Course.ts | 3 +-
src/common/api-helper/cheminot/Program.ts | 8 +-
.../cheminot/cheminot.controller.ts | 5 +-
.../api-helper/cheminot/cheminot.module.ts | 1 +
.../api-helper/cheminot/cheminot.service.ts | 7 +-
.../ets/course/ets-course.controller.ts | 10 +-
.../ets/course/ets-course.service.ts | 66 ++++--
.../ets/program/ets-program.service.ts | 9 +-
src/common/utils/pdf/fileUtil.ts | 2 +-
src/common/utils/pdf/parser/pdfParserUtil.ts | 2 +-
src/common/utils/stringUtil.ts | 5 +
.../pdf/pdf-parser/horaire/HoraireCours.ts | 3 +-
.../horaire/horaire-cours.service.ts | 4 +-
.../planification-cours.service.ts | 4 +-
.../website-helper/pdf/pdf.controller.ts | 4 +-
.../course-instance.service.ts | 25 ++-
.../course-prerequisite.service.ts | 66 +++---
src/course/course.module.ts | 1 +
src/course/course.service.ts | 156 ++++++++------
src/jobs/processors/courses.processor.ts | 190 +++++++++++++++++-
src/jobs/processors/programs.processor.ts | 5 +-
src/jobs/queues.service.ts | 29 +--
.../program-course.controller.ts | 10 +
src/program-course/program-course.module.ts | 13 ++
src/program-course/program-course.service.ts | 136 +++++++++++++
src/program/program.service.ts | 64 +++++-
src/session/session.service.ts | 2 +-
.../ets/course/ets-course.service.test.ts | 164 +++++++++++++++
tsconfig.json | 2 +-
40 files changed, 907 insertions(+), 164 deletions(-)
create mode 100644 prisma/migrations/20240830020859_update_course_table_credits_attribute_to_nullable/migration.sql
create mode 100644 prisma/migrations/20240830021032_update_course_table_code_attribute_to_not_null/migration.sql
create mode 100644 prisma/migrations/20240830022905_update_course_table_add_cycle_attribute/migration.sql
create mode 100644 prisma/migrations/20240831045028_course_prerequisite_table_change_relation_to_one_to_many_program_course_table/migration.sql
create mode 100644 prisma/migrations/20240902020425_update_program_course_table_add_type_attribute/migration.sql
create mode 100644 src/common/utils/stringUtil.ts
create mode 100644 src/program-course/program-course.controller.ts
create mode 100644 src/program-course/program-course.module.ts
create mode 100644 src/program-course/program-course.service.ts
create mode 100644 test/common/api-helper/ets/course/ets-course.service.test.ts
diff --git a/.env.example b/.env.example
index 048c876..029adf4 100644
--- a/.env.example
+++ b/.env.example
@@ -5,3 +5,6 @@ PORT=3000
DATABASE_URL="postgresql://postgres@localhost:5432/planifetsDB?schema=public"
LOG_LEVELS="log,error,warn,debug,verbose"
+
+REDIS_HOST="localhost"
+REDIS_PORT=6379
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 67e00b8..31a0eca 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -3,7 +3,7 @@
"[typescript]": {
"editor.tabSize": 2,
"editor.formatOnSave": true,
- "editor.defaultFormatter": "esbenp.prettier-vscode"
+ "editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"files.eol": "\n",
"typescript.preferences.importModuleSpecifier": "relative",
diff --git a/package.json b/package.json
index ea22a0a..046356b 100644
--- a/package.json
+++ b/package.json
@@ -104,7 +104,7 @@
"collectCoverageFrom": [
"**/*.(t|j)s"
],
- "coverageDirectory": "../coverage",
+ "coverageDirectory": "./coverage",
"testEnvironment": "node"
}
}
diff --git a/prisma/ERD.svg b/prisma/ERD.svg
index 6607ec2..68719ce 100644
--- a/prisma/ERD.svg
+++ b/prisma/ERD.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/prisma/migrations/20240830020859_update_course_table_credits_attribute_to_nullable/migration.sql b/prisma/migrations/20240830020859_update_course_table_credits_attribute_to_nullable/migration.sql
new file mode 100644
index 0000000..87e30a1
--- /dev/null
+++ b/prisma/migrations/20240830020859_update_course_table_credits_attribute_to_nullable/migration.sql
@@ -0,0 +1,3 @@
+-- AlterTable
+ALTER TABLE "Course" ALTER COLUMN "code" DROP NOT NULL,
+ALTER COLUMN "credits" DROP NOT NULL;
diff --git a/prisma/migrations/20240830021032_update_course_table_code_attribute_to_not_null/migration.sql b/prisma/migrations/20240830021032_update_course_table_code_attribute_to_not_null/migration.sql
new file mode 100644
index 0000000..df07840
--- /dev/null
+++ b/prisma/migrations/20240830021032_update_course_table_code_attribute_to_not_null/migration.sql
@@ -0,0 +1,8 @@
+/*
+ Warnings:
+
+ - Made the column `code` on table `Course` required. This step will fail if there are existing NULL values in that column.
+
+*/
+-- AlterTable
+ALTER TABLE "Course" ALTER COLUMN "code" SET NOT NULL;
diff --git a/prisma/migrations/20240830022905_update_course_table_add_cycle_attribute/migration.sql b/prisma/migrations/20240830022905_update_course_table_add_cycle_attribute/migration.sql
new file mode 100644
index 0000000..d5ff05b
--- /dev/null
+++ b/prisma/migrations/20240830022905_update_course_table_add_cycle_attribute/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "Course" ADD COLUMN "cycle" INTEGER;
diff --git a/prisma/migrations/20240831045028_course_prerequisite_table_change_relation_to_one_to_many_program_course_table/migration.sql b/prisma/migrations/20240831045028_course_prerequisite_table_change_relation_to_one_to_many_program_course_table/migration.sql
new file mode 100644
index 0000000..d0a3591
--- /dev/null
+++ b/prisma/migrations/20240831045028_course_prerequisite_table_change_relation_to_one_to_many_program_course_table/migration.sql
@@ -0,0 +1,24 @@
+/*
+ Warnings:
+
+ - The primary key for the `CoursePrerequisite` table will be changed. If it partially fails, the table could be left without primary key constraint.
+ - Added the required column `programId` to the `CoursePrerequisite` table without a default value. This is not possible if the table is not empty.
+
+*/
+-- DropForeignKey
+ALTER TABLE "CoursePrerequisite" DROP CONSTRAINT "CoursePrerequisite_courseId_fkey";
+
+-- DropForeignKey
+ALTER TABLE "CoursePrerequisite" DROP CONSTRAINT "CoursePrerequisite_prerequisiteId_fkey";
+
+-- AlterTable
+ALTER TABLE "CoursePrerequisite" DROP CONSTRAINT "CoursePrerequisite_pkey",
+ADD COLUMN "programId" INTEGER NOT NULL,
+ADD COLUMN "unstructuredPrerequisite" TEXT,
+ADD CONSTRAINT "CoursePrerequisite_pkey" PRIMARY KEY ("courseId", "programId", "prerequisiteId");
+
+-- AddForeignKey
+ALTER TABLE "CoursePrerequisite" ADD CONSTRAINT "CoursePrerequisite_courseId_programId_fkey" FOREIGN KEY ("courseId", "programId") REFERENCES "ProgramCourse"("courseId", "programId") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "CoursePrerequisite" ADD CONSTRAINT "CoursePrerequisite_prerequisiteId_programId_fkey" FOREIGN KEY ("prerequisiteId", "programId") REFERENCES "ProgramCourse"("courseId", "programId") ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git a/prisma/migrations/20240902020425_update_program_course_table_add_type_attribute/migration.sql b/prisma/migrations/20240902020425_update_program_course_table_add_type_attribute/migration.sql
new file mode 100644
index 0000000..a338cae
--- /dev/null
+++ b/prisma/migrations/20240902020425_update_program_course_table_add_type_attribute/migration.sql
@@ -0,0 +1,3 @@
+-- AlterTable
+ALTER TABLE "ProgramCourse" ADD COLUMN "type" TEXT,
+ALTER COLUMN "typicalSessionIndex" DROP NOT NULL;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index c67c989..3789d09 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -51,13 +51,11 @@ model Course {
code String @unique
title String
description String
- credits Int
+ credits Int?
+ cycle Int?
programs ProgramCourse[]
courseInstances CourseInstance[]
- prerequisites CoursePrerequisite[] @relation("CourseToPrerequisite")
- prerequisiteOf CoursePrerequisite[] @relation("PrerequisiteToCourse")
-
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -65,26 +63,32 @@ model Course {
}
model CoursePrerequisite {
- courseId Int
- prerequisiteId Int
+ courseId Int
+ prerequisiteId Int
+ unstructuredPrerequisite String?
+ programId Int
- course Course @relation("CourseToPrerequisite", fields: [courseId], references: [id])
- prerequisite Course @relation("PrerequisiteToCourse", fields: [prerequisiteId], references: [id])
+ programCourse ProgramCourse @relation("CourseToPrerequisites", fields: [courseId, programId], references: [courseId, programId])
+ prerequisite ProgramCourse @relation("PrerequisiteToCourse", fields: [prerequisiteId, programId], references: [courseId, programId])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
- @@id([courseId, prerequisiteId])
+ @@id([courseId, programId, prerequisiteId])
}
model ProgramCourse {
courseId Int
programId Int
- typicalSessionIndex Int
+ type String?
+ typicalSessionIndex Int?
course Course @relation(fields: [courseId], references: [id])
program Program @relation(fields: [programId], references: [id])
+ prerequisites CoursePrerequisite[] @relation("CourseToPrerequisites")
+ prerequisiteToCourse CoursePrerequisite[] @relation("PrerequisiteToCourse")
+
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
diff --git a/src/app.module.ts b/src/app.module.ts
index 34aa3ff..6a2ba38 100644
--- a/src/app.module.ts
+++ b/src/app.module.ts
@@ -17,6 +17,7 @@ import { QueuesEnum } from './jobs/queues.enum';
import { QueuesService } from './jobs/queues.service';
import { PrismaModule } from './prisma/prisma.module';
import { ProgramModule } from './program/program.module';
+import { ProgramCourseModule } from './program-course/program-course.module';
import { SessionModule } from './session/session.module';
@Module({
@@ -47,6 +48,7 @@ import { SessionModule } from './session/session.module';
CoursePrerequisiteModule,
SessionModule,
ProgramModule,
+ ProgramCourseModule,
],
providers: [ProgramsProcessor, CoursesProcessor, QueuesService],
controllers: [AppController],
diff --git a/src/common/api-helper/cheminot/Course.ts b/src/common/api-helper/cheminot/Course.ts
index aa84822..2d84735 100644
--- a/src/common/api-helper/cheminot/Course.ts
+++ b/src/common/api-helper/cheminot/Course.ts
@@ -4,7 +4,8 @@ export class Course {
public static readonly COURSE_LINE_PARTS_COUNT = 11;
public static readonly INTERNSHIP_LINE_PARTS_COUNT = 12;
- private static courseCodeValidationPipe = new CourseCodeValidationPipe();
+ private static readonly courseCodeValidationPipe =
+ new CourseCodeValidationPipe();
constructor(
public type: string,
diff --git a/src/common/api-helper/cheminot/Program.ts b/src/common/api-helper/cheminot/Program.ts
index 5e2a049..5846175 100644
--- a/src/common/api-helper/cheminot/Program.ts
+++ b/src/common/api-helper/cheminot/Program.ts
@@ -1,11 +1,11 @@
import { Course } from './Course';
export class Program {
- private horsProgramme: string[] = [];
+ private readonly horsProgramme: string[] = [];
constructor(
- private code: number,
- private courses: Course[],
+ public code: string,
+ public courses: Course[],
) {}
public static isProgramLine(line: string): boolean {
@@ -20,7 +20,7 @@ export class Program {
return null;
}
- const code = parseInt(parts[1], 10);
+ const code = parts[1];
return new Program(code, []);
}
diff --git a/src/common/api-helper/cheminot/cheminot.controller.ts b/src/common/api-helper/cheminot/cheminot.controller.ts
index 2893399..dbca58a 100644
--- a/src/common/api-helper/cheminot/cheminot.controller.ts
+++ b/src/common/api-helper/cheminot/cheminot.controller.ts
@@ -23,9 +23,6 @@ export class CheminotController {
summary: 'Parse the programs and courses from the cheminements.txt file',
})
public async parseProgramsAndCoursesFromCheminotTxtFile() {
- await this.cheminotService.loadPrograms();
- const data = this.cheminotService.getPrograms();
-
- return data;
+ return this.cheminotService.parseProgramsAndCoursesCheminot();
}
}
diff --git a/src/common/api-helper/cheminot/cheminot.module.ts b/src/common/api-helper/cheminot/cheminot.module.ts
index 18c3774..2cd1272 100644
--- a/src/common/api-helper/cheminot/cheminot.module.ts
+++ b/src/common/api-helper/cheminot/cheminot.module.ts
@@ -7,5 +7,6 @@ import { FileExtractionService } from './file-extraction.service';
@Module({
controllers: [CheminotController],
providers: [CheminotService, FileExtractionService],
+ exports: [CheminotService],
})
export class CheminotModule {}
diff --git a/src/common/api-helper/cheminot/cheminot.service.ts b/src/common/api-helper/cheminot/cheminot.service.ts
index 1338db3..6fe700d 100644
--- a/src/common/api-helper/cheminot/cheminot.service.ts
+++ b/src/common/api-helper/cheminot/cheminot.service.ts
@@ -6,10 +6,15 @@ import { Program } from './Program';
@Injectable()
export class CheminotService {
- private programs: Program[] = [];
+ private readonly programs: Program[] = [];
constructor(private readonly fileExtractionService: FileExtractionService) {}
+ public async parseProgramsAndCoursesCheminot() {
+ await this.loadPrograms();
+ return this.getPrograms();
+ }
+
public async loadPrograms() {
const fileContent =
await this.fileExtractionService.extractCheminementsFile();
diff --git a/src/common/api-helper/ets/course/ets-course.controller.ts b/src/common/api-helper/ets/course/ets-course.controller.ts
index 40a2886..1571fc1 100644
--- a/src/common/api-helper/ets/course/ets-course.controller.ts
+++ b/src/common/api-helper/ets/course/ets-course.controller.ts
@@ -3,8 +3,8 @@ import { ApiTags } from '@nestjs/swagger';
import {
EtsCourseService,
- IEtsCourse,
- IEtsCoursesData,
+ ICourseEtsAPI,
+ ICourseWithCredits,
} from './ets-course.service';
@ApiTags('ÉTS API')
@@ -13,12 +13,12 @@ export class EtsCourseController {
constructor(private readonly etsCourseService: EtsCourseService) {}
@Get()
- public fetchAllCourses(): Promise {
- return this.etsCourseService.fetchAllCourses();
+ public fetchAllCourses(): Promise {
+ return this.etsCourseService.fetchAllCoursesWithCredits();
}
@Get(':id')
- public fetchCoursesById(@Param('id') id: string): Promise {
+ public fetchCoursesById(@Param('id') id: string): Promise {
if (!id) {
throw new Error('The id parameter is required');
}
diff --git a/src/common/api-helper/ets/course/ets-course.service.ts b/src/common/api-helper/ets/course/ets-course.service.ts
index 5cb3032..0f8915f 100644
--- a/src/common/api-helper/ets/course/ets-course.service.ts
+++ b/src/common/api-helper/ets/course/ets-course.service.ts
@@ -6,50 +6,92 @@ import {
ETS_API_GET_ALL_COURSES,
ETS_API_GET_COURSES_BY_IDS,
} from '../../../constants/url';
+import { extractNumberFromString } from '../../../utils/stringUtil';
-export interface IEtsCoursesData {
+export interface ICoursesEtsAPI {
id: number;
title: string;
+ description: string;
code: string;
cycle: string | null;
}
-export interface IEtsCourse extends IEtsCoursesData {
- credits: string;
+export interface ICourseEtsAPI {
+ id: number;
+ title: string;
+ code: string;
+ credits: number | null;
+}
+
+export interface ICourses {
+ id: number;
+ title: string;
+ description: string;
+ code: string;
+ cycle: number | null;
+}
+
+export interface ICourseWithCredits extends ICourses {
+ credits: number | null;
}
@Injectable()
export class EtsCourseService {
constructor(private readonly httpService: HttpService) {}
- // Fetches all courses
- public async fetchAllCourses(): Promise {
+ public async fetchAllCoursesWithCredits(): Promise {
+ const courses = await this.fetchAllCoursesWithoutCredits();
+
+ // Fetch credits for all courses by their IDs
+ const courseIds = courses.map((course) => course.id).join(',');
+ const coursesFetchedById = await this.fetchCoursesById(courseIds);
+
+ // Add credits to the courses
+ return courses.map((course) => {
+ const courseCreds = coursesFetchedById.find((cc) => cc.id === course.id);
+ return {
+ ...course,
+ credits: courseCreds?.credits ?? null,
+ };
+ });
+ }
+
+ //Fetches all courses without credits
+ public async fetchAllCoursesWithoutCredits(): Promise {
const response = await firstValueFrom(
this.httpService.get(ETS_API_GET_ALL_COURSES),
);
-
const courses = response.data.results;
- return courses.map((course: IEtsCoursesData) => ({
+
+ if (!courses.length) {
+ throw new Error('No courses fetched.');
+ }
+
+ return courses.map((course: ICoursesEtsAPI) => ({
id: course.id,
title: course.title,
+ description: course.description,
code: course.code,
- cycle: course.cycle,
+ cycle: course.cycle ? extractNumberFromString(course.cycle) : null,
}));
}
// Fetches one or more courses by their ids
// The ids are passed as a string with comma-separated values, ex: "349682,349710"
- public async fetchCoursesById(ids: string): Promise {
+ public async fetchCoursesById(ids: string): Promise {
const response = await firstValueFrom(
this.httpService.get(`${ETS_API_GET_COURSES_BY_IDS}${ids}`),
);
-
const courses = response.data;
- return courses.map((course: IEtsCourse) => ({
+
+ if (!courses.length) {
+ throw new Error('No courses fetched.');
+ }
+
+ return courses.map((course: ICourseEtsAPI) => ({
id: course.id,
title: course.title,
code: course.code,
- cycle: course.cycle,
credits: course.credits,
}));
}
diff --git a/src/common/api-helper/ets/program/ets-program.service.ts b/src/common/api-helper/ets/program/ets-program.service.ts
index 84da21d..3d0cd85 100644
--- a/src/common/api-helper/ets/program/ets-program.service.ts
+++ b/src/common/api-helper/ets/program/ets-program.service.ts
@@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
import { firstValueFrom } from 'rxjs';
import { ETS_API_GET_ALL_PROGRAMS } from '../../../constants/url';
+import { extractNumberFromString } from '../../../utils/stringUtil';
interface IProgramEtsAPI {
id: number;
@@ -48,7 +49,7 @@ export class EtsProgramService {
(program: IProgramEtsAPI) => ({
id: program.id,
title: program.title,
- cycle: this.extractCycleNumber(program.cycle),
+ cycle: extractNumberFromString(program.cycle),
code: program.code,
credits: program.credits,
programTypes: {
@@ -60,10 +61,4 @@ export class EtsProgramService {
return { types, programs };
}
-
- private extractCycleNumber(cycle: string): number {
- const match = RegExp(/\d+/).exec(cycle);
-
- return match ? parseInt(match[0], 10) : 0;
- }
}
diff --git a/src/common/utils/pdf/fileUtil.ts b/src/common/utils/pdf/fileUtil.ts
index 2067c38..de63e50 100644
--- a/src/common/utils/pdf/fileUtil.ts
+++ b/src/common/utils/pdf/fileUtil.ts
@@ -4,7 +4,7 @@ import path from 'path';
@Injectable()
export class FileUtil {
- private logger = new Logger(FileUtil.name);
+ private readonly logger = new Logger(FileUtil.name);
public writeDataToFile(
data: T,
diff --git a/src/common/utils/pdf/parser/pdfParserUtil.ts b/src/common/utils/pdf/parser/pdfParserUtil.ts
index df275ed..02fd763 100644
--- a/src/common/utils/pdf/parser/pdfParserUtil.ts
+++ b/src/common/utils/pdf/parser/pdfParserUtil.ts
@@ -2,7 +2,7 @@ import { Logger } from '@nestjs/common';
import PDFParser, { Output } from 'pdf2json';
export class PdfParserUtil {
- private static logger = new Logger(PdfParserUtil.name);
+ private static readonly logger = new Logger(PdfParserUtil.name);
public static async parsePdfBuffer(
pdfBuffer: Buffer,
diff --git a/src/common/utils/stringUtil.ts b/src/common/utils/stringUtil.ts
new file mode 100644
index 0000000..717b348
--- /dev/null
+++ b/src/common/utils/stringUtil.ts
@@ -0,0 +1,5 @@
+export function extractNumberFromString(cycle: string): number {
+ const match = RegExp(/\d+/).exec(cycle);
+
+ return match ? parseInt(match[0], 10) : 0;
+}
diff --git a/src/common/website-helper/pdf/pdf-parser/horaire/HoraireCours.ts b/src/common/website-helper/pdf/pdf-parser/horaire/HoraireCours.ts
index 8ec2de9..51f80fc 100644
--- a/src/common/website-helper/pdf/pdf-parser/horaire/HoraireCours.ts
+++ b/src/common/website-helper/pdf/pdf-parser/horaire/HoraireCours.ts
@@ -7,7 +7,8 @@ export class HoraireCours implements IHoraireCours {
private static readonly TITLE_FONT_SIZE = 10.998999999999999;
private static readonly COURS_X_AXIS = 0.551;
- private static courseCodeValidationPipe = new CourseCodeValidationPipe();
+ private static readonly courseCodeValidationPipe =
+ new CourseCodeValidationPipe();
constructor(
public code: string = '',
diff --git a/src/common/website-helper/pdf/pdf-parser/horaire/horaire-cours.service.ts b/src/common/website-helper/pdf/pdf-parser/horaire/horaire-cours.service.ts
index facf404..ba1a489 100644
--- a/src/common/website-helper/pdf/pdf-parser/horaire/horaire-cours.service.ts
+++ b/src/common/website-helper/pdf/pdf-parser/horaire/horaire-cours.service.ts
@@ -14,9 +14,9 @@ export class HoraireCoursService {
private readonly END_PAGE_CONTENT_Y_AXIS = 59;
private readonly PREALABLE_X_AXIS = 29.86;
- constructor(private httpService: HttpService) {}
+ constructor(private readonly httpService: HttpService) {}
- private logger = new Logger(HoraireCoursService.name);
+ private readonly logger = new Logger(HoraireCoursService.name);
public async parsePdfFromUrl(pdfUrl: string) {
try {
diff --git a/src/common/website-helper/pdf/pdf-parser/planification/planification-cours.service.ts b/src/common/website-helper/pdf/pdf-parser/planification/planification-cours.service.ts
index 78a85df..860d1d7 100644
--- a/src/common/website-helper/pdf/pdf-parser/planification/planification-cours.service.ts
+++ b/src/common/website-helper/pdf/pdf-parser/planification/planification-cours.service.ts
@@ -13,9 +13,9 @@ import { Row } from './Row';
export class PlanificationCoursService {
private readonly BORDER_OFFSET = 0.124;
- private courseCodeValidationPipe = new CourseCodeValidationPipe();
+ private readonly courseCodeValidationPipe = new CourseCodeValidationPipe();
- constructor(private httpService: HttpService) {}
+ constructor(private readonly httpService: HttpService) {}
public async parsePdfFromUrl(
pdfUrl: string,
diff --git a/src/common/website-helper/pdf/pdf.controller.ts b/src/common/website-helper/pdf/pdf.controller.ts
index c78f7fb..31ef3c4 100644
--- a/src/common/website-helper/pdf/pdf.controller.ts
+++ b/src/common/website-helper/pdf/pdf.controller.ts
@@ -18,8 +18,8 @@ import { ICoursePlanification } from './pdf-parser/planification/planification-c
@Controller('pdf')
export class PdfController {
constructor(
- private horaireCoursService: HoraireCoursService,
- private planificationCoursService: PlanificationCoursService,
+ private readonly horaireCoursService: HoraireCoursService,
+ private readonly planificationCoursService: PlanificationCoursService,
) {}
@Get('horaire-cours')
diff --git a/src/course-instance/course-instance.service.ts b/src/course-instance/course-instance.service.ts
index ee2b352..1899984 100644
--- a/src/course-instance/course-instance.service.ts
+++ b/src/course-instance/course-instance.service.ts
@@ -1,5 +1,5 @@
import { Injectable, Logger } from '@nestjs/common';
-import { CourseInstance, Prisma } from '@prisma/client';
+import { CourseInstance, Prisma, Session } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
@@ -7,7 +7,7 @@ import { PrismaService } from '../prisma/prisma.service';
export class CourseInstanceService {
constructor(private readonly prisma: PrismaService) {}
- private logger = new Logger(CourseInstanceService.name);
+ private readonly logger = new Logger(CourseInstanceService.name);
public getCourseInstance(
courseInstanceWhereUniqueInput: Prisma.CourseInstanceWhereUniqueInput,
@@ -40,9 +40,26 @@ export class CourseInstanceService {
});
}
- // This will be used to get all the infos about a course instance
+ public async getCourseAvailability(
+ courseId: number,
+ ): Promise<{ session: Session; available: boolean }[]> {
+ this.logger.verbose('getCourseAvailability', courseId);
+
+ const courseInstances = await this.prisma.courseInstance.findMany({
+ where: { courseId },
+ include: {
+ session: true,
+ },
+ });
+
+ return courseInstances.map((ci) => ({
+ session: ci.session,
+ available: true,
+ }));
+ }
+
public async getCourseInstancesByCourse(
- courseId: string,
+ courseId: number,
): Promise {
this.logger.log('getCourseInstancesByCourse', courseId);
diff --git a/src/course-prerequisite/course-prerequisite.service.ts b/src/course-prerequisite/course-prerequisite.service.ts
index e353252..f0a43bc 100644
--- a/src/course-prerequisite/course-prerequisite.service.ts
+++ b/src/course-prerequisite/course-prerequisite.service.ts
@@ -7,48 +7,59 @@ import { PrismaService } from '../prisma/prisma.service';
export class CoursePrerequisiteService {
constructor(private readonly prisma: PrismaService) {}
- private logger = new Logger(CoursePrerequisiteService.name);
+ private readonly logger = new Logger(CoursePrerequisiteService.name);
public async getPrerequisites(data: Prisma.CoursePrerequisiteWhereInput) {
- this.logger.log('coursePrerequisiteById', data);
+ this.logger.verbose('Fetching course prerequisites', data);
return this.prisma.coursePrerequisite.findMany({
where: data,
- include: { prerequisite: true },
+ include: {
+ programCourse: true,
+ prerequisite: true,
+ },
});
}
public async getAllCoursePrerequisites() {
- this.logger.log('getAllCoursePrerequisites');
+ this.logger.verbose('Fetching all course prerequisites');
return this.prisma.coursePrerequisite.findMany({
- include: { prerequisite: true },
+ include: {
+ programCourse: true,
+ prerequisite: true,
+ },
});
}
- private async createCoursePrerequisite(
+ private async createPrerequisite(
data: Prisma.CoursePrerequisiteCreateInput,
): Promise {
- this.logger.log('createCoursePrerequisite', data);
+ this.logger.verbose('createCoursePrerequisite', data);
- const courseId = data.course.connect?.id;
- const prerequisiteId = data.prerequisite.connect?.id;
+ const courseId = data.programCourse.connect?.courseId as number;
+ const programId = data.programCourse.connect?.programId as number;
+ const prerequisiteId = data.prerequisite.connect?.courseId as number;
- if (!courseId || !prerequisiteId) {
- throw new Error('courseId and prerequisiteId must be provided.');
+ if (!courseId || !programId || !prerequisiteId) {
+ this.logger.error(
+ 'courseId, programId, and prerequisiteId must be provided.',
+ );
}
const existingPrerequisite =
await this.prisma.coursePrerequisite.findUnique({
where: {
- courseId_prerequisiteId: {
+ courseId_programId_prerequisiteId: {
courseId,
+ programId,
prerequisiteId,
},
},
});
if (existingPrerequisite) {
+ this.logger.verbose('Prerequisite already exists', existingPrerequisite);
return existingPrerequisite;
}
@@ -57,27 +68,30 @@ export class CoursePrerequisiteService {
});
}
- public async createCoursePrerequisites(
+ public async createProgramCoursePrerequisites(
data: Prisma.CoursePrerequisiteCreateInput[],
): Promise {
- this.logger.log('ensurePrerequisitesExist', data);
+ this.logger.verbose('ensurePrerequisitesExist', data);
- const ensuredPrerequisites = await Promise.all(
- data.map((prerequisiteData) =>
- this.createCoursePrerequisite(prerequisiteData),
- ),
+ return Promise.all(
+ data.map((prerequisiteData) => this.createPrerequisite(prerequisiteData)),
);
-
- return ensuredPrerequisites;
}
- public async deleteCoursePrerequisite(
- where: Prisma.CoursePrerequisiteWhereUniqueInput,
- ): Promise {
- this.logger.log('deleteCoursePrerequisite', where);
+ public async deletePrerequisitesForProgramCourse(
+ programId: number,
+ courseId: number,
+ ): Promise {
+ this.logger.verbose('deletePrerequisitesForProgramCourse', {
+ programId,
+ courseId,
+ });
- return this.prisma.coursePrerequisite.delete({
- where,
+ return this.prisma.coursePrerequisite.deleteMany({
+ where: {
+ programId: programId,
+ courseId: courseId,
+ },
});
}
}
diff --git a/src/course/course.module.ts b/src/course/course.module.ts
index 35c6d1c..666ab35 100644
--- a/src/course/course.module.ts
+++ b/src/course/course.module.ts
@@ -8,5 +8,6 @@ import { CourseService } from './course.service';
imports: [PrismaModule],
controllers: [CourseController],
providers: [CourseService],
+ exports: [CourseService],
})
export class CourseModule {}
diff --git a/src/course/course.service.ts b/src/course/course.service.ts
index 39fdcae..95907c7 100644
--- a/src/course/course.service.ts
+++ b/src/course/course.service.ts
@@ -1,5 +1,5 @@
import { Injectable, Logger } from '@nestjs/common';
-import { Course, Prisma, Session } from '@prisma/client';
+import { Course, Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
@@ -7,28 +7,41 @@ import { PrismaService } from '../prisma/prisma.service';
export class CourseService {
constructor(private readonly prisma: PrismaService) {}
- private logger = new Logger(CourseService.name);
+ private readonly logger = new Logger(CourseService.name);
public async getCourse(
courseWhereUniqueInput: Prisma.CourseWhereUniqueInput,
): Promise {
- this.logger.log('courseById', courseWhereUniqueInput);
-
const course = await this.prisma.course.findUnique({
where: courseWhereUniqueInput,
});
+ if (!course) {
+ this.logger.warn(
+ `Course code "${courseWhereUniqueInput.code}" not found`,
+ );
+ return null;
+ }
return course;
}
+ private async getCoursesByIds(
+ courseIds: number[],
+ ): Promise