interviewbot / backend /services /job_description_service.py
sajith-0701's picture
v5
15145f6
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)