HR-ATS-Portal/backend/job/app.py

796 lines
28 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,CandidateView
from job.interviews.views import Interview
from job.notes.views import Note
from job.activity.views import ActivityLog
from job.feedback.views import FeedbackView
from job.pipeline.views import Pipeline
from job.assignment.views import Assignment
from job.cost.views import HiringCost
from sqlalchemy.ext.asyncio import AsyncSession
from users.permissions import PermissionTag, require_permission
from job.job_post.views import JobPost,JobPostCreate
import logging
from users.views import User
from job.job_post.plugins import PlatformAlias
from fastapi import UploadFile, File, Form
from dotenv import load_dotenv
from datetime import datetime, time, timezone
from pydantic import BaseModel
from uuid import UUID
load_dotenv()
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
router = APIRouter()
class CandidateUpdate(BaseModel):
favorite: bool | None = None
rating: float | None = None
class InterviewCreate(BaseModel):
inbox_id: int
interview_date: datetime | None = None
interview_time: datetime | None = None
interview_type: str | None = None
interview_status: str | None = None
class InterviewUpdate(BaseModel):
interview_date: datetime | None = None
interview_time: datetime | None = None
interview_type: str | None = None
interview_status: str | None = None
inbox_id: int | None = None
class NoteCreate(BaseModel):
user_id: UUID
note: str
class NoteUpdate(BaseModel):
note: str | None = None
class ActivityCreate(BaseModel):
message_id: UUID | None = None
user_id: UUID | None = None
inbox_id: int | None = None
activity_type: str | None = None
activity_status: str | None = None
description: str | None = None
activity_date: datetime | None = None
activity_time: datetime | None = None
class FeedbackCreate(BaseModel):
inbox_id: int | None = None
review: str | None = None
financial_status: str | None = None
score: float | None = None
note: str | None = None
reviewed_by: UUID | None = None
class FeedbackUpdate(BaseModel):
review: str | None = None
financial_status: str | None = None
score: float | None = None
note: str | None = None
inbox_id: int | None = None
reviewed_by: UUID | None = None
class StageChange(BaseModel):
inbox_id: int
to_stage: str
change_reason: str | None = None
class JobAssignmentCreate(BaseModel):
job_post_id: UUID
user_id: UUID
assignment_role: str | None = None
class ApplicationAssignmentCreate(BaseModel):
inbox_id: int
user_id: UUID
assignment_role: str | None = None
class HiringCostCreate(BaseModel):
cost_type: str
amount: float
job_post_id: UUID | None = None
currency: str | None = None
description: str | None = None
incurred_at: datetime | None = None
@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/create/candidate")
async def create_manual_candidate(
file: UploadFile = File(...),
candidate_email: str | None = Form(None),
candidate_name: str | None = Form(None),
candidate_phone: str | None = Form(None),
job_post_id: str | None = Form(None),
current_company: str | None = Form(None),
current_position: str | None = Form(None),
platform: str | None = Form(None),
experience: str | None = Form(None),
status: str | None = Form(None),
referral_by: str | None = Form(None),
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_CREATE)),
session: AsyncSession = Depends(get_session),
):
saved_path=None
try:
file_content = await file.read()
logger.info(f"Received file: {file.filename} ({len(file_content)} bytes)")
reader=FileRead(session=session,filename=file.filename,file=file_content)
# Parse first: an unreadable PDF is a 400, and doing it before the write
# keeps a file that can never back a row off the disk entirely.
parsed=await reader.injest_manual_upload()
saved=await reader.save_manual_upload()
saved_path=saved.get("file_path")
service=CandidateView(session=session)
data=await service.create_candidate(
candidate_email=candidate_email,
candidate_name=candidate_name,
candidate_phone=candidate_phone,
job_post_id=job_post_id,
current_company=current_company,
current_position=current_position,
platform=platform,
experience=experience,
status=status,
referral_by=referral_by,
file_name=saved.get("file_name"),
file_path=saved_path,
full_text=parsed.get("text") or "",
current_user=current_user.get("id"),
)
return JSONResponse(content={"data":data,"status_code":200})
except HTTPException:
# create_candidate rejects a blank email with a 422 AFTER the file has
# landed, so without this every such attempt would leave an orphan PDF.
FileRead.discard_upload(saved_path)
raise
except Exception as e:
FileRead.discard_upload(saved_path)
raise HTTPException(status_code=500,detail=str(e))
@router.get("/candidate/fetch/users")
async def fetch_users(
role_id:int=Query(8),
top:int=Query(10),
skip:int=Query(0),
search:str=Query(None),
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_VIEW)),
session: AsyncSession = Depends(get_session),
):
try:
service=User(session=session)
data=await service.get_users(role_id=role_id,top=top,skip=skip)
return JSONResponse(content={"data":data,"status_code":200})
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
@router.post("/candidate/cv_upload")
async def cv_upload(
file: UploadFile = File(...),
candidate_email: str | None = Form(None),
candidate_name: str | None = Form(None),
candidate_phone: str | None = Form(None),
job_post_id: str | None = Form(None),
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.ingest_upload(
candidate_email=candidate_email,candidate_name=candidate_name,
)
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/scored/fetch")
async def fetch_scored_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_job_posts(
search: str | None = Query(None),
top: int | None = Query(None),
skip: int = Query(0, ge=0),
ids: str | None = Query(None),
active_only: bool = Query(True),
# Either job-board or candidate viewers may list jobs — recruiters scoring
# CVs need a job to score against (CV Import picker).
current_user: dict = Depends(
require_permission(
PermissionTag.JOB_BOARD_VIEW, PermissionTag.CANDIDATES_VIEW, require_all=False
)
),
session: AsyncSession = Depends(get_session),
):
try:
service=JobPost(session=session)
id_list=[x.strip() for x in (ids or "").split(",") if x.strip()] or None
data,total=await service.fetch_job_posts(
search=search,
top=top,
skip=skip,
ids=id_list,
active_only=active_only,
)
return JSONResponse(content={"data":data,"total":total,"status_code":200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
@router.get("/jobs/fetch")
async def fetch_jobs(
search: str | None = Query(None),
department: str | None = Query(None),
requisition_status: str | None = Query(None),
employment_type: str | None = Query(None),
top: int | None = Query(None),
skip: int = Query(0, ge=0),
# Defaults False, unlike /job/fetch: a requisition list must show CLOSED
# requisitions, and those carry is_active = false. Soft-deleted rows are still
# excluded by the include_deleted branch in fetch_job_posts.
active_only: bool = Query(False),
current_user: dict = Depends(require_permission(PermissionTag.JOBS_VIEW)),
session: AsyncSession = Depends(get_session),
):
try:
service=JobPost(session=session)
data,total=await service.fetch_jobs(
search=search,department=department,requisition_status=requisition_status,
employment_type=employment_type,top=top,skip=skip,active_only=active_only,
)
return JSONResponse(content={"data":data,"total":total,"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))
@router.get("/candidate/fetch")
async def fetch_candidate(
user_id:str=Query(None),
limit:int=Query(10),
offset:int=Query(0),
search:str=Query(None),
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_VIEW)),
session: AsyncSession = Depends(get_session),
):
try:
service=CandidateView(session=session)
data=await service.get_candidate(user_id=user_id,limit=limit,offset=offset,search=search)
total=await service.count_candidates(user_id=user_id,search=search) if isinstance(data,list) else 1
return JSONResponse(content={"data":data,"total":total,"status_code":200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
@router.patch("/candidate/update")
async def update_candidate(
user_id:str=Query(...),
payload:CandidateUpdate=...,
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_EDIT)),
session: AsyncSession = Depends(get_session),
):
try:
service=CandidateView(session=session)
data=await service.update_candidate(user_id,payload.model_dump(exclude_unset=True))
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))
@router.get("/interview/fetch")
async def fetch_interview(
interview_id:str=Query(None),
inbox_id:int=Query(None),
from_date:datetime=Query(None),
to_date:datetime=Query(None),
status:str=Query(None),
top:int=Query(None),
skip:int=Query(0,ge=0),
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_VIEW)),
session: AsyncSession = Depends(get_session),
):
try:
service=Interview(session=session)
if not interview_id and inbox_id is None and (from_date is not None or to_date is not None or status is not None or top is not None):
data,total=await service.get_interviews_range(
from_date=from_date,to_date=to_date,status=status,top=top,skip=skip,
)
return JSONResponse(content={"data":data,"total":total,"status_code":200})
data=await service.get_interview(
interview_id=interview_id,inbox_id=inbox_id,
from_date=from_date,to_date=to_date,status=status,top=top,skip=skip,
)
if isinstance(data,tuple):
data,total=data
else:
total=1 if isinstance(data,dict) else len(data)
return JSONResponse(content={"data":data,"total":total,"status_code":200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
@router.post("/interview/create")
async def create_interview(
payload:InterviewCreate,
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_CREATE)),
session: AsyncSession = Depends(get_session),
):
try:
service=Interview(session=session)
data=await service.create_interview(payload.model_dump(exclude_unset=True))
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))
@router.patch("/interview/update")
async def update_interview(
interview_id:str=Query(...),
payload:InterviewUpdate=...,
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_EDIT)),
session: AsyncSession = Depends(get_session),
):
try:
service=Interview(session=session)
data=await service.update_interview(interview_id,payload.model_dump(exclude_unset=True))
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))
@router.get("/notes/fetch")
async def fetch_notes(
note_id:str=Query(None),
user_id:str=Query(None),
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_VIEW)),
session: AsyncSession = Depends(get_session),
):
try:
service=Note(session=session)
data=await service.get_note(note_id=note_id,user_id=user_id)
total=1 if isinstance(data,dict) else len(data)
return JSONResponse(content={"data":data,"total":total,"status_code":200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
@router.post("/notes/create")
async def create_note(
payload:NoteCreate,
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_CREATE)),
session: AsyncSession = Depends(get_session),
):
try:
service=Note(session=session)
data=await service.create_note(payload.model_dump(exclude_unset=True),current_user)
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))
@router.patch("/notes/update")
async def update_note(
note_id:str=Query(...),
payload:NoteUpdate=...,
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_EDIT)),
session: AsyncSession = Depends(get_session),
):
try:
service=Note(session=session)
data=await service.update_note(note_id,payload.model_dump(exclude_unset=True))
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))
@router.get("/activity/fetch")
async def fetch_activity(
activity_id:str=Query(None),
inbox_id:int=Query(None),
top:int=Query(None),
skip:int=Query(0,ge=0),
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_VIEW)),
session: AsyncSession = Depends(get_session),
):
try:
service=ActivityLog(session=session)
if not activity_id and inbox_id is None and top is not None:
data,total=await service.get_activity_feed(top=top,skip=skip)
return JSONResponse(content={"data":data,"total":total,"status_code":200})
data=await service.get_activity(activity_id=activity_id,inbox_id=inbox_id,top=top,skip=skip)
if isinstance(data,tuple):
data,total=data
else:
total=1 if isinstance(data,dict) else len(data)
return JSONResponse(content={"data":data,"total":total,"status_code":200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
@router.post("/activity/create")
async def create_activity(
payload:ActivityCreate,
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_CREATE)),
session: AsyncSession = Depends(get_session),
):
try:
service=ActivityLog(session=session)
data=await service.create_activity(payload.model_dump(exclude_unset=True))
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))
@router.get("/feedback/fetch")
async def fetch_feedback(
feedback_id:str=Query(None),
inbox_id:int=Query(None),
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_VIEW)),
session: AsyncSession = Depends(get_session),
):
try:
service=FeedbackView(session=session)
data=await service.get_feedback(feedback_id=feedback_id,inbox_id=inbox_id)
total=1 if isinstance(data,dict) else len(data)
return JSONResponse(content={"data":data,"total":total,"status_code":200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
@router.post("/feedback/create")
async def create_feedback(
payload:FeedbackCreate,
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_CREATE)),
session: AsyncSession = Depends(get_session),
):
try:
service=FeedbackView(session=session)
data=await service.create_feedback(payload.model_dump(exclude_unset=True),current_user)
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))
@router.patch("/feedback/update")
async def update_feedback(
feedback_id:str=Query(...),
payload:FeedbackUpdate=...,
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_EDIT)),
session: AsyncSession = Depends(get_session),
):
try:
service=FeedbackView(session=session)
data=await service.update_feedback(feedback_id,payload.model_dump(exclude_unset=True))
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))
@router.patch("/candidate/stage")
async def change_candidate_stage(
payload:StageChange,
current_user: dict = Depends(require_permission(PermissionTag.PIPELINE_EDIT)),
session: AsyncSession = Depends(get_session),
):
try:
service=Pipeline(session=session)
data=await service.change_stage(
payload.inbox_id,payload.to_stage,current_user,change_reason=payload.change_reason,
)
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))
@router.get("/pipeline/transitions/fetch")
async def fetch_pipeline_transitions(
transition_id:str=Query(None),
inbox_id:int=Query(None),
current_user: dict = Depends(require_permission(PermissionTag.PIPELINE_VIEW)),
session: AsyncSession = Depends(get_session),
):
try:
service=Pipeline(session=session)
data=await service.get_transitions(inbox_id=inbox_id,transition_id=transition_id)
total=1 if isinstance(data,dict) else len(data)
return JSONResponse(content={"data":data,"total":total,"status_code":200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
@router.get("/job/assignments/fetch")
async def fetch_job_assignments(
job_post_id:str=Query(...),
current_user: dict = Depends(require_permission(PermissionTag.JOBS_VIEW)),
session: AsyncSession = Depends(get_session),
):
try:
service=Assignment(session=session)
data=await service.list_job_assignments(job_post_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.post("/job/assignments/create")
async def create_job_assignment(
payload:JobAssignmentCreate,
current_user: dict = Depends(require_permission(PermissionTag.JOBS_EDIT)),
session: AsyncSession = Depends(get_session),
):
try:
service=Assignment(session=session)
data=await service.create_job_assignment(payload.model_dump(exclude_unset=True),current_user)
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))
@router.get("/candidate/assignments/fetch")
async def fetch_application_assignments(
inbox_id:int=Query(...),
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_VIEW)),
session: AsyncSession = Depends(get_session),
):
try:
service=Assignment(session=session)
data=await service.list_application_assignments(inbox_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.post("/candidate/assignments/create")
async def create_application_assignment(
payload:ApplicationAssignmentCreate,
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_EDIT)),
session: AsyncSession = Depends(get_session),
):
try:
service=Assignment(session=session)
data=await service.create_application_assignment(payload.model_dump(exclude_unset=True),current_user)
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))
@router.get("/job/costs/fetch")
async def fetch_hiring_costs(
job_post_id:str=Query(None),
from_date:datetime=Query(None),
to_date:datetime=Query(None),
top:int=Query(None),
skip:int=Query(0,ge=0),
current_user: dict = Depends(require_permission(PermissionTag.JOBS_VIEW)),
session: AsyncSession = Depends(get_session),
):
try:
service=HiringCost(session=session)
data,total=await service.list_costs(
job_post_id=job_post_id,from_date=from_date,to_date=to_date,top=top,skip=skip,
)
return JSONResponse(content={"data":data,"total":total,"status_code":200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
@router.post("/job/costs/create")
async def create_hiring_cost(
payload:HiringCostCreate,
current_user: dict = Depends(require_permission(PermissionTag.JOBS_EDIT)),
session: AsyncSession = Depends(get_session),
):
try:
service=HiringCost(session=session)
data=await service.create_cost(payload.model_dump(exclude_unset=True),current_user)
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))