from fastapi import APIRouter, File, UploadFile, Form, HTTPException, Depends from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from pydantic import BaseModel, EmailStr from typing import Optional import io import os from database.database import get_db, Profile as DBProfile from database.minio_processor import MinIOProcessor from openrouter_client import openrouter_client minio_client = MinIOProcessor( endpoint=os.getenv("MINIO_ENDPOINT", "localhost:9000"), access_key=os.getenv("MINIO_ACCESS_KEY", "minioadmin"), secret_key=os.getenv("MINIO_SECRET_KEY", "minioadmin"), secure=False ) MINIO_BUCKET = os.getenv("MINIO_BUCKET", "resumes") router = APIRouter(prefix="/api/profile", tags=["profile"]) class ProfileResponse(BaseModel): id: int name: str email: str position: str competencies: Optional[str] experience: Optional[str] skills: Optional[str] country: Optional[str] languages: Optional[str] employment_format: Optional[str] rate: Optional[str] relocation: Optional[str] cv_url: Optional[str] class Config: from_attributes = True @router.post("/upload-cv", response_model=ProfileResponse) async def upload_and_parse_cv( cv: UploadFile = File(...), db: AsyncSession = Depends(get_db) ): if cv.content_type != "application/pdf": raise HTTPException(status_code=400, detail="CV must be a PDF file") pdf_content = await cv.read() try: parsed_data = await openrouter_client.parse_cv_from_pdf(pdf_content) except Exception as e: raise HTTPException(status_code=500, detail=f"Error parsing CV with AI: {str(e)}") if not parsed_data.get("email"): raise HTTPException(status_code=400, detail="Could not extract email from CV") email = parsed_data["email"] result = await db.execute(select(DBProfile).where(DBProfile.email == email)) existing_profile = result.scalar_one_or_none() object_name = f"cv/{email.replace('@', '_')}_{cv.filename}" try: await cv.seek(0) cv_file_data = await cv.read() await cv.seek(0) minio_client.put_object( bucket_name=MINIO_BUCKET, object_name=object_name, data=io.BytesIO(cv_file_data), length=len(cv_file_data), content_type="application/pdf" ) cv_url = f"minio://{MINIO_BUCKET}/{object_name}" except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to upload CV to storage: {str(e)}") if existing_profile: existing_profile.name = parsed_data.get("name") or existing_profile.name existing_profile.position = parsed_data.get("position") or existing_profile.position existing_profile.competencies = parsed_data.get("competencies") existing_profile.experience = parsed_data.get("experience") existing_profile.skills = parsed_data.get("skills") existing_profile.country = parsed_data.get("country") existing_profile.languages = parsed_data.get("languages") existing_profile.employment_format = parsed_data.get("employment_format") existing_profile.rate = parsed_data.get("rate") existing_profile.relocation = parsed_data.get("relocation") existing_profile.cv_url = cv_url profile = existing_profile else: profile = DBProfile( email=email, name=parsed_data.get("name", "Unknown"), position=parsed_data.get("position", "Not specified"), competencies=parsed_data.get("competencies"), experience=parsed_data.get("experience"), skills=parsed_data.get("skills"), country=parsed_data.get("country"), languages=parsed_data.get("languages"), employment_format=parsed_data.get("employment_format"), rate=parsed_data.get("rate"), relocation=parsed_data.get("relocation"), cv_url=cv_url ) db.add(profile) await db.commit() await db.refresh(profile) return profile @router.post("/save", response_model=ProfileResponse) async def save_profile_manual( name: str = Form(...), email: EmailStr = Form(...), position: str = Form(...), competencies: Optional[str] = Form(None), experience: Optional[str] = Form(None), skills: Optional[str] = Form(None), country: Optional[str] = Form(None), languages: Optional[str] = Form(None), employment_format: Optional[str] = Form(None), rate: Optional[str] = Form(None), relocation: Optional[str] = Form(None), db: AsyncSession = Depends(get_db) ): result = await db.execute(select(DBProfile).where(DBProfile.email == email)) existing_profile = result.scalar_one_or_none() if existing_profile: existing_profile.name = name existing_profile.position = position existing_profile.competencies = competencies existing_profile.experience = experience existing_profile.skills = skills existing_profile.country = country existing_profile.languages = languages existing_profile.employment_format = employment_format existing_profile.rate = rate existing_profile.relocation = relocation profile = existing_profile else: profile = DBProfile( name=name, email=email, position=position, competencies=competencies, experience=experience, skills=skills, country=country, languages=languages, employment_format=employment_format, rate=rate, relocation=relocation ) db.add(profile) await db.commit() await db.refresh(profile) return profile @router.get("/{email}", response_model=ProfileResponse) async def get_profile(email: str, db: AsyncSession = Depends(get_db)): result = await db.execute(select(DBProfile).where(DBProfile.email == email)) profile = result.scalar_one_or_none() if not profile: raise HTTPException(status_code=404, detail="Profile not found") return profile