because I got bored of customising my CV for every job
at main 120 lines 3.2 kB view raw
1import { PrismaService } from "@cv/system"; 2import { Injectable } from "@nestjs/common"; 3 4/** 5 * Draft entity representation - ID is null if no match found 6 */ 7export interface DraftEntity { 8 id: string | null; 9 name: string; 10} 11 12/** 13 * Resolved job experience with draft entities 14 */ 15export interface ResolvedJobExperience { 16 company: DraftEntity; 17 role: DraftEntity; 18 level: DraftEntity; 19 skills: DraftEntity[]; 20 startDate: Date; 21 endDate: Date | null; 22 description: string | null; 23} 24 25/** 26 * Resolved education with draft entities 27 */ 28export interface ResolvedEducation { 29 institution: DraftEntity; 30 degree: string; 31 fieldOfStudy: string | null; 32 skills: DraftEntity[]; 33 startDate: Date; 34 endDate: Date | null; 35 description: string | null; 36} 37 38/** 39 * Service for resolving entity names to existing database entities 40 * Returns stubs with null IDs for unmatched names 41 */ 42@Injectable() 43export class EntityResolverService { 44 constructor(private readonly prisma: PrismaService) {} 45 46 /** 47 * Resolve a company name to an existing entity 48 */ 49 async resolveCompany(name: string): Promise<DraftEntity> { 50 const match = await this.prisma.company.findFirst({ 51 where: { name: { equals: name, mode: "insensitive" } }, 52 }); 53 54 return match ? { id: match.id, name: match.name } : { id: null, name }; 55 } 56 57 /** 58 * Resolve a role name to an existing entity 59 */ 60 async resolveRole(name: string): Promise<DraftEntity> { 61 const match = await this.prisma.role.findFirst({ 62 where: { name: { equals: name, mode: "insensitive" } }, 63 }); 64 65 return match ? { id: match.id, name: match.name } : { id: null, name }; 66 } 67 68 /** 69 * Resolve a level name to an existing entity 70 * Falls back to "Mid-level" if not provided 71 */ 72 async resolveLevel(name?: string): Promise<DraftEntity> { 73 const searchName = name || "Mid-level"; 74 const match = await this.prisma.level.findFirst({ 75 where: { name: { equals: searchName, mode: "insensitive" } }, 76 }); 77 78 return match 79 ? { id: match.id, name: match.name } 80 : { id: null, name: searchName }; 81 } 82 83 /** 84 * Resolve a skill name to an existing entity 85 */ 86 async resolveSkill(name: string): Promise<DraftEntity> { 87 const trimmed = name.trim(); 88 if (!trimmed) { 89 return { id: null, name: "" }; 90 } 91 92 const match = await this.prisma.skill.findFirst({ 93 where: { name: { equals: trimmed, mode: "insensitive" } }, 94 }); 95 96 return match 97 ? { id: match.id, name: match.name } 98 : { id: null, name: trimmed }; 99 } 100 101 /** 102 * Resolve an institution name to an existing entity 103 */ 104 async resolveInstitution(name: string): Promise<DraftEntity> { 105 const match = await this.prisma.institution.findFirst({ 106 where: { name: { equals: name, mode: "insensitive" } }, 107 }); 108 109 return match ? { id: match.id, name: match.name } : { id: null, name }; 110 } 111 112 /** 113 * Resolve multiple skills in parallel 114 */ 115 async resolveSkills(names: string[]): Promise<DraftEntity[]> { 116 const uniqueNames = [...new Set(names.filter((s) => s?.trim()))]; 117 118 return Promise.all(uniqueNames.map((name) => this.resolveSkill(name))); 119 } 120}