Skip to content

Commit 96e11bb

Browse files
committed
Merge tag 'NXD-8-alpha.4' into NXD-9-Add-main-app-design
2 parents 5878753 + e1c1e28 commit 96e11bb

19 files changed

Lines changed: 816 additions & 90 deletions
Binary file not shown.
Binary file not shown.
78.6 KB
Binary file not shown.
Binary file not shown.
Binary file not shown.

nextstep-backend/src/config/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ export const config = {
2020
resumesDirectoryPath: () => 'resources/resumes',
2121
resumeMaxSize: () => 5 * 1024 * 1024 // Max file size: 5MB
2222
},
23+
assets: {
24+
resumeTemplatesDirectoryPath: () => 'assets/resume-templates',
25+
},
2326
chatAi: {
2427
api_url: () => process.env.CHAT_AI_API_URL || 'https://openrouter.ai/api/v1/chat/completions',
2528
api_key: () => process.env.OPENROUTER_API_KEY || undefined,

nextstep-backend/src/controllers/resume_controller.ts

Lines changed: 26 additions & 5 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 } 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";
@@ -69,7 +69,28 @@ const getStreamResumeScore = async (req: Request, res: Response) => {
6969
}
7070
};
7171

72-
export default {
73-
getResumeScore,
74-
getStreamResumeScore
75-
};
72+
const getTemplates = async (req: Request, res: Response) => {
73+
try {
74+
const templates = await getResumeTemplates();
75+
return res.status(200).json(templates);
76+
} catch (error) {
77+
handleError(error, res);
78+
}
79+
};
80+
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: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,110 @@ paths:
952952
'500':
953953
description: Internal server error
954954

955+
/resume/templates:
956+
get:
957+
tags:
958+
- Resume
959+
summary: Get available resume templates
960+
security:
961+
- BearerAuth: []
962+
responses:
963+
'200':
964+
description: List of available resume templates retrieved successfully
965+
content:
966+
application/json:
967+
schema:
968+
type: array
969+
items:
970+
type: object
971+
properties:
972+
name:
973+
type: string
974+
description: The name of the template (with extension)
975+
content:
976+
type: string
977+
description: Base64 encoded content of the template file
978+
type:
979+
type: string
980+
description: MIME type of the template file
981+
enum:
982+
- application/pdf
983+
- application/msword
984+
- application/vnd.openxmlformats-officedocument.wordprocessingml.document
985+
'401':
986+
description: Unauthorized
987+
'500':
988+
description: Internal server error
989+
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+
9551059
/room/user/{receiverUserId}:
9561060
get:
9571061
tags:

nextstep-backend/src/routes/resume_routes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,8 @@ router.get('/score/:filename', Resume.getResumeScore);
88

99
router.get('/streamScore/:filename', Resume.getStreamResumeScore);
1010

11+
router.get('/templates', Resume.getTemplates);
12+
13+
router.post('/generate', Resume.generateResume);
14+
1115
export default router;

nextstep-backend/src/services/resume_service.ts

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,92 @@ const streamScoreResume = async (
166166
}
167167
};
168168

169-
export { scoreResume, streamScoreResume };
169+
const getResumeTemplates = async (): Promise<{ name: string; content: string; type: string }[]> => {
170+
try {
171+
const templatesDir = config.assets.resumeTemplatesDirectoryPath();
172+
if (!fs.existsSync(templatesDir)) {
173+
return [];
174+
}
175+
176+
const files = fs.readdirSync(templatesDir);
177+
const templates = await Promise.all(
178+
files
179+
.filter(file => {
180+
const ext = path.extname(file).toLowerCase();
181+
return ['.pdf', '.doc', '.docx'].includes(ext);
182+
})
183+
.map(async file => {
184+
const filePath = path.join(templatesDir, file);
185+
const content = fs.readFileSync(filePath);
186+
const base64Content = content.toString('base64');
187+
const mimeType = {
188+
'.pdf': 'application/pdf',
189+
'.doc': 'application/msword',
190+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
191+
}[path.extname(file).toLowerCase()] || 'application/octet-stream';
192+
193+
return {
194+
name: path.basename(file),
195+
content: base64Content,
196+
type: mimeType
197+
};
198+
})
199+
);
200+
201+
return templates;
202+
} catch (error) {
203+
console.error('Error reading resume templates:', error);
204+
return [];
205+
}
206+
};
207+
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);
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 };

0 commit comments

Comments
 (0)