Skip to content

Commit 2cacb05

Browse files
committed
Add Generate Resume Route
Signed-off-by: Tal Jacob <taljacob2@gmail.com>
1 parent 3821ad7 commit 2cacb05

5 files changed

Lines changed: 182 additions & 17 deletions

File tree

nextstep-backend/src/controllers/resume_controller.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Request, Response } from 'express';
22
import { config } from '../config/config';
33
import fs from 'fs';
44
import path from 'path';
5-
import { scoreResume, streamScoreResume, getResumeTemplates } from '../services/resume_service';
5+
import { scoreResume, streamScoreResume, getResumeTemplates, generateImprovedResume } from '../services/resume_service';
66
import multer from 'multer';
77
import { CustomRequest } from "types/customRequest";
88
import { handleError } from "../utils/handle_error";
@@ -78,8 +78,19 @@ const getTemplates = async (req: Request, res: Response) => {
7878
}
7979
};
8080

81-
export default {
82-
getResumeScore,
83-
getStreamResumeScore,
84-
getTemplates
85-
};
81+
const generateResume = async (req: Request, res: Response) => {
82+
try {
83+
const { feedback, jobDescription, templateName } = req.body;
84+
85+
if (!feedback || !jobDescription || !templateName) {
86+
return res.status(400).json({ error: 'Missing required fields' });
87+
}
88+
89+
const result = await generateImprovedResume(feedback, jobDescription, templateName);
90+
return res.status(200).json(result);
91+
} catch (error) {
92+
handleError(error, res);
93+
}
94+
};
95+
96+
export default { getResumeScore, getStreamResumeScore, getTemplates, generateResume };

nextstep-backend/src/openapi/swagger.yaml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,75 @@ paths:
987987
'500':
988988
description: Internal server error
989989

990+
/resume/generate:
991+
post:
992+
tags:
993+
- Resume
994+
summary: Generate an improved resume based on a resume template, feedback and job description
995+
security:
996+
- BearerAuth: []
997+
requestBody:
998+
required: true
999+
content:
1000+
application/json:
1001+
schema:
1002+
type: object
1003+
required:
1004+
- feedback
1005+
- jobDescription
1006+
- templateName
1007+
properties:
1008+
feedback:
1009+
type: string
1010+
description: The feedback received from the resume scoring process
1011+
jobDescription:
1012+
type: string
1013+
description: The job description used for scoring the resume
1014+
templateName:
1015+
type: string
1016+
description: The name of the template to use for generating the improved resume
1017+
responses:
1018+
'200':
1019+
description: The generated improved resume
1020+
content:
1021+
application/json:
1022+
schema:
1023+
type: object
1024+
properties:
1025+
content:
1026+
type: string
1027+
description: Base64 encoded content of the generated resume file
1028+
type:
1029+
type: string
1030+
description: MIME type of the generated resume file
1031+
enum:
1032+
- application/msword
1033+
- application/vnd.openxmlformats-officedocument.wordprocessingml.document
1034+
'400':
1035+
description: Bad request - Missing required fields
1036+
content:
1037+
application/json:
1038+
schema:
1039+
type: object
1040+
properties:
1041+
error:
1042+
type: string
1043+
description: Error message indicating missing fields
1044+
'401':
1045+
description: Unauthorized - Missing or invalid authentication token
1046+
'404':
1047+
description: Template not found
1048+
content:
1049+
application/json:
1050+
schema:
1051+
type: object
1052+
properties:
1053+
error:
1054+
type: string
1055+
description: Error message indicating template not found
1056+
'500':
1057+
description: Internal server error
1058+
9901059
/room/user/{receiverUserId}:
9911060
get:
9921061
tags:

nextstep-backend/src/routes/resume_routes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@ router.get('/streamScore/:filename', Resume.getStreamResumeScore);
1010

1111
router.get('/templates', Resume.getTemplates);
1212

13+
router.post('/generate', Resume.generateResume);
14+
1315
export default router;

nextstep-backend/src/services/resume_service.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,4 +205,53 @@ const getResumeTemplates = async (): Promise<{ name: string; content: string; ty
205205
}
206206
};
207207

208-
export { scoreResume, streamScoreResume, getResumeTemplates };
208+
const generateImprovedResume = async (
209+
feedback: string,
210+
jobDescription: string,
211+
templateName: string
212+
): Promise<{ content: string; type: string }> => {
213+
try {
214+
const templatesDir = config.assets.resumeTemplatesDirectoryPath();
215+
const templatePath = path.join(templatesDir, `${templateName}.docx`);
216+
217+
if (!fs.existsSync(templatePath)) {
218+
throw new Error(`Template ${templateName} not found`);
219+
}
220+
221+
const prompt = `You are an expert resume writer. Using the following feedback and job description, modify the resume template to implement all suggested improvements.
222+
223+
Feedback:
224+
${feedback}
225+
226+
Job Description:
227+
${jobDescription}
228+
229+
Please modify the template to:
230+
1. Keep the exact same format and structure as the template
231+
2. Implement all suggested improvements from the feedback
232+
3. Ensure the content matches the job description requirements
233+
4. Maintain professional formatting and style
234+
235+
Return the modified resume in the same format as the template.`;
236+
237+
if (!config.chatAi.turned_on()) {
238+
throw new Error('Chat AI feature is turned off');
239+
}
240+
241+
const modifiedContent = await chatWithAI(prompt, SYSTEM_TEMPLATE);
242+
243+
// For now, we'll return the original template content
244+
// TODO: Implement actual document modification
245+
const content = fs.readFileSync(templatePath);
246+
247+
return {
248+
content: content.toString('base64'),
249+
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
250+
};
251+
} catch (error) {
252+
console.error('Error generating improved resume:', error);
253+
throw error;
254+
}
255+
};
256+
257+
export { scoreResume, streamScoreResume, getResumeTemplates, generateImprovedResume };

nextstep-frontend/src/pages/Resume.tsx

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,11 @@ const Resume: React.FC = () => {
134134
const [error, setError] = useState('');
135135
const [templates, setTemplates] = useState<Array<{ name: string; content: string; type: string }>>([]);
136136
const [selectedTemplate, setSelectedTemplate] = useState<number | null>(null);
137+
const [previewOpen, setPreviewOpen] = useState(false);
138+
const [previewTemplate, setPreviewTemplate] = useState<{ name: string; content: string; type: string } | null>(null);
137139
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
138140
const [previewUrlCache, setPreviewUrlCache] = useState<Record<string, string>>({});
141+
const [generatedResume, setGeneratedResume] = useState<{ content: string; type: string } | null>(null);
139142
const fileInputRef = useRef<HTMLInputElement>(null);
140143
const feedbackEndRef = useRef<HTMLDivElement>(null);
141144

@@ -244,9 +247,30 @@ const Resume: React.FC = () => {
244247
setActiveStep((prevStep) => prevStep - 1);
245248
};
246249

247-
const handleGenerateResume = () => {
248-
// TODO: Implement resume generation
249-
console.log('Generating resume with template:', selectedTemplate);
250+
const handleGenerateResume = async () => {
251+
if (selectedTemplate === null) {
252+
setError('Please select a template');
253+
return;
254+
}
255+
256+
setLoading(true);
257+
setError('');
258+
259+
try {
260+
const selectedTemplateData = templates[selectedTemplate];
261+
const response = await api.post('/resume/generate', {
262+
feedback,
263+
jobDescription,
264+
templateName: selectedTemplateData.name
265+
});
266+
267+
setGeneratedResume(response.data);
268+
setActiveStep(2);
269+
} catch (err) {
270+
setError(err instanceof Error ? err.message : 'Failed to generate resume');
271+
} finally {
272+
setLoading(false);
273+
}
250274
};
251275

252276
const uploadToTmpFiles = async (base64Content: string, fileName: string): Promise<string> => {
@@ -515,13 +539,23 @@ const Resume: React.FC = () => {
515539
<Typography variant="h4" gutterBottom>
516540
Generate matching resume
517541
</Typography>
518-
<Button
519-
variant="contained"
520-
onClick={handleGenerateResume}
521-
disabled={selectedTemplate === null}
522-
>
523-
Generate Resume
524-
</Button>
542+
{generatedResume ? (
543+
<Box sx={{ mt: 3 }}>
544+
<Button
545+
variant="contained"
546+
href={`data:${generatedResume.type};base64,${generatedResume.content}`}
547+
download={`improved_resume${generatedResume.type.includes('docx') ? '.docx' : '.doc'}`}
548+
>
549+
Download Improved Resume
550+
</Button>
551+
</Box>
552+
) : (
553+
<Box sx={{ mt: 3, textAlign: 'center' }}>
554+
<Typography variant="body1" color="text.secondary">
555+
Your improved resume will be ready for download here.
556+
</Typography>
557+
</Box>
558+
)}
525559
</Box>
526560
);
527561
default:

0 commit comments

Comments
 (0)