Skip to content

Commit 3b51712

Browse files
authored
Merge pull request #273 from Skill-Forge-Project/development
Skill Forge v1.4.3 patch release
2 parents 5b9a656 + a9ec285 commit 3b51712

File tree

15 files changed

+84
-268
lines changed

15 files changed

+84
-268
lines changed

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ FROM python:latest
33

44
# Image Labels. Update values for each build
55
LABEL Name="Skill-Forge"
6-
LABEL Version="1.4.2"
6+
LABEL Version="1.4.3"
77
LABEL Release="public"
8-
LABEL ReleaseDate="06.10.2024"
8+
LABEL ReleaseDate="08.10.2024"
99
LABEL Description="Skill Forge is a open-source platform for learning and practicing programming languages."
1010
LABEL Maintainer="Aleksandar Karastoyanov <karastoqnov.alexadar@gmail.com>"
1111
LABEL License="GNU GPL v3.0 license"

app/forms.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from flask_wtf import FlaskForm
2+
from flask_wtf.file import FileField, FileAllowed
23
from flask import flash
34
from wtforms import StringField, PasswordField, SubmitField, HiddenField, SelectField, TextAreaField, RadioField, FileField, BooleanField
45
from wtforms.validators import Email, Length, EqualTo, DataRequired, ValidationError, Regexp, Optional
@@ -190,16 +191,16 @@ class ContactForm(FlaskForm):
190191

191192
########### User Profile Form - update user's profile ###########
192193
class UserProfileForm(FlaskForm):
193-
about_me = TextAreaField('About Me', validators=[Optional()])
194-
first_name = StringField('First Name', validators=[Optional()])
195-
last_name = StringField('Last Name', validators=[Optional()])
196-
email = StringField('Email', validators=[Optional(), Email(), latin_characters_only])
197-
facebook_profile = StringField('Facebook Profile', validators=[Optional()])
198-
instagram_profile = StringField('Instagram', validators=[Optional()])
199-
github_profile = StringField('GitHub', validators=[Optional()])
200-
discord_id = StringField('Discord ID', validators=[Optional()])
201-
linked_in = StringField('LinkedIn', validators=[Optional()])
202-
avatar = FileField('Upload Avatar', name="update_avatar", validators=[Optional()])
194+
about_me = TextAreaField('About Me', validators=[Optional(), Length(max=500)])
195+
first_name = StringField('First Name', validators=[Optional(), Length(max=30)])
196+
last_name = StringField('Last Name', validators=[Optional(), Length(max=30)])
197+
email = StringField('Email', validators=[Optional(), Email(), latin_characters_only, Length(max=120)])
198+
facebook_profile = StringField('Facebook Profile', validators=[Optional(), Length(max=120)])
199+
instagram_profile = StringField('Instagram', validators=[Optional(), Length(max=120)])
200+
github_profile = StringField('GitHub', validators=[Optional(), Length(max=120)])
201+
discord_id = StringField('Discord ID', validators=[Optional(), Length(max=120)])
202+
linked_in = StringField('LinkedIn', validators=[Optional(), Length(max=120)])
203+
avatar = FileField('Upload Avatar', name="update_avatar", validators=[Optional(), FileAllowed(['jpg', 'jpeg', 'png'], "File type not allowed! Please upload an image file(jpg, jpeg, png).")])
203204
submit = SubmitField('Update Profile', name="submit")
204205

205206
########### Submit Quest Form - as a Regular User ###########

app/routes/routes.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from app.mailtrap import send_reset_email, send_welcome_mail, send_contact_email
77
# Import the database instance
88
from app.database.db_init import db
9+
from sqlalchemy import func
910
from app import bcrypt
1011
# Import the forms and models
1112
from app.forms import LoginForm, RegistrationForm, EmailResetForm, PasswordResetForm, ContactForm
@@ -24,7 +25,11 @@
2425
def login():
2526
form = LoginForm()
2627
if form.validate_on_submit():
27-
user = User.query.filter((User.username==form.username.data) | (User.email==form.username.data)).first()
28+
# Convert the input (username or email) to lowercase
29+
input_lower = form.username.data.lower()
30+
31+
# Check for a user by username or email (case-insensitive check for email)
32+
user = User.query.filter((User.username == input_lower) | (func.lower(User.email) == input_lower)).first()
2833
if user:
2934
if not user.is_banned:
3035
if bcrypt.check_password_hash(user.password, form.password.data):
@@ -62,14 +67,19 @@ def logout():
6267
def register():
6368
form = RegistrationForm()
6469
if form.validate_on_submit():
70+
# Convert the email and the username to lowercase
71+
email_lower = form.email.data.lower()
72+
username_lower = form.username.data.lower()
6573
# Check if the email or username is already in use
66-
existing_user = User.query.filter((User.email == form.email.data) | (User.username == form.username.data)).first()
74+
existing_user = User.query.filter((User.email == email_lower) | (User.username == username_lower)).first()
75+
6776
if existing_user:
6877
flash('Email or username already in use', 'error')
6978
return render_template('register.html', form=form)
79+
7080
# Create a new user
7181
hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
72-
new_user = User(email=form.email.data, username=form.username.data,
82+
new_user = User(email=email_lower, username=username_lower,
7383
first_name=form.first_name.data, last_name=form.last_name.data,
7484
password=hashed_password)
7585

@@ -90,7 +100,7 @@ def register():
90100

91101
db.session.add(new_user)
92102
db.session.commit()
93-
send_welcome_mail(form.email.data, form.username.data)
103+
send_welcome_mail(email_lower, username_lower)
94104
flash('Your account has been created! You are now able to log in.', 'success')
95105
return render_template('index.html', form=LoginForm())
96106
else:
@@ -112,7 +122,7 @@ def open_forgot_password():
112122
def send_email_token():
113123
form = EmailResetForm()
114124
if form.validate_on_submit():
115-
email = form.email_address.data
125+
email = form.email_address.data.lower()
116126
current_user = User.query.filter_by(email=email).first()
117127
all_token = ResetToken.query.filter_by(user_email=email).first()
118128

app/routes/user_routes.py

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from bson import ObjectId
44
from flask import Blueprint, redirect, url_for, request, flash, render_template, abort, send_file, jsonify
55
from flask_login import login_required, current_user
6+
from sqlalchemy import func
67
from sqlalchemy.orm import joinedload
78
# Import the forms and models
89
from app.models import SubmitedSolution, User, UserAchievement, Quest, ReportedQuest, SubmitedQuest, Achievement
@@ -19,7 +20,7 @@
1920

2021
bp_usr = Blueprint('usr', __name__)
2122

22-
# Get the user's avatar, used in the comments section
23+
# Open the user profile page
2324
@bp_usr.route('/my_profile', methods=['GET', 'POST'])
2425
@login_required
2526
def open_user_profile():
@@ -28,28 +29,42 @@ def open_user_profile():
2829
user = User.query.get(user_id)
2930

3031
if form.validate_on_submit():
32+
new_email = form.email.data.lower()
33+
current_email = user.email.lower()
34+
if new_email != current_email:
35+
is_email_taken = User.query.filter(func.lower(User.email) == new_email).first()
36+
if is_email_taken:
37+
flash('This email is already taken. Please choose another one.', 'danger')
38+
return redirect(url_for('usr.open_user_profile'))
39+
3140
if 'submit' in request.form:
32-
user.about_me = form.about_me.data
33-
user.first_name = form.first_name.data
34-
user.last_name = form.last_name.data
35-
user.email = form.email.data
36-
user.facebook_profile = form.facebook_profile.data
37-
user.instagram_profile = form.instagram_profile.data
38-
user.github_profile = form.github_profile.data
39-
user.discord_id = form.discord_id.data
40-
user.linked_in = form.linked_in.data
41+
try:
42+
user.about_me = form.about_me.data
43+
user.first_name = form.first_name.data
44+
user.last_name = form.last_name.data
45+
user.email = form.email.data
46+
user.facebook_profile = form.facebook_profile.data
47+
user.instagram_profile = form.instagram_profile.data
48+
user.github_profile = form.github_profile.data
49+
user.discord_id = form.discord_id.data
50+
user.linked_in = form.linked_in.data
4151

42-
db.session.commit()
52+
db.session.commit()
4353

44-
mongo_transaction(
45-
'user_info_update',
46-
action=f'User {user.username} updated their info',
47-
user_id=user_id,
48-
username=user.username,
49-
timestamp=datetime.now().strftime('%Y-%m-%d %H:%M:%S')
50-
)
51-
flash('Profile updated successfully', 'success')
52-
return redirect(url_for('usr.open_user_profile'))
54+
mongo_transaction(
55+
'user_info_update',
56+
action=f'User {user.username} updated their info',
57+
user_id=user_id,
58+
username=user.username,
59+
timestamp=datetime.now().strftime('%Y-%m-%d %H:%M:%S')
60+
)
61+
flash('Profile updated successfully', 'success')
62+
return redirect(url_for('usr.open_user_profile'))
63+
except Exception as e:
64+
flash(f'Error during updating user\'s profile!', 'danger')
65+
return redirect(url_for('usr.open_user_profile'))
66+
67+
5368
if 'update_avatar' in request.form:
5469
if form.avatar.data:
5570
avatar_data = form.avatar.data.read()
@@ -118,7 +133,7 @@ def open_user_profile():
118133

119134

120135
# Get the users avatar
121-
@bp_usr.route('/avatar/<user_id>')
136+
@bp_usr.route('/avatar/<user_id>', methods=['GET'])
122137
def get_user_avatar(user_id):
123138
user = User.query.filter_by(user_id=user_id).first_or_404()
124139
if user.avatar:
@@ -129,7 +144,7 @@ def get_user_avatar(user_id):
129144
return send_file(io.BytesIO(img_data), mimetype='image/jpeg')
130145

131146
# Open user for editing from the Admin Panel
132-
@bp_usr.route('/edit_user/<user_id>')
147+
@bp_usr.route('/edit_user/<user_id>', methods=['GET'])
133148
@login_required
134149
@admin_required
135150
def open_edit_user(user_id):
@@ -268,7 +283,7 @@ def open_user_profile_view(username):
268283
return abort(404)
269284

270285
# Redirect to the Admin Panel (Admin Role in the database is needed)
271-
@bp_usr.route('/admin_panel')
286+
@bp_usr.route('/admin_panel', methods=['GET'])
272287
@login_required
273288
@admin_required
274289
def open_admin_panel():
@@ -345,15 +360,23 @@ def convert_objectid_to_string(submission):
345360
flash('You must be an admin to access this page.', 'error')
346361
return redirect(url_for('main.login'))
347362

348-
@bp_usr.route('/submissions_logs/<submission_id>')
363+
@bp_usr.route('/submissions_logs/<submission_id>', methods=['GET'])
349364
@login_required
350365
@admin_required
351366
def submission_log(submission_id):
352367
with mongo1_client.start_session() as session:
353368
with session.start_transaction():
354369
try:
355370
db = mongo1_client['skill_forge_logs']
356-
submission = db['python_submissions'].find_one({'submission_id': submission_id})
371+
collections = ['python_submissions', 'java_submissions', 'csharp_submissions', 'javascript_submissions']
372+
373+
submission = None
374+
375+
for collection in collections:
376+
submission = db[collection].find_one({'submission_id': submission_id})
377+
if submission:
378+
break
379+
357380
# Convert ObjectId and datetime fields to strings
358381
if isinstance(submission['_id'], ObjectId):
359382
submission['_id'] = str(submission['_id'])
@@ -367,11 +390,10 @@ def submission_log(submission_id):
367390
quest = {}
368391
flash(f'An error occurred while fetching the submission. {e}', 'error')
369392
return redirect(url_for('usr.open_admin_panel'))
370-
371393
return render_template('display_submission_log.html', submission=submission_json, quest=quest)
372394

373395
# Ban user route
374-
@bp_usr.route('/ban_user/<user_id>')
396+
@bp_usr.route('/ban_user/<user_id>', methods=['POST'])
375397
@login_required
376398
@admin_required
377399
def ban_user(user_id, ban_reason='no reason'):
@@ -386,7 +408,7 @@ def ban_user(user_id, ban_reason='no reason'):
386408
return redirect(url_for('usr.open_admin_panel'))
387409

388410
# Unban user route
389-
@bp_usr.route('/unban_user/<user_id>')
411+
@bp_usr.route('/unban_user/<user_id>', methods=['POST'])
390412
@login_required
391413
@admin_required
392414
def unban_user(user_id):

app/static/css/user_profile.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ body {
131131
margin-bottom: 20px;
132132
border: 0px;
133133
text-align: justify;
134+
word-wrap: break-word;
135+
overflow-wrap: break-word;
136+
overflow: hidden;
134137
}
135138

136139

app/templates/register.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ <h2 style="font-family: 'JetBrainsMonoBold', sans-serif">Create new account</h2>
5858
<div class="user-box">
5959
<h3>Password Requirements:</h3>
6060
<ul>
61-
<li>At least 8 characters long</li>
61+
<li>At least 10 characters long</li>
6262
<li>Contains at least one uppercase letter</li>
6363
<li>Contains at least one number</li>
6464
<li>Contains at least one special character (e.g., @, #, $)</li>

app/templates/user_profile_view.html

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,6 @@ <h3 class="font-semibold text-center mt-3 -mb-2 text-xl font-bold mb-4 user_prof
107107
Find me on
108108
</h3>
109109
<div class="flex justify-center items-center gap-6 my-6">
110-
<a href="mailto:{{ user.email }}" target="_blank">
111-
<img src="../static/images/email.jpg" width="32" height="32" alt="Facebook Profile">
112-
</a>
113110
{% if user.facebook_profile %}
114111
<a href="{{ user.facebook_profile }}" target="_blank">
115112
<img src="../static/images/facebook.svg" width="12" height="12" alt="Facebook Profile">

old-build/Dockerfile.postgres

Lines changed: 0 additions & 5 deletions
This file was deleted.

old-build/Dockerfile.skill_forge

Lines changed: 0 additions & 36 deletions
This file was deleted.

old-build/achievements.sql

Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
 (0)