husseinelsaadi commited on
Commit
d8acd61
·
1 Parent(s): 504df0f

fixed-login

Browse files

* fixed-login

* Updated after copilot feedback

.venv/bin/email_validator ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ '''exec' "/Users/husseinelsaadi/Documents/Data Science USAL/Spring 24-25/FYP - Codingo/Codingo/.venv/bin/python3.12" "$0" "$@"
3
+ ' '''
4
+ # -*- coding: utf-8 -*-
5
+ import re
6
+ import sys
7
+ from email_validator.__main__ import main
8
+ if __name__ == '__main__':
9
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
10
+ sys.exit(main())
backend/app.py CHANGED
@@ -1,102 +1,111 @@
1
  from flask import Flask, render_template, redirect, url_for, flash, request
 
2
  from werkzeug.utils import secure_filename
3
  import os
4
  import sys
5
  import json
6
  from datetime import datetime
7
 
8
- # Add the parent directory to sys.path to help with imports
9
  current_dir = os.path.dirname(os.path.abspath(__file__))
10
  parent_dir = os.path.dirname(current_dir)
11
  sys.path.append(parent_dir)
12
  sys.path.append(current_dir)
13
 
14
- # Import local modules with error handling
15
- try:
16
- from form.JobApplicationForm import JobApplicationForm
17
- except ImportError:
18
- try:
19
- from backend.form.JobApplicationForm import JobApplicationForm
20
- except ImportError:
21
- print("Error importing JobApplicationForm. Check the path.")
22
- sys.exit(1)
 
 
 
 
23
 
 
24
  try:
25
- from models.database import db, Job, Application, init_db
26
  except ImportError:
27
- try:
28
- from backend.models.database import db, Job, Application, init_db
29
- except ImportError:
30
- print("Error importing database models. Check the path.")
31
- sys.exit(1)
32
 
33
  try:
34
- from models.resume_parser.resume_to_features import extract_resume_features
35
  except ImportError:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  try:
37
- from backend.models.resume_parser.resume_to_features import extract_resume_features
38
- except ImportError:
39
- print("Error importing resume_to_features. Check if the function is defined in the module.")
40
- sys.exit(1)
41
 
42
- app = Flask(__name__)
43
- app.config['SECRET_KEY'] = 'your-secret-key'
44
- app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///codingo.db'
45
- app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
46
- app.config['UPLOAD_FOLDER'] = 'uploads/resumes'
47
 
48
- # Create upload folder if it doesn't exist
49
- os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
 
 
50
 
51
- # Initialize the database with the app
52
- init_db(app)
53
 
54
  # Routes
55
  @app.route('/')
56
  def index():
57
  return render_template('index.html')
58
 
59
-
60
  @app.route('/jobs')
61
  def jobs():
62
  all_jobs = Job.query.order_by(Job.date_posted.desc()).all()
63
  return render_template('jobs.html', jobs=all_jobs)
64
 
65
-
66
  @app.route('/job/<int:job_id>')
67
  def job_detail(job_id):
68
  job = Job.query.get_or_404(job_id)
69
  return render_template('job_detail.html', job=job)
70
 
71
-
72
  @app.route('/apply/<int:job_id>', methods=['GET', 'POST'])
 
73
  def apply(job_id):
74
  job = Job.query.get_or_404(job_id)
75
  form = JobApplicationForm()
76
  form.job_id.data = job_id
77
 
78
  if form.validate_on_submit():
79
- # Save resume file
80
  resume_file = form.resume.data
81
- filename = secure_filename(
82
- f"{form.name.data.replace(' ', '_')}_{datetime.now().strftime('%Y%m%d%H%M%S')}.{resume_file.filename.split('.')[-1]}")
83
- resume_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
84
- resume_file.save(resume_path)
85
-
86
- # Extract features from resume
87
- try:
88
- features = extract_resume_features(resume_path)
89
- features_json = json.dumps(features)
90
- except Exception as e:
91
- print(f"Error extracting features: {e}")
92
- features_json = "{}"
93
-
94
- # Create new application
95
  application = Application(
96
  job_id=job_id,
97
  name=form.name.data,
98
  email=form.email.data,
99
- resume_path=resume_path,
100
  cover_letter=form.cover_letter.data,
101
  extracted_features=features_json
102
  )
@@ -109,36 +118,32 @@ def apply(job_id):
109
 
110
  return render_template('apply.html', form=form, job=job)
111
 
112
-
113
  @app.route('/parse_resume', methods=['POST'])
114
  def parse_resume():
115
- if 'resume' not in request.files:
116
- return {"error": "No file uploaded"}, 400
117
 
118
- file = request.files['resume']
119
- if file.filename == '':
120
- return {"error": "No selected file"}, 400
121
 
122
- filename = secure_filename(file.filename)
123
- file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
124
- file.save(file_path)
 
 
 
 
 
 
 
 
 
125
 
126
- # Extract features from resume
127
- try:
128
- features = extract_resume_features(file_path)
129
- response = {
130
- "name": features.get('name', ''),
131
- "email": features.get('email', ''),
132
- "mobile_number": features.get('mobile_number', ''),
133
- "skills": features.get('skills', []),
134
- "experience": features.get('experience', [])
135
- }
136
- return response, 200
137
- except Exception as e:
138
- print(f"Error parsing resume: {e}")
139
- return {"error": "Failed to parse resume"}, 500
140
 
141
 
142
  if __name__ == '__main__':
143
  print("Starting Codingo application...")
144
- app.run(debug=True)
 
 
 
1
  from flask import Flask, render_template, redirect, url_for, flash, request
2
+ from flask_login import LoginManager, login_required, current_user
3
  from werkzeug.utils import secure_filename
4
  import os
5
  import sys
6
  import json
7
  from datetime import datetime
8
 
9
+ # Adjust sys.path for import flexibility
10
  current_dir = os.path.dirname(os.path.abspath(__file__))
11
  parent_dir = os.path.dirname(current_dir)
12
  sys.path.append(parent_dir)
13
  sys.path.append(current_dir)
14
 
15
+ # Initialize Flask app
16
+ app = Flask(__name__)
17
+ app.config['SECRET_KEY'] = 'your-secret-key'
18
+ app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///codingo.db'
19
+ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
20
+
21
+ # Import and initialize DB
22
+ from backend.models.database import db, Job, Application, init_db
23
+ init_db(app)
24
+
25
+ # Import models/routes AFTER initializing the DB
26
+ from backend.models.user import User
27
+ from backend.routes.auth import auth_bp
28
 
29
+ # Import other modules
30
  try:
31
+ from backend.form.JobApplicationForm import JobApplicationForm
32
  except ImportError:
33
+ from form.JobApplicationForm import JobApplicationForm
 
 
 
 
34
 
35
  try:
36
+ from backend.models.resume_parser.resume_to_features import extract_resume_features
37
  except ImportError:
38
+ from models.resume_parser.resume_to_features import extract_resume_features
39
+
40
+ # Flask-Login setup
41
+ login_manager = LoginManager()
42
+ login_manager.login_view = 'auth.login'
43
+ login_manager.init_app(app)
44
+
45
+ @login_manager.user_loader
46
+ def load_user(user_id):
47
+ return db.session.get(User, int(user_id))
48
+
49
+ # Register auth blueprint
50
+ app.register_blueprint(auth_bp)
51
+
52
+
53
+ def handle_resume_upload(file):
54
+ """
55
+ Save uploaded file temporarily, extract features, then clean up.
56
+ Returns (features_dict, error_message, filename)
57
+ """
58
+ if not file or file.filename == '':
59
+ return None, "No file uploaded", None
60
+
61
  try:
62
+ filename = secure_filename(file.filename)
63
+ filepath = os.path.join(current_dir, 'temp', filename)
64
+ os.makedirs(os.path.dirname(filepath), exist_ok=True)
65
+ file.save(filepath)
66
 
67
+ features = extract_resume_features(filepath)
68
+ os.remove(filepath) # Clean up after parsing
 
 
 
69
 
70
+ return features, None, filename
71
+ except Exception as e:
72
+ print(f"Error in handle_resume_upload: {e}")
73
+ return None, str(e), None
74
 
 
 
75
 
76
  # Routes
77
  @app.route('/')
78
  def index():
79
  return render_template('index.html')
80
 
 
81
  @app.route('/jobs')
82
  def jobs():
83
  all_jobs = Job.query.order_by(Job.date_posted.desc()).all()
84
  return render_template('jobs.html', jobs=all_jobs)
85
 
 
86
  @app.route('/job/<int:job_id>')
87
  def job_detail(job_id):
88
  job = Job.query.get_or_404(job_id)
89
  return render_template('job_detail.html', job=job)
90
 
 
91
  @app.route('/apply/<int:job_id>', methods=['GET', 'POST'])
92
+ @login_required
93
  def apply(job_id):
94
  job = Job.query.get_or_404(job_id)
95
  form = JobApplicationForm()
96
  form.job_id.data = job_id
97
 
98
  if form.validate_on_submit():
 
99
  resume_file = form.resume.data
100
+
101
+ features, error, filename = handle_resume_upload(resume_file)
102
+ features_json = json.dumps(features or {})
103
+
 
 
 
 
 
 
 
 
 
 
104
  application = Application(
105
  job_id=job_id,
106
  name=form.name.data,
107
  email=form.email.data,
108
+ resume_path=filename,
109
  cover_letter=form.cover_letter.data,
110
  extracted_features=features_json
111
  )
 
118
 
119
  return render_template('apply.html', form=form, job=job)
120
 
 
121
  @app.route('/parse_resume', methods=['POST'])
122
  def parse_resume():
123
+ file = request.files.get('resume')
124
+ features, error, _ = handle_resume_upload(file)
125
 
126
+ if error:
127
+ print(f"[Resume Error] {error}")
128
+ return {"error": "Error parsing resume. Please try again."}, 400
129
 
130
+ if not features:
131
+ print("[Resume Error] No features extracted.")
132
+ return {"error": "Failed to extract resume details."}, 400
133
+
134
+ response = {
135
+ "name": features.get('name', ''),
136
+ "email": features.get('email', ''),
137
+ "mobile_number": features.get('mobile_number', ''),
138
+ "skills": features.get('skills', []),
139
+ "experience": features.get('experience', [])
140
+ }
141
+ return response, 200
142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
 
145
  if __name__ == '__main__':
146
  print("Starting Codingo application...")
147
+ with app.app_context():
148
+ db.create_all()
149
+ app.run(debug=True, port=5001)
backend/form/AuthForms.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from flask_wtf import FlaskForm
3
+ from wtforms import StringField, PasswordField, SubmitField, SelectField
4
+ from wtforms.validators import DataRequired, Email, EqualTo
5
+
6
+ class LoginForm(FlaskForm):
7
+ email = StringField('Email', validators=[DataRequired(), Email()])
8
+ password = PasswordField('Password', validators=[DataRequired()])
9
+ submit = SubmitField('Login')
10
+
11
+ class SignupForm(FlaskForm):
12
+ username = StringField('Username', validators=[DataRequired()])
13
+ email = StringField('Email', validators=[DataRequired(), Email()])
14
+ password = PasswordField('Password', validators=[DataRequired()])
15
+ confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
16
+ role = SelectField('Role', choices=[('unemployed', 'Unemployed'), ('recruiter', 'Recruiter')])
17
+ submit = SubmitField('Sign Up')
backend/instance/codingo.db CHANGED
Binary files a/backend/instance/codingo.db and b/backend/instance/codingo.db differ
 
backend/models/database.py CHANGED
@@ -7,7 +7,6 @@ from datetime import datetime
7
 
8
  db = SQLAlchemy()
9
 
10
-
11
  class Job(db.Model):
12
  """Job model representing a job posting."""
13
  id = db.Column(db.Integer, primary_key=True)
 
7
 
8
  db = SQLAlchemy()
9
 
 
10
  class Job(db.Model):
11
  """Job model representing a job posting."""
12
  id = db.Column(db.Integer, primary_key=True)
backend/models/user.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask_login import UserMixin
2
+ from werkzeug.security import generate_password_hash, check_password_hash
3
+ from backend.models.database import db # ✅ Use shared db instance only
4
+
5
+ class User(UserMixin, db.Model):
6
+ __tablename__ = 'user'
7
+ __table_args__ = {'extend_existing': True}
8
+
9
+ id = db.Column(db.Integer, primary_key=True)
10
+ username = db.Column(db.String(150), unique=True, nullable=False)
11
+ email = db.Column(db.String(150), unique=True, nullable=False)
12
+ password_hash = db.Column(db.String(256), nullable=False)
13
+ role = db.Column(db.String(50), nullable=False)
14
+
15
+ def set_password(self, password):
16
+ self.password_hash = generate_password_hash(password)
17
+
18
+ def check_password(self, password):
19
+ return check_password_hash(self.password_hash, password)
backend/routes/auth.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, render_template, redirect, url_for, flash, request
2
+ from flask_login import login_user, logout_user, login_required
3
+ from backend.form.AuthForms import LoginForm, SignupForm
4
+ from backend.models.user import User
5
+ from backend.models.database import db
6
+
7
+ auth_bp = Blueprint('auth', __name__)
8
+
9
+ @auth_bp.route('/login', methods=['GET', 'POST'])
10
+ def login():
11
+ form = LoginForm()
12
+ if form.validate_on_submit():
13
+ user = User.query.filter_by(email=form.email.data).first()
14
+ if user and user.check_password(form.password.data):
15
+ login_user(user)
16
+ return redirect(url_for('index'))
17
+ else:
18
+ flash('Invalid credentials', 'danger')
19
+ return render_template('login.html', form=form)
20
+
21
+ @auth_bp.route('/signup', methods=['GET', 'POST'])
22
+ def signup():
23
+ form = SignupForm()
24
+ if form.validate_on_submit():
25
+ existing_user = User.query.filter_by(email=form.email.data).first()
26
+ if existing_user:
27
+ flash('Email already registered.', 'warning')
28
+ else:
29
+ user = User(
30
+ username=form.username.data,
31
+ email=form.email.data,
32
+ role=form.role.data
33
+ )
34
+ user.set_password(form.password.data)
35
+ db.session.add(user)
36
+ db.session.commit()
37
+ flash('Account created successfully!', 'success')
38
+ return redirect(url_for('auth.login'))
39
+ return render_template('signup.html', form=form)
40
+
41
+ @auth_bp.route('/logout')
42
+ @login_required
43
+ def logout():
44
+ logout_user()
45
+ return redirect(url_for('auth.login'))
backend/templates/apply.html CHANGED
@@ -21,7 +21,7 @@
21
  <li><a href="{{ url_for('job_detail', job_id=job.id) }}">{{ job.title }}</a></li>
22
  <li>Apply</li>
23
  </ul>
24
-
25
  <div class="card">
26
  <div class="card-header">
27
  <h2>Complete Your Application</h2>
@@ -29,17 +29,15 @@
29
  </div>
30
 
31
  <div class="card-body">
32
- <form id="resumeForm" method="POST" enctype="multipart/form-data">
33
- <div class="form-group">
34
- <label for="resume">Upload Resume</label>
35
- <input type="file" id="resume" name="resume" class="form-control" required>
36
- </div>
37
- </form>
38
-
39
  <form method="POST" enctype="multipart/form-data">
40
  {{ form.hidden_tag() }}
41
  {{ form.job_id }}
42
 
 
 
 
 
 
43
  <div class="form-group">
44
  {{ form.name.label }}
45
  {{ form.name(class="form-control", id="name", placeholder="Enter your full name") }}
@@ -57,12 +55,14 @@
57
 
58
  <div class="form-group">
59
  <label for="skills">Skills</label>
60
- <textarea id="skills" class="form-control" placeholder="Skills extracted from your resume..."></textarea>
 
61
  </div>
62
 
63
  <div class="form-group">
64
  <label for="experience">Previous Experience</label>
65
- <textarea id="experience" class="form-control" placeholder="Experience extracted from your resume..."></textarea>
 
66
  </div>
67
 
68
  <div class="form-group">
@@ -75,17 +75,18 @@
75
  {{ form.submit(class="btn btn-primary") }}
76
  </div>
77
  </form>
 
78
  </div>
79
  </div>
80
  </section>
81
 
82
  <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
83
  <script>
84
- $(document).ready(function() {
85
  $("#resume").on("change", function () {
86
  var formData = new FormData();
87
  formData.append("resume", $("#resume")[0].files[0]);
88
-
89
  $.ajax({
90
  url: "{{ url_for('parse_resume') }}",
91
  type: "POST",
 
21
  <li><a href="{{ url_for('job_detail', job_id=job.id) }}">{{ job.title }}</a></li>
22
  <li>Apply</li>
23
  </ul>
24
+
25
  <div class="card">
26
  <div class="card-header">
27
  <h2>Complete Your Application</h2>
 
29
  </div>
30
 
31
  <div class="card-body">
 
 
 
 
 
 
 
32
  <form method="POST" enctype="multipart/form-data">
33
  {{ form.hidden_tag() }}
34
  {{ form.job_id }}
35
 
36
+ <div class="form-group">
37
+ <label for="resume">Upload Resume</label>
38
+ {{ form.resume(class="form-control", id="resume", required=True) }}
39
+ </div>
40
+
41
  <div class="form-group">
42
  {{ form.name.label }}
43
  {{ form.name(class="form-control", id="name", placeholder="Enter your full name") }}
 
55
 
56
  <div class="form-group">
57
  <label for="skills">Skills</label>
58
+ <textarea id="skills" class="form-control"
59
+ placeholder="Skills extracted from your resume..."></textarea>
60
  </div>
61
 
62
  <div class="form-group">
63
  <label for="experience">Previous Experience</label>
64
+ <textarea id="experience" class="form-control"
65
+ placeholder="Experience extracted from your resume..."></textarea>
66
  </div>
67
 
68
  <div class="form-group">
 
75
  {{ form.submit(class="btn btn-primary") }}
76
  </div>
77
  </form>
78
+
79
  </div>
80
  </div>
81
  </section>
82
 
83
  <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
84
  <script>
85
+ $(document).ready(function () {
86
  $("#resume").on("change", function () {
87
  var formData = new FormData();
88
  formData.append("resume", $("#resume")[0].files[0]);
89
+
90
  $.ajax({
91
  url: "{{ url_for('parse_resume') }}",
92
  type: "POST",
backend/templates/base.html CHANGED
@@ -94,9 +94,46 @@
94
 
95
  .login-buttons {
96
  display: flex;
 
97
  gap: 1rem;
98
  }
99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  .btn {
101
  padding: 0.5rem 1.5rem;
102
  border-radius: 5px;
@@ -248,14 +285,26 @@
248
  }
249
 
250
  @keyframes pulse {
251
- 0% { transform: scale(0.9); opacity: 0.5; }
252
- 100% { transform: scale(1.1); opacity: 0.7; }
 
 
 
 
 
 
253
  }
254
 
255
  @keyframes float {
256
- 0% { transform: translateY(0px) rotateY(0deg); }
257
- 50% { transform: translateY(-15px) rotateY(5deg); }
258
- 100% { transform: translateY(0px) rotateY(0deg); }
 
 
 
 
 
 
259
  }
260
 
261
  .luna-avatar img {
@@ -679,53 +728,59 @@
679
  </style>
680
  </head>
681
  <body>
682
- <header>
683
- <div class="container nav-container">
684
- <a href="{{ url_for('index') }}" class="logo">
685
- <span class="logo-part1">Cod</span><span class="logo-part2">in</span><span class="logo-part3">go</span>
686
- </a>
687
- <div class="login-buttons">
688
- <a href="{{ url_for('index') }}" class="btn btn-outline">Log In</a>
689
- <a href="{{ url_for('index') }}" class="btn btn-primary">Sign Up</a>
690
- </div>
 
 
 
 
691
  </div>
692
- </header>
693
-
694
- {% block hero %}{% endblock %}
695
-
696
- <main>
697
- <div class="container">
698
- {% with messages = get_flashed_messages(with_categories=true) %}
699
- {% if messages %}
700
- <div class="flash-messages">
701
- {% for category, message in messages %}
702
- <div class="alert alert-{{ category }}">{{ message }}</div>
703
- {% endfor %}
704
- </div>
705
- {% endif %}
706
- {% endwith %}
707
-
708
- {% block content %}{% endblock %}
709
  </div>
710
- </main>
711
-
712
- <footer>
713
- <div class="container">
714
- <div class="footer-grid">
715
- <div class="footer-col">
716
- <h3>Codingo</h3>
717
- <p>AI-powered recruitment platform that revolutionizes how companies hire technical talent.</p>
718
- <div class="social-links">
719
- <a href="#"><span>f</span></a>
720
- <a href="#"><span>t</span></a>
721
- <a href="#"><span>in</span></a>
722
- </div>
 
 
 
 
723
  </div>
724
  </div>
725
- <div class="copyright">
726
- <p>&copy; 2025 Codingo. All rights reserved.</p>
727
- </div>
728
  </div>
729
- </footer>
 
 
 
 
730
  </body>
731
  </html>
 
94
 
95
  .login-buttons {
96
  display: flex;
97
+ align-items: center;
98
  gap: 1rem;
99
  }
100
 
101
+ /* Style for the welcome message */
102
+ .welcome-message {
103
+ color: white;
104
+ font-weight: 500;
105
+ margin-right: 0.5rem;
106
+ display: flex;
107
+ align-items: center;
108
+ background-color: rgba(255, 255, 255, 0.1);
109
+ padding: 0.5rem 1rem;
110
+ border-radius: 5px;
111
+ transition: all 0.3s ease;
112
+ }
113
+
114
+ .welcome-message:before {
115
+ content: '👋 ';
116
+ margin-right: 0.5rem;
117
+ }
118
+
119
+ /* Enhanced logout button */
120
+ .btn-logout {
121
+ background-color: transparent;
122
+ border: 2px solid var(--accent);
123
+ color: var(--accent);
124
+ font-weight: 600;
125
+ padding: 0.5rem 1.5rem;
126
+ border-radius: 5px;
127
+ transition: all 0.3s ease;
128
+ }
129
+
130
+ .btn-logout:hover {
131
+ background-color: var(--accent);
132
+ color: var(--dark);
133
+ transform: translateY(-2px);
134
+ box-shadow: 0 5px 15px rgba(76, 201, 240, 0.3);
135
+ }
136
+
137
  .btn {
138
  padding: 0.5rem 1.5rem;
139
  border-radius: 5px;
 
285
  }
286
 
287
  @keyframes pulse {
288
+ 0% {
289
+ transform: scale(0.9);
290
+ opacity: 0.5;
291
+ }
292
+ 100% {
293
+ transform: scale(1.1);
294
+ opacity: 0.7;
295
+ }
296
  }
297
 
298
  @keyframes float {
299
+ 0% {
300
+ transform: translateY(0px) rotateY(0deg);
301
+ }
302
+ 50% {
303
+ transform: translateY(-15px) rotateY(5deg);
304
+ }
305
+ 100% {
306
+ transform: translateY(0px) rotateY(0deg);
307
+ }
308
  }
309
 
310
  .luna-avatar img {
 
728
  </style>
729
  </head>
730
  <body>
731
+ <header>
732
+ <div class="container nav-container">
733
+ <a href="{{ url_for('index') }}" class="logo">
734
+ <span class="logo-part1">Cod</span><span class="logo-part2">in</span><span class="logo-part3">go</span>
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
+
746
+ </div>
747
+ </header>
748
+
749
+ {% block hero %}{% endblock %}
750
+
751
+ <main>
752
+ <div class="container">
753
+ {% with messages = get_flashed_messages(with_categories=true) %}
754
+ {% if messages %}
755
+ <div class="flash-messages">
756
+ {% for category, message in messages %}
757
+ <div class="alert alert-{{ category }}">{{ message }}</div>
758
+ {% endfor %}
 
 
 
759
  </div>
760
+ {% endif %}
761
+ {% endwith %}
762
+
763
+ {% block content %}{% endblock %}
764
+ </div>
765
+ </main>
766
+
767
+ <footer>
768
+ <div class="container">
769
+ <div class="footer-grid">
770
+ <div class="footer-col">
771
+ <h3>Codingo</h3>
772
+ <p>AI-powered recruitment platform that revolutionizes how companies hire technical talent.</p>
773
+ <div class="social-links">
774
+ <a href="#"><span>f</span></a>
775
+ <a href="#"><span>t</span></a>
776
+ <a href="#"><span>in</span></a>
777
  </div>
778
  </div>
 
 
 
779
  </div>
780
+ <div class="copyright">
781
+ <p>&copy; 2025 Codingo. All rights reserved.</p>
782
+ </div>
783
+ </div>
784
+ </footer>
785
  </body>
786
  </html>
backend/templates/login.html ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Codingo - Login{% endblock %}
4
+
5
+ {% block content %}
6
+ <section class="content-section">
7
+ <div class="container">
8
+ <div class="card" style="max-width: 500px; margin: 2rem auto;">
9
+ <div class="card-header">
10
+ <h2 style="text-align: center; margin: 0; color: white;">Login to Codingo</h2>
11
+ </div>
12
+ <div class="card-body">
13
+ <form method="POST" class="auth-form">
14
+ {{ form.hidden_tag() }}
15
+
16
+ <div class="form-group">
17
+ {{ form.email.label(class="form-label") }}
18
+ {{ form.email(class="form-control", placeholder="Enter your email") }}
19
+ {% if form.email.errors %}
20
+ <div class="alert alert-danger">
21
+ {% for error in form.email.errors %}
22
+ <span>{{ error }}</span>
23
+ {% endfor %}
24
+ </div>
25
+ {% endif %}
26
+ </div>
27
+
28
+ <div class="form-group">
29
+ {{ form.password.label(class="form-label") }}
30
+ {{ form.password(class="form-control", placeholder="Enter your password") }}
31
+ {% if form.password.errors %}
32
+ <div class="alert alert-danger">
33
+ {% for error in form.password.errors %}
34
+ <span>{{ error }}</span>
35
+ {% endfor %}
36
+ </div>
37
+ {% endif %}
38
+ </div>
39
+
40
+ <div class="form-group" style="margin-top: 2rem;">
41
+ {{ form.submit(class="btn btn-primary", style="width: 100%;") }}
42
+ </div>
43
+ </form>
44
+
45
+ <div style="text-align: center; margin-top: 1.5rem;">
46
+ <p>Don't have an account? <a href="{{ url_for('auth.signup') }}" style="color: var(--primary); font-weight: 500;">Sign up here</a>.</p>
47
+ </div>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ </section>
52
+
53
+ <style>
54
+ .auth-form .form-label {
55
+ display: block;
56
+ margin-bottom: 0.5rem;
57
+ font-weight: 500;
58
+ color: var(--dark);
59
+ }
60
+
61
+ .auth-form .form-control {
62
+ width: 100%;
63
+ padding: 0.75rem;
64
+ border: 1px solid #ddd;
65
+ border-radius: 5px;
66
+ font-size: 1rem;
67
+ transition: all 0.3s ease;
68
+ }
69
+
70
+ .auth-form .form-control:focus {
71
+ border-color: var(--primary);
72
+ outline: none;
73
+ box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.2);
74
+ }
75
+
76
+ .auth-form .btn-primary {
77
+ background: linear-gradient(135deg, var(--primary), var(--secondary));
78
+ color: white;
79
+ padding: 0.75rem;
80
+ border: none;
81
+ border-radius: 5px;
82
+ font-size: 1rem;
83
+ font-weight: 500;
84
+ cursor: pointer;
85
+ transition: all 0.3s ease;
86
+ position: relative;
87
+ overflow: hidden;
88
+ z-index: 1;
89
+ }
90
+
91
+ .auth-form .btn-primary::before {
92
+ content: '';
93
+ position: absolute;
94
+ top: 0;
95
+ left: 0;
96
+ width: 0%;
97
+ height: 100%;
98
+ background-color: rgba(255, 255, 255, 0.1);
99
+ transition: all 0.3s ease;
100
+ z-index: -1;
101
+ }
102
+
103
+ .auth-form .btn-primary:hover::before {
104
+ width: 100%;
105
+ }
106
+
107
+ .auth-form .btn-primary:hover {
108
+ transform: translateY(-2px);
109
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
110
+ }
111
+
112
+ .alert-danger {
113
+ color: var(--danger);
114
+ background-color: rgba(231, 76, 60, 0.1);
115
+ border: 1px solid var(--danger);
116
+ border-radius: 5px;
117
+ padding: 0.5rem;
118
+ margin-top: 0.5rem;
119
+ font-size: 0.9rem;
120
+ }
121
+ </style>
122
+ {% endblock %}
backend/templates/signup.html ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Codingo - Sign Up{% endblock %}
4
+
5
+ {% block content %}
6
+ <section class="content-section">
7
+ <div class="container">
8
+ <div class="card" style="max-width: 500px; margin: 2rem auto;">
9
+ <div class="card-header">
10
+ <h2 style="text-align: center; margin: 0; color: white;">Sign Up for Codingo</h2>
11
+ </div>
12
+ <div class="card-body">
13
+ <form method="POST" class="auth-form">
14
+ {{ form.hidden_tag() }}
15
+
16
+ <div class="form-group">
17
+ {{ form.username.label(class="form-label") }}
18
+ {{ form.username(class="form-control", placeholder="Choose a username") }}
19
+ {% if form.username.errors %}
20
+ <div class="alert alert-danger">
21
+ {% for error in form.username.errors %}
22
+ <span>{{ error }}</span>
23
+ {% endfor %}
24
+ </div>
25
+ {% endif %}
26
+ </div>
27
+
28
+ <div class="form-group">
29
+ {{ form.email.label(class="form-label") }}
30
+ {{ form.email(class="form-control", placeholder="Enter your email") }}
31
+ {% if form.email.errors %}
32
+ <div class="alert alert-danger">
33
+ {% for error in form.email.errors %}
34
+ <span>{{ error }}</span>
35
+ {% endfor %}
36
+ </div>
37
+ {% endif %}
38
+ </div>
39
+
40
+ <div class="form-group">
41
+ {{ form.password.label(class="form-label") }}
42
+ {{ form.password(class="form-control", placeholder="Create a password") }}
43
+ {% if form.password.errors %}
44
+ <div class="alert alert-danger">
45
+ {% for error in form.password.errors %}
46
+ <span>{{ error }}</span>
47
+ {% endfor %}
48
+ </div>
49
+ {% endif %}
50
+ <div class="password-strength">
51
+ <div class="password-strength-bar" id="password-strength-bar"></div>
52
+ </div>
53
+ </div>
54
+
55
+ <div class="form-group">
56
+ {{ form.confirm_password.label(class="form-label") }}
57
+ {{ form.confirm_password(class="form-control", placeholder="Confirm your password") }}
58
+ {% if form.confirm_password.errors %}
59
+ <div class="alert alert-danger">
60
+ {% for error in form.confirm_password.errors %}
61
+ <span>{{ error }}</span>
62
+ {% endfor %}
63
+ </div>
64
+ {% endif %}
65
+ </div>
66
+
67
+ <div class="form-group">
68
+ {{ form.role.label(class="form-label") }}
69
+ {{ form.role(class="form-control") }}
70
+ {% if form.role.errors %}
71
+ <div class="alert alert-danger">
72
+ {% for error in form.role.errors %}
73
+ <span>{{ error }}</span>
74
+ {% endfor %}
75
+ </div>
76
+ {% endif %}
77
+ </div>
78
+
79
+ <div class="form-group" style="margin-top: 2rem;">
80
+ {{ form.submit(class="btn btn-primary", style="width: 100%;") }}
81
+ </div>
82
+ </form>
83
+
84
+ <div style="text-align: center; margin-top: 1.5rem;">
85
+ <p>Already have an account? <a href="{{ url_for('auth.login') }}" style="color: var(--primary); font-weight: 500;">Login here</a>.</p>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ </div>
90
+ </section>
91
+
92
+ <style>
93
+ .auth-form .form-label {
94
+ display: block;
95
+ margin-bottom: 0.5rem;
96
+ font-weight: 500;
97
+ color: var(--dark);
98
+ }
99
+
100
+ .auth-form .form-control {
101
+ width: 100%;
102
+ padding: 0.75rem;
103
+ border: 1px solid #ddd;
104
+ border-radius: 5px;
105
+ font-size: 1rem;
106
+ transition: all 0.3s ease;
107
+ }
108
+
109
+ .auth-form .form-control:focus {
110
+ border-color: var(--primary);
111
+ outline: none;
112
+ box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.2);
113
+ }
114
+
115
+ .auth-form .btn-primary {
116
+ background: linear-gradient(135deg, var(--primary), var(--secondary));
117
+ color: white;
118
+ padding: 0.75rem;
119
+ border: none;
120
+ border-radius: 5px;
121
+ font-size: 1rem;
122
+ font-weight: 500;
123
+ cursor: pointer;
124
+ transition: all 0.3s ease;
125
+ position: relative;
126
+ overflow: hidden;
127
+ z-index: 1;
128
+ }
129
+
130
+ .auth-form .btn-primary::before {
131
+ content: '';
132
+ position: absolute;
133
+ top: 0;
134
+ left: 0;
135
+ width: 0%;
136
+ height: 100%;
137
+ background-color: rgba(255, 255, 255, 0.1);
138
+ transition: all 0.3s ease;
139
+ z-index: -1;
140
+ }
141
+
142
+ .auth-form .btn-primary:hover::before {
143
+ width: 100%;
144
+ }
145
+
146
+ .auth-form .btn-primary:hover {
147
+ transform: translateY(-2px);
148
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
149
+ }
150
+
151
+ .alert-danger {
152
+ color: var(--danger);
153
+ background-color: rgba(231, 76, 60, 0.1);
154
+ border: 1px solid var(--danger);
155
+ border-radius: 5px;
156
+ padding: 0.5rem;
157
+ margin-top: 0.5rem;
158
+ font-size: 0.9rem;
159
+ }
160
+
161
+ .password-strength {
162
+ height: 5px;
163
+ width: 100%;
164
+ background-color: #eee;
165
+ margin-top: 5px;
166
+ border-radius: 3px;
167
+ overflow: hidden;
168
+ }
169
+
170
+ .password-strength-bar {
171
+ height: 100%;
172
+ width: 0;
173
+ border-radius: 3px;
174
+ transition: width 0.3s, background-color 0.3s;
175
+ }
176
+ </style>
177
+
178
+ <script>
179
+ document.addEventListener('DOMContentLoaded', function() {
180
+ const passwordInput = document.querySelector('input[name="password"]');
181
+ const strengthBar = document.getElementById('password-strength-bar');
182
+
183
+ if (passwordInput && strengthBar) {
184
+ passwordInput.addEventListener('input', function() {
185
+ const password = this.value;
186
+ const strength = checkPasswordStrength(password);
187
+
188
+ strengthBar.style.width = `${strength.percentage}%`;
189
+ strengthBar.style.backgroundColor = strength.color;
190
+ });
191
+ }
192
+
193
+ function checkPasswordStrength(password) {
194
+ const requirements = {
195
+ length: password.length >= 8,
196
+ uppercase: /[A-Z]/.test(password),
197
+ lowercase: /[a-z]/.test(password),
198
+ number: /\d/.test(password),
199
+ special: /[^A-Za-z0-9]/.test(password)
200
+ };
201
+
202
+ const strength = Object.values(requirements).filter(Boolean).length;
203
+ const percentage = (strength / 5) * 100;
204
+
205
+ return {
206
+ valid: Object.values(requirements).every(Boolean),
207
+ percentage,
208
+ color: strength < 2 ? '#e74c3c' : strength < 4 ? '#f39c12' : '#27ae60'
209
+ };
210
+ }
211
+ });
212
+ </script>
213
+ {% endblock %}
backend/uploads/resumes/Hussein_El_Saadi_-_CV.pdf DELETED
Binary file (72.9 kB)
 
backend/uploads/resumes/Mohamad_MoallemCV-2024.pdf DELETED
Binary file (58.9 kB)
 
instance/codingo.db ADDED
Binary file (24.6 kB). View file
 
requirements.txt CHANGED
@@ -8,4 +8,5 @@ spacy>=3.0.0
8
  nltk
9
  pyresparser
10
  flask_sqlalchemy
11
- flask_wtf
 
 
8
  nltk
9
  pyresparser
10
  flask_sqlalchemy
11
+ flask_wtf
12
+ email-validator