feat(client/import): implement import json from reactive resume v2

This commit is contained in:
Amruth Pillai 2022-03-14 21:50:04 +01:00
parent ed78f8fc4e
commit 42408ce8c5
No known key found for this signature in database
GPG Key ID: E3C57DF9B80855AD
5 changed files with 368 additions and 4 deletions

View File

@ -24,6 +24,7 @@ const ImportExternalModal: React.FC = () => {
const linkedinInputRef = useRef<HTMLInputElement>(null);
const jsonResumeInputRef = useRef<HTMLInputElement>(null);
const reactiveResumeInputRef = useRef<HTMLInputElement>(null);
const reactiveResumeV2InputRef = useRef<HTMLInputElement>(null);
const { open: isOpen } = useAppSelector((state) => state.modal['dashboard.import-external']);
@ -49,6 +50,11 @@ const ImportExternalModal: React.FC = () => {
reactiveResumeInputRef.current.click();
reactiveResumeInputRef.current.value = '';
}
} else if (integration === 'reactive-resume-v2') {
if (reactiveResumeV2InputRef.current) {
reactiveResumeV2InputRef.current.click();
reactiveResumeV2InputRef.current.value = '';
}
}
};
@ -171,7 +177,7 @@ const ImportExternalModal: React.FC = () => {
<p className="mb-2">{t('modals.dashboard.import-external.reactive-resume.body')}</p>
<div>
<div className="flex gap-4">
<Button
variant="contained"
disabled={isLoading}
@ -181,6 +187,15 @@ const ImportExternalModal: React.FC = () => {
{t('modals.dashboard.import-external.reactive-resume.actions.upload-json')}
</Button>
<Button
variant="contained"
disabled={isLoading}
startIcon={<UploadFile />}
onClick={() => handleClick('reactive-resume-v2')}
>
{t('modals.dashboard.import-external.reactive-resume.actions.upload-json-v2')}
</Button>
<input
hidden
type="file"
@ -188,6 +203,14 @@ const ImportExternalModal: React.FC = () => {
onChange={(event) => handleChange(event, 'reactive-resume')}
accept="application/json"
/>
<input
hidden
type="file"
ref={reactiveResumeV2InputRef}
onChange={(event) => handleChange(event, 'reactive-resume-v2')}
accept="application/json"
/>
</div>
</div>
</BaseModal>

View File

@ -111,9 +111,10 @@
},
"reactive-resume": {
"actions": {
"upload-json": "Upload JSON"
"upload-json": "Upload JSON",
"upload-json-v2": "Upload JSON from v2"
},
"body": "If you have a JSON that was exported with the current version of Reactive Resume, you can import it back here to get an editable version again. Previous versions of Reactive Resume are unfortunately not supported at the moment.",
"body": "If you have a JSON that was exported with the current version of Reactive Resume, you can import it back here to get an editable version again.",
"heading": "Import From Reactive Resume"
}
},

View File

@ -1 +1 @@
export type Integration = 'linkedin' | 'json-resume' | 'reactive-resume';
export type Integration = 'linkedin' | 'json-resume' | 'reactive-resume' | 'reactive-resume-v2';

View File

@ -42,4 +42,15 @@ export class IntegrationsController {
return this.integrationsService.reactiveResume(userId, file.path);
}
@UseGuards(JwtAuthGuard)
@Post('reactive-resume-v2')
@UseInterceptors(FileInterceptor('file'))
reactiveResumeV2(@User('id') userId: number, @UploadedFile() file: Express.Multer.File) {
if (!file) {
throw new HttpException('You must upload a valid JSON file.', HttpStatus.BAD_REQUEST);
}
return this.integrationsService.reactiveResumeV2(userId, file.path);
}
}

View File

@ -616,4 +616,333 @@ export class IntegrationsService {
await unlink(path);
}
}
async reactiveResumeV2(userId: number, path: string): Promise<ResumeEntity> {
try {
const jsonResume = JSON.parse(await readFile(path, 'utf8'));
const resume: Partial<Resume> = cloneDeep(defaultState);
// Metadata
const timestamp = dayjs().format(FILENAME_TIMESTAMP);
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
name: `Imported from Reactive Resume V2 (${timestamp})`,
slug: `imported-from-reactive-resume-v2-${timestamp}`,
});
// Basics
try {
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
basics: {
name: get(jsonResume, 'profile.firstName') + ' ' + get(jsonResume, 'profile.lastName'),
headline: get(jsonResume, 'profile.subtitle'),
photo: {
url: get(jsonResume, 'profile.photograph'),
},
email: get(jsonResume, 'profile.email'),
phone: get(jsonResume, 'profile.phone'),
website: get(jsonResume, 'profile.website'),
summary: get(jsonResume, 'objective'),
location: {
address: get(jsonResume, 'profile.address.line1'),
postalCode: get(jsonResume, 'profile.address.pincode'),
city: get(jsonResume, 'profile.address.city'),
},
},
});
} catch {
// pass through
}
// Profiles
try {
const profiles: any[] = get(jsonResume, 'social.items', []);
profiles.forEach((profile) => {
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
basics: {
profiles: [
...resume.basics.profiles,
{
id: uuidv4(),
url: get(profile, 'url'),
network: get(profile, 'network'),
username: get(profile, 'username'),
},
],
},
});
});
} catch {
// pass through
}
// Work
try {
const work: any[] = get(jsonResume, 'work.items', []);
work.forEach((item) => {
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
sections: {
work: {
items: [
...get(resume, 'sections.work.items', []),
{
id: uuidv4(),
name: get(item, 'company'),
position: get(item, 'position'),
summary: get(item, 'summary'),
url: get(item, 'website'),
date: {
start: dayjs(get(item, 'startDate')).toISOString(),
end: dayjs(get(item, 'endDate')).toISOString(),
},
} as WorkExperience,
],
},
},
});
});
} catch {
// pass through
}
// Education
try {
const education: any[] = get(jsonResume, 'education.items', []);
education.forEach((item) => {
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
sections: {
education: {
items: [
...get(resume, 'sections.education.items', []),
{
id: uuidv4(),
institution: get(item, 'institution'),
degree: get(item, 'studyType'),
url: get(item, 'url'),
score: get(item, 'gpa'),
area: get(item, 'field'),
summary: get(item, 'summary'),
courses: get(item, 'courses', []),
date: {
start: dayjs(get(item, 'startDate')).toISOString(),
end: dayjs(get(item, 'endDate')).toISOString(),
},
} as Education,
],
},
},
});
});
} catch {
// pass through
}
// Awards
try {
const awards: any[] = get(jsonResume, 'awards.items', []);
awards.forEach((award) => {
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
sections: {
awards: {
items: [
...get(resume, 'sections.awards.items', []),
{
id: uuidv4(),
title: get(award, 'title'),
awarder: get(award, 'awarder'),
summary: get(award, 'summary'),
date: dayjs(get(award, 'date')).toISOString(),
} as Award,
],
},
},
});
});
} catch {
// pass through
}
// Certifications
try {
const certifications: any[] = get(jsonResume, 'certifications.items', []);
certifications.forEach((certificate) => {
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
sections: {
certifications: {
items: [
...get(resume, 'sections.certifications.items', []),
{
id: uuidv4(),
name: get(certificate, 'title'),
issuer: get(certificate, 'issuer'),
summary: get(certificate, 'summary'),
date: dayjs(get(certificate, 'date')).toISOString(),
} as Certificate,
],
},
},
});
});
} catch {
// pass through
}
// Skills
try {
const skills: any[] = get(jsonResume, 'skills.items', []);
skills.forEach((skill) => {
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
sections: {
skills: {
items: [
...get(resume, 'sections.skills.items', []),
{
id: uuidv4(),
name: get(skill, 'name'),
level: get(skill, 'level'),
levelNum: 5,
} as Skill,
],
},
},
});
});
} catch {
// pass through
}
// Languages
try {
const languages: any[] = get(jsonResume, 'languages.items', []);
languages.forEach((language) => {
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
sections: {
languages: {
items: [
...get(resume, 'sections.languages.items', []),
{
id: uuidv4(),
name: get(language, 'name'),
level: get(language, 'fluency'),
levelNum: 5,
} as Language,
],
},
},
});
});
} catch {
// pass through
}
// Hobbies
try {
const hobbies: any[] = get(jsonResume, 'hobbies.items', []);
hobbies.forEach((hobby) => {
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
sections: {
interests: {
items: [
...get(resume, 'sections.interests.items', []),
{
id: uuidv4(),
name: get(hobby, 'name'),
keywords: get(hobby, 'keywords', []),
} as Interest,
],
},
},
});
});
} catch {
// pass through
}
// References
try {
const references: any[] = get(jsonResume, 'references.items', []);
references.forEach((reference) => {
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
sections: {
references: {
items: [
...get(resume, 'sections.references.items', []),
{
id: uuidv4(),
name: get(reference, 'name'),
relationship: get(reference, 'position'),
phone: get(reference, 'phone'),
email: get(reference, 'email'),
summary: get(reference, 'summary'),
} as Reference,
],
},
},
});
});
} catch {
// pass through
}
// Projects
try {
const projects: any[] = get(jsonResume, 'projects.items', []);
projects.forEach((project) => {
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
sections: {
projects: {
items: [
...get(resume, 'sections.projects.items', []),
{
id: uuidv4(),
name: get(project, 'title'),
summary: get(project, 'summary'),
keywords: get(project, 'keywords'),
url: get(project, 'link'),
date: {
start: dayjs(get(project, 'date')).toISOString(),
},
} as Project,
],
},
},
});
});
} catch {
// pass through
}
// Metadata
const template = get(jsonResume, 'metadata.template');
const templateWhitelist = ['onyx', 'pikachu', 'gengar', 'castform', 'glalie'];
merge<Partial<Resume>, DeepPartial<Resume>>(resume, {
metadata: {
...get(resume, 'metadata'),
typography: {
family: {
heading: get(jsonResume, 'metadata.font'),
body: get(jsonResume, 'metadata.font'),
},
size: {
heading: get(jsonResume, 'metadata.fontSize'),
body: get(jsonResume, 'metadata.fontSize'),
},
},
theme: {
background: get(jsonResume, 'metadata.colors.background'),
primary: get(jsonResume, 'metadata.colors.primary'),
text: get(jsonResume, 'metadata.colors.text'),
},
locale: get(jsonResume, 'metadata.language'),
template: templateWhitelist.includes(template) ? template : 'kakuna',
},
});
return this.resumeService.import(resume, userId);
} catch {
throw new HttpException('You must upload a valid JSON Resume file.', HttpStatus.BAD_REQUEST);
} finally {
await unlink(path);
}
}
}