husseinelsaadi commited on
Commit
3c4bd31
·
1 Parent(s): 9a7d4db
app.py CHANGED
@@ -41,6 +41,24 @@ app = Flask(
41
 
42
  app.config['SECRET_KEY'] = 'your-secret-key'
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  # Configure the database connection
45
  # Use /tmp directory for database in Hugging Face Spaces
46
  # Note: Data will be lost when the space restarts
 
41
 
42
  app.config['SECRET_KEY'] = 'your-secret-key'
43
 
44
+ # -----------------------------------------------------------------------------
45
+ # Cookie configuration for Hugging Face Spaces
46
+ #
47
+ # When running this app inside an iframe (as is typical on Hugging Face Spaces),
48
+ # browsers will drop cookies that have the default SameSite policy of ``Lax``.
49
+ # This prevents the Flask session cookie from being stored and means that
50
+ # ``login_user()`` will appear to have no effect – the user will be redirected
51
+ # back to the home page but remain anonymous. By explicitly setting the
52
+ # SameSite policy to ``None`` and enabling the ``Secure`` flag, we allow the
53
+ # session and remember cookies to be sent even when the app is embedded in an
54
+ # iframe. Without these settings the sign‑up and login flows work locally
55
+ # but silently fail in Spaces, causing the "redirect to home page without
56
+ # anything" behaviour reported by users.
57
+ app.config['SESSION_COOKIE_SAMESITE'] = 'None'
58
+ app.config['SESSION_COOKIE_SECURE'] = True
59
+ app.config['REMEMBER_COOKIE_SAMESITE'] = 'None'
60
+ app.config['REMEMBER_COOKIE_SECURE'] = True
61
+
62
  # Configure the database connection
63
  # Use /tmp directory for database in Hugging Face Spaces
64
  # Note: Data will be lost when the space restarts
backend/models/database.py CHANGED
@@ -21,6 +21,21 @@ class Job(db.Model):
21
  recruiter_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True)
22
  recruiter = db.relationship('User', backref='posted_jobs')
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  def __repr__(self):
25
  return f"<Job {self.role} at {self.company}>"
26
 
@@ -41,6 +56,12 @@ class Application(db.Model):
41
 
42
  user = db.relationship('User', backref='applications')
43
 
 
 
 
 
 
 
44
  def __repr__(self):
45
  return f"Application('{self.name}', '{self.email}', Job ID: {self.job_id})"
46
 
 
21
  recruiter_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True)
22
  recruiter = db.relationship('User', backref='posted_jobs')
23
 
24
+ @property
25
+ def skills_list(self):
26
+ """Return a list of skills parsed from the JSON string stored in ``skills``.
27
+
28
+ The ``skills`` column stores a JSON encoded list of skills (e.g. '["Python", "Flask"]').
29
+ In templates it is convenient to work with a Python list so that skills can be joined
30
+ or iterated over. If parsing fails for any reason an empty list is returned.
31
+ """
32
+ try:
33
+ # Import json lazily to avoid circular imports at module import time.
34
+ import json as _json
35
+ return _json.loads(self.skills) if self.skills else []
36
+ except Exception:
37
+ return []
38
+
39
  def __repr__(self):
40
  return f"<Job {self.role} at {self.company}>"
41
 
 
56
 
57
  user = db.relationship('User', backref='applications')
58
 
59
+ # Set up a relationship back to the Job so that templates can access
60
+ # ``application.job`` directly. Without this relationship you'd need to
61
+ # query the Job model manually in the route or template, which is less
62
+ # convenient and can lead to additional database queries.
63
+ job = db.relationship('Job', backref='applications', lazy='joined')
64
+
65
  def __repr__(self):
66
  return f"Application('{self.name}', '{self.email}', Job ID: {self.job_id})"
67
 
backend/routes/auth.py CHANGED
@@ -40,10 +40,17 @@ def handle_resume_upload(file):
40
  @auth_bp.route('/login', methods=['GET', 'POST'])
41
  def login():
42
  form = LoginForm()
 
 
 
 
 
43
  if form.validate_on_submit():
44
  user = User.query.filter_by(email=form.email.data).first()
45
  if user and user.check_password(form.password.data):
46
  login_user(user)
 
 
47
  return redirect(url_for('index'))
48
  else:
49
  flash('Invalid credentials', 'danger')
 
40
  @auth_bp.route('/login', methods=['GET', 'POST'])
41
  def login():
42
  form = LoginForm()
43
+ # When the form is submitted and passes validation, attempt to authenticate
44
+ # the user. If the email/password pair is valid, log them in and provide
45
+ # a success flash message so the user has feedback. Without this message
46
+ # the application silently redirects to the home page, which can be
47
+ # confusing. If authentication fails, flash an error message.
48
  if form.validate_on_submit():
49
  user = User.query.filter_by(email=form.email.data).first()
50
  if user and user.check_password(form.password.data):
51
  login_user(user)
52
+ # ✅ Provide a success message so users know the login worked
53
+ flash('You have been logged in successfully!', 'success')
54
  return redirect(url_for('index'))
55
  else:
56
  flash('Invalid credentials', 'danger')
backend/templates/apply.html CHANGED
@@ -1,13 +1,13 @@
1
  {% extends "base.html" %}
2
 
3
- {% block title %}Apply for {{ job.title }} - Codingo{% endblock %}
4
 
5
  {% block hero %}
6
  <section class="hero" style="padding: 3rem 1rem;">
7
  <div class="container">
8
  <div class="hero-content">
9
- <h1>Apply for {{ job.title }}</h1>
10
- <p>{{ job.company }} • {{ job.location }}</p>
11
  </div>
12
  </div>
13
  </section>
@@ -18,7 +18,7 @@
18
  <ul class="breadcrumbs">
19
  <li><a href="{{ url_for('index') }}">Home</a></li>
20
  <li><a href="{{ url_for('jobs') }}">Jobs</a></li>
21
- <li><a href="{{ url_for('job_detail', job_id=job.id) }}">{{ job.title }}</a></li>
22
  <li>Apply</li>
23
  </ul>
24
 
 
1
  {% extends "base.html" %}
2
 
3
+ {% block title %}Apply for {{ job.role }} - Codingo{% endblock %}
4
 
5
  {% block hero %}
6
  <section class="hero" style="padding: 3rem 1rem;">
7
  <div class="container">
8
  <div class="hero-content">
9
+ <h1>Apply for {{ job.role }}</h1>
10
+ <p>{{ job.company }}{% if job.seniority %} • {{ job.seniority }}{% endif %}</p>
11
  </div>
12
  </div>
13
  </section>
 
18
  <ul class="breadcrumbs">
19
  <li><a href="{{ url_for('index') }}">Home</a></li>
20
  <li><a href="{{ url_for('jobs') }}">Jobs</a></li>
21
+ <li><a href="{{ url_for('job_detail', job_id=job.id) }}">{{ job.role }}</a></li>
22
  <li>Apply</li>
23
  </ul>
24
 
backend/templates/base.html CHANGED
@@ -735,11 +735,17 @@
735
  </a>
736
  <div class="login-buttons">
737
  {% if current_user.is_authenticated %}
738
- <span class="welcome-message">Welcome, {{ current_user.username }}</span>
739
- <a href="{{ url_for('auth.logout') }}" class="btn btn-logout">Logout</a>
 
 
 
 
 
 
740
  {% else %}
741
- <a href="{{ url_for('auth.login') }}" class="btn btn-outline">Log In</a>
742
- <a href="{{ url_for('auth.signup') }}" class="btn btn-primary">Sign Up</a>
743
  {% endif %}
744
  </div>
745
 
 
735
  </a>
736
  <div class="login-buttons">
737
  {% if current_user.is_authenticated %}
738
+ <!-- Show a link to the user's dashboard based on their role -->
739
+ {% if current_user.role == 'unemployed' %}
740
+ <a href="{{ url_for('my_applications') }}" class="btn btn-outline">My Applications</a>
741
+ {% elif current_user.role == 'recruiter' %}
742
+ <a href="{{ url_for('jobs') }}" class="btn btn-outline">Browse Candidates</a>
743
+ {% endif %}
744
+ <span class="welcome-message">Welcome, {{ current_user.username }}</span>
745
+ <a href="{{ url_for('auth.logout') }}" class="btn btn-logout">Logout</a>
746
  {% else %}
747
+ <a href="{{ url_for('auth.login') }}" class="btn btn-outline">Log In</a>
748
+ <a href="{{ url_for('auth.signup') }}" class="btn btn-primary">Sign Up</a>
749
  {% endif %}
750
  </div>
751
 
backend/templates/job_detail.html CHANGED
@@ -1,21 +1,21 @@
1
  {% extends "base.html" %}
2
 
3
- {% block title %}{{ job.title }} - Codingo{% endblock %}
4
 
5
  {% block content %}
6
  <section class="content-section">
7
  <ul class="breadcrumbs">
8
  <li><a href="{{ url_for('index') }}">Home</a></li>
9
  <li><a href="{{ url_for('jobs') }}">Jobs</a></li>
10
- <li>{{ job.title }}</li>
11
  </ul>
12
 
13
  <div class="card">
14
  <div class="card-header">
15
- <h2>{{ job.title }}</h2>
16
  <div style="display: flex; justify-content: space-between; margin-top: 0.5rem;">
17
  <span>{{ job.company }}</span>
18
- <span>{{ job.location }}</span>
19
  </div>
20
  </div>
21
  <div class="card-body">
@@ -25,8 +25,16 @@
25
  </div>
26
 
27
  <div style="margin-bottom: 2rem;">
28
- <h3 style="color: var(--primary); margin-bottom: 1rem;">Requirements</h3>
29
- <p>{{ job.requirements }}</p>
 
 
 
 
 
 
 
 
30
  </div>
31
 
32
  <div style="text-align: center; margin-top: 2rem;">
 
1
  {% extends "base.html" %}
2
 
3
+ {% block title %}{{ job.role }} - Codingo{% endblock %}
4
 
5
  {% block content %}
6
  <section class="content-section">
7
  <ul class="breadcrumbs">
8
  <li><a href="{{ url_for('index') }}">Home</a></li>
9
  <li><a href="{{ url_for('jobs') }}">Jobs</a></li>
10
+ <li>{{ job.role }}</li>
11
  </ul>
12
 
13
  <div class="card">
14
  <div class="card-header">
15
+ <h2>{{ job.role }}</h2>
16
  <div style="display: flex; justify-content: space-between; margin-top: 0.5rem;">
17
  <span>{{ job.company }}</span>
18
+ <span>{{ job.seniority }}</span>
19
  </div>
20
  </div>
21
  <div class="card-body">
 
25
  </div>
26
 
27
  <div style="margin-bottom: 2rem;">
28
+ <h3 style="color: var(--primary); margin-bottom: 1rem;">Required Skills</h3>
29
+ <!-- Display the skills as a comma separated list. The Job model exposes
30
+ a ``skills_list`` property to parse the JSON stored in the database. -->
31
+ <p>
32
+ {% if job.skills_list %}
33
+ {{ job.skills_list | join(', ') }}
34
+ {% else %}
35
+ Not specified
36
+ {% endif %}
37
+ </p>
38
  </div>
39
 
40
  <div style="text-align: center; margin-top: 2rem;">
backend/templates/jobs.html CHANGED
@@ -30,10 +30,14 @@
30
  {% for job in jobs %}
31
  <div class="job-card">
32
  <div class="job-header">
33
- <h3>{{ job.title }}</h3>
 
 
 
 
34
  <div class="job-info">
35
  <span>{{ job.company }}</span>
36
- <span>{{ job.location }}</span>
37
  </div>
38
  </div>
39
  <div class="job-body">
 
30
  {% for job in jobs %}
31
  <div class="job-card">
32
  <div class="job-header">
33
+ <!-- Use the Job model's fields instead of undefined 'title' and 'location'.
34
+ Each job has a 'role' (job title), 'company' and 'seniority' (e.g. Junior/Mid/Senior).
35
+ The previous template referenced 'job.title' and 'job.location' which do not exist on
36
+ our SQLAlchemy model and caused rendering errors. -->
37
+ <h3>{{ job.role }}</h3>
38
  <div class="job-info">
39
  <span>{{ job.company }}</span>
40
+ <span>{{ job.seniority }}</span>
41
  </div>
42
  </div>
43
  <div class="job-body">
backend/templates/my_applications.html ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}My Applications - Codingo{% endblock %}
4
+
5
+ {% block content %}
6
+ <section class="content-section">
7
+ <div class="section-title">
8
+ <h2>My Applications</h2>
9
+ <p>Your submitted job applications are listed below.</p>
10
+ </div>
11
+
12
+ <ul class="breadcrumbs">
13
+ <li><a href="{{ url_for('index') }}">Home</a></li>
14
+ <li>My Applications</li>
15
+ </ul>
16
+
17
+ <div class="application-list">
18
+ {% if applications %}
19
+ {% for application in applications %}
20
+ <div class="application-card">
21
+ <div class="application-header">
22
+ <h3>{{ application.job.role if application.job else 'Unknown Role' }}</h3>
23
+ <div class="application-info">
24
+ <span>{{ application.job.company if application.job else '' }}</span>
25
+ <span>Status: {{ application.status }}</span>
26
+ </div>
27
+ </div>
28
+ <div class="application-body">
29
+ <p>Applied on {{ application.date_applied.strftime('%B %d, %Y') }}</p>
30
+ {% if application.job %}
31
+ <p>{{ application.job.description[:150] }}{% if application.job.description|length > 150 %}...{% endif %}</p>
32
+ {% endif %}
33
+ </div>
34
+ <div class="application-footer">
35
+ {% if application.job %}
36
+ <a href="{{ url_for('job_detail', job_id=application.job.id) }}" class="btn btn-outline">View Job</a>
37
+ {% endif %}
38
+ {% if application.extracted_features %}
39
+ <!-- Offer to take the interview if CV data exists -->
40
+ <a href="{{ url_for('interview_page', job_id=application.job.id) }}" class="btn btn-primary">Take Interview</a>
41
+ {% endif %}
42
+ </div>
43
+ </div>
44
+ {% endfor %}
45
+ {% else %}
46
+ <div class="card">
47
+ <div class="card-body">
48
+ <p>You haven't applied to any jobs yet. Browse available positions on the <a href="{{ url_for('jobs') }}">jobs page</a>.</p>
49
+ </div>
50
+ </div>
51
+ {% endif %}
52
+ </div>
53
+ </section>
54
+
55
+ <style>
56
+ .application-list {
57
+ display: grid;
58
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
59
+ gap: 1.5rem;
60
+ }
61
+
62
+ .application-card {
63
+ background-color: var(--light);
64
+ border: 1px solid #eee;
65
+ border-radius: 8px;
66
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
67
+ display: flex;
68
+ flex-direction: column;
69
+ justify-content: space-between;
70
+ padding: 1rem;
71
+ }
72
+
73
+ .application-header h3 {
74
+ margin-bottom: 0.5rem;
75
+ color: var(--primary);
76
+ }
77
+
78
+ .application-info span {
79
+ margin-right: 1rem;
80
+ color: var(--dark);
81
+ font-weight: 500;
82
+ }
83
+
84
+ .application-footer {
85
+ margin-top: 1rem;
86
+ display: flex;
87
+ justify-content: flex-end;
88
+ gap: 0.5rem;
89
+ }
90
+ </style>
91
+ {% endblock %}