Added education endpoints & information

This commit is contained in:
Thom Werring 2023-10-22 15:11:35 +02:00
parent c3dc1f9e48
commit 7aca1b737d
8 changed files with 235 additions and 3 deletions

View file

@ -5,14 +5,17 @@ import { SkillsService } from "@/skills/skills.service";
import { ExperiencesService } from "@/experiences/experiences.service"; import { ExperiencesService } from "@/experiences/experiences.service";
import { SkillDto } from "@/skills/skills.types"; import { SkillDto } from "@/skills/skills.types";
import { ExperienceDto } from "@/experiences/experiences.types"; import { ExperienceDto } from "@/experiences/experiences.types";
import { EducationDto } from "@/education/education.types";
import { EducationService } from "@/education/education.service";
@Controller() @Controller()
@ApiExtraModels(SkillDto, ExperienceDto) @ApiExtraModels(SkillDto, ExperienceDto, EducationDto)
export class AppController { export class AppController {
constructor( constructor(
private readonly appService: AppService, private readonly appService: AppService,
private readonly skillsService: SkillsService, private readonly skillsService: SkillsService,
private readonly experiencesService: ExperiencesService private readonly experiencesService: ExperiencesService,
private readonly educationService: EducationService
) {} ) {}
@Get() @Get()
@ -48,4 +51,18 @@ export class AppController {
getExperiences(): readonly ExperienceDto[] { getExperiences(): readonly ExperienceDto[] {
return this.experiencesService.getMany(); 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();
}
} }

View file

@ -3,10 +3,11 @@ import { AppController } from "@/app.controller";
import { AppService } from "@/app.service"; import { AppService } from "@/app.service";
import { SkillsService } from "@/skills/skills.service"; import { SkillsService } from "@/skills/skills.service";
import { ExperiencesService } from "@/experiences/experiences.service"; import { ExperiencesService } from "@/experiences/experiences.service";
import { EducationService } from "@/education/education.service";
@Module({ @Module({
imports: [], imports: [],
controllers: [AppController], controllers: [AppController],
providers: [AppService, SkillsService, ExperiencesService], providers: [AppService, SkillsService, ExperiencesService, EducationService],
}) })
export class AppModule {} export class AppModule {}

59
src/cvdocs/education.ts Normal file
View file

@ -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
? `<a target="_blank" href="${education.url}">${education.institute}</a>`
: education.institute;
const description = education.description.split("\n").join("</p><p>");
return `<header><strong>${institute}</strong>, <em>${education.city}</em><br/>
<strong>${education.level}</strong> - ${education.course}<br/>
<time>${period}</time></header>
<p>${description}</p>`;
};
const formatDate = (date: Date, locale: string | string[] = "en-GB"): string => {
const dateFormatter = new Intl.DateTimeFormat(locale, {
dateStyle: "long",
});
const dateParts = dateFormatter.formatToParts(date).reduce<Record<"day" | "month" | "year", string>>(
(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;
};

View file

@ -4,6 +4,7 @@ import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
import { addExperiences } from "@/cvdocs/experience"; import { addExperiences } from "@/cvdocs/experience";
import { addSkills } from "@/cvdocs/skills"; import { addSkills } from "@/cvdocs/skills";
import { addIntro } from "@/cvdocs/intro"; import { addIntro } from "@/cvdocs/intro";
import { addEducation } from "@/cvdocs/education";
const apiDocument = ( const apiDocument = (
options: { options: {
@ -80,6 +81,7 @@ export default async (app: INestApplication) => {
addIntro(config, redocOptions); addIntro(config, redocOptions);
addSkills(config, 5, redocOptions); addSkills(config, 5, redocOptions);
addExperiences(config, 3, redocOptions); addExperiences(config, 3, redocOptions);
addEducation(config, 2, redocOptions);
await initDocs({ await initDocs({
app, app,

View file

@ -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>(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);
});
});
});

View file

@ -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<Omit<EducationType, "startDate" | "endDate">>) {
const filtersValues = Object.entries(filter ?? {}).map(([key, filterValue]) => [
key,
new RegExp(filterValue, "i"),
]) as Array<[keyof Omit<EducationType, "startDate" | "endDate">, RegExp]>;
if (!filter || filtersValues.length === 0) {
return this.education;
}
return this.education.filter((education) =>
filtersValues.some(([key, filterValue]) => filterValue.test(education[key]))
);
}
}

View file

@ -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[];

View file

@ -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<EducationType, EducationDto>() 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,
});
}
}