feat(client/import): implement import json from reactive resume v2
This commit is contained in:
parent
ed78f8fc4e
commit
42408ce8c5
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1 +1 @@
|
|||
export type Integration = 'linkedin' | 'json-resume' | 'reactive-resume';
|
||||
export type Integration = 'linkedin' | 'json-resume' | 'reactive-resume' | 'reactive-resume-v2';
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue