180 lines
6.0 KiB
Python
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
|