Added education endpoints & information
This commit is contained in:
parent
c3dc1f9e48
commit
7aca1b737d
8 changed files with 235 additions and 3 deletions
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
59
src/cvdocs/education.ts
Normal 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;
|
||||||
|
};
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
32
src/education/education.service.spec.ts
Normal file
32
src/education/education.service.spec.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
28
src/education/education.service.ts
Normal file
28
src/education/education.service.ts
Normal 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]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/education/education.ts
Normal file
47
src/education/education.ts
Normal 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[];
|
||||||
46
src/education/education.types.ts
Normal file
46
src/education/education.types.ts
Normal 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue