Spaces:
Sleeping
Sleeping
| from bson import ObjectId | |
| from database import get_db | |
| from models.collections import JOB_DESCRIPTIONS | |
| from utils.helpers import utc_now, str_objectid, str_objectids | |
| from utils.resume_text import extract_resume_text | |
| from utils.gemini import parse_jd_with_gemini | |
| def _normalize_required_skills(required_skills): | |
| items = required_skills or [] | |
| if not isinstance(items, list): | |
| return [] | |
| seen = set() | |
| output = [] | |
| for raw in items: | |
| skill = (raw or "").strip() | |
| if not skill: | |
| continue | |
| key = skill.lower() | |
| if key in seen: | |
| continue | |
| seen.add(key) | |
| output.append(skill) | |
| return output | |
| def _build_update_data(data: dict) -> dict: | |
| update_data = {} | |
| if "title" in data: | |
| title = (data.get("title") or "").strip() | |
| if not title: | |
| raise ValueError("title is required") | |
| update_data["title"] = title | |
| if "company" in data: | |
| update_data["company"] = (data.get("company") or "").strip() or None | |
| if "description" in data: | |
| description = (data.get("description") or "").strip() | |
| if not description: | |
| raise ValueError("description is required") | |
| update_data["description"] = description | |
| if "required_skills" in data: | |
| update_data["required_skills"] = _normalize_required_skills(data.get("required_skills")) | |
| if not update_data: | |
| raise ValueError("No fields to update") | |
| update_data["updated_at"] = utc_now() | |
| return update_data | |
| async def create_job_description( | |
| user_id: str, | |
| owner_role: str, | |
| title: str, | |
| description: str, | |
| company: str | None = None, | |
| required_skills: list[str] | None = None, | |
| ) -> dict: | |
| db = get_db() | |
| title = (title or "").strip() | |
| description = (description or "").strip() | |
| if not title: | |
| raise ValueError("title is required") | |
| if not description: | |
| raise ValueError("description is required") | |
| doc = { | |
| "user_id": user_id, | |
| "owner_role": owner_role if owner_role in {"student", "admin"} else "student", | |
| "title": title, | |
| "company": (company or "").strip() or None, | |
| "description": description, | |
| "required_skills": _normalize_required_skills(required_skills), | |
| "created_at": utc_now(), | |
| "updated_at": utc_now(), | |
| } | |
| result = await db[JOB_DESCRIPTIONS].insert_one(doc) | |
| doc["_id"] = result.inserted_id | |
| return str_objectid(doc) | |
| async def list_my_job_descriptions(user_id: str) -> list: | |
| db = get_db() | |
| docs = await db[JOB_DESCRIPTIONS].find({"user_id": user_id}).sort("updated_at", -1).to_list(length=300) | |
| return str_objectids(docs) | |
| async def update_my_job_description(user_id: str, jd_id: str, data: dict) -> dict: | |
| db = get_db() | |
| try: | |
| oid = ObjectId(jd_id) | |
| except Exception as exc: | |
| raise ValueError("Invalid job description id") from exc | |
| existing = await db[JOB_DESCRIPTIONS].find_one({"_id": oid, "user_id": user_id}) | |
| if not existing: | |
| raise ValueError("Job description not found") | |
| update_data = _build_update_data(data) | |
| await db[JOB_DESCRIPTIONS].update_one({"_id": oid}, {"$set": update_data}) | |
| updated = await db[JOB_DESCRIPTIONS].find_one({"_id": oid}) | |
| return str_objectid(updated) | |
| async def delete_my_job_description(user_id: str, jd_id: str) -> bool: | |
| db = get_db() | |
| try: | |
| oid = ObjectId(jd_id) | |
| except Exception: | |
| return False | |
| result = await db[JOB_DESCRIPTIONS].delete_one({"_id": oid, "user_id": user_id}) | |
| return result.deleted_count > 0 | |
| async def list_admin_job_descriptions(owner_user_id: str | None = None) -> list: | |
| db = get_db() | |
| query = {"user_id": owner_user_id} if owner_user_id else {} | |
| docs = await db[JOB_DESCRIPTIONS].find(query).sort("updated_at", -1).to_list(length=1000) | |
| return str_objectids(docs) | |
| async def update_admin_job_description(jd_id: str, data: dict) -> dict: | |
| db = get_db() | |
| try: | |
| oid = ObjectId(jd_id) | |
| except Exception as exc: | |
| raise ValueError("Invalid job description id") from exc | |
| existing = await db[JOB_DESCRIPTIONS].find_one({"_id": oid}) | |
| if not existing: | |
| raise ValueError("Job description not found") | |
| update_data = _build_update_data(data) | |
| await db[JOB_DESCRIPTIONS].update_one({"_id": oid}, {"$set": update_data}) | |
| updated = await db[JOB_DESCRIPTIONS].find_one({"_id": oid}) | |
| return str_objectid(updated) | |
| async def delete_admin_job_description(jd_id: str) -> bool: | |
| db = get_db() | |
| try: | |
| oid = ObjectId(jd_id) | |
| except Exception: | |
| return False | |
| result = await db[JOB_DESCRIPTIONS].delete_one({"_id": oid}) | |
| return result.deleted_count > 0 | |
| async def get_job_description_for_user(user_id: str, jd_id: str) -> dict: | |
| db = get_db() | |
| try: | |
| oid = ObjectId(jd_id) | |
| except Exception as exc: | |
| raise ValueError("Invalid job description id") from exc | |
| doc = await db[JOB_DESCRIPTIONS].find_one({"_id": oid, "user_id": user_id}) | |
| if not doc: | |
| raise ValueError("Job description not found") | |
| return str_objectid(doc) | |
| async def parse_jd_from_file(filename: str, file_content: bytes) -> dict: | |
| """Extract text from an uploaded JD file and use AI to parse it into structured fields.""" | |
| text = extract_resume_text(filename, file_content) | |
| if not text or len(text.strip()) < 20: | |
| raise ValueError("Could not extract readable text from the uploaded file") | |
| return await parse_jd_with_gemini(text) | |