Linking user to candidate to job
parent
09fdb39109
commit
08068b2b18
|
|
@ -68,4 +68,9 @@ def parse_match_response(data, allowed_ids) -> tuple[list[str], str, str]:
|
||||||
reasoning = "\n".join(str(item) for item in reasoning)
|
reasoning = "\n".join(str(item) for item in reasoning)
|
||||||
if not isinstance(reasoning, str):
|
if not isinstance(reasoning, str):
|
||||||
reasoning = ""
|
reasoning = ""
|
||||||
return suggested, summary.strip(), reasoning.strip()
|
|
||||||
|
experience = data.get("experience")
|
||||||
|
if not isinstance(experience, str):
|
||||||
|
experience = ""
|
||||||
|
|
||||||
|
return suggested, summary.strip(), reasoning.strip(), experience.strip()
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ class AgentState(TypedDict, total=False):
|
||||||
|
|
||||||
subject: str
|
subject: str
|
||||||
resume_text: str
|
resume_text: str
|
||||||
|
experience: str
|
||||||
job_posts: list[dict]
|
job_posts: list[dict]
|
||||||
suggested_job_post_ids: list[str]
|
suggested_job_post_ids: list[str]
|
||||||
summary: str
|
summary: str
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@ Respond with JSON only:
|
||||||
{
|
{
|
||||||
"suggested_job_post_ids": ["uuid", "..."],
|
"suggested_job_post_ids": ["uuid", "..."],
|
||||||
"summary": "one short sentence for the recruiter",
|
"summary": "one short sentence for the recruiter",
|
||||||
"reasoning": "brief bullet-style explanation per suggested match"
|
"reasoning": "brief bullet-style explanation per suggested match",
|
||||||
|
"experience": "the relevant experience of the candidate in years for the suggested match"
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ def serialize_agent_result(state: dict) -> dict:
|
||||||
"suggested_job_post_ids": state.get("suggested_job_post_ids") or [],
|
"suggested_job_post_ids": state.get("suggested_job_post_ids") or [],
|
||||||
"summary": state.get("summary") or "",
|
"summary": state.get("summary") or "",
|
||||||
"reasoning": state.get("reasoning") or "",
|
"reasoning": state.get("reasoning") or "",
|
||||||
|
"experience": state.get("experience") or "",
|
||||||
"status": state.get("status") or "failed",
|
"status": state.get("status") or "failed",
|
||||||
"error": state.get("error") or "",
|
"error": state.get("error") or "",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,12 +62,13 @@ async def match_jobs(state: AgentState) -> dict:
|
||||||
try:
|
try:
|
||||||
data = await llm_call(prompt(), user_prompt(state), json_mode=True)
|
data = await llm_call(prompt(), user_prompt(state), json_mode=True)
|
||||||
allowed_ids = {item["id"] for item in state.get("job_posts") or []}
|
allowed_ids = {item["id"] for item in state.get("job_posts") or []}
|
||||||
suggested, summary, reasoning = parse_match_response(data, allowed_ids)
|
suggested, summary, reasoning, experience = parse_match_response(data, allowed_ids)
|
||||||
return {
|
return {
|
||||||
"status": "matched",
|
"status": "matched",
|
||||||
"suggested_job_post_ids": suggested,
|
"suggested_job_post_ids": suggested,
|
||||||
"summary": summary,
|
"summary": summary,
|
||||||
"reasoning": reasoning,
|
"reasoning": reasoning,
|
||||||
|
"experience": experience,
|
||||||
}
|
}
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception("agent match_jobs failed")
|
logger.exception("agent match_jobs failed")
|
||||||
|
|
@ -77,6 +78,7 @@ async def match_jobs(state: AgentState) -> dict:
|
||||||
"suggested_job_post_ids": [],
|
"suggested_job_post_ids": [],
|
||||||
"summary": "",
|
"summary": "",
|
||||||
"reasoning": "",
|
"reasoning": "",
|
||||||
|
"experience": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -86,6 +88,7 @@ async def finalize(state: AgentState) -> dict:
|
||||||
"suggested_job_post_ids": state.get("suggested_job_post_ids") or [],
|
"suggested_job_post_ids": state.get("suggested_job_post_ids") or [],
|
||||||
"summary": state.get("summary") or "",
|
"summary": state.get("summary") or "",
|
||||||
"reasoning": state.get("reasoning") or "",
|
"reasoning": state.get("reasoning") or "",
|
||||||
|
"experience": state.get("experience") or "",
|
||||||
"status": state.get("status") or "failed",
|
"status": state.get("status") or "failed",
|
||||||
"error": state.get("error") or "",
|
"error": state.get("error") or "",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ router = APIRouter()
|
||||||
async def fetch_email(
|
async def fetch_email(
|
||||||
top:int=Query(100),
|
top:int=Query(100),
|
||||||
skip:int=Query(0,ge=0),
|
skip:int=Query(0,ge=0),
|
||||||
|
test_on: bool = Query(True),
|
||||||
token: str | None = Query(None),
|
token: str | None = Query(None),
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
):
|
):
|
||||||
|
|
@ -27,13 +28,17 @@ async def fetch_email(
|
||||||
items_lst=[]
|
items_lst=[]
|
||||||
for item in value:
|
for item in value:
|
||||||
message_id=item.get("id")
|
message_id=item.get("id")
|
||||||
service_per_email=await service.get_email_by_id(message_id)
|
service_per_email=await service.get_email_by_id(message_id,test_on)
|
||||||
items_lst.append({"message_id":message_id,"email_contents":service_per_email})
|
items_lst.append({"message_id":message_id,"email_contents":service_per_email})
|
||||||
|
|
||||||
if service.pending_match_ids:
|
if service.pending_match_ids:
|
||||||
await service.enqueue_matching(list(service.pending_match_ids),force=False)
|
await service.enqueue_matching(list(service.pending_match_ids),force=False)
|
||||||
|
|
||||||
return JSONResponse(content={"data":items_lst,"status_code":200})
|
account_setup=[]
|
||||||
|
if service.pending_confirmation_emails:
|
||||||
|
account_setup=await service.send_account_setup(list(service.pending_confirmation_emails))
|
||||||
|
|
||||||
|
return JSONResponse(content={"data":items_lst,"account_setup":account_setup,"status_code":200})
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,31 @@
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
from inbox.enums import Candidate_application_Status
|
from inbox.enums import Candidate_application_Status
|
||||||
|
from role.models import EnumRoles, Roles
|
||||||
from sqlalchemy import Column, DateTime, func, or_, update
|
from sqlalchemy import Column, DateTime, func, or_, update
|
||||||
from sqlalchemy.dialects.postgresql import JSONB
|
from sqlalchemy.dialects.postgresql import JSONB
|
||||||
|
from sqlalchemy.exc import IntegrityError
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlmodel import Field, Relationship, SQLModel, select, true
|
from sqlmodel import Field, Relationship, SQLModel, select, true
|
||||||
|
|
||||||
from users.models import Users
|
from users.models import Users
|
||||||
|
from users.plugins import hash_password
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
logger = logging.getLogger("inbox.models")
|
||||||
|
|
||||||
|
# Placeholder only. The account lands inactive and the candidate is mailed a
|
||||||
|
# confirmation link; the real password comes from the reset flow afterwards.
|
||||||
|
DEFAULT_CANDIDATE_PASSWORD = os.getenv("DEFAULT_CANDIDATE_PASSWORD", "Utopia!@#")
|
||||||
|
CANDIDATE_ROLE_ID_FALLBACK = 8 # mirrors users/views.py:signup_user
|
||||||
|
SKIP_SENDER_PREFIXES = ("noreply", "no-reply", "donotreply", "do-not-reply",
|
||||||
|
"mailer-daemon", "postmaster", "bounce")
|
||||||
|
|
||||||
|
|
||||||
class Inbox(SQLModel, table=True):
|
class Inbox(SQLModel, table=True):
|
||||||
|
|
@ -65,6 +82,7 @@ class Inbox_Messages(SQLModel, table=True):
|
||||||
file_name: str | None = Field(default=None)
|
file_name: str | None = Field(default=None)
|
||||||
file_path: str | None = Field(default=None)
|
file_path: str | None = Field(default=None)
|
||||||
resume_text: str | None = Field(default=None)
|
resume_text: str | None = Field(default=None)
|
||||||
|
experience: str | None = Field(default=None)
|
||||||
suggested_job_post_ids: list[str] | None = Field(default=None, sa_column=Column(JSONB))
|
suggested_job_post_ids: list[str] | None = Field(default=None, sa_column=Column(JSONB))
|
||||||
match_summary: str | None = Field(default=None)
|
match_summary: str | None = Field(default=None)
|
||||||
match_reasoning: str | None = Field(default=None)
|
match_reasoning: str | None = Field(default=None)
|
||||||
|
|
@ -83,6 +101,15 @@ class Inbox_Messages(SQLModel, table=True):
|
||||||
return body
|
return body
|
||||||
return email_data.get("bodyPreview") or ""
|
return email_data.get("bodyPreview") or ""
|
||||||
|
|
||||||
|
# @classmethod
|
||||||
|
# async def get_candidate_profile(cls,session:AsyncSession,user_id:uuid.UUID|None=None):
|
||||||
|
# try:
|
||||||
|
# qryy=select(cls,Users).join(cls,cls.)
|
||||||
|
# if user_id
|
||||||
|
|
||||||
|
# except Exception as e:
|
||||||
|
# raise HTTPException(status_code=500,detail=str(e))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_all_applications(cls,session:AsyncSession,message_id:uuid.UUID|int|None=None):
|
async def get_all_applications(cls,session:AsyncSession,message_id:uuid.UUID|int|None=None):
|
||||||
try:
|
try:
|
||||||
|
|
@ -101,6 +128,7 @@ class Inbox_Messages(SQLModel, table=True):
|
||||||
record_id,
|
record_id,
|
||||||
*,
|
*,
|
||||||
resume_text=None,
|
resume_text=None,
|
||||||
|
experience=None,
|
||||||
suggested_job_post_ids=None,
|
suggested_job_post_ids=None,
|
||||||
summary="",
|
summary="",
|
||||||
reasoning="",
|
reasoning="",
|
||||||
|
|
@ -118,6 +146,7 @@ class Inbox_Messages(SQLModel, table=True):
|
||||||
row.match_reasoning = reasoning or None
|
row.match_reasoning = reasoning or None
|
||||||
row.match_status = status or None
|
row.match_status = status or None
|
||||||
row.match_error = error or None
|
row.match_error = error or None
|
||||||
|
row.experience = experience or None
|
||||||
row.matched_at = datetime.now(timezone.utc)
|
row.matched_at = datetime.now(timezone.utc)
|
||||||
session.add(row)
|
session.add(row)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
@ -156,6 +185,72 @@ class Inbox_Messages(SQLModel, table=True):
|
||||||
"full_email_response": email_data,
|
"full_email_response": email_data,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _sender_address(cls, email_data: dict) -> str:
|
||||||
|
return (
|
||||||
|
email_data.get("from", {})
|
||||||
|
.get("emailAddress", {})
|
||||||
|
.get("address", "")
|
||||||
|
or ""
|
||||||
|
).strip().lower()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _sender_display_name(cls, email_data: dict, address: str) -> str:
|
||||||
|
name = (
|
||||||
|
email_data.get("from", {})
|
||||||
|
.get("emailAddress", {})
|
||||||
|
.get("name")
|
||||||
|
or ""
|
||||||
|
).strip()
|
||||||
|
if name:
|
||||||
|
return name
|
||||||
|
return address.split("@", 1)[0] if address else "candidate"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _is_linkable_sender(cls, address: str) -> bool:
|
||||||
|
if not address or "@" not in address:
|
||||||
|
return False
|
||||||
|
local = address.split("@", 1)[0]
|
||||||
|
return not local.startswith(SKIP_SENDER_PREFIXES)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def _link_sender(cls,session:AsyncSession,email_data:dict,email):
|
||||||
|
address=cls._sender_address(email_data)
|
||||||
|
if not cls._is_linkable_sender(address):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
user=(await session.execute(
|
||||||
|
select(Users).where(func.lower(Users.email)==address)
|
||||||
|
)).scalars().first()
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
role=await Roles.get_role_by_name(session,EnumRoles.CANDIDATE.value)
|
||||||
|
user=Users(
|
||||||
|
name=cls._sender_display_name(email_data,address),
|
||||||
|
email=address,
|
||||||
|
role_id=role.id if role else CANDIDATE_ROLE_ID_FALLBACK,
|
||||||
|
password=hash_password(DEFAULT_CANDIDATE_PASSWORD),
|
||||||
|
)
|
||||||
|
session.add(user)
|
||||||
|
session.add(Inbox(user_id=user.id,message_id=email.id))
|
||||||
|
await session.commit()
|
||||||
|
return address
|
||||||
|
|
||||||
|
link=(await session.execute(
|
||||||
|
select(Inbox).where(Inbox.message_id==email.id,Inbox.user_id==user.id)
|
||||||
|
)).scalars().first()
|
||||||
|
if not link:
|
||||||
|
session.add(Inbox(user_id=user.id,message_id=email.id))
|
||||||
|
await session.commit()
|
||||||
|
return None
|
||||||
|
except IntegrityError:
|
||||||
|
await session.rollback()
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
await session.rollback()
|
||||||
|
logger.warning("sender link failed for %s: %s",address,e)
|
||||||
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def insert_email(
|
async def insert_email(
|
||||||
cls,
|
cls,
|
||||||
|
|
@ -163,9 +258,11 @@ class Inbox_Messages(SQLModel, table=True):
|
||||||
email_data: dict,
|
email_data: dict,
|
||||||
file_path: list[str] | None = None,
|
file_path: list[str] | None = None,
|
||||||
):
|
):
|
||||||
|
"""Returns (row, new_user_email). new_user_email is set only when this call
|
||||||
|
created the sender's Users row."""
|
||||||
fields = cls._fields_from_email(email_data, file_path)
|
fields = cls._fields_from_email(email_data, file_path)
|
||||||
external_id = fields.get("message_id")
|
external_id = fields.get("message_id")
|
||||||
|
link_user=None
|
||||||
if external_id:
|
if external_id:
|
||||||
existing = (
|
existing = (
|
||||||
await session.execute(
|
await session.execute(
|
||||||
|
|
@ -178,12 +275,18 @@ class Inbox_Messages(SQLModel, table=True):
|
||||||
session.add(existing)
|
session.add(existing)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
await session.refresh(existing)
|
await session.refresh(existing)
|
||||||
return existing
|
|
||||||
|
if fields.get("attachment"):
|
||||||
|
link_user=await cls._link_sender(session, email_data, existing)
|
||||||
|
return existing, link_user
|
||||||
|
|
||||||
email = cls(**fields)
|
email = cls(**fields)
|
||||||
session.add(email)
|
session.add(email)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
return email
|
|
||||||
|
if fields.get("attachment"):
|
||||||
|
link_user=await cls._link_sender(session, email_data, email)
|
||||||
|
return email, link_user
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _search_filter(cls, search: str):
|
def _search_filter(cls, search: str):
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,25 @@ load_dotenv()
|
||||||
|
|
||||||
EMAIL_URL=os.getenv("EMAIL_URL")
|
EMAIL_URL=os.getenv("EMAIL_URL")
|
||||||
EMAIL_API_TOKEN=os.getenv("EMAIL_API_TOKEN")
|
EMAIL_API_TOKEN=os.getenv("EMAIL_API_TOKEN")
|
||||||
|
BACKEND_URL=os.getenv("BACKEND_URL","http://localhost:8000")
|
||||||
|
|
||||||
_ATTACHMENTS_DIR=Path(__file__).resolve().parent/"decoded_attachments"
|
_ATTACHMENTS_DIR=Path(__file__).resolve().parent/"decoded_attachments"
|
||||||
|
|
||||||
|
|
||||||
|
async def request_email_confirmation(email):
|
||||||
|
"""POST /users/confirm-email/resend on this same service -> status code.
|
||||||
|
|
||||||
|
Goes through the endpoint rather than importing Confirmation so the token row,
|
||||||
|
resend cooldown and mail send stay on one code path.
|
||||||
|
"""
|
||||||
|
async with httpx.AsyncClient(timeout=20.0) as client:
|
||||||
|
response=await client.post(
|
||||||
|
f"{BACKEND_URL.rstrip('/')}/users/confirm-email/resend",
|
||||||
|
json={"email":email},
|
||||||
|
)
|
||||||
|
return response.status_code
|
||||||
|
|
||||||
|
|
||||||
async def fetch_read_status_delta(folder, since=None, limit=1000, max_pages=10, token=None):
|
async def fetch_read_status_delta(folder, since=None, limit=1000, max_pages=10, token=None):
|
||||||
"""GET /sync/read-status -> the raw round dict."""
|
"""GET /sync/read-status -> the raw round dict."""
|
||||||
if not EMAIL_URL:
|
if not EMAIL_URL:
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ def serialize_application(message: Inbox_Messages) -> dict:
|
||||||
"resume_text": message.resume_text,
|
"resume_text": message.resume_text,
|
||||||
"ats_score": None,
|
"ats_score": None,
|
||||||
"phone": None,
|
"phone": None,
|
||||||
"experience": None,
|
"experience": message.experience or "",
|
||||||
"recruiter": None,
|
"recruiter": None,
|
||||||
"duplicate": None,
|
"duplicate": None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,12 +80,13 @@ async def match_inbox_message(record_id:str,force:bool=False) -> dict:
|
||||||
|
|
||||||
if status=="failed":
|
if status=="failed":
|
||||||
raise RuntimeError(error or "agent returned failed status")
|
raise RuntimeError(error or "agent returned failed status")
|
||||||
|
logger.info("agent result: %s,%s",result,result.get("experience"))
|
||||||
async with session_scope() as session:
|
async with session_scope() as session:
|
||||||
await Inbox_Messages.set_match_result(
|
await Inbox_Messages.set_match_result(
|
||||||
session,
|
session,
|
||||||
record_id,
|
record_id,
|
||||||
resume_text=text,
|
resume_text=text,
|
||||||
|
experience=result.get("experience") or "",
|
||||||
suggested_job_post_ids=result.get("suggested_job_post_ids") or [],
|
suggested_job_post_ids=result.get("suggested_job_post_ids") or [],
|
||||||
summary=result.get("summary") or "",
|
summary=result.get("summary") or "",
|
||||||
reasoning=result.get("reasoning") or "",
|
reasoning=result.get("reasoning") or "",
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ from inbox.plugins import (
|
||||||
EMAIL_API_TOKEN,
|
EMAIL_API_TOKEN,
|
||||||
fetch_message_read_status,
|
fetch_message_read_status,
|
||||||
load_message_files,
|
load_message_files,
|
||||||
|
request_email_confirmation,
|
||||||
)
|
)
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
@ -24,6 +25,7 @@ class Email:
|
||||||
self.get_url=os.getenv("EMAIL_URL")
|
self.get_url=os.getenv("EMAIL_URL")
|
||||||
self.token=token or EMAIL_API_TOKEN
|
self.token=token or EMAIL_API_TOKEN
|
||||||
self.pending_match_ids:list[str]=[]
|
self.pending_match_ids:list[str]=[]
|
||||||
|
self.pending_confirmation_emails:list[str]=[]
|
||||||
|
|
||||||
# async def get_all_applications(self,app_id=None):
|
# async def get_all_applications(self,app_id=None):
|
||||||
# try:
|
# try:
|
||||||
|
|
@ -49,7 +51,7 @@ class Email:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500,detail=str(e))
|
raise HTTPException(status_code=500,detail=str(e))
|
||||||
|
|
||||||
async def get_email_by_id(self,message_id):
|
async def get_email_by_id(self,message_id,test_on=True):
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
try:
|
try:
|
||||||
response=await client.get(f"{self.get_url}/emails/{message_id}",
|
response=await client.get(f"{self.get_url}/emails/{message_id}",
|
||||||
|
|
@ -58,14 +60,14 @@ class Email:
|
||||||
if response.status_code==200:
|
if response.status_code==200:
|
||||||
data=response.json()
|
data=response.json()
|
||||||
re_create_file=await decode_attachment(data.get("attachments"))
|
re_create_file=await decode_attachment(data.get("attachments"))
|
||||||
insert_func=await Inbox_Messages.insert_email(session=self.session,email_data=data,file_path=re_create_file)
|
row,new_user_email=await Inbox_Messages.insert_email(session=self.session,email_data=data,file_path=re_create_file)
|
||||||
if (
|
if row.attachment and row.file_path and row.match_status is None:
|
||||||
insert_func.attachment
|
self.pending_match_ids.append(str(row.id))
|
||||||
and insert_func.file_path
|
if test_on:
|
||||||
and insert_func.match_status is None
|
return data
|
||||||
):
|
if new_user_email:
|
||||||
self.pending_match_ids.append(str(insert_func.id))
|
self.pending_confirmation_emails.append(new_user_email)
|
||||||
return response.json()
|
return data
|
||||||
else:
|
else:
|
||||||
raise HTTPException(status_code=response.status_code,detail=response.text)
|
raise HTTPException(status_code=response.status_code,detail=response.text)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -128,6 +130,20 @@ class Email:
|
||||||
task_ids.append(task.task_id)
|
task_ids.append(task.task_id)
|
||||||
return task_ids
|
return task_ids
|
||||||
|
|
||||||
|
async def send_account_setup(self,emails):
|
||||||
|
"""Mail a confirmation link per freshly created sender. Best effort: a failed
|
||||||
|
mail must not fail a fetch whose messages are already stored."""
|
||||||
|
results=[]
|
||||||
|
for email in emails or []:
|
||||||
|
try:
|
||||||
|
status=await request_email_confirmation(email)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning("confirmation request failed for %s: %s",email,exc)
|
||||||
|
results.append({"email":email,"sent":False})
|
||||||
|
continue
|
||||||
|
results.append({"email":email,"sent":status==200})
|
||||||
|
return results
|
||||||
|
|
||||||
async def count_inbox_messages(self,search=None,isread:bool=True,application_status:Candidate_application_Status=Candidate_application_Status.CLOSED):
|
async def count_inbox_messages(self,search=None,isread:bool=True,application_status:Candidate_application_Status=Candidate_application_Status.CLOSED):
|
||||||
if application_status == Candidate_application_Status.PROCESS or application_status==Candidate_application_Status.REJECTED:
|
if application_status == Candidate_application_Status.PROCESS or application_status==Candidate_application_Status.REJECTED:
|
||||||
return await Inbox_Messages.count_inbox_messages(self.session,search,application_status=application_status)
|
return await Inbox_Messages.count_inbox_messages(self.session,search,application_status=application_status)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ from fastapi import APIRouter,Depends,Query
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
from db_setup import get_session
|
from db_setup import get_session
|
||||||
from job.candidate.views import FileRead
|
from job.candidate.views import FileRead,CandidateView
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from users.permissions import PermissionTag, require_permission
|
from users.permissions import PermissionTag, require_permission
|
||||||
from job.job_post.views import JobPost,JobPostCreate
|
from job.job_post.views import JobPost,JobPostCreate
|
||||||
|
|
@ -101,3 +101,22 @@ async def buffer_channels(
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500,detail=str(e))
|
raise HTTPException(status_code=500,detail=str(e))
|
||||||
|
|
||||||
|
@router.get("/candidate/fetch")
|
||||||
|
async def fetch_candidate(
|
||||||
|
user_id:str=Query(None),
|
||||||
|
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_VIEW)),
|
||||||
|
session: AsyncSession = Depends(get_session),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
service=CandidateView(session=session)
|
||||||
|
if user_id:
|
||||||
|
data=await service.get_candidate(user_id=user_id)
|
||||||
|
else:
|
||||||
|
data=await service.get_candidate()
|
||||||
|
return JSONResponse(content={"data":data,"status_code":200})
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500,detail=str(e))
|
||||||
|
|
||||||
|
|
@ -4,6 +4,7 @@ from datetime import datetime,timezone
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
from pypdf import PdfReader
|
from pypdf import PdfReader
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
from sqlmodel import true
|
||||||
from inbox.models import Inbox_Messages
|
from inbox.models import Inbox_Messages
|
||||||
from job.candidate.plugins import normalize_spaced_text
|
from job.candidate.plugins import normalize_spaced_text
|
||||||
|
|
||||||
|
|
@ -66,3 +67,13 @@ class FileRead:
|
||||||
# try:
|
# try:
|
||||||
# get_subject=Inbox_Messages.candidate_x_inbox(self.session,self.candidate_id)
|
# get_subject=Inbox_Messages.candidate_x_inbox(self.session,self.candidate_id)
|
||||||
# get_file=
|
# get_file=
|
||||||
|
|
||||||
|
class CandidateView:
|
||||||
|
def __init__(self,session:AsyncSession):
|
||||||
|
self.session=session
|
||||||
|
|
||||||
|
async def get_candidate(self,user_id=None):
|
||||||
|
try:
|
||||||
|
call_func=Inbox_Messages.get_candidate_profile(user_id=user_id)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500,detail=str(e))
|
||||||
Loading…
Reference in New Issue