202 lines
7.1 KiB
Python
202 lines
7.1 KiB
Python
from fastapi import APIRouter,Depends,Query
|
|
from fastapi.responses import JSONResponse
|
|
from fastapi import HTTPException
|
|
from db_setup import get_session
|
|
from job.candidate.views import CandidateScoring,FileRead
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from users.permissions import PermissionTag, require_permission
|
|
from job.job_post.views import JobPost,JobPostCreate
|
|
from job.job_post.models import JobPosts
|
|
from job.job_post.serializers import serialize_job_post
|
|
import logging
|
|
from job.job_post.plugins import PlatformAlias
|
|
from fastapi import UploadFile, File, Form
|
|
from pydantic import BaseModel
|
|
from dotenv import load_dotenv
|
|
from datetime import datetime, time, timezone
|
|
load_dotenv()
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
|
|
@router.get("/jobs/alias")
|
|
async def get_job_alias():
|
|
try:
|
|
alias_lst=[k.name for k in PlatformAlias]
|
|
return JSONResponse(content={"data":alias_lst,"status_code":200})
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500,detail=str(e))
|
|
|
|
|
|
@router.post("/candidate/cv_upload")
|
|
async def cv_upload(
|
|
file: UploadFile = File(...),
|
|
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_CREATE)),
|
|
session: AsyncSession = Depends(get_session),
|
|
):
|
|
try:
|
|
file_content = await file.read()
|
|
logger.info(f"Received file: {file.filename} ({len(file_content)} bytes)")
|
|
service=FileRead(session=session,filename=file.filename,file=file_content)
|
|
data=await service.read_file()
|
|
|
|
return JSONResponse(content={"data":data,"status_code":200})
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500,detail=str(e))
|
|
|
|
|
|
@router.post("/candidate/inbox-match")
|
|
async def candidate_inbox_match(
|
|
inbox_message_id: str = Query(...),
|
|
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_EDIT)),
|
|
session: AsyncSession = Depends(get_session),
|
|
):
|
|
try:
|
|
service=FileRead(session=session)
|
|
data=await service.match_inbox_cv(inbox_message_id)
|
|
return JSONResponse(content={"data":data,"status_code":200})
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500,detail=str(e))
|
|
|
|
|
|
@router.post("/job/post-job")
|
|
async def post_job(
|
|
payload: JobPostCreate,
|
|
current_user: dict = Depends(require_permission(PermissionTag.JOB_BOARD_CREATE)),
|
|
session: AsyncSession = Depends(get_session),
|
|
):
|
|
try:
|
|
service=JobPost(session=session)
|
|
data=payload.model_dump()
|
|
if data['mode']=="customScheduled":
|
|
data['due_at']=datetime.combine(
|
|
data['scheduler_date'],
|
|
data['scheduler_time'] or time(0, 0, 0),
|
|
tzinfo=timezone.utc,
|
|
).strftime("%Y-%m-%dT%H:%M:%S.000Z")
|
|
result=await service.post_job(data,current_user)
|
|
return JSONResponse(content={"data":result,"status_code":200})
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500,detail=str(e))
|
|
|
|
|
|
@router.get("/job/buffer/channels")
|
|
async def buffer_channels(
|
|
current_user: dict = Depends(require_permission(PermissionTag.JOB_BOARD_VIEW)),
|
|
session: AsyncSession = Depends(get_session),
|
|
):
|
|
try:
|
|
service=JobPost(session=session)
|
|
data=await service.list_channels()
|
|
return JSONResponse(content={"data":data,"total":len(data),"status_code":200})
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500,detail=str(e))
|
|
|
|
class InboxScoreRequest(BaseModel):
|
|
job_id: str
|
|
message_ids: list[str] # inbox_messages PK uuids, not Graph message ids
|
|
|
|
|
|
@router.post("/candidate/score")
|
|
async def score_candidates(
|
|
job_id: str = Form(...),
|
|
files: list[UploadFile] = File(...),
|
|
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_CREATE)),
|
|
session: AsyncSession = Depends(get_session),
|
|
):
|
|
"""Score uploaded CV PDFs against a job post; persists and returns the leaderboard."""
|
|
try:
|
|
pairs=[(f.filename,await f.read()) for f in files]
|
|
service=CandidateScoring(session=session)
|
|
data=await service.score_uploads(job_id,pairs,current_user)
|
|
return JSONResponse(content={"data":data,"total":len(data),"status_code":200})
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500,detail=str(e))
|
|
|
|
|
|
@router.post("/candidate/score_inbox")
|
|
async def score_inbox_candidates(
|
|
payload: InboxScoreRequest,
|
|
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_CREATE)),
|
|
session: AsyncSession = Depends(get_session),
|
|
):
|
|
"""Score the decoded attachments of inbox messages against a job post."""
|
|
try:
|
|
service=CandidateScoring(session=session)
|
|
data=await service.score_inbox(payload.job_id,payload.message_ids,current_user)
|
|
return JSONResponse(content={"data":data,"total":len(data),"status_code":200})
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500,detail=str(e))
|
|
|
|
|
|
@router.get("/candidate/fetch")
|
|
async def fetch_candidates(
|
|
job_id: str = Query(None),
|
|
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_VIEW)),
|
|
session: AsyncSession = Depends(get_session),
|
|
):
|
|
"""Persisted leaderboard: completed by score desc, failures last. Without job_id
|
|
returns the whole pool across jobs."""
|
|
try:
|
|
service=CandidateScoring(session=session)
|
|
data=await service.fetch_candidates(job_id)
|
|
return JSONResponse(content={"data":data,"total":len(data),"status_code":200})
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500,detail=str(e))
|
|
|
|
|
|
@router.get("/job/fetch")
|
|
async def fetch_jobs(
|
|
current_user: dict = Depends(
|
|
require_permission(
|
|
PermissionTag.JOB_BOARD_VIEW, PermissionTag.CANDIDATES_VIEW, require_all=False
|
|
)
|
|
),
|
|
session: AsyncSession = Depends(get_session),
|
|
):
|
|
"""Active job posts, for pickers and boards. Either job-board or candidate viewers
|
|
may list them — recruiters scoring CVs need a job to score against."""
|
|
try:
|
|
rows=await JobPosts.get_active_job_posts(session)
|
|
data=[serialize_job_post(row) for row in rows]
|
|
return JSONResponse(content={"data":data,"total":len(data),"status_code":200})
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500,detail=str(e))
|
|
|
|
|
|
@router.get("/candidate/fetch_by_id")
|
|
async def fetch_candidate_by_id(
|
|
candidate_id: str = Query(...),
|
|
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_VIEW)),
|
|
session: AsyncSession = Depends(get_session),
|
|
):
|
|
try:
|
|
service=CandidateScoring(session=session)
|
|
data=await service.fetch_candidate_by_id(candidate_id)
|
|
return JSONResponse(content={"data":data,"total":1,"status_code":200})
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500,detail=str(e))
|