290 lines
10 KiB
Python
290 lines
10 KiB
Python
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"}
|