mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-13 00:03:27 +10:00
release: v4.1.0
This commit is contained in:
@ -2,7 +2,7 @@ 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> {
|
||||
export type Parser<Data = unknown, T = ZodDto, Result = ResumeData> = {
|
||||
schema?: Schema;
|
||||
|
||||
readFile(file: File): Promise<Data>;
|
||||
@ -10,4 +10,4 @@ export interface Parser<Data = unknown, T = ZodDto, Result = ResumeData> {
|
||||
validate(data: Data): T | Promise<T>;
|
||||
|
||||
convert(data: T): Result | Promise<Result>;
|
||||
}
|
||||
};
|
||||
|
||||
@ -33,19 +33,22 @@ export class JsonResumeParser implements Parser<Json, JsonResume> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
// eslint-disable-next-line unicorn/prefer-add-event-listener
|
||||
reader.onload = () => {
|
||||
try {
|
||||
const result = JSON.parse(reader.result as string) as Json;
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
reject(new Error("Failed to parse JSON"));
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line unicorn/prefer-add-event-listener
|
||||
reader.onerror = () => {
|
||||
reject(new Error("Failed to read the file"));
|
||||
};
|
||||
|
||||
// eslint-disable-next-line unicorn/prefer-blob-reading-methods
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
|
||||
@ -4,10 +4,7 @@ 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",
|
||||
);
|
||||
.regex(/^([12]\d{3}-[01]\d-[0-3]\d|[12]\d{3}-[01]\d|[12]\d{3})$/, "ISO8601 Date Format");
|
||||
|
||||
const locationSchema = z.object({
|
||||
address: z.string().optional(),
|
||||
|
||||
@ -20,6 +20,11 @@ import { LinkedIn, linkedInSchema } from "./schema";
|
||||
|
||||
export * from "./schema";
|
||||
|
||||
const avoidTooShort = (name: string, len: number) => {
|
||||
if (!name || name.length < len) return "Unknown";
|
||||
return name;
|
||||
};
|
||||
|
||||
export class LinkedInParser implements Parser<JSZip, LinkedIn> {
|
||||
schema: Schema;
|
||||
|
||||
@ -44,8 +49,7 @@ export class LinkedInParser implements Parser<JSZip, LinkedIn> {
|
||||
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;
|
||||
result[key] = await parseCSV(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -56,11 +60,6 @@ export class LinkedInParser implements Parser<JSZip, LinkedIn> {
|
||||
convert(data: LinkedIn) {
|
||||
const result = JSON.parse(JSON.stringify(defaultResumeData)) as ResumeData;
|
||||
|
||||
const avoidTooShort = (name: string, len: number) => {
|
||||
if (!name || name.length < len) return "Unknown";
|
||||
return name;
|
||||
};
|
||||
|
||||
// Profile
|
||||
if (data.Profile && data.Profile.length > 0) {
|
||||
const profile = data.Profile[0];
|
||||
@ -94,8 +93,8 @@ export class LinkedInParser implements Parser<JSZip, LinkedIn> {
|
||||
}
|
||||
|
||||
// Positions
|
||||
if (data["Positions"] && data["Positions"].length > 0) {
|
||||
for (const position of data["Positions"]) {
|
||||
if (data.Positions && data.Positions.length > 0) {
|
||||
for (const position of data.Positions) {
|
||||
result.sections.experience.items.push({
|
||||
...defaultExperience,
|
||||
id: createId(),
|
||||
@ -109,8 +108,8 @@ export class LinkedInParser implements Parser<JSZip, LinkedIn> {
|
||||
}
|
||||
|
||||
// Education
|
||||
if (data["Education"] && data["Education"].length > 0) {
|
||||
for (const education of data["Education"]) {
|
||||
if (data.Education && data.Education.length > 0) {
|
||||
for (const education of data.Education) {
|
||||
result.sections.education.items.push({
|
||||
...defaultEducation,
|
||||
id: createId(),
|
||||
@ -123,8 +122,8 @@ export class LinkedInParser implements Parser<JSZip, LinkedIn> {
|
||||
}
|
||||
|
||||
// Skills
|
||||
if (data["Skills"] && data["Skills"].length > 0) {
|
||||
for (const skill of data["Skills"]) {
|
||||
if (data.Skills && data.Skills.length > 0) {
|
||||
for (const skill of data.Skills) {
|
||||
result.sections.skills.items.push({
|
||||
...defaultSkill,
|
||||
id: createId(),
|
||||
@ -134,8 +133,8 @@ export class LinkedInParser implements Parser<JSZip, LinkedIn> {
|
||||
}
|
||||
|
||||
// Languages
|
||||
if (data["Languages"] && data["Languages"].length > 0) {
|
||||
for (const language of data["Languages"]) {
|
||||
if (data.Languages && data.Languages.length > 0) {
|
||||
for (const language of data.Languages) {
|
||||
result.sections.languages.items.push({
|
||||
...defaultLanguage,
|
||||
id: createId(),
|
||||
@ -146,8 +145,8 @@ export class LinkedInParser implements Parser<JSZip, LinkedIn> {
|
||||
}
|
||||
|
||||
// Certifications
|
||||
if (data["Certifications"] && data["Certifications"].length > 0) {
|
||||
for (const certification of data["Certifications"]) {
|
||||
if (data.Certifications && data.Certifications.length > 0) {
|
||||
for (const certification of data.Certifications) {
|
||||
result.sections.certifications.items.push({
|
||||
...defaultCertification,
|
||||
id: createId(),
|
||||
@ -160,8 +159,8 @@ export class LinkedInParser implements Parser<JSZip, LinkedIn> {
|
||||
}
|
||||
|
||||
// Projects
|
||||
if (data["Projects"] && data["Projects"].length > 0) {
|
||||
for (const project of data["Projects"]) {
|
||||
if (data.Projects && data.Projects.length > 0) {
|
||||
for (const project of data.Projects) {
|
||||
result.sections.projects.items.push({
|
||||
...defaultProject,
|
||||
id: createId(),
|
||||
|
||||
@ -3,7 +3,7 @@ import { z } from "zod";
|
||||
export const educationSchema = z.object({
|
||||
"School Name": z.string(),
|
||||
"Start Date": z.string(),
|
||||
"End Date": z.string(),
|
||||
"End Date": z.string().optional(),
|
||||
Notes: z.string().optional(),
|
||||
"Degree Name": z.string(),
|
||||
Activities: z.string(),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import {
|
||||
defaultAward,
|
||||
@ -34,19 +35,22 @@ export class ReactiveResumeV3Parser implements Parser<Json, ReactiveResumeV3> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
// eslint-disable-next-line unicorn/prefer-add-event-listener
|
||||
reader.onload = () => {
|
||||
try {
|
||||
const result = JSON.parse(reader.result as string) as Json;
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
reject(new Error("Failed to parse JSON"));
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line unicorn/prefer-add-event-listener
|
||||
reader.onerror = () => {
|
||||
reject(new Error("Failed to read the file"));
|
||||
};
|
||||
|
||||
// eslint-disable-next-line unicorn/prefer-blob-reading-methods
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
@ -70,7 +74,7 @@ export class ReactiveResumeV3Parser implements Parser<Json, ReactiveResumeV3> {
|
||||
result.basics.picture.url = isUrl(data.basics.photo.url) ? data.basics.photo.url! : "";
|
||||
|
||||
// Profiles
|
||||
if (data.basics.profiles) {
|
||||
if (data.basics.profiles && data.basics.profiles.length > 0) {
|
||||
for (const profile of data.basics.profiles) {
|
||||
result.sections.profiles.items.push({
|
||||
...defaultProfile,
|
||||
@ -84,7 +88,7 @@ export class ReactiveResumeV3Parser implements Parser<Json, ReactiveResumeV3> {
|
||||
}
|
||||
|
||||
// Work
|
||||
if (data.sections.work.items) {
|
||||
if (data.sections.work?.items && data.sections.work.items.length > 0) {
|
||||
for (const work of data.sections.work.items) {
|
||||
if (!work) continue;
|
||||
|
||||
@ -101,7 +105,7 @@ export class ReactiveResumeV3Parser implements Parser<Json, ReactiveResumeV3> {
|
||||
}
|
||||
|
||||
// Awards
|
||||
if (data.sections.awards.items) {
|
||||
if (data.sections.awards?.items && data.sections.awards.items.length > 0) {
|
||||
for (const award of data.sections.awards.items) {
|
||||
if (!award) continue;
|
||||
|
||||
@ -118,7 +122,7 @@ export class ReactiveResumeV3Parser implements Parser<Json, ReactiveResumeV3> {
|
||||
}
|
||||
|
||||
// Skills
|
||||
if (data.sections.skills.items) {
|
||||
if (data.sections.skills?.items && data.sections.skills.items.length > 0) {
|
||||
for (const skill of data.sections.skills.items) {
|
||||
if (!skill) continue;
|
||||
|
||||
@ -136,7 +140,7 @@ export class ReactiveResumeV3Parser implements Parser<Json, ReactiveResumeV3> {
|
||||
}
|
||||
|
||||
// Projects
|
||||
if (data.sections.projects.items) {
|
||||
if (data.sections.projects?.items && data.sections.projects.items.length > 0) {
|
||||
for (const project of data.sections.projects.items) {
|
||||
if (!project) continue;
|
||||
|
||||
@ -156,7 +160,7 @@ export class ReactiveResumeV3Parser implements Parser<Json, ReactiveResumeV3> {
|
||||
}
|
||||
|
||||
// Education
|
||||
if (data.sections.education.items) {
|
||||
if (data.sections.education?.items && data.sections.education.items.length > 0) {
|
||||
for (const education of data.sections.education.items) {
|
||||
if (!education) continue;
|
||||
|
||||
@ -175,7 +179,7 @@ export class ReactiveResumeV3Parser implements Parser<Json, ReactiveResumeV3> {
|
||||
}
|
||||
|
||||
// Interests
|
||||
if (data.sections.interests.items) {
|
||||
if (data.sections.interests?.items && data.sections.interests.items.length > 0) {
|
||||
for (const interest of data.sections.interests.items) {
|
||||
if (!interest) continue;
|
||||
|
||||
@ -191,7 +195,7 @@ export class ReactiveResumeV3Parser implements Parser<Json, ReactiveResumeV3> {
|
||||
}
|
||||
|
||||
// Languages
|
||||
if (data.sections.languages.items) {
|
||||
if (data.sections.languages?.items && data.sections.languages.items.length > 0) {
|
||||
for (const language of data.sections.languages.items) {
|
||||
if (!language) continue;
|
||||
|
||||
@ -206,7 +210,7 @@ export class ReactiveResumeV3Parser implements Parser<Json, ReactiveResumeV3> {
|
||||
}
|
||||
|
||||
// Volunteer
|
||||
if (data.sections.volunteer.items) {
|
||||
if (data.sections.volunteer?.items && data.sections.volunteer.items.length > 0) {
|
||||
for (const volunteer of data.sections.volunteer.items) {
|
||||
if (!volunteer) continue;
|
||||
|
||||
@ -223,7 +227,7 @@ export class ReactiveResumeV3Parser implements Parser<Json, ReactiveResumeV3> {
|
||||
}
|
||||
|
||||
// References
|
||||
if (data.sections.references.items) {
|
||||
if (data.sections.references?.items && data.sections.references.items.length > 0) {
|
||||
for (const reference of data.sections.references.items) {
|
||||
if (!reference) continue;
|
||||
|
||||
@ -238,7 +242,7 @@ export class ReactiveResumeV3Parser implements Parser<Json, ReactiveResumeV3> {
|
||||
}
|
||||
|
||||
// Publications
|
||||
if (data.sections.publications.items) {
|
||||
if (data.sections.publications?.items && data.sections.publications.items.length > 0) {
|
||||
for (const publication of data.sections.publications.items) {
|
||||
if (!publication) continue;
|
||||
|
||||
@ -254,7 +258,7 @@ export class ReactiveResumeV3Parser implements Parser<Json, ReactiveResumeV3> {
|
||||
}
|
||||
|
||||
// Certifications
|
||||
if (data.sections.certifications.items) {
|
||||
if (data.sections.certifications?.items && data.sections.certifications.items.length > 0) {
|
||||
for (const certification of data.sections.certifications.items) {
|
||||
if (!certification) continue;
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ const basicsSchema = z.object({
|
||||
.optional(),
|
||||
birthdate: z.string().optional(),
|
||||
website: z.string().optional(),
|
||||
profiles: z.array(profileSchema),
|
||||
profiles: z.array(profileSchema).optional(),
|
||||
location: z.object({
|
||||
address: z.string().optional(),
|
||||
postalCode: z.string().optional(),
|
||||
@ -206,19 +206,21 @@ 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),
|
||||
}),
|
||||
work: sectionSchema.extend({ items: z.array(workSchema) }).optional(),
|
||||
awards: sectionSchema.extend({ items: z.array(awardSchema) }).optional(),
|
||||
skills: sectionSchema.extend({ items: z.array(skillSchema) }).optional(),
|
||||
projects: sectionSchema.extend({ items: z.array(projectSchema) }).optional(),
|
||||
education: sectionSchema.extend({ items: z.array(educationSchema) }).optional(),
|
||||
interests: sectionSchema.extend({ items: z.array(interestSchema) }).optional(),
|
||||
languages: sectionSchema.extend({ items: z.array(languageSchema) }).optional(),
|
||||
volunteer: sectionSchema.extend({ items: z.array(volunteerSchema) }).optional(),
|
||||
references: sectionSchema.extend({ items: z.array(referenceSchema) }).optional(),
|
||||
publications: sectionSchema.extend({ items: z.array(publicationSchema) }).optional(),
|
||||
certifications: sectionSchema
|
||||
.extend({
|
||||
items: z.array(certificationSchema),
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
metadata: metadataSchema,
|
||||
});
|
||||
|
||||
@ -15,19 +15,22 @@ export class ReactiveResumeParser implements Parser<Json, ResumeData> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
// eslint-disable-next-line unicorn/prefer-add-event-listener
|
||||
reader.onload = () => {
|
||||
try {
|
||||
const result = JSON.parse(reader.result as string) as Json;
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
reject(new Error("Failed to parse JSON"));
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line unicorn/prefer-add-event-listener
|
||||
reader.onerror = () => {
|
||||
reject(new Error("Failed to read the file"));
|
||||
};
|
||||
|
||||
// eslint-disable-next-line unicorn/prefer-blob-reading-methods
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user