Add experience endpoint & data
This commit is contained in:
parent
d5132520cb
commit
b3812c2b40
6 changed files with 191 additions and 6 deletions
|
|
@ -2,23 +2,27 @@ import { Test, TestingModule } from "@nestjs/testing";
|
||||||
import { AppController } from "@/app.controller";
|
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 { SkillDto } from "@/skills/skills.types";
|
import { SkillDto } from "@/skills/skills.types";
|
||||||
|
import { ExperienceDto, ExperienceType } from "@/experiences/experiences.types";
|
||||||
|
|
||||||
describe("AppController", () => {
|
describe("AppController", () => {
|
||||||
let appController: AppController;
|
let appController: AppController;
|
||||||
let appService: AppService;
|
let appService: AppService;
|
||||||
let skillsService: SkillsService;
|
let skillsService: SkillsService;
|
||||||
|
let experiencesService: ExperiencesService;
|
||||||
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const app: TestingModule = await Test.createTestingModule({
|
const app: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService, SkillsService],
|
providers: [AppService, SkillsService, ExperiencesService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
appController = app.get<AppController>(AppController);
|
appController = app.get<AppController>(AppController);
|
||||||
appService = app.get<AppService>(AppService);
|
appService = app.get<AppService>(AppService);
|
||||||
skillsService = app.get<SkillsService>(SkillsService);
|
skillsService = app.get<SkillsService>(SkillsService);
|
||||||
|
experiencesService = app.get<ExperiencesService>(ExperiencesService);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("root", () => {
|
describe("root", () => {
|
||||||
|
|
@ -46,5 +50,22 @@ describe("AppController", () => {
|
||||||
expect(skillsService.getMany).toBeCalled();
|
expect(skillsService.getMany).toBeCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("getExperience", () => {
|
||||||
|
it("Should return an Array of ExperienceDtos", () => {
|
||||||
|
const result: ExperienceDto[] = ExperienceDto.asDto([{
|
||||||
|
name: "experienceName",
|
||||||
|
city: "experienceCity",
|
||||||
|
jobTitle: "experienceJobTitle",
|
||||||
|
startDate: new Date(),
|
||||||
|
endDate: new Date(),
|
||||||
|
description: "experienceDescription",
|
||||||
|
skills: []
|
||||||
|
}]);
|
||||||
|
|
||||||
|
jest.spyOn(experiencesService, "getMany").mockImplementation(() => result)
|
||||||
|
expect(appController.getExperiences()).toBe(result);
|
||||||
|
expect(experiencesService.getMany).toBeCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@ import { ApiExcludeEndpoint, ApiExtraModels, ApiOkResponse, ApiTags, getSchemaPa
|
||||||
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 { SkillDto } from "@/skills/skills.types";
|
import { SkillDto } from "@/skills/skills.types";
|
||||||
|
import { ExperienceDto } from "@/experiences/experiences.types";
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
@ApiExtraModels(SkillDto)
|
@ApiExtraModels(SkillDto, ExperienceDto)
|
||||||
export class AppController {
|
export class AppController {
|
||||||
constructor(private readonly appService: AppService, private readonly skillsService: SkillsService, private readonly experiencesService: ExperiencesService) {
|
constructor(private readonly appService: AppService, private readonly skillsService: SkillsService, private readonly experiencesService: ExperiencesService) {
|
||||||
}
|
}
|
||||||
|
|
@ -30,4 +31,18 @@ export class AppController {
|
||||||
getSkills(): ReadonlyArray<SkillDto> {
|
getSkills(): ReadonlyArray<SkillDto> {
|
||||||
return this.skillsService.getMany();
|
return this.skillsService.getMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get("experiences")
|
||||||
|
@ApiTags("Experience")
|
||||||
|
@ApiOkResponse({
|
||||||
|
description: "Returns a list of previous work experiences",
|
||||||
|
schema: {
|
||||||
|
items: {
|
||||||
|
$ref: getSchemaPath(ExperienceDto),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
getExperiences(): ReadonlyArray<ExperienceDto> {
|
||||||
|
return this.experiencesService.getMany();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { ExperiencesService } from './experiences.service';
|
import { ExperiencesService } from './experiences.service';
|
||||||
|
import { SkillsService } from "@/skills/skills.service";
|
||||||
|
|
||||||
describe('ExperiencesService', () => {
|
describe('ExperiencesService', () => {
|
||||||
let service: ExperiencesService;
|
let service: ExperiencesService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [ExperiencesService],
|
providers: [ExperiencesService, SkillsService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<ExperiencesService>(ExperiencesService);
|
service = module.get<ExperiencesService>(ExperiencesService);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,35 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { ExperienceDto, ExperienceType } from "@/experiences/experiences.types";
|
||||||
|
import { SkillsService } from "@/skills/skills.service";
|
||||||
|
import { experiences } from "@/experiences/experiences";
|
||||||
|
import { SkillDto } from "@/skills/skills.types";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExperiencesService {
|
export class ExperiencesService {
|
||||||
private readonly
|
private readonly experiences: ReadonlyArray<ExperienceDto>;
|
||||||
constructor() {
|
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly skillsService: SkillsService
|
||||||
|
) {
|
||||||
|
this.experiences = ExperienceDto.asDto(this.linkSkills(experiences))
|
||||||
|
.sort((experienceA, experienceB) => experienceA.startDate > experienceB.startDate ? 1 : -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMany(filter?: Partial<Omit<ExperienceType, "startDate" | "endDate" | "skills">>) {
|
||||||
|
const filtersValues = Object.entries(filter ?? {}).map(([key, filterValue]) => ([key, new RegExp(filterValue, "i")])) as [keyof Omit<ExperienceType, "startDate" | "endDate" | "skills">, RegExp][];
|
||||||
|
if (!filter || filtersValues.length === 0) {
|
||||||
|
return this.experiences;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.experiences.filter((experience) => filtersValues.every(([key, filterValue]) => filterValue.test(experience[key])));
|
||||||
|
}
|
||||||
|
|
||||||
|
private linkSkills(experiences: ExperienceType[]): Array<ExperienceType & { skills: ReadonlyArray<SkillDto> }> {
|
||||||
|
return experiences.map((experience) => {
|
||||||
|
return {
|
||||||
|
...experience,
|
||||||
|
skills: experience.skills.map(partialSkill => this.skillsService.getMany(partialSkill)).flat()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
71
cv/src/experiences/experiences.ts
Normal file
71
cv/src/experiences/experiences.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { ExperienceType } from "@/experiences/experiences.types";
|
||||||
|
|
||||||
|
export const experiences: ExperienceType[] = [
|
||||||
|
{
|
||||||
|
name: "Saysimple / Just Internet Group",
|
||||||
|
city: "Haarlem",
|
||||||
|
url: "https://saysimple.com/",
|
||||||
|
jobTitle: "Senior Developer / DevOps",
|
||||||
|
startDate: new Date(2018, 9, 1),
|
||||||
|
endDate: null,
|
||||||
|
description: `I started at Just Internet Group in 2018 working on Cocoon, a SaaS digital Asset Management (DAM) system. Here I implemented ElasticSearch & Kibana to keep track of assets uploaded and downloaded.
|
||||||
|
Later on I switched to Saysimple, to start building our brand new Customer Communication Platform. For Saysimple I was responsible for creating and maintaining our CI/CD pipelines, the EKS cluster and our HAProxy loadbalancers.
|
||||||
|
During my time
|
||||||
|
`,
|
||||||
|
skills: [
|
||||||
|
{
|
||||||
|
category: "AWS|Containerization|DevOps|Business Intelligence|Team Management"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NodeJs|Git"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Blackorange",
|
||||||
|
city: "Amsterdam",
|
||||||
|
url: "http://blackorange.nl/",
|
||||||
|
jobTitle: "Junior Developer / System Administrator",
|
||||||
|
startDate: new Date(2018, 9, 1),
|
||||||
|
endDate: new Date(2018, 8, 30),
|
||||||
|
description: "",
|
||||||
|
skills: [
|
||||||
|
{
|
||||||
|
category: "AWS|Containerization|DevOps"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NodeJs|Git|Server"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Werring Webdevelopment",
|
||||||
|
city: "Middenbeemster",
|
||||||
|
jobTitle: "ZZP",
|
||||||
|
startDate: new Date(2018, 9, 1),
|
||||||
|
endDate: new Date(2018, 8, 30),
|
||||||
|
description: "",
|
||||||
|
skills: [
|
||||||
|
{
|
||||||
|
name: "PHP|Webhosting"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Sv. Ingenium",
|
||||||
|
city: "Utrecht",
|
||||||
|
url: "http://ingeniumcabobianci.nl/",
|
||||||
|
jobTitle: "Penningmeester",
|
||||||
|
startDate: new Date(2018, 9, 1),
|
||||||
|
endDate: new Date(2018, 8, 30),
|
||||||
|
description: "",
|
||||||
|
skills: [
|
||||||
|
{
|
||||||
|
name: "Finances"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "Organization"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] satisfies ExperienceType[];
|
||||||
52
cv/src/experiences/experiences.types.ts
Normal file
52
cv/src/experiences/experiences.types.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { ApiProperty, getSchemaPath } from "@nestjs/swagger";
|
||||||
|
import { DtoClass } from "@/common/types";
|
||||||
|
import { SkillDto } from "@/skills/skills.types";
|
||||||
|
|
||||||
|
export type ExperienceType = {
|
||||||
|
name: string;
|
||||||
|
city: string;
|
||||||
|
url?: string;
|
||||||
|
jobTitle: string;
|
||||||
|
description: string;
|
||||||
|
startDate: Date;
|
||||||
|
endDate: Date | null;
|
||||||
|
skills: Partial<SkillDto>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExperienceDto extends DtoClass<ExperienceType, ExperienceDto>() implements ExperienceType {
|
||||||
|
@ApiProperty()
|
||||||
|
readonly name: string;
|
||||||
|
@ApiProperty()
|
||||||
|
readonly city: string;
|
||||||
|
@ApiProperty()
|
||||||
|
readonly url?: string
|
||||||
|
@ApiProperty()
|
||||||
|
readonly jobTitle: string;
|
||||||
|
@ApiProperty()
|
||||||
|
readonly description: string;
|
||||||
|
@ApiProperty()
|
||||||
|
readonly startDate: Date;
|
||||||
|
@ApiProperty()
|
||||||
|
readonly endDate: Date | null;
|
||||||
|
@ApiProperty({
|
||||||
|
type: [SkillDto],
|
||||||
|
items: {
|
||||||
|
$ref: getSchemaPath(SkillDto)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
readonly skills: SkillDto[];
|
||||||
|
|
||||||
|
constructor(experience: ExperienceType) {
|
||||||
|
super(experience);
|
||||||
|
Object.assign(this, {
|
||||||
|
name: experience.name,
|
||||||
|
city: experience.city,
|
||||||
|
url: experience.url,
|
||||||
|
jobTitle: experience.jobTitle,
|
||||||
|
description: experience.description,
|
||||||
|
startDate: experience.startDate,
|
||||||
|
endDate: experience.endDate,
|
||||||
|
skills: experience.skills,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue