File size: 5,724 Bytes
504df0f
 
2ae57cb
504df0f
 
 
9d4b413
 
 
504df0f
2ae57cb
 
504df0f
2ae57cb
504df0f
2ae57cb
9d4b413
2ae57cb
504df0f
 
49fbb3a
 
 
 
 
 
 
 
2ae57cb
 
504df0f
3c4bd31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
504df0f
2ae57cb
504df0f
 
2ae57cb
 
504df0f
2ae57cb
 
 
504df0f
 
2ae57cb
 
 
 
504df0f
1e8e58c
504df0f
2ae57cb
 
3c4bd31
 
 
 
 
 
504df0f
 
 
2ae57cb
 
 
 
 
504df0f
 
 
 
49fbb3a
 
 
504df0f
49fbb3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d23da13
 
 
 
 
 
 
 
 
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
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
import json

db = SQLAlchemy()

# ✅ Import User model here, before Job and Application use it
from backend.models.user import User

class Job(db.Model):
    __tablename__ = 'jobs'

    id = db.Column(db.Integer, primary_key=True)
    role = db.Column(db.String(100), nullable=False)
    description = db.Column(db.Text, nullable=False)
    seniority = db.Column(db.String(50), nullable=False)
    skills = db.Column(db.Text, nullable=False)
    company = db.Column(db.String(100), nullable=False)
    date_posted = db.Column(db.DateTime, default=datetime.utcnow)

    # Number of interview questions for this job. Recruiters can set this
    # value when posting a job. Defaults to 3 to preserve existing
    # behaviour where the interview consists of three questions. The
    # interview API uses this field to determine when to stop asking
    # follow‑up questions. See backend/routes/interview_api.py for
    # details.
    num_questions = db.Column(db.Integer, nullable=False, default=3)

    recruiter_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True)
    recruiter = db.relationship('User', backref='posted_jobs')

    @property
    def skills_list(self):
        """Return a list of skills parsed from the JSON string stored in ``skills``.

        The ``skills`` column stores a JSON encoded list of skills (e.g. '["Python", "Flask"]').
        In templates it is convenient to work with a Python list so that skills can be joined
        or iterated over.  If parsing fails for any reason an empty list is returned.
        """
        try:
            # Import json lazily to avoid circular imports at module import time.
            import json as _json
            return _json.loads(self.skills) if self.skills else []
        except Exception:
            return []

    def __repr__(self):
        return f"<Job {self.role} at {self.company}>"

class Application(db.Model):
    __tablename__ = 'applications'

    id = db.Column(db.Integer, primary_key=True)
    job_id = db.Column(db.Integer, db.ForeignKey('jobs.id'), nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)

    name = db.Column(db.String(100), nullable=False)
    email = db.Column(db.String(100), nullable=False)
    resume_path = db.Column(db.String(255), nullable=True)
    cover_letter = db.Column(db.Text, nullable=True)
    extracted_features = db.Column(db.Text, nullable=True)
    status = db.Column(db.String(50), default='applied')
    date_applied = db.Column(db.DateTime, default=datetime.utcnow)
    interview_log = db.Column(db.Text)

    user = db.relationship('User', backref='applications')

    # Set up a relationship back to the Job so that templates can access
    # ``application.job`` directly.  Without this relationship you'd need to
    # query the Job model manually in the route or template, which is less
    # convenient and can lead to additional database queries.
    job = db.relationship('Job', backref='applications', lazy='joined')

    def __repr__(self):
        return f"Application('{self.name}', '{self.email}', Job ID: {self.job_id})"

    def get_profile_data(self):
        try:
            return json.loads(self.extracted_features) if self.extracted_features else {}
        except:
            return {}

def init_db(app):
    db.init_app(app)
    with app.app_context():
        # Create any missing tables.  SQLAlchemy does not automatically add
        # columns to existing tables, so we call create_all() first to ensure
        # new tables (like ``applications`` or ``jobs``) are present.
        db.create_all()

        # Dynamically add the ``num_questions`` column to the ``jobs`` table
        # if it is missing.  When deploying an updated application against an
        # existing database, the new field will not appear until we run an
        # explicit ALTER TABLE.  Inspect the current table schema and add
        # ``num_questions`` with a default of 3 if it doesn't exist.  This
        # logic is idempotent: the ALTER TABLE statement runs only when
        # necessary.
        from sqlalchemy import inspect
        inspector = inspect(db.engine)
        try:
            columns = [col['name'] for col in inspector.get_columns('jobs')]
            if 'num_questions' not in columns:
                # SQLite supports adding new columns via ALTER TABLE.  The
                # default value of 3 matches the default declared in the
                # Job model.  If you are using a different database backend,
                # verify that this syntax is supported.
                db.session.execute('ALTER TABLE jobs ADD COLUMN num_questions INTEGER NOT NULL DEFAULT 3')
                db.session.commit()
        except Exception:
            # If inspection fails (e.g. the table does not exist yet), rely on
            # SQLAlchemy's create_all() to create a fresh schema with the
            # ``num_questions`` column.
            pass

        # Database tables are created on application start.  We intentionally do not
        # seed any sample data here.  Previously a block of code inserted dummy
        # job listings whenever the jobs table was empty.  In production, jobs
        # should only be added by authenticated recruiters via the job posting
        # interface.  Leaving the seeding logic in place would result in fake
        # positions appearing every time the application starts, which is
        # undesirable for a live recruitment platform.  If your environment
        # requires initial data for testing, insert it manually via the
        # database or through the new recruiter job posting page.