ai-hackaton-backend/app/routers/resume_router.py

290 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from fastapi import (
APIRouter,
Depends,
File,
Form,
HTTPException,
Query,
Request,
UploadFile,
)
from app.core.session_middleware import get_current_session
from app.models.resume import ResumeCreate, ResumeRead, ResumeStatus, ResumeUpdate
from app.models.session import Session
from app.services.file_service import FileService
from app.services.resume_service import ResumeService
from celery_worker.celery_app import celery_app
from celery_worker.tasks import parse_resume_task
router = APIRouter(prefix="/resumes", tags=["resumes"])
@router.post("/", response_model=ResumeRead)
async def create_resume(
request: Request,
vacancy_id: int = Form(...),
applicant_name: str = Form(...),
applicant_email: str = Form(...),
applicant_phone: str | None = Form(None),
cover_letter: str | None = Form(None),
resume_file: UploadFile = File(...),
current_session: Session = Depends(get_current_session),
resume_service: ResumeService = Depends(ResumeService),
):
if not current_session:
raise HTTPException(status_code=401, detail="No active session")
file_service = FileService()
upload_result = await file_service.upload_resume_file(resume_file)
if not upload_result:
raise HTTPException(status_code=400, detail="Failed to upload resume file")
resume_file_url, local_file_path = upload_result
resume_data = ResumeCreate(
vacancy_id=vacancy_id,
applicant_name=applicant_name,
applicant_email=applicant_email,
applicant_phone=applicant_phone,
resume_file_url=resume_file_url,
cover_letter=cover_letter,
)
# Создаем резюме в БД
created_resume = await resume_service.create_resume_with_session(
resume_data, current_session.id
)
# Запускаем асинхронную задачу парсинга резюме
try:
# Запускаем Celery task для парсинга с локальным файлом
task_result = parse_resume_task.delay(str(created_resume.id), local_file_path)
# Добавляем task_id в ответ для отслеживания статуса
response_data = created_resume.model_dump()
response_data["parsing_task_id"] = task_result.id
response_data["parsing_status"] = "started"
return response_data
except Exception as e:
# Если не удалось запустить парсинг, оставляем резюме в статусе PENDING
print(f"Failed to start parsing task for resume {created_resume.id}: {str(e)}")
return created_resume
@router.get("/", response_model=list[ResumeRead])
async def get_resumes(
request: Request,
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
vacancy_id: int | None = Query(None),
status: ResumeStatus | None = Query(None),
current_session: Session = Depends(get_current_session),
service: ResumeService = Depends(ResumeService),
):
if not current_session:
raise HTTPException(status_code=401, detail="No active session")
# Получаем только резюме текущего пользователя
if vacancy_id:
return await service.get_resumes_by_vacancy_and_session(
vacancy_id, current_session.id
)
return await service.get_resumes_by_session(
current_session.id, skip=skip, limit=limit
)
@router.get("/{resume_id}", response_model=ResumeRead)
async def get_resume(
request: Request,
resume_id: int,
current_session: Session = Depends(get_current_session),
service: ResumeService = Depends(ResumeService),
):
if not current_session:
raise HTTPException(status_code=401, detail="No active session")
resume = await service.get_resume(resume_id)
if not resume:
raise HTTPException(status_code=404, detail="Resume not found")
# Проверяем, что резюме принадлежит текущей сессии
if resume.session_id != current_session.id:
raise HTTPException(status_code=403, detail="Access denied")
return resume
@router.put("/{resume_id}", response_model=ResumeRead)
async def update_resume(
request: Request,
resume_id: int,
resume: ResumeUpdate,
current_session: Session = Depends(get_current_session),
service: ResumeService = Depends(ResumeService),
):
if not current_session:
raise HTTPException(status_code=401, detail="No active session")
existing_resume = await service.get_resume(resume_id)
if not existing_resume:
raise HTTPException(status_code=404, detail="Resume not found")
# Проверяем, что резюме принадлежит текущей сессии
if existing_resume.session_id != current_session.id:
raise HTTPException(status_code=403, detail="Access denied")
updated_resume = await service.update_resume(resume_id, resume)
return updated_resume
@router.patch("/{resume_id}/status", response_model=ResumeRead)
async def update_resume_status(
request: Request,
resume_id: int,
status: ResumeStatus,
current_session: Session = Depends(get_current_session),
service: ResumeService = Depends(ResumeService),
):
if not current_session:
raise HTTPException(status_code=401, detail="No active session")
existing_resume = await service.get_resume(resume_id)
if not existing_resume:
raise HTTPException(status_code=404, detail="Resume not found")
# Проверяем, что резюме принадлежит текущей сессии
if existing_resume.session_id != current_session.id:
raise HTTPException(status_code=403, detail="Access denied")
updated_resume = await service.update_resume_status(resume_id, status)
return updated_resume
@router.post("/{resume_id}/interview-report")
async def upload_interview_report(
request: Request,
resume_id: int,
report_file: UploadFile = File(...),
current_session: Session = Depends(get_current_session),
resume_service: ResumeService = Depends(ResumeService),
):
if not current_session:
raise HTTPException(status_code=401, detail="No active session")
file_service = FileService()
existing_resume = await resume_service.get_resume(resume_id)
if not existing_resume:
raise HTTPException(status_code=404, detail="Resume not found")
# Проверяем, что резюме принадлежит текущей сессии
if existing_resume.session_id != current_session.id:
raise HTTPException(status_code=403, detail="Access denied")
report_url = await file_service.upload_interview_report(report_file)
if not report_url:
raise HTTPException(status_code=400, detail="Failed to upload interview report")
updated_resume = await resume_service.add_interview_report(resume_id, report_url)
return {
"message": "Interview report uploaded successfully",
"report_url": report_url,
}
@router.get("/{resume_id}/parsing-status")
async def get_parsing_status(
request: Request,
resume_id: int,
task_id: str = Query(..., description="Task ID from resume upload response"),
current_session: Session = Depends(get_current_session),
service: ResumeService = Depends(ResumeService),
):
"""Получить статус парсинга резюме по task_id"""
if not current_session:
raise HTTPException(status_code=401, detail="No active session")
# Проверяем доступ к резюме
resume = await service.get_resume(resume_id)
if not resume:
raise HTTPException(status_code=404, detail="Resume not found")
if resume.session_id != current_session.id:
raise HTTPException(status_code=403, detail="Access denied")
# Получаем статус задачи из Celery
try:
task_result = celery_app.AsyncResult(task_id)
response = {
"task_id": task_id,
"task_state": task_result.state,
"resume_status": resume.status,
}
if task_result.state == "PENDING":
response.update({"status": "В очереди на обработку", "progress": 0})
elif task_result.state == "PROGRESS":
response.update(
{
"status": task_result.info.get("status", "Обрабатывается"),
"progress": task_result.info.get("progress", 0),
}
)
elif task_result.state == "SUCCESS":
response.update(
{
"status": "Завершено успешно",
"progress": 100,
"result": task_result.info,
}
)
elif task_result.state == "FAILURE":
response.update(
{
"status": f"Ошибка: {str(task_result.info)}",
"progress": 0,
"error": str(task_result.info),
}
)
return response
except Exception as e:
return {
"task_id": task_id,
"task_state": "UNKNOWN",
"resume_status": resume.status,
"error": f"Failed to get task status: {str(e)}",
}
@router.delete("/{resume_id}")
async def delete_resume(
request: Request,
resume_id: int,
current_session: Session = Depends(get_current_session),
service: ResumeService = Depends(ResumeService),
):
if not current_session:
raise HTTPException(status_code=401, detail="No active session")
existing_resume = await service.get_resume(resume_id)
if not existing_resume:
raise HTTPException(status_code=404, detail="Resume not found")
# Проверяем, что резюме принадлежит текущей сессии
if existing_resume.session_id != current_session.id:
raise HTTPException(status_code=403, detail="Access denied")
success = await service.delete_resume(resume_id)
return {"message": "Resume deleted successfully"}