import uuid from fastapi import HTTPException from sqlalchemy.ext.asyncio import AsyncSession from saved_search.models import SavedSearches from saved_search.serializers import serialize_saved_search VALID_ENTITIES = ("candidates", "jobs", "tasks", "inbox") def _as_uuid(value): if value in (None, ""): return None try: return uuid.UUID(str(value)) except (TypeError, ValueError): return None def _user_id(current_user): if not current_user or not current_user.get("id"): raise HTTPException(status_code=401, detail="Not authenticated") uid = _as_uuid(current_user["id"]) if uid is None: raise HTTPException(status_code=401, detail="Invalid user id") return uid class SavedSearch: def __init__(self, session: AsyncSession): self.session = session async def get_saved_searches(self, current_user, entity=None): if entity and entity not in VALID_ENTITIES: raise HTTPException( status_code=422, detail=f"entity must be one of {', '.join(VALID_ENTITIES)}" ) rows, total = await SavedSearches.fetch_saved_searches( self.session, user_id=_user_id(current_user), entity=entity ) return [serialize_saved_search(r) for r in rows], total async def create_saved_search(self, payload, current_user): name = (payload.get("name") or "").strip() if not name: raise HTTPException(status_code=422, detail="name is required") entity = (payload.get("entity") or "").strip() if entity not in VALID_ENTITIES: raise HTTPException( status_code=422, detail=f"entity must be one of {', '.join(VALID_ENTITIES)}" ) filters = payload.get("filters") if isinstance(payload.get("filters"), dict) else {} row = await SavedSearches.insert_saved_search(self.session, { "user_id": _user_id(current_user), "name": name, "entity": entity, "filters": filters, }) return serialize_saved_search(row) async def update_saved_search(self, record_id, payload, current_user): uid = _user_id(current_user) fields = {} if "name" in payload: name = (payload.get("name") or "").strip() if not name: raise HTTPException(status_code=422, detail="name cannot be blank") fields["name"] = name if "entity" in payload: entity = (payload.get("entity") or "").strip() if entity not in VALID_ENTITIES: raise HTTPException( status_code=422, detail=f"entity must be one of {', '.join(VALID_ENTITIES)}" ) fields["entity"] = entity if "filters" in payload: if payload["filters"] is not None and not isinstance(payload["filters"], dict): raise HTTPException(status_code=422, detail="filters must be an object") fields["filters"] = payload["filters"] or {} if not fields: raise HTTPException(status_code=400, detail="No fields to update") row = await SavedSearches.update_saved_search( self.session, record_id, fields, user_id=uid ) if not row: raise HTTPException(status_code=404, detail="Saved search not found") return serialize_saved_search(row) async def delete_saved_search(self, record_id, current_user): row = await SavedSearches.soft_delete_saved_search( self.session, record_id, user_id=_user_id(current_user) ) if not row: raise HTTPException(status_code=404, detail="Saved search not found") return {"id": str(row.id), "deleted": True}