refactor(v4.0.0-alpha): beginning of a new era

This commit is contained in:
Amruth Pillai
2023-11-05 12:31:42 +01:00
parent 0ba6a444e2
commit 22933bd412
505 changed files with 81829 additions and 0 deletions

View File

@ -0,0 +1,30 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": [
"error",
{
"ignoredFiles": ["{projectRoot}/vite.config.{js,ts,mjs,mts}"]
}
]
}
}
]
}

29
libs/parser/.swcrc Normal file
View File

@ -0,0 +1,29 @@
{
"jsc": {
"target": "es2017",
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": true
},
"transform": {
"decoratorMetadata": true,
"legacyDecorator": true
},
"keepClassNames": true,
"externalHelpers": true,
"loose": true
},
"module": {
"type": "commonjs"
},
"sourceMaps": true,
"exclude": [
"jest.config.ts",
".*\\.spec.tsx?$",
".*\\.test.tsx?$",
"./src/jest-setup.ts$",
"./**/jest-setup.ts$",
".*.js$"
]
}

20
libs/parser/package.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "@reactive-resume/parser",
"version": "0.0.3",
"private": false,
"type": "commonjs",
"main": "./src/index.js",
"typings": "./src/index.d.ts",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@swc/helpers": "~0.5.2",
"@reactive-resume/schema": "*",
"nestjs-zod": "^3.0.0",
"zod": "^3.22.4",
"@paralleldrive/cuid2": "^2.2.2",
"@reactive-resume/utils": "*",
"jszip": "^3.10.1"
}
}

34
libs/parser/project.json Normal file
View File

@ -0,0 +1,34 @@
{
"name": "parser",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/parser/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/js:swc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/parser",
"main": "libs/parser/src/index.ts",
"tsConfig": "libs/parser/tsconfig.lib.json",
"assets": ["libs/parser/*.md"]
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/parser/**/*.ts", "libs/parser/package.json"]
}
},
"test": {
"executor": "@nx/vite:test",
"outputs": ["{options.reportsDirectory}"],
"options": {
"passWithNoTests": true,
"reportsDirectory": "../../coverage/libs/parser"
}
}
},
"tags": ["frontend"]
}

4
libs/parser/src/index.ts Normal file
View File

@ -0,0 +1,4 @@
export * from "./json-resume";
export * from "./linkedin";
export * from "./reactive-resume";
export * from "./reactive-resume-v3";

View File

@ -0,0 +1,13 @@
import { ResumeData } from "@reactive-resume/schema";
import { ZodDto } from "nestjs-zod/dto";
import { Schema } from "zod";
export interface Parser<Data = unknown, T = ZodDto, Result = ResumeData> {
schema?: Schema;
readFile(file: File): Promise<Data>;
validate(data: Data): T | Promise<T>;
convert(data: T): Result | Promise<Result>;
}

View 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;
}
}

View 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>;

View File

@ -0,0 +1,168 @@
import { createId } from "@paralleldrive/cuid2";
import {
defaultCertification,
defaultEducation,
defaultExperience,
defaultLanguage,
defaultProfile,
defaultProject,
defaultResumeData,
defaultSkill,
ResumeData,
resumeDataSchema,
} from "@reactive-resume/schema";
import { extractUrl, Json, parseCSV } from "@reactive-resume/utils";
import * as JSZip from "jszip";
import { Schema } from "zod";
import { Parser } from "../interfaces/parser";
import { LinkedIn, linkedInSchema } from "./schema";
export * from "./schema";
export class LinkedInParser implements Parser<JSZip, LinkedIn> {
schema: Schema;
constructor() {
this.schema = linkedInSchema;
}
async readFile(file: File): Promise<JSZip> {
const data = await JSZip.loadAsync(file);
if (Object.keys(data.files).length === 0) {
throw new Error("ParserError: There were no files found inside the zip archive.");
}
return data;
}
async validate(data: JSZip) {
const result: Json = {};
for (const [name, file] of Object.entries(data.files)) {
for (const key of Object.keys(linkedInSchema.shape)) {
if (name.includes(key)) {
const content = await file.async("text");
const jsonArray = await parseCSV(content);
result[key] = jsonArray;
}
}
}
return linkedInSchema.parse(result);
}
convert(data: LinkedIn) {
const result = JSON.parse(JSON.stringify(defaultResumeData)) as ResumeData;
// Profile
if (data.Profile && data.Profile.length > 0) {
const profile = data.Profile[0];
const twitterHandle = profile["Twitter Handles"];
result.basics.name = `${profile["First Name"]} ${profile["Last Name"]}`;
result.basics.location = profile["Geo Location"];
result.basics.headline = profile.Headline;
result.basics.url.href = extractUrl(profile.Websites) ?? "";
result.sections.summary.content = profile.Summary;
result.sections.profiles.items.push({
...defaultProfile,
id: createId(),
icon: "twitter",
network: "Twitter",
username: twitterHandle,
url: { ...defaultProfile.url, href: `https://twitter.com/${twitterHandle}` },
});
}
// Email Addresses
if (data["Email Addresses"] && data["Email Addresses"].length > 0) {
const email = data["Email Addresses"][0];
result.basics.email = email["Email Address"];
}
// Positions
if (data["Positions"] && data["Positions"].length > 0) {
for (const position of data["Positions"]) {
result.sections.experience.items.push({
...defaultExperience,
id: createId(),
company: position["Company Name"],
position: position.Title,
location: position.Location,
summary: position.Description ?? "",
date: `${position["Started On"]} - ${position["Finished On"] ?? "Present"}`,
});
}
}
// Education
if (data["Education"] && data["Education"].length > 0) {
for (const education of data["Education"]) {
result.sections.education.items.push({
...defaultEducation,
id: createId(),
institution: education["School Name"],
studyType: education["Degree Name"],
summary: education.Notes ?? "",
date: `${education["Start Date"]} - ${education["End Date"] ?? "Present"}`,
});
}
}
// Skills
if (data["Skills"] && data["Skills"].length > 0) {
for (const skill of data["Skills"]) {
result.sections.skills.items.push({
...defaultSkill,
id: createId(),
name: skill.Name,
});
}
}
// Languages
if (data["Languages"] && data["Languages"].length > 0) {
for (const language of data["Languages"]) {
result.sections.languages.items.push({
...defaultLanguage,
id: createId(),
name: language.Name,
fluency: language.Proficiency ?? "",
});
}
}
// Certifications
if (data["Certifications"] && data["Certifications"].length > 0) {
for (const certification of data["Certifications"]) {
result.sections.certifications.items.push({
...defaultCertification,
id: createId(),
name: certification.Name,
issuer: certification.Authority,
url: { ...defaultCertification.url, href: certification.Url },
date: `${certification["Started On"]} - ${certification["Finished On"] ?? "Present"}`,
});
}
}
// Projects
if (data["Projects"] && data["Projects"].length > 0) {
for (const project of data["Projects"]) {
result.sections.projects.items.push({
...defaultProject,
id: createId(),
name: project.Title,
description: project.Description,
url: { ...defaultProject.url, href: project.Url },
date: `${project["Started On"]} - ${project["Finished On"] ?? "Present"}`,
});
}
}
return resumeDataSchema.parse(result);
}
}

View File

@ -0,0 +1,10 @@
import { z } from "zod";
export const certificationSchema = z.object({
Name: z.string(),
Url: z.string().url(),
Authority: z.string(),
"Started On": z.string(),
"Finished On": z.string().optional(),
"License Number": z.string(),
});

View File

@ -0,0 +1,10 @@
import { z } from "zod";
export const educationSchema = z.object({
"School Name": z.string(),
"Start Date": z.string(),
"End Date": z.string(),
Notes: z.string().optional(),
"Degree Name": z.string(),
Activities: z.string(),
});

View File

@ -0,0 +1,8 @@
import { z } from "zod";
export const emailSchema = z.object({
"Email Address": z.string().email(),
Confirmed: z.enum(["Yes", "No"]),
Primary: z.enum(["Yes", "No"]),
"Updated On": z.string(),
});

View File

@ -0,0 +1,23 @@
import { z } from "zod";
import { certificationSchema } from "./certification";
import { educationSchema } from "./education";
import { emailSchema } from "./email";
import { languageSchema } from "./language";
import { positionSchema } from "./position";
import { profileSchema } from "./profile";
import { projectSchema } from "./project";
import { skillSchema } from "./skill";
export const linkedInSchema = z.object({
Profile: z.array(profileSchema).optional(),
"Email Addresses": z.array(emailSchema).optional(),
Certifications: z.array(certificationSchema).optional(),
Education: z.array(educationSchema).optional(),
Languages: z.array(languageSchema).optional(),
Positions: z.array(positionSchema).optional(),
Projects: z.array(projectSchema).optional(),
Skills: z.array(skillSchema).optional(),
});
export type LinkedIn = z.infer<typeof linkedInSchema>;

View File

@ -0,0 +1,6 @@
import { z } from "zod";
export const languageSchema = z.object({
Name: z.string(),
Proficiency: z.string().optional(),
});

View File

@ -0,0 +1,10 @@
import { z } from "zod";
export const positionSchema = z.object({
"Company Name": z.string(),
Title: z.string(),
Description: z.string().optional(),
Location: z.string(),
"Started On": z.string(),
"Finished On": z.string().optional(),
});

View File

@ -0,0 +1,17 @@
import { z } from "zod";
export const profileSchema = z.object({
"First Name": z.string(),
"Last Name": z.string(),
"Maiden Name": z.string().optional(),
Address: z.string(),
"Birth Date": z.string(),
Headline: z.string(),
Summary: z.string(),
Industry: z.string(),
"Zip Code": z.string().optional(),
"Geo Location": z.string(),
"Twitter Handles": z.string(),
Websites: z.string(),
"Instant Messengers": z.string().optional(),
});

View File

@ -0,0 +1,9 @@
import { z } from "zod";
export const projectSchema = z.object({
Title: z.string(),
Description: z.string(),
Url: z.string().url(),
"Started On": z.string(),
"Finished On": z.string().optional(),
});

View File

@ -0,0 +1,5 @@
import { z } from "zod";
export const skillSchema = z.object({
Name: z.string(),
});

View File

@ -0,0 +1,248 @@
import { createId } from "@paralleldrive/cuid2";
import {
defaultAward,
defaultCertification,
defaultEducation,
defaultExperience,
defaultInterest,
defaultLanguage,
defaultProfile,
defaultProject,
defaultPublication,
defaultReference,
defaultResumeData,
defaultSkill,
defaultVolunteer,
ResumeData,
} from "@reactive-resume/schema";
import { isUrl, Json } from "@reactive-resume/utils";
import { Schema } from "zod";
import { Parser } from "../interfaces/parser";
import { ReactiveResumeV3, reactiveResumeV3Schema } from "./schema";
export * from "./schema";
export class ReactiveResumeV3Parser implements Parser<Json, ReactiveResumeV3> {
schema: Schema;
constructor() {
this.schema = reactiveResumeV3Schema;
}
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 ReactiveResumeV3;
}
convert(data: ReactiveResumeV3) {
const result = JSON.parse(JSON.stringify(defaultResumeData)) as ResumeData;
// Basics
result.basics.name = data.basics.name;
result.basics.email = data.basics.email;
result.basics.phone = data.basics.phone;
result.basics.headline = data.basics.headline;
result.basics.location = data.basics.location.address;
result.sections.summary.content = data.basics.summary;
result.basics.picture.url = isUrl(data.basics.photo.url) ? data.basics.photo.url : "";
// Profiles
if (data.basics.profiles) {
for (const profile of data.basics.profiles) {
result.sections.profiles.items.push({
...defaultProfile,
id: createId(),
network: profile.network,
username: profile.username,
icon: profile.network.toLocaleLowerCase(),
url: { ...defaultProfile.url, href: isUrl(profile.url) ? profile.url : "" },
});
}
}
// Work
if (data.sections.work.items) {
for (const work of data.sections.work.items) {
result.sections.experience.items.push({
...defaultExperience,
id: createId(),
company: work.name,
position: work.position,
summary: work.summary,
date: `${work.date.start} - ${work.date.end}`,
url: { ...defaultExperience.url, href: isUrl(work.url) ? work.url : "" },
});
}
}
// Awards
if (data.sections.awards.items) {
for (const award of data.sections.awards.items) {
result.sections.awards.items.push({
...defaultAward,
id: createId(),
title: award.title,
awarder: award.awarder,
date: award.date,
summary: award.summary,
url: { ...defaultAward.url, href: isUrl(award.url) ? award.url : "" },
});
}
}
// Skills
if (data.sections.skills.items) {
for (const skill of data.sections.skills.items) {
result.sections.skills.items.push({
...defaultSkill,
id: createId(),
name: skill.name,
level: Math.floor(skill.levelNum / 2),
description: skill.level,
keywords: skill.keywords,
});
}
}
// Projects
if (data.sections.projects.items) {
for (const project of data.sections.projects.items) {
result.sections.projects.items.push({
...defaultProject,
id: createId(),
name: project.name,
summary: project.summary,
description: project.description,
date: `${project.date.start} - ${project.date.end}`,
keywords: project.keywords,
url: { ...defaultProject.url, href: isUrl(project.url) ? project.url : "" },
});
}
}
// Education
if (data.sections.education.items) {
for (const education of data.sections.education.items) {
result.sections.education.items.push({
...defaultEducation,
id: createId(),
institution: education.institution,
studyType: education.degree,
area: education.area,
score: education.score,
summary: education.summary,
date: `${education.date.start} - ${education.date.end}`,
url: { ...defaultEducation.url, href: isUrl(education.url) ? education.url : "" },
});
}
}
// Interests
if (data.sections.interests.items) {
for (const interest of data.sections.interests.items) {
result.sections.interests.items.push({
...defaultInterest,
id: createId(),
name: interest.name,
keywords: interest.keywords,
});
}
}
// Languages
if (data.sections.languages.items) {
for (const language of data.sections.languages.items) {
result.sections.languages.items.push({
...defaultLanguage,
id: createId(),
name: language.name,
fluency: language.level,
fluencyLevel: Math.floor(language.levelNum / 2),
});
}
}
// Volunteer
if (data.sections.volunteer.items) {
for (const volunteer of data.sections.volunteer.items) {
result.sections.volunteer.items.push({
...defaultVolunteer,
id: createId(),
organization: volunteer.organization,
position: volunteer.position,
summary: volunteer.summary,
date: `${volunteer.date.start} - ${volunteer.date.end}`,
url: { ...defaultVolunteer.url, href: isUrl(volunteer.url) ? volunteer.url : "" },
});
}
}
// References
if (data.sections.references.items) {
for (const reference of data.sections.references.items) {
result.sections.references.items.push({
...defaultReference,
id: createId(),
name: reference.name,
summary: reference.summary,
description: reference.relationship,
});
}
}
// Publications
if (data.sections.publications.items) {
for (const publication of data.sections.publications.items) {
result.sections.publications.items.push({
...defaultPublication,
id: createId(),
name: publication.name,
summary: publication.summary,
date: publication.date,
url: { ...defaultPublication.url, href: isUrl(publication.url) ? publication.url : "" },
});
}
}
// Certifications
if (data.sections.certifications.items) {
for (const certification of data.sections.certifications.items) {
result.sections.certifications.items.push({
...defaultCertification,
id: createId(),
name: certification.name,
issuer: certification.issuer,
summary: certification.summary,
date: certification.date,
url: {
...defaultCertification.url,
href: isUrl(certification.url) ? certification.url : "",
},
});
}
}
return result;
}
}

View File

@ -0,0 +1,179 @@
import { z } from "zod";
const dateSchema = z.object({ start: z.string(), end: z.string() });
const profileSchema = z.object({
id: z.string(),
url: z.string(),
network: z.string(),
username: z.string(),
});
const basicsSchema = z.object({
name: z.string(),
email: z.literal("").or(z.string().email()),
phone: z.string(),
headline: z.string(),
summary: z.string(),
birthdate: z.string(),
website: z.string(),
profiles: z.array(profileSchema),
location: z.object({
address: z.string(),
postalCode: z.string(),
city: z.string(),
country: z.string(),
region: z.string(),
}),
photo: z.object({
visible: z.boolean(),
url: z.string(),
filters: z.object({
shape: z.string(),
size: z.number(),
border: z.boolean(),
grayscale: z.boolean(),
}),
}),
});
const sectionSchema = z.object({
id: z.string(),
name: z.string(),
type: z.enum(["basic", "custom"]),
columns: z.number(),
visible: z.boolean(),
});
const workSchema = z.object({
id: z.string(),
url: z.string(),
date: dateSchema,
name: z.string(),
position: z.string(),
summary: z.string(),
});
const awardSchema = z.object({
id: z.string(),
url: z.string(),
date: z.string(),
title: z.string(),
awarder: z.string(),
summary: z.string(),
});
const skillSchema = z.object({
id: z.string(),
name: z.string(),
level: z.string(),
keywords: z.array(z.string()),
levelNum: z.number(),
});
const projectSchema = z.object({
id: z.string(),
url: z.string(),
date: dateSchema,
name: z.string(),
summary: z.string(),
keywords: z.array(z.string()),
description: z.string(),
});
const educationSchema = z.object({
id: z.string(),
url: z.string(),
area: z.string(),
date: dateSchema,
score: z.string(),
degree: z.string(),
courses: z.array(z.string()),
summary: z.string(),
institution: z.string(),
});
const interestSchema = z.object({
id: z.string(),
name: z.string(),
keywords: z.array(z.string()),
});
const languageSchema = z.object({
id: z.string(),
name: z.string(),
level: z.string(),
levelNum: z.number(),
});
const volunteerSchema = z.object({
id: z.string(),
organization: z.string(),
position: z.string(),
date: dateSchema,
url: z.string(),
summary: z.string(),
});
const referenceSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string(),
phone: z.string(),
summary: z.string(),
relationship: z.string(),
});
const publicationSchema = z.object({
id: z.string(),
url: z.string(),
date: z.string(),
name: z.string(),
publisher: z.string(),
summary: z.string(),
});
const certificationSchema = z.object({
id: z.string(),
url: z.string(),
date: z.string(),
name: z.string(),
issuer: z.string(),
summary: z.string(),
});
const metadataSchema = z.object({
css: z.object({ value: z.string(), visible: z.boolean() }),
date: z.object({ format: z.string() }),
theme: z.object({ text: z.string(), primary: z.string(), background: z.string() }),
layout: z.array(z.array(z.array(z.string()))),
locale: z.string(),
template: z.string(),
typography: z.object({
size: z.object({ body: z.number(), heading: z.number() }),
family: z.object({ body: z.string(), heading: z.string() }),
}),
});
export const reactiveResumeV3Schema = z.object({
public: z.boolean(),
basics: basicsSchema,
sections: z.object({
work: sectionSchema.extend({ items: z.array(workSchema) }),
awards: sectionSchema.extend({ items: z.array(awardSchema) }),
skills: sectionSchema.extend({ items: z.array(skillSchema) }),
projects: sectionSchema.extend({ items: z.array(projectSchema) }),
education: sectionSchema.extend({ items: z.array(educationSchema) }),
interests: sectionSchema.extend({ items: z.array(interestSchema) }),
languages: sectionSchema.extend({ items: z.array(languageSchema) }),
volunteer: sectionSchema.extend({ items: z.array(volunteerSchema) }),
references: sectionSchema.extend({ items: z.array(referenceSchema) }),
publications: sectionSchema.extend({ items: z.array(publicationSchema) }),
certifications: sectionSchema.extend({
items: z.array(certificationSchema),
}),
}),
metadata: metadataSchema,
});
export type ReactiveResumeV3 = z.infer<typeof reactiveResumeV3Schema>;

View File

@ -0,0 +1,42 @@
import { ResumeData, resumeDataSchema } from "@reactive-resume/schema";
import { Json } from "@reactive-resume/utils";
import { Schema } from "zod";
import { Parser } from "../interfaces/parser";
export class ReactiveResumeParser implements Parser<Json, ResumeData> {
schema: Schema;
constructor() {
this.schema = resumeDataSchema;
}
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 ResumeData;
}
convert(data: ResumeData) {
return data;
}
}

22
libs/parser/tsconfig.json Normal file
View File

@ -0,0 +1,22 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
}

View File

@ -0,0 +1,19 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"]
},
"include": [
"vite.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
]
}

View File

@ -0,0 +1,15 @@
import { nxViteTsPaths } from "@nx/vite/plugins/nx-tsconfig-paths.plugin";
import { defineConfig } from "vite";
export default defineConfig({
cacheDir: "../../node_modules/.vite/parser",
plugins: [nxViteTsPaths()],
test: {
globals: true,
environment: "jsdom",
cache: { dir: "../../node_modules/.vitest" },
include: ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
},
});