qa-and-rag-ai-assistant/routers/profile.py
2026-01-13 15:38:27 +03:00

180 lines
6.0 KiB
Python

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