Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
989d5da
Add Resume Upload Controller
taljacob2 Apr 12, 2025
9f12d47
Document ResumeController In `swagger.yaml`
taljacob2 Apr 12, 2025
357391b
Moved Resume Uploading Logic To `resources_service.ts`
taljacob2 Apr 12, 2025
d65c385
Remove Redundant Comments From `swagger.yaml`
taljacob2 Apr 12, 2025
1832700
Reorder Resume Uploading And Downloading To Be In Resources Controller
taljacob2 Apr 12, 2025
c07223c
Reformat Resume Upload Response To Text Only
taljacob2 Apr 12, 2025
4189a6e
Reorder Route Imports
taljacob2 Apr 12, 2025
4ef8079
Add Resume Tests
taljacob2 Apr 12, 2025
8abf5a6
Rename Resources Tests
taljacob2 Apr 12, 2025
df6d457
Add Upload & Download Resume Tests
taljacob2 Apr 12, 2025
fad6780
Add Prompt To AI For Resume Scoring
taljacob2 Apr 13, 2025
441874e
Add Streaming Resume Feedback Score
taljacob2 Apr 13, 2025
fffa215
Fix Parsing From Pdf, Docx, Doc And Txt
taljacob2 Apr 13, 2025
5b3dc1e
Support Uploading Resume As `.txt`/`.text`
taljacob2 Apr 13, 2025
5d6ad0c
Remove Redundant jobDescription From `swagger.yaml` Of `/resource/res…
taljacob2 Apr 13, 2025
a2e1be5
Throw Error If We Cannot Parse Resume File
taljacob2 Apr 13, 2025
59c037b
Document 400 HttpStatus In `swagger.yaml` when could not parse the re…
taljacob2 Apr 13, 2025
3d7a313
Add Initial ResumePage For Scoring Resume
taljacob2 Apr 13, 2025
d0e3d8e
Setup Token In Query Params For Streaming Scoring Resume
taljacob2 Apr 13, 2025
dadad0a
Update UI With Markdown Formatting And AutoScroll
taljacob2 Apr 13, 2025
3714195
Align The Response Text To The Left
taljacob2 Apr 13, 2025
3d7f38f
Working On Refactoring CSS
taljacob2 Apr 13, 2025
273af00
Add Common Layout For All Pages To Have Constant MarginTop
taljacob2 Apr 13, 2025
957d22a
Add Resume To TopBar
taljacob2 Apr 13, 2025
2cfbd44
Reorder Layout CSS And Index CSS
taljacob2 Apr 13, 2025
7a75de6
Fix Footer Position To To Be Relative, While Keeping Top Margin 10vh
taljacob2 Apr 13, 2025
ae15725
Extend Y Scroll
taljacob2 Apr 13, 2025
6abad9a
Fix Resume Tests With Authentication Bearer
taljacob2 Apr 13, 2025
46d64b9
Fix frontend Build, And Rename ResumePage To Resume
taljacob2 Apr 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions nextstep-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
},
"homepage": "https://github.com/NextStepFinalProject/NextStep#readme",
"dependencies": {
"@types/pdf-parse": "^1.1.5",
"axios": "^1.8.3",
"bcrypt": "^5.1.1",
"body-parser": "^1.20.3",
"cors": "^2.8.5",
Expand All @@ -34,13 +36,15 @@
"jest-junit": "^16.0.0",
"js-yaml": "^4.1.0",
"jsonwebtoken": "^9.0.2",
"mammoth": "^1.9.0",
"mongoose": "^8.8.2",
"multer": "^1.4.5-lts.1",
"office-text-extractor": "^3.0.3",
"pdf-lib": "^1.17.1",
"socket.io": "^4.8.1",
"supertest": "^7.0.0",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1",
"axios": "^1.8.3"
"swagger-ui-express": "^5.0.1"
},
"devDependencies": {
"@babel/preset-env": "^7.26.0",
Expand Down
7 changes: 2 additions & 5 deletions nextstep-backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {config} from "./config/config";
import validateUser from "./middleware/validateUser";
import loadOpenApiFile from "./openapi/openapi_loader";
import resource_routes from './routes/resources_routes';

import resume_routes from './routes/resume_routes';

const specs = swaggerJsdoc(options);

Expand Down Expand Up @@ -43,10 +43,8 @@ app.use(bodyParser.json());
app.use(removeUndefinedOrEmptyFields);
app.use(bodyParser.urlencoded({ extended: true }));


app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(loadOpenApiFile() as JsonObject));


// Add Authentication for all routes except the ones listed below
app.use(authenticateToken.unless({
path: [
Expand All @@ -68,14 +66,13 @@ app.use(authenticateToken.unless({
// To block queries without Authentication
app.use(authenticateTokenForParams);



app.use('/auth', authRoutes);
app.use('/comment', commentsRoutes);
app.use('/post', postsRoutes);
app.use("/user/:id", validateUser);
app.use('/user', usersRoutes);
app.use('/resource', resource_routes);
app.use('/room', roomsRoutes);
app.use('/resume', resume_routes);

export { app, corsOptions };
4 changes: 3 additions & 1 deletion nextstep-backend/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export const config = {
},
resources: {
imagesDirectoryPath: () => 'resources/images',
imageMaxSize: () => 10 * 1024 * 1024 // Max file size: 10MB
imageMaxSize: () => 10 * 1024 * 1024, // Max file size: 10MB
resumesDirectoryPath: () => 'resources/resumes',
resumeMaxSize: () => 5 * 1024 * 1024 // Max file size: 5MB
},
chatAi: {
api_url: () => process.env.CHAT_AI_API_URL || 'https://openrouter.ai/api/v1/chat/completions',
Expand Down
39 changes: 36 additions & 3 deletions nextstep-backend/src/controllers/resources_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import { Request, Response } from 'express';
import { config } from '../config/config';
import fs from 'fs';
import path from 'path';
import { uploadImage } from '../services/resources_service';
import { uploadResume, uploadImage } from '../services/resources_service';
import multer from 'multer';
import {CustomRequest} from "types/customRequest";
import {updateUserById} from "../services/users_service";
import {handleError} from "../utils/handle_error";


const createUserImageResource = async (req: CustomRequest, res: Response) => {
try {
const imageFilename = await uploadImage(req);
Expand Down Expand Up @@ -49,4 +48,38 @@ const getImageResource = async (req: Request, res: Response) => {
}
};

export default { createUserImageResource, createImageResource, getImageResource };
const createResumeResource = async (req: Request, res: Response) => {
try {
const resumeFilename = await uploadResume(req);
return res.status(201).send(resumeFilename);
} catch (error) {
if (error instanceof multer.MulterError || error instanceof TypeError) {
return res.status(400).send(error.message);
} else {
handleError(error, res);
}
}
};

const getResumeResource = async (req: Request, res: Response) => {
try {
const { filename } = req.params;
const resumePath = path.resolve(config.resources.resumesDirectoryPath(), filename);

if (!fs.existsSync(resumePath)) {
return res.status(404).send('Resume not found');
}

res.sendFile(resumePath);
} catch (error) {
handleError(error, res);
}
};

export default {
createUserImageResource,
createImageResource,
getImageResource,
getResumeResource,
createResumeResource
};
75 changes: 75 additions & 0 deletions nextstep-backend/src/controllers/resume_controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Request, Response } from 'express';
import { config } from '../config/config';
import fs from 'fs';
import path from 'path';
import { scoreResume, streamScoreResume } from '../services/resume_service';
import multer from 'multer';
import { CustomRequest } from "types/customRequest";
import { handleError } from "../utils/handle_error";

const getResumeScore = async (req: Request, res: Response) => {
try {
const { filename } = req.params;
const resumePath = path.resolve(config.resources.resumesDirectoryPath(), filename);
const jobDescription = req.query.jobDescription as string;

if (!fs.existsSync(resumePath)) {
return res.status(404).send('Resume not found');
}

const scoreAndFeedback = await scoreResume(resumePath, jobDescription);
return res.status(200).send(scoreAndFeedback);
} catch (error) {
if (error instanceof TypeError) {
return res.status(400).send(error.message);
} else {
handleError(error, res);
}
}
};

const getStreamResumeScore = async (req: Request, res: Response) => {
try {
const { filename } = req.params;
const resumePath = path.resolve(config.resources.resumesDirectoryPath(), filename);
const jobDescription = req.query.jobDescription as string;

if (!fs.existsSync(resumePath)) {
return res.status(404).send('Resume not found');
}

// Set headers for SSE
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');

// Handle client disconnect
req.on('close', () => {
res.end();
});

// Stream the response
const score = await streamScoreResume(
resumePath,
jobDescription,
(chunk) => {
res.write(`data: ${JSON.stringify({ chunk })}\n\n`);
}
);

// Send the final score
res.write(`data: ${JSON.stringify({ score, done: true })}\n\n`);
res.end();
} catch (error) {
if (error instanceof TypeError) {
return res.status(400).send(error.message);
} else {
handleError(error, res);
}
}
};

export default {
getResumeScore,
getStreamResumeScore
};
10 changes: 9 additions & 1 deletion nextstep-backend/src/middleware/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,16 @@ const getTokenFromHeader = (req: CustomRequest): string | undefined => {
return authHeader?.split(' ')[1];
}

const getTokenFromQueryParams = (req: CustomRequest): string | undefined => {
return req.query.accessToken as string;
}

const authenticateTokenHandler: any & { unless: typeof unless } = async (req: CustomRequest, res: Response, next: NextFunction, ignoreExpiration = false): Promise<void> => {
const token = getTokenFromHeader(req);
let token = getTokenFromHeader(req);
if (!token) {
// If the token was not found in the headers, fall back to the query params.
token = getTokenFromQueryParams(req);
}

if (!token) {
res.status(401).json({ message: 'Access token required' });
Expand Down
Loading
Loading