diff --git a/backend/app/__init__.py b/backend/app/__init__.py index 943f0c8..9487bbb 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -3,6 +3,18 @@ from dotenv import load_dotenv import os +from flask import Flask +from flask_cors import CORS +from routes.resume_routes import resume_bp + +app = Flask(__name__) +CORS(app) + +# Register resume blueprint +app.register_blueprint(resume_bp, url_prefix='/api/resume') + +# ... rest of your existing code + def create_app(): # Load environment variables load_dotenv() diff --git a/backend/app/models/resume_model.py b/backend/app/models/resume_model.py new file mode 100644 index 0000000..a663787 --- /dev/null +++ b/backend/app/models/resume_model.py @@ -0,0 +1,22 @@ +""" +Resume Database Model +Author: Akshit +""" + +from datetime import datetime +from flask_sqlalchemy import SQLAlchemy + +db = SQLAlchemy() + +class Resume(db.Model): + """Resume model for database""" + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + filename = db.Column(db.String(255), nullable=False) + filepath = db.Column(db.String(500), nullable=False) + upload_date = db.Column(db.DateTime, default=datetime.utcnow) + analysis_score = db.Column(db.Integer) + skills = db.Column(db.JSON) + + def __repr__(self): + return f'' diff --git a/backend/app/routes/resume_routes.py b/backend/app/routes/resume_routes.py new file mode 100644 index 0000000..aa26903 --- /dev/null +++ b/backend/app/routes/resume_routes.py @@ -0,0 +1,59 @@ +""" +Resume Upload Routes +Author: Akshit +Date: October 13, 2025 +""" + +from flask import Blueprint, request, jsonify +from werkzeug.utils import secure_filename +import os +from services.resume_analyzer import ResumeAnalyzer + +resume_bp = Blueprint('resume', __name__) +UPLOAD_FOLDER = 'backend/uploads' +ALLOWED_EXTENSIONS = {'pdf', 'docx', 'txt'} + +def allowed_file(filename): + return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + +@resume_bp.route('/upload', methods=['POST']) +def upload_resume(): + """Upload and analyze resume""" + if 'file' not in request.files: + return jsonify({'error': 'No file provided'}), 400 + + file = request.files['file'] + + if file.filename == '': + return jsonify({'error': 'No file selected'}), 400 + + if file and allowed_file(file.filename): + filename = secure_filename(file.filename) + filepath = os.path.join(UPLOAD_FOLDER, filename) + file.save(filepath) + + # Analyze resume + analyzer = ResumeAnalyzer() + analysis = analyzer.analyze_file(filepath) + + return jsonify({ + 'success': True, + 'filename': filename, + 'analysis': analysis + }), 200 + + return jsonify({'error': 'Invalid file type'}), 400 + +@resume_bp.route('/analyze', methods=['POST']) +def analyze_resume(): + """Analyze resume text""" + data = request.json + text = data.get('text', '') + + analyzer = ResumeAnalyzer() + analysis = analyzer.analyze_text(text) + + return jsonify({ + 'success': True, + 'analysis': analysis + }), 200 diff --git a/backend/app/services/resume_analyzer.py b/backend/app/services/resume_analyzer.py new file mode 100644 index 0000000..8b80bf1 --- /dev/null +++ b/backend/app/services/resume_analyzer.py @@ -0,0 +1,78 @@ +""" +AI Resume Analyzer Service +Author: Akshit +Date: October 13, 2025 +""" + +import PyPDF2 +import docx +import re +from collections import Counter + +class ResumeAnalyzer: + def __init__(self): + self.tech_skills = [ + 'Python', 'Java', 'JavaScript', 'React', 'Node.js', + 'SQL', 'MongoDB', 'AWS', 'Docker', 'Kubernetes', + 'Machine Learning', 'Deep Learning', 'TensorFlow', + 'PyTorch', 'HTML', 'CSS', 'Git', 'REST API' + ] + + def extract_text_from_pdf(self, filepath): + """Extract text from PDF""" + with open(filepath, 'rb') as file: + reader = PyPDF2.PdfReader(file) + text = '' + for page in reader.pages: + text += page.extract_text() + return text + + def extract_text_from_docx(self, filepath): + """Extract text from DOCX""" + doc = docx.Document(filepath) + text = '\n'.join([para.text for para in doc.paragraphs]) + return text + + def extract_text_from_txt(self, filepath): + """Extract text from TXT""" + with open(filepath, 'r', encoding='utf-8') as file: + return file.read() + + def analyze_file(self, filepath): + """Analyze resume file""" + ext = filepath.rsplit('.', 1)[1].lower() + + if ext == 'pdf': + text = self.extract_text_from_pdf(filepath) + elif ext == 'docx': + text = self.extract_text_from_docx(filepath) + else: + text = self.extract_text_from_txt(filepath) + + return self.analyze_text(text) + + def analyze_text(self, text): + """Analyze resume text with AI""" + # Detect skills + detected_skills = [skill for skill in self.tech_skills + if skill.lower() in text.lower()] + + # Extract email + email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' + emails = re.findall(email_pattern, text) + + # Extract phone + phone_pattern = r'[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}' + phones = re.findall(phone_pattern, text) + + # Calculate score + score = min(100, len(detected_skills) * 10 + 20) + + return { + 'skills': detected_skills, + 'email': emails[0] if emails else None, + 'phone': phones[0] if phones else None, + 'score': score, + 'word_count': len(text.split()), + 'experience_level': 'Mid-Level' if len(detected_skills) > 5 else 'Junior' + } diff --git a/frontend/src/components/resumeupload.jsx b/frontend/src/components/resumeupload.jsx new file mode 100644 index 0000000..4db8737 --- /dev/null +++ b/frontend/src/components/resumeupload.jsx @@ -0,0 +1,53 @@ +/** + * Resume Upload Component + * Author: Akshit + */ + +import React, { useState } from 'react'; +import resumeApi from '../utils/resumeApi'; + +const ResumeUpload = () => { + const [file, setFile] = useState(null); + const [uploading, setUploading] = useState(false); + const [progress, setProgress] = useState(0); + const [analysis, setAnalysis] = useState(null); + + const handleFileChange = (e) => { + setFile(e.target.files[0]); + }; + + const handleUpload = async () => { + if (!file) return; + + setUploading(true); + const response = await resumeApi.uploadResume(file, (prog) => { + setProgress(prog); + }); + + if (response.success) { + setAnalysis(response.data.analysis); + } + setUploading(false); + }; + + return ( +
+

📤 Upload Resume

+ + + + {analysis && ( +
+

Analysis Results

+

Score: {analysis.score}/100

+

Skills: {analysis.skills.join(', ')}

+

Experience: {analysis.experience_level}

+
+ )} +
+ ); +}; + +export default ResumeUpload; diff --git a/frontend/src/pages/profilepage.jsx b/frontend/src/pages/profilepage.jsx new file mode 100644 index 0000000..8a232c4 --- /dev/null +++ b/frontend/src/pages/profilepage.jsx @@ -0,0 +1,18 @@ +/** + * Profile Page with Resume Upload + * Author: Akshit + */ + +import React from 'react'; +import ResumeUpload from '../components/ResumeUpload'; + +const ProfilePage = () => { + return ( +
+

👤 User Profile

+ +
+ ); +}; + +export default ProfilePage; diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index e69de29..9303f23 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -0,0 +1,239 @@ +/** + * Axios API Service for ML Simulator + * Author: Akshit + * Date: October 13, 2025 + * Purpose: Centralized API service for secure backend communication + */ + +import axios from 'axios'; + +// Base API configuration +const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || 'http://localhost:5000/api'; +const API_TIMEOUT = 30000; // 30 seconds + +/** + * Create Axios instance with default configuration + */ +const apiClient = axios.create({ + baseURL: API_BASE_URL, + timeout: API_TIMEOUT, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, +}); + +/** + * Request Interceptor + * Add authentication token and modify request before sending + */ +apiClient.interceptors.request.use( + (config) => { + // Add authentication token if available + const token = localStorage.getItem('auth_token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + + // Add timestamp to prevent caching + config.headers['X-Request-Time'] = new Date().getTime(); + + // Log request in development + if (process.env.NODE_ENV === 'development') { + console.log('🚀 API Request:', { + method: config.method?.toUpperCase(), + url: config.url, + data: config.data, + }); + } + + return config; + }, + (error) => { + console.error('❌ Request Error:', error); + return Promise.reject(error); + } +); + +/** + * Response Interceptor + * Handle responses and errors globally + */ +apiClient.interceptors.response.use( + (response) => { + // Log response in development + if (process.env.NODE_ENV === 'development') { + console.log('✅ API Response:', { + status: response.status, + url: response.config.url, + data: response.data, + }); + } + + return response.data; + }, + (error) => { + // Handle different error scenarios + if (error.response) { + // Server responded with error status + const { status, data } = error.response; + + switch (status) { + case 400: + console.error('❌ Bad Request:', data.message || 'Invalid request'); + break; + case 401: + console.error('❌ Unauthorized: Please login again'); + // Clear token and redirect to login + localStorage.removeItem('auth_token'); + window.location.href = '/login'; + break; + case 403: + console.error('❌ Forbidden: You do not have permission'); + break; + case 404: + console.error('❌ Not Found:', data.message || 'Resource not found'); + break; + case 500: + console.error('❌ Server Error:', data.message || 'Internal server error'); + break; + case 503: + console.error('❌ Service Unavailable: Server is down'); + break; + default: + console.error('❌ Error:', data.message || 'An error occurred'); + } + + return Promise.reject({ + status, + message: data.message || 'An error occurred', + data: data, + }); + } else if (error.request) { + // Request made but no response received + console.error('❌ Network Error: No response from server'); + return Promise.reject({ + status: 0, + message: 'Network error. Please check your connection.', + data: null, + }); + } else { + // Something else happened + console.error('❌ Error:', error.message); + return Promise.reject({ + status: 0, + message: error.message || 'An unexpected error occurred', + data: null, + }); + } + } +); + +/** + * API Service Methods + */ +const apiService = { + /** + * GET request + * @param {string} endpoint - API endpoint + * @param {object} params - Query parameters + * @returns {Promise} Response data + */ + get: async (endpoint, params = {}) => { + try { + const response = await apiClient.get(endpoint, { params }); + return { success: true, data: response }; + } catch (error) { + return { success: false, error }; + } + }, + + /** + * POST request + * @param {string} endpoint - API endpoint + * @param {object} data - Request body + * @returns {Promise} Response data + */ + post: async (endpoint, data = {}) => { + try { + const response = await apiClient.post(endpoint, data); + return { success: true, data: response }; + } catch (error) { + return { success: false, error }; + } + }, + + /** + * PUT request + * @param {string} endpoint - API endpoint + * @param {object} data - Request body + * @returns {Promise} Response data + */ + put: async (endpoint, data = {}) => { + try { + const response = await apiClient.put(endpoint, data); + return { success: true, data: response }; + } catch (error) { + return { success: false, error }; + } + }, + + /** + * PATCH request + * @param {string} endpoint - API endpoint + * @param {object} data - Request body + * @returns {Promise} Response data + */ + patch: async (endpoint, data = {}) => { + try { + const response = await apiClient.patch(endpoint, data); + return { success: true, data: response }; + } catch (error) { + return { success: false, error }; + } + }, + + /** + * DELETE request + * @param {string} endpoint - API endpoint + * @returns {Promise} Response data + */ + delete: async (endpoint) => { + try { + const response = await apiClient.delete(endpoint); + return { success: true, data: response }; + } catch (error) { + return { success: false, error }; + } + }, + + /** + * Upload file with multipart/form-data + * @param {string} endpoint - API endpoint + * @param {FormData} formData - Form data with file + * @param {function} onProgress - Progress callback + * @returns {Promise} Response data + */ + uploadFile: async (endpoint, formData, onProgress = null) => { + try { + const response = await apiClient.post(endpoint, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + onUploadProgress: (progressEvent) => { + if (onProgress) { + const percentCompleted = Math.round( + (progressEvent.loaded * 100) / progressEvent.total + ); + onProgress(percentCompleted); + } + }, + }); + return { success: true, data: response }; + } catch (error) { + return { success: false, error }; + } + }, +}; + +export default apiService; diff --git a/frontend/src/services/resumeapi.js b/frontend/src/services/resumeapi.js new file mode 100644 index 0000000..3dcad58 --- /dev/null +++ b/frontend/src/services/resumeapi.js @@ -0,0 +1,20 @@ +/** + * Resume API Service + * Author: Akshit + */ + +import apiService from './api'; + +const resumeApi = { + uploadResume: async (file, onProgress) => { + const formData = new FormData(); + formData.append('file', file); + return await apiService.uploadFile('/resume/upload', formData, onProgress); + }, + + analyzeResume: async (text) => { + return await apiService.post('/resume/analyze', { text }); + }, +}; + +export default resumeApi;