diff --git a/backend/job/app.py b/backend/job/app.py index fe715c6..af14011 100644 --- a/backend/job/app.py +++ b/backend/job/app.py @@ -4,13 +4,13 @@ from fastapi import HTTPException from db_setup import get_session from job.candidate.views import FileRead from sqlalchemy.ext.asyncio import AsyncSession -from pydantic import BaseModel, model_validator from users.permissions import PermissionTag, require_permission -from job.job_post.views import JobPost +from job.job_post.views import JobPost,JobPostCreate import logging from job.job_post.plugins import PlatformAlias from fastapi import UploadFile, File from dotenv import load_dotenv +from datetime import datetime, time, timezone load_dotenv() logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -18,30 +18,6 @@ logger = logging.getLogger(__name__) router = APIRouter() -class JobPostCreate(BaseModel): - title: str - experience_min: int | None = None - experience_max: int | None = None - requirements: list[str] = [] - optional_skills: list[str] = [] - salary: str = "Anonymous" - location: str | None = None - employment_type: str | None = None - platform: str = "linkedin" - description: str | None = None - platform: str | None = None - channel_id: str | None = None - mode: str = "addToQueue" - due_at: str | None = None - - @model_validator(mode="after") - def validate_mode_and_due_at(self): - allowed = {"addToQueue", "shareNow", "customScheduled"} - if self.mode not in allowed: - raise ValueError(f"mode must be one of {sorted(allowed)}") - if self.mode == "customScheduled" and not self.due_at: - raise ValueError("due_at is required when mode is customScheduled") - return self @router.get("/jobs/alias") async def get_job_alias(): @@ -65,7 +41,7 @@ async def cv_upload( 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(file_content, file.filename) - + return JSONResponse(content={"data":data,"status_code":200}) except HTTPException: raise @@ -81,8 +57,15 @@ async def post_job( ): try: service=JobPost(session=session) - data=await service.post_job(payload.model_dump(),current_user) - return JSONResponse(content={"data":data,"status_code":200}) + 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: diff --git a/backend/job/job_post/views.py b/backend/job/job_post/views.py index b80d985..3d64f93 100644 --- a/backend/job/job_post/views.py +++ b/backend/job/job_post/views.py @@ -1,10 +1,11 @@ +from datetime import date, time import os import httpx from dotenv import load_dotenv from fastapi import HTTPException from sqlalchemy.ext.asyncio import AsyncSession - +from pydantic import BaseModel, model_validator from job.job_post.models import JobPosts from job.job_post.plugins import ( BufferError, @@ -21,6 +22,33 @@ from job.job_post.serializers import serialize_job_post load_dotenv() +class JobPostCreate(BaseModel): + title: str + experience_min: int | None = None + experience_max: int | None = None + requirements: list[str] = [] + optional_skills: list[str] = [] + salary: str = "Anonymous" + location: str | None = None + employment_type: str | None = None + platform: str = "linkedin" + description: str | None = None + platform: str | None = None + channel_id: str | None = None + mode: str = "addToQueue" + scheduler_time: time | None = time(0, 0, 0) + scheduler_date: date | None = None + due_at: str | None = None + + @model_validator(mode="after") + def validate_mode_and_due_at(self): + allowed = {"addToQueue", "shareNow", "customScheduled"} + if self.mode not in allowed: + raise ValueError(f"mode must be one of {sorted(allowed)}") + if self.mode == "customScheduled" and not self.due_at: + raise ValueError("due_at is required when mode is customScheduled") + return self + class JobPost: def __init__(self,session:AsyncSession): self.session=session @@ -84,15 +112,10 @@ class JobPost: due_at=payload.get("due_at"), ) except (httpx.HTTPError,BufferError,RuntimeError) as e: - # Include the reason: Buffer's rejections are actionable (duplicate text, - # daily limit, disconnected channel) and an opaque 502 sends the caller - # digging through job_posts.buffer_error to find out. + await JobPosts.mark_failed(self.session,str(row.id),str(e)) raise HTTPException(status_code=502,detail=f"Failed to publish job post to Buffer: {e}") from e - # Buffer accepting the mutation is not the same as the network publishing it: - # the default addToQueue mode returns `scheduled`, so the row only reads - # "published" once Buffer reports `sent`. saved=await JobPosts.mark_buffer_result( self.session, str(row.id),