Spaces:
Paused
Paused
| import os | |
| import sys | |
| # Hugging Face safe cache | |
| os.environ["HF_HOME"] = "/tmp/huggingface" | |
| os.environ["TRANSFORMERS_CACHE"] = "/tmp/huggingface/transformers" | |
| os.environ["HUGGINGFACE_HUB_CACHE"] = "/tmp/huggingface/hub" | |
| # Force Flask instance path to a writable temporary folder | |
| safe_instance_path = "/tmp/flask_instance" | |
| # Create the safe instance path after imports | |
| os.makedirs(safe_instance_path, exist_ok=True) | |
| from flask import Flask, render_template, redirect, url_for, flash, request | |
| from flask_login import LoginManager, login_required, current_user | |
| from werkzeug.utils import secure_filename | |
| import sys | |
| import json | |
| from datetime import datetime | |
| # Adjust sys.path for import flexibility | |
| current_dir = os.path.dirname(os.path.abspath(__file__)) | |
| sys.path.append(current_dir) | |
| # Import and initialize DB | |
| from backend.models.database import db, Job, Application, init_db | |
| from backend.models.user import User | |
| from backend.routes.auth import auth_bp | |
| from backend.routes.interview_api import interview_api | |
| from backend.models.resume_parser.resume_to_features import extract_resume_features | |
| # Initialize Flask app | |
| app = Flask( | |
| __name__, | |
| static_folder='backend/static', | |
| static_url_path='/static', | |
| template_folder='backend/templates', | |
| instance_path=safe_instance_path # ✅ points to writable '/tmp/flask_instance' | |
| ) | |
| app.config['SECRET_KEY'] = 'your-secret-key' | |
| # ----------------------------------------------------------------------------- | |
| # Cookie configuration for Hugging Face Spaces | |
| # | |
| # When running this app inside an iframe (as is typical on Hugging Face Spaces), | |
| # browsers will drop cookies that have the default SameSite policy of ``Lax``. | |
| # This prevents the Flask session cookie from being stored and means that | |
| # ``login_user()`` will appear to have no effect – the user will be redirected | |
| # back to the home page but remain anonymous. By explicitly setting the | |
| # SameSite policy to ``None`` and enabling the ``Secure`` flag, we allow the | |
| # session and remember cookies to be sent even when the app is embedded in an | |
| # iframe. Without these settings the sign‑up and login flows work locally | |
| # but silently fail in Spaces, causing the "redirect to home page without | |
| # anything" behaviour reported by users. | |
| app.config['SESSION_COOKIE_SAMESITE'] = 'None' | |
| app.config['SESSION_COOKIE_SECURE'] = True | |
| app.config['REMEMBER_COOKIE_SAMESITE'] = 'None' | |
| app.config['REMEMBER_COOKIE_SECURE'] = True | |
| # Configure the database connection | |
| # Use /tmp directory for database in Hugging Face Spaces | |
| # Note: Data will be lost when the space restarts | |
| app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/codingo.db' | |
| app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False | |
| from flask_wtf.csrf import CSRFProtect | |
| # csrf = CSRFProtect(app) | |
| # Create necessary directories in writable locations | |
| os.makedirs('/tmp/static/audio', exist_ok=True) | |
| os.makedirs('/tmp/temp', exist_ok=True) | |
| # Initialize DB with app | |
| init_db(app) | |
| # Flask-Login setup | |
| login_manager = LoginManager() | |
| login_manager.login_view = 'auth.login' | |
| login_manager.init_app(app) | |
| def load_user(user_id): | |
| return db.session.get(User, int(user_id)) | |
| # Register blueprints | |
| app.register_blueprint(auth_bp) | |
| app.register_blueprint(interview_api, url_prefix="/api") | |
| def handle_resume_upload(file): | |
| """Save uploaded file temporarily, extract features, then clean up.""" | |
| if not file or file.filename == '': | |
| return None, "No file uploaded", None | |
| try: | |
| filename = secure_filename(file.filename) | |
| temp_dir = '/tmp/temp' # Use /tmp for temporary files | |
| os.makedirs(temp_dir, exist_ok=True) | |
| filepath = os.path.join(temp_dir, filename) | |
| file.save(filepath) | |
| features = extract_resume_features(filepath) | |
| # Clean up | |
| try: | |
| os.remove(filepath) | |
| except: | |
| pass | |
| return features, None, filename | |
| except Exception as e: | |
| print(f"Error in handle_resume_upload: {e}") | |
| return None, str(e), None | |
| # Routes (keep your existing routes) | |
| def index(): | |
| return render_template('index.html') | |
| def jobs(): | |
| all_jobs = Job.query.order_by(Job.date_posted.desc()).all() | |
| return render_template('jobs.html', jobs=all_jobs) | |
| def job_detail(job_id): | |
| job = Job.query.get_or_404(job_id) | |
| return render_template('job_detail.html', job=job) | |
| def apply(job_id): | |
| job = Job.query.get_or_404(job_id) | |
| if request.method == 'POST': | |
| file = request.files.get('resume') | |
| features, error, _ = handle_resume_upload(file) | |
| if error or not features: | |
| flash("Resume parsing failed. Please try again.", "danger") | |
| return render_template('apply.html', job=job) | |
| application = Application( | |
| job_id=job_id, | |
| user_id=current_user.id, | |
| name=current_user.username, | |
| email=current_user.email, | |
| extracted_features=json.dumps(features) | |
| ) | |
| db.session.add(application) | |
| db.session.commit() | |
| flash('Your application has been submitted successfully!', 'success') | |
| return redirect(url_for('jobs')) | |
| return render_template('apply.html', job=job) | |
| def my_applications(): | |
| applications = Application.query.filter_by( | |
| user_id=current_user.id | |
| ).order_by(Application.date_applied.desc()).all() | |
| return render_template('my_applications.html', applications=applications) | |
| def parse_resume(): | |
| file = request.files.get('resume') | |
| features, error, _ = handle_resume_upload(file) | |
| if error: | |
| return {"error": "Error parsing resume. Please try again."}, 400 | |
| if not features: | |
| return {"error": "Failed to extract resume details."}, 400 | |
| response = { | |
| "name": features.get('name', ''), | |
| "email": features.get('email', ''), | |
| "mobile_number": features.get('mobile_number', ''), | |
| "skills": features.get('skills', []), | |
| "experience": features.get('experience', []), | |
| "education": features.get('education', []), | |
| "summary": features.get('summary', '') | |
| } | |
| return response, 200 | |
| def interview_page(job_id): | |
| job = Job.query.get_or_404(job_id) | |
| application = Application.query.filter_by( | |
| user_id=current_user.id, | |
| job_id=job_id | |
| ).first() | |
| if not application or not application.extracted_features: | |
| flash("Please apply for this job and upload your resume first.", "warning") | |
| return redirect(url_for('job_detail', job_id=job_id)) | |
| cv_data = json.loads(application.extracted_features) | |
| return render_template("interview.html", job=job, cv=cv_data) | |
| if __name__ == '__main__': | |
| print("Starting Codingo application...") | |
| with app.app_context(): | |
| db.create_all() | |
| # Use port from environment or default to 7860 | |
| port = int(os.environ.get('PORT', 7860)) | |
| app.run(debug=True, host='0.0.0.0', port=port) |