From 7aca1b737d2fded120a31ad70e53960d1cc39da9 Mon Sep 17 00:00:00 2001
From: Thom Werring
Date: Sun, 22 Oct 2023 15:11:35 +0200
Subject: [PATCH] Added education endpoints & information
---
src/app.controller.ts | 21 ++++++++-
src/app.module.ts | 3 +-
src/cvdocs/education.ts | 59 +++++++++++++++++++++++++
src/cvdocs/openApi.ts | 2 +
src/education/education.service.spec.ts | 32 ++++++++++++++
src/education/education.service.ts | 28 ++++++++++++
src/education/education.ts | 47 ++++++++++++++++++++
src/education/education.types.ts | 46 +++++++++++++++++++
8 files changed, 235 insertions(+), 3 deletions(-)
create mode 100644 src/cvdocs/education.ts
create mode 100644 src/education/education.service.spec.ts
create mode 100644 src/education/education.service.ts
create mode 100644 src/education/education.ts
create mode 100644 src/education/education.types.ts
diff --git a/src/app.controller.ts b/src/app.controller.ts
index 187be1c..deccbc9 100644
--- a/src/app.controller.ts
+++ b/src/app.controller.ts
@@ -5,14 +5,17 @@ import { SkillsService } from "@/skills/skills.service";
import { ExperiencesService } from "@/experiences/experiences.service";
import { SkillDto } from "@/skills/skills.types";
import { ExperienceDto } from "@/experiences/experiences.types";
+import { EducationDto } from "@/education/education.types";
+import { EducationService } from "@/education/education.service";
@Controller()
-@ApiExtraModels(SkillDto, ExperienceDto)
+@ApiExtraModels(SkillDto, ExperienceDto, EducationDto)
export class AppController {
constructor(
private readonly appService: AppService,
private readonly skillsService: SkillsService,
- private readonly experiencesService: ExperiencesService
+ private readonly experiencesService: ExperiencesService,
+ private readonly educationService: EducationService
) {}
@Get()
@@ -48,4 +51,18 @@ export class AppController {
getExperiences(): readonly ExperienceDto[] {
return this.experiencesService.getMany();
}
+
+ @Get("education")
+ @ApiTags("Education")
+ @ApiOkResponse({
+ description: "Returns a list of received education",
+ schema: {
+ items: {
+ $ref: getSchemaPath(EducationDto),
+ },
+ },
+ })
+ getEducation(): readonly EducationDto[] {
+ return this.educationService.getMany();
+ }
}
diff --git a/src/app.module.ts b/src/app.module.ts
index b859374..69e0b8a 100644
--- a/src/app.module.ts
+++ b/src/app.module.ts
@@ -3,10 +3,11 @@ import { AppController } from "@/app.controller";
import { AppService } from "@/app.service";
import { SkillsService } from "@/skills/skills.service";
import { ExperiencesService } from "@/experiences/experiences.service";
+import { EducationService } from "@/education/education.service";
@Module({
imports: [],
controllers: [AppController],
- providers: [AppService, SkillsService, ExperiencesService],
+ providers: [AppService, SkillsService, ExperiencesService, EducationService],
})
export class AppModule {}
diff --git a/src/cvdocs/education.ts b/src/cvdocs/education.ts
new file mode 100644
index 0000000..5167bf9
--- /dev/null
+++ b/src/cvdocs/education.ts
@@ -0,0 +1,59 @@
+import { EducationType } from "@/education/education.types";
+import { DocumentBuilder } from "@nestjs/swagger";
+import { RedocOptions } from "@juicyllama/nestjs-redoc";
+import { education } from "@/education/education";
+
+const formatEducation = (education: EducationType) => {
+ const period = `${formatDate(education.startDate)} - ${
+ education.endDate ? formatDate(education.endDate) : "present"
+ }`;
+ const institute = education.url
+ ? `${education.institute}`
+ : education.institute;
+ const description = education.description.split("\n").join("
");
+
+ return `${institute}, ${education.city}
+${education.level} - ${education.course}
+
+
${description}
`;
+};
+
+const formatDate = (date: Date, locale: string | string[] = "en-GB"): string => {
+ const dateFormatter = new Intl.DateTimeFormat(locale, {
+ dateStyle: "long",
+ });
+ const dateParts = dateFormatter.formatToParts(date).reduce>(
+ (result, datePart: Intl.DateTimeFormatPart) => {
+ if (datePart.type !== "literal") {
+ result[datePart.type] = datePart.value;
+ }
+
+ return result;
+ },
+ { day: "", month: "", year: "" }
+ );
+
+ return `${dateParts.month} ${dateParts.year}`;
+};
+
+export const addEducation = (
+ document: DocumentBuilder,
+ count: number = 5,
+ redocOptions: RedocOptions
+): DocumentBuilder => {
+ let educationTagGroup = redocOptions.tagGroups.find((tagGroup) => tagGroup.name === "Education");
+ if (!educationTagGroup) {
+ educationTagGroup = {
+ name: "Education",
+ tags: ["Education"],
+ };
+ redocOptions.tagGroups.push(educationTagGroup);
+ }
+
+ education.slice(0, count).forEach((education) => {
+ educationTagGroup.tags.push(education.institute);
+ document.addTag(education.institute, formatEducation(education));
+ });
+
+ return document;
+};
diff --git a/src/cvdocs/openApi.ts b/src/cvdocs/openApi.ts
index a33c466..e1ddedd 100644
--- a/src/cvdocs/openApi.ts
+++ b/src/cvdocs/openApi.ts
@@ -4,6 +4,7 @@ import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
import { addExperiences } from "@/cvdocs/experience";
import { addSkills } from "@/cvdocs/skills";
import { addIntro } from "@/cvdocs/intro";
+import { addEducation } from "@/cvdocs/education";
const apiDocument = (
options: {
@@ -80,6 +81,7 @@ export default async (app: INestApplication) => {
addIntro(config, redocOptions);
addSkills(config, 5, redocOptions);
addExperiences(config, 3, redocOptions);
+ addEducation(config, 2, redocOptions);
await initDocs({
app,
diff --git a/src/education/education.service.spec.ts b/src/education/education.service.spec.ts
new file mode 100644
index 0000000..1d06d4f
--- /dev/null
+++ b/src/education/education.service.spec.ts
@@ -0,0 +1,32 @@
+import { Test, TestingModule } from "@nestjs/testing";
+import { EducationService } from "src/education/education.service";
+
+describe("SkillsService", () => {
+ let service: EducationService;
+
+ beforeEach(async () => {
+ const module: TestingModule = await Test.createTestingModule({
+ providers: [EducationService],
+ }).compile();
+
+ service = module.get(EducationService);
+ });
+
+ it("should be defined", () => {
+ expect(service).toBeDefined();
+ });
+
+ describe("getMany method", () => {
+ it("should be defined", () => {
+ expect(service.getMany).toBeDefined();
+ });
+
+ it("should return an array", () => {
+ expect(Array.isArray(service.getMany())).toBe(true);
+ });
+
+ it("should return an array", () => {
+ expect(Array.isArray(service.getMany())).toBe(true);
+ });
+ });
+});
diff --git a/src/education/education.service.ts b/src/education/education.service.ts
new file mode 100644
index 0000000..89b6006
--- /dev/null
+++ b/src/education/education.service.ts
@@ -0,0 +1,28 @@
+import { Injectable } from "@nestjs/common";
+import { EducationDto, EducationType } from "@/education/education.types";
+import { education } from "@/education/education";
+
+@Injectable()
+export class EducationService {
+ private readonly education: readonly EducationDto[];
+
+ constructor() {
+ this.education = EducationDto.asDto(education).sort((educationA, educationB) =>
+ educationA.startDate < educationB.startDate ? 1 : -1
+ );
+ }
+
+ getMany(filter?: Partial>) {
+ const filtersValues = Object.entries(filter ?? {}).map(([key, filterValue]) => [
+ key,
+ new RegExp(filterValue, "i"),
+ ]) as Array<[keyof Omit, RegExp]>;
+ if (!filter || filtersValues.length === 0) {
+ return this.education;
+ }
+
+ return this.education.filter((education) =>
+ filtersValues.some(([key, filterValue]) => filterValue.test(education[key]))
+ );
+ }
+}
diff --git a/src/education/education.ts b/src/education/education.ts
new file mode 100644
index 0000000..911b7f7
--- /dev/null
+++ b/src/education/education.ts
@@ -0,0 +1,47 @@
+import { EducationType } from "@/education/education.types";
+
+export const education: EducationType[] = [
+ {
+ institute: "Hogeschool Utrecht",
+ city: "Utrecht",
+ url: "https://hu.nl",
+ course: "Mediatechnologie",
+ level: "HBO",
+ startDate: new Date(2010, 8),
+ endDate: new Date(2015, 9),
+ description: `My time at Hogeschool Utrecht was a period of academic and personal growth.
+While pursuing my Media Technology degree, I took a gap year to co-found and serve as treasurer of Sv. Ingenium, a university-affiliated student society. This experience sharpened my organizational, communication, and leadership skills, proving invaluable throughout my studies and beyond.
+Additionally, active participation in projects and presentations enhanced my presentation skills and public speaking confidence.`,
+ },
+ {
+ institute: "Mediacollege Amsterdam",
+ city: "Amsterdam",
+ url: "https://www.ma-web.nl/",
+ course: "Interactive Design & Media technology",
+ level: "MBO 4",
+ startDate: new Date(2006, 8),
+ endDate: new Date(2010, 5),
+ description: `My time at Mediacollege Amsterdam began with Interactive Design, but programming quickly captured my interest. ActionScript 2, now a relic of the past, marked my introduction to the world of programming, while PHP and JavaScript truly sparked my passion.
+With help of my teachers and a careful consideration of course, I transitioned towards Media Technology, paving the way for my future in software development.`,
+ },
+ {
+ institute: "Clusius college",
+ city: "Castricum",
+ url: "https://www.vonknh.nl/vmbo/castricum",
+ course: "General secondary education",
+ level: "VMBO - GL",
+ startDate: new Date(2002, 8),
+ endDate: new Date(2006, 5),
+ description: "",
+ },
+ {
+ institute: "Watermolen",
+ city: "Koog aan de zaan",
+ url: "https://www.obsdewatermolen.nl/",
+ course: "General primary education",
+ level: "Primary education",
+ startDate: new Date(1994, 8),
+ endDate: new Date(2002, 5),
+ description: "",
+ },
+] satisfies EducationType[];
diff --git a/src/education/education.types.ts b/src/education/education.types.ts
new file mode 100644
index 0000000..d261a72
--- /dev/null
+++ b/src/education/education.types.ts
@@ -0,0 +1,46 @@
+import { ApiProperty } from "@nestjs/swagger";
+import { DtoClass } from "@/common/dtoClass.factory";
+
+export type EducationType = {
+ institute: string;
+ city: string;
+ url?: string;
+ course: string;
+ level: string;
+ startDate: Date;
+ endDate: Date;
+ description: string;
+};
+
+export class EducationDto extends DtoClass() implements EducationType {
+ @ApiProperty()
+ readonly institute: string;
+ @ApiProperty()
+ readonly city: string;
+ @ApiProperty()
+ readonly url?: string;
+ @ApiProperty()
+ readonly course: string;
+ @ApiProperty()
+ readonly level: string;
+ @ApiProperty()
+ readonly startDate: Date;
+ @ApiProperty()
+ readonly endDate: Date;
+ @ApiProperty()
+ readonly description: string;
+
+ constructor(education: EducationType) {
+ super(education);
+ Object.assign(this, {
+ institute: education.institute,
+ city: education.city,
+ url: education.url,
+ course: education.course,
+ level: education.level,
+ startDate: education.startDate,
+ endDate: education.endDate,
+ description: education.description,
+ });
+ }
+}