mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-20 11:41:38 +10:00
refactor(v4.0.0-alpha): beginning of a new era
This commit is contained in:
224
libs/parser/src/json-resume/index.ts
Normal file
224
libs/parser/src/json-resume/index.ts
Normal file
@ -0,0 +1,224 @@
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import {
|
||||
defaultAward,
|
||||
defaultCertification,
|
||||
defaultEducation,
|
||||
defaultExperience,
|
||||
defaultInterest,
|
||||
defaultLanguage,
|
||||
defaultProfile,
|
||||
defaultPublication,
|
||||
defaultReference,
|
||||
defaultResumeData,
|
||||
defaultSkill,
|
||||
defaultVolunteer,
|
||||
ResumeData,
|
||||
} from "@reactive-resume/schema";
|
||||
import { Json } from "@reactive-resume/utils";
|
||||
import { Schema } from "zod";
|
||||
|
||||
import { Parser } from "../interfaces/parser";
|
||||
import { JsonResume, jsonResumeSchema } from "./schema";
|
||||
|
||||
export * from "./schema";
|
||||
|
||||
export class JsonResumeParser implements Parser<Json, JsonResume> {
|
||||
schema: Schema;
|
||||
|
||||
constructor() {
|
||||
this.schema = jsonResumeSchema;
|
||||
}
|
||||
|
||||
readFile(file: File): Promise<Json> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = () => {
|
||||
try {
|
||||
const result = JSON.parse(reader.result as string) as Json;
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
reject(new Error("Failed to parse JSON"));
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = () => {
|
||||
reject(new Error("Failed to read the file"));
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
|
||||
validate(data: Json) {
|
||||
return this.schema.parse(data) as JsonResume;
|
||||
}
|
||||
|
||||
convert(data: JsonResume) {
|
||||
const result = JSON.parse(JSON.stringify(defaultResumeData)) as ResumeData;
|
||||
|
||||
// Basics
|
||||
result.basics.name = data.basics?.name ?? "";
|
||||
result.basics.headline = data.basics?.label ?? "";
|
||||
result.basics.picture.url = data.basics?.image ?? "";
|
||||
result.basics.email = data.basics?.email ?? "";
|
||||
result.basics.phone = data.basics?.phone ?? "";
|
||||
result.basics.location = data.basics?.location?.address ?? "";
|
||||
result.basics.url.href = data.basics?.url ?? "";
|
||||
result.sections.summary.content = data.basics?.summary ?? "";
|
||||
|
||||
// Profiles
|
||||
if (data.basics?.profiles) {
|
||||
for (const profile of data.basics.profiles) {
|
||||
result.sections.profiles.items.push({
|
||||
...defaultProfile,
|
||||
id: createId(),
|
||||
icon: profile.network?.toLocaleLowerCase() ?? "",
|
||||
network: profile.network ?? "",
|
||||
username: profile.username ?? "",
|
||||
url: { ...defaultProfile.url, href: profile.url ?? "" },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Work
|
||||
if (data.work) {
|
||||
for (const work of data.work) {
|
||||
result.sections.experience.items.push({
|
||||
...defaultExperience,
|
||||
id: createId(),
|
||||
company: work.name ?? "",
|
||||
position: work.position ?? "",
|
||||
summary: work.summary ?? "",
|
||||
date: `${work.startDate} - ${work.endDate}`,
|
||||
url: { ...defaultExperience.url, href: work.url ?? "" },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Volunteer
|
||||
if (data.volunteer) {
|
||||
for (const volunteer of data.volunteer) {
|
||||
result.sections.volunteer.items.push({
|
||||
...defaultVolunteer,
|
||||
id: createId(),
|
||||
organization: volunteer.organization ?? "",
|
||||
date: `${volunteer.startDate} - ${volunteer.endDate}`,
|
||||
position: volunteer.position ?? "",
|
||||
summary: volunteer.summary ?? "",
|
||||
url: { ...defaultVolunteer.url, href: volunteer.url ?? "" },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Education
|
||||
if (data.education) {
|
||||
for (const education of data.education) {
|
||||
result.sections.education.items.push({
|
||||
...defaultEducation,
|
||||
id: createId(),
|
||||
institution: education.institution ?? "",
|
||||
studyType: education.studyType ?? "",
|
||||
area: education.area ?? "",
|
||||
score: education.score ?? "",
|
||||
date: `${education.startDate} - ${education.endDate}`,
|
||||
url: { ...defaultEducation.url, href: education.url ?? "" },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Awards
|
||||
if (data.awards) {
|
||||
for (const award of data.awards) {
|
||||
result.sections.awards.items.push({
|
||||
...defaultAward,
|
||||
id: createId(),
|
||||
title: award.title ?? "",
|
||||
date: award.date ?? "",
|
||||
awarder: award.awarder ?? "",
|
||||
summary: award.summary ?? "",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Certificates
|
||||
if (data.certificates) {
|
||||
for (const certificate of data.certificates) {
|
||||
result.sections.certifications.items.push({
|
||||
...defaultCertification,
|
||||
id: createId(),
|
||||
name: certificate.title ?? "",
|
||||
date: certificate.date ?? "",
|
||||
issuer: certificate.issuer ?? "",
|
||||
summary: certificate.summary ?? "",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Publications
|
||||
if (data.publications) {
|
||||
for (const publication of data.publications) {
|
||||
result.sections.publications.items.push({
|
||||
...defaultPublication,
|
||||
id: createId(),
|
||||
name: publication.name ?? "",
|
||||
publisher: publication.publisher ?? "",
|
||||
summary: publication.summary ?? "",
|
||||
date: publication.releaseDate ?? "",
|
||||
url: { ...defaultPublication.url, href: publication.url ?? "" },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Skills
|
||||
if (data.skills) {
|
||||
for (const skill of data.skills) {
|
||||
result.sections.skills.items.push({
|
||||
...defaultSkill,
|
||||
id: createId(),
|
||||
name: skill.name ?? "",
|
||||
description: skill.level ?? "",
|
||||
keywords: skill.keywords ?? [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Languages
|
||||
if (data.languages) {
|
||||
for (const language of data.languages) {
|
||||
result.sections.languages.items.push({
|
||||
...defaultLanguage,
|
||||
id: createId(),
|
||||
name: language.language ?? "",
|
||||
fluency: language.fluency ?? "",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Interests
|
||||
if (data.interests) {
|
||||
for (const interest of data.interests) {
|
||||
result.sections.interests.items.push({
|
||||
...defaultInterest,
|
||||
id: createId(),
|
||||
name: interest.name ?? "",
|
||||
keywords: interest.keywords ?? [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// References
|
||||
if (data.references) {
|
||||
for (const reference of data.references) {
|
||||
result.sections.references.items.push({
|
||||
...defaultReference,
|
||||
id: createId(),
|
||||
name: reference.name ?? "",
|
||||
summary: reference.reference ?? "",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
126
libs/parser/src/json-resume/schema.ts
Normal file
126
libs/parser/src/json-resume/schema.ts
Normal file
@ -0,0 +1,126 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const urlSchema = z.literal("").or(z.string().url()).optional();
|
||||
|
||||
const iso8601 = z
|
||||
.string()
|
||||
.regex(
|
||||
/^([1-2][0-9]{3}-[0-1][0-9]-[0-3][0-9]|[1-2][0-9]{3}-[0-1][0-9]|[1-2][0-9]{3})$/,
|
||||
"ISO8601 Date Format",
|
||||
);
|
||||
|
||||
const locationSchema = z.object({
|
||||
address: z.string().optional(),
|
||||
postalCode: z.string().optional(),
|
||||
city: z.string().optional(),
|
||||
countryCode: z.string().optional(),
|
||||
region: z.string().optional(),
|
||||
});
|
||||
|
||||
const profileSchema = z.object({
|
||||
network: z.string().optional(),
|
||||
username: z.string().optional(),
|
||||
url: urlSchema,
|
||||
});
|
||||
|
||||
const basicsSchema = z.object({
|
||||
name: z.string().optional(),
|
||||
label: z.string().optional(),
|
||||
image: z.literal("").or(z.string().url()).optional(),
|
||||
email: z.literal("").or(z.string().email()).optional(),
|
||||
phone: z.string().optional(),
|
||||
url: urlSchema,
|
||||
summary: z.string().optional(),
|
||||
location: locationSchema.optional(),
|
||||
profiles: z.array(profileSchema).optional(),
|
||||
});
|
||||
|
||||
const workSchema = z.object({
|
||||
name: z.string().optional(),
|
||||
position: z.string().optional(),
|
||||
url: urlSchema,
|
||||
startDate: iso8601.optional(),
|
||||
endDate: iso8601.optional(),
|
||||
summary: z.string().optional(),
|
||||
highlights: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
const volunteerSchema = z.object({
|
||||
organization: z.string().optional(),
|
||||
position: z.string().optional(),
|
||||
url: urlSchema,
|
||||
startDate: iso8601.optional(),
|
||||
endDate: iso8601.optional(),
|
||||
summary: z.string().optional(),
|
||||
highlights: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
const awardsSchema = z.object({
|
||||
title: z.string().optional(),
|
||||
date: iso8601.optional(),
|
||||
awarder: z.string().optional(),
|
||||
summary: z.string().optional(),
|
||||
});
|
||||
|
||||
const certificatesSchema = z.object({
|
||||
title: z.string().optional(),
|
||||
date: iso8601.optional(),
|
||||
issuer: z.string().optional(),
|
||||
summary: z.string().optional(),
|
||||
});
|
||||
|
||||
const educationSchema = z.object({
|
||||
institution: z.string().optional(),
|
||||
url: urlSchema,
|
||||
area: z.string().optional(),
|
||||
studyType: z.string().optional(),
|
||||
startDate: iso8601.optional(),
|
||||
endDate: iso8601.optional(),
|
||||
score: z.string().optional(),
|
||||
courses: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
const publicationsSchema = z.object({
|
||||
name: z.string().optional(),
|
||||
publisher: z.string().optional(),
|
||||
releaseDate: iso8601.optional(),
|
||||
url: urlSchema,
|
||||
summary: z.string().optional(),
|
||||
});
|
||||
|
||||
const skillsSchema = z.object({
|
||||
name: z.string().optional(),
|
||||
level: z.string().optional(),
|
||||
keywords: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
const languagesSchema = z.object({
|
||||
language: z.string().optional(),
|
||||
fluency: z.string().optional(),
|
||||
});
|
||||
|
||||
const interestsSchema = z.object({
|
||||
name: z.string().optional(),
|
||||
keywords: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
const referencesSchema = z.object({
|
||||
name: z.string().optional(),
|
||||
reference: z.string().optional(),
|
||||
});
|
||||
|
||||
export const jsonResumeSchema = z.object({
|
||||
basics: basicsSchema.optional(),
|
||||
work: z.array(workSchema).optional(),
|
||||
volunteer: z.array(volunteerSchema).optional(),
|
||||
education: z.array(educationSchema).optional(),
|
||||
awards: z.array(awardsSchema).optional(),
|
||||
certificates: z.array(certificatesSchema).optional(),
|
||||
publications: z.array(publicationsSchema).optional(),
|
||||
skills: z.array(skillsSchema).optional(),
|
||||
languages: z.array(languagesSchema).optional(),
|
||||
interests: z.array(interestsSchema).optional(),
|
||||
references: z.array(referencesSchema).optional(),
|
||||
});
|
||||
|
||||
export type JsonResume = z.infer<typeof jsonResumeSchema>;
|
||||
Reference in New Issue
Block a user