File size: 5,541 Bytes
d50ee26
 
 
 
 
15145f6
 
d50ee26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15145f6
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
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)