All files / controllers resume_controller.ts

28.04% Statements 23/82
23.07% Branches 6/26
25% Functions 2/8
28.04% Lines 23/82

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160                        1x 1x   1x 2x   1x 3x 3x 3x 3x   3x 1x     2x 2x 2x       2x 1x 1x   1x     1x         1x                                                                                                                         1x                                       1x                                 1x                              
import { Request, Response } from 'express';
import { config } from '../config/config';
import fs from 'fs';
import path from 'path';
import { scoreResume, streamScoreResume, parseResumeFields,
    saveParsedResume, getResumeByOwner, updateResume } from '../services/resume_service';
import multer from 'multer';
import {getResumeBuffer, resumeExists} from '../services/resources_service';
import { CustomRequest } from "types/customRequest";
import { handleError } from "../utils/handle_error";
 
// Simple in-memory cache: { [key: string]: { scoreAndFeedback, timestamp } }
const resumeScoreCache: Record<string, { data: any, timestamp: number }> = {};
const CACHE_TTL_MS = 24* 60 * 60 * 1000; // 24 hour
 
const getCacheKey = (filename: string, jobDescription?: string) =>
    `${filename}::${jobDescription || ''}`;
 
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 cacheKey = getCacheKey(filename, jobDescription);
        const cached = resumeScoreCache[cacheKey];
        Iif (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
            return res.status(200).send(cached.data);
        }
 
        const scoreAndFeedback = await scoreResume(resumePath, jobDescription);
        resumeScoreCache[cacheKey] = { data: scoreAndFeedback, timestamp: Date.now() };
        return res.status(200).send(scoreAndFeedback);
    } catch (error) {
        Iif (error instanceof TypeError) {
            return res.status(400).send(error.message);
        } else {
            handleError(error, res);
        }
    }
};
 
const getStreamResumeScore = async (req: CustomRequest, 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 cacheKey = getCacheKey(filename, jobDescription);
        const cached = resumeScoreCache[cacheKey];
        if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
            // Stream cached result as SSE
            res.setHeader('Content-Type', 'text/event-stream');
            res.setHeader('Cache-Control', 'no-cache');
            res.setHeader('Connection', 'keep-alive');
            res.write(`data: ${JSON.stringify({ ...cached.data, done: true })}\n\n`);
            res.end();
            return;
        }
 
        // 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
        let fullChunk = '';
        const [score, fullText] = await streamScoreResume(
            resumePath,
            jobDescription,
            (chunk) => {
                fullChunk += 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();
 
        // Optionally cache the result (score and fullText)
        resumeScoreCache[cacheKey] = { data: { score, fullText }, timestamp: Date.now() };
        await updateResume(req.user.id, jobDescription, fullText, score);
 
    } catch (error) {
        if (error instanceof TypeError) {
            return res.status(400).send(error.message);
        } else {
            handleError(error, res);
        }
    }
};
 
 
const parseResume = async (req: CustomRequest, res: Response) => {
    try {
      if (!req.body.resumefileName) {
        return res.status(400).json({ error: 'No resume file uploaded' });
      }
      else if (!resumeExists(req.body.resumefileName)) {
          return res.status(400).json({ error: 'No resume file uploaded' });
      }
 
      const resumeFilename = req.body.resumefileName;
      const parsed = await parseResumeFields(getResumeBuffer(req.body.resumefileName), resumeFilename);
      const resumeData = await saveParsedResume(parsed, req.user.id, resumeFilename, req.body.originfilename);
 
      return res.status(200).json(parsed);
    } catch (err: any) {
      console.error('Error parsing resume:', err);
      return handleError(err, res);
    }
  };
 
const getResume = async (req: CustomRequest, res: Response) => {
    try {
        const ownerId = req.user.id;
        const resume = await getResumeByOwner(ownerId);
 
        if (!resume) {
            return res.status(404).json({ error: 'Resume not found' });
        }
 
        return res.status(200).json(resume);
    } catch (error) {
        console.error('Error retrieving resume:', error);
        return handleError(error, res);
    }
}
 
 
const getResumeData = async (req: CustomRequest, res: Response) => {
    try {
        const ownerId = req.user.id;
        // Get the optional version parameter from query string
        const version = req.query.version ? parseInt(req.query.version as string) : undefined;
        const resume = await getResumeByOwner(ownerId, version);
 
        return res.status(200).json(resume);
    } catch (error) {
        console.error('Error retrieving resume data:', error);
        return handleError(error, res);
    }
};
 
export default { parseResume, getResumeScore,
    getStreamResumeScore, getResumeData, getResume };