Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
177 changes: 155 additions & 22 deletions Backend/controllers/Interview.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,10 @@ import emotionAnalyzer from "../services/emotionAnalyzer.js";
import resultsGenerator from "../services/resultsGenerator.js";
import mongoose from "mongoose";
import groqService from "../services/groqService.js";
import fs from "fs";
import pdfParse from "pdf-parse";
import { uploadToCloudinary } from "../config/cloudinary.js";
import InterviewSession from "../models/InterviewSession.js";

// Helper function to safely get number
function safeNumber(value, defaultValue = 0) {
Expand Down Expand Up @@ -710,26 +714,14 @@ class InterviewController {

const userId = req.user.id;

if (!domain || !subDomain || !interviewType) {
return res.status(400).json({
success: false,
message: "Please provide all required fields (domain, subDomain, interviewType)",
});
}
let role = req.body.role;
let resumeUrl = "";
let resumeText = "";
let scheduledAt = req.body.scheduledAt || new Date();

const validTypes = [
"behavioral",
"technical",
"system-design",
"coding",
"mixed",
];
if (!validTypes.includes(interviewType)) {
return res.status(400).json({
success: false,
message: `Invalid interview type. Must be one of: ${validTypes.join(", ")}`,
});
}
let finalDomain = domain;
let finalSubDomain = subDomain;
let finalInterviewType = interviewType || "mixed";

let parsedQuestions = [];
if (customQuestions) {
Expand All @@ -745,13 +737,96 @@ class InterviewController {
}
}

if (role) {
// Resume-based flow
const resumeFile = req.files && req.files.resume;
if (!resumeFile) {
return res.status(400).json({
success: false,
message: "Resume file is required",
});
}

// 1. Parse resume text
try {
if (resumeFile.name.endsWith(".pdf")) {
const fileBuffer = fs.readFileSync(resumeFile.tempFilePath);
Comment thread
bhaikd marked this conversation as resolved.
Outdated
const pdfData = await pdfParse(fileBuffer);
resumeText = pdfData.text;
} else {
resumeText = `Resume content of ${resumeFile.name}`;
}
} catch (parseError) {
console.error("Error parsing resume:", parseError);
resumeText = "Error parsing resume content";
}

// 2. Upload resume to Cloudinary
try {
const uploadResult = await uploadToCloudinary(resumeFile, "resumes");
resumeUrl = uploadResult.secure_url;
} catch (uploadError) {
console.error("Cloudinary upload error:", uploadError);
return res.status(500).json({
success: false,
message: "Failed to upload resume to Cloudinary",
error: uploadError.message,
});
}
Comment on lines +765 to +775

// 3. Infer domain, subDomain, and interviewType from role
const roleLower = role.toLowerCase();
finalDomain = "Engineering";
finalSubDomain = role;

if (roleLower.includes("front")) {
finalDomain = "Frontend";
} else if (roleLower.includes("back")) {
finalDomain = "Backend";
} else if (roleLower.includes("full")) {
finalDomain = "Full Stack";
} else if (roleLower.includes("data")) {
finalDomain = "Data Science";
} else if (roleLower.includes("devops")) {
finalDomain = "DevOps";
} else if (roleLower.includes("mobile") || roleLower.includes("android") || roleLower.includes("ios")) {
finalDomain = "Mobile";
}
} else {
// Direct configuration flow
if (!finalDomain || !finalSubDomain || !finalInterviewType) {
return res.status(400).json({
success: false,
message: "Please provide all required fields (domain, subDomain, interviewType)",
});
}
Comment on lines +797 to +802

const validTypes = [
"behavioral",
"technical",
"system-design",
"coding",
"mixed",
];
if (!validTypes.includes(finalInterviewType)) {
return res.status(400).json({
success: false,
message: `Invalid interview type. Must be one of: ${validTypes.join(", ")}`,
});
}
}

// Create interview
const interview = new Interview({
userId,
role,
resumeUrl,
resumeText,
scheduledAt,
config: {
domain,
subDomain,
interviewType,
domain: finalDomain,
subDomain: finalSubDomain,
interviewType: finalInterviewType,
difficulty: difficulty || "medium",
duration: duration || 30,
targetCompany: targetCompany || "General",
Expand Down Expand Up @@ -1999,6 +2074,64 @@ Be encouraging but honest. Provide specific, actionable feedback.`,
});
}
}

// Get Interview by ID
async getInterviewById(req, res) {
try {
const { interviewId } = req.params;
const userId = req.user.id;

const interview = await Interview.findOne({ _id: interviewId, userId });

Comment on lines +2081 to +2085
if (!interview) {
return res.status(404).json({
success: false,
message: "Interview not found",
});
}

res.status(200).json({
success: true,
data: interview,
});
} catch (error) {
console.error("Get interview by ID error:", error);
res.status(500).json({
success: false,
message: "Failed to fetch interview details",
error: error.message,
});
}
}

// Get Interview Session
async getInterviewSession(req, res) {
try {
const { interviewId } = req.params;
const userId = req.user.id;

const session = await InterviewSession.findOne({ interviewId, userId });

Comment on lines +2110 to +2114
if (!session) {
return res.status(404).json({
success: false,
message: "Interview session not found",
});
}

res.status(200).json({
success: true,
data: session,
});
} catch (error) {
console.error("Get interview session error:", error);
res.status(500).json({
success: false,
message: "Failed to fetch interview session",
error: error.message,
});
}
}
}

export default new InterviewController();
10 changes: 7 additions & 3 deletions Backend/models/Interview.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ const InterviewSchema = new mongoose.Schema({
ref: "User",
required: true,
},
role: { type: String },
resumeUrl: { type: String },
resumeText: { type: String },
scheduledAt: { type: Date },
config: {
domain: { type: String, required: true },
subDomain: { type: String, required: true },
domain: { type: String },
subDomain: { type: String },
interviewType: {
type: String,
enum: ["behavioral", "technical", "system-design", "coding", "mixed"],
required: true,
default: "mixed",
},
difficulty: {
type: String,
Expand Down
7 changes: 6 additions & 1 deletion Backend/routes/interview.route.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,13 @@ router.get(
interviewController.getDetailedResults,
);

// ==================== HISTORY ====================
// Get interview history
router.get("/history", interviewController.getInterviewHistory);

// Get single interview details
router.get("/:interviewId", interviewController.getInterviewById);

// Get interview session
router.get("/:interviewId/session", interviewController.getInterviewSession);
Comment on lines +86 to +90

export default router;
2 changes: 1 addition & 1 deletion Frontend/src/components/AiInterview/InterviewSetup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ const InterviewSetup = () => {

try {
const interview = await createInterview(data, setLoading, token);
navigate(`/interview/${interview._id}`);
navigate(`/interview/${interview.id || interview.interviewId}`);
} catch (error) {
Comment on lines 92 to 95
console.error("Setup error:", error);
}
Expand Down
Loading