Skip to content

Commit

Permalink
Merge pull request #286 from Skill-Forge-Project/development
Browse files Browse the repository at this point in the history
Skill Forge v1.4.5 patch release
  • Loading branch information
karastoyanov authored Oct 13, 2024
2 parents 95f1fe7 + a71e4ba commit 51115fe
Show file tree
Hide file tree
Showing 14 changed files with 157 additions and 100 deletions.
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Use the base image containing Python 3.8, NodeJS, npm, mono-complete compiler and java compiler
FROM python:latest
FROM python:3.12.7

# Image Labels. Update values for each build
LABEL Name="Skill-Forge"
LABEL Version="1.4.4"
LABEL Version="1.4.5"
LABEL Release="public"
LABEL ReleaseDate="08.10.2024"
LABEL ReleaseDate="13.10.2024"
LABEL Description="Skill Forge is a open-source platform for learning and practicing programming languages."
LABEL Maintainer="Aleksandar Karastoyanov <[email protected]>"
LABEL License="GNU GPL v3.0 license"
Expand Down
20 changes: 10 additions & 10 deletions app/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,20 @@ class LoginForm(FlaskForm):

########### Register Form ###########
class RegistrationForm(FlaskForm):
username = StringField('', render_kw={'placeholder': 'Username'}, validators=[DataRequired(), Length(min=4, max=25)])
first_name = StringField('', render_kw={'placeholder': 'First name'}, validators=[DataRequired(), Length(min=1, max=30)])
last_name = StringField('', render_kw={'placeholder': 'Last name'}, validators=[DataRequired(), Length(min=1, max=30)])
email = StringField('', render_kw={'placeholder': 'Email address'}, validators=[DataRequired(), Email(), latin_characters_only])
username = StringField('', render_kw={'placeholder': 'Username'}, validators=[DataRequired(message="Username is required"), Length(min=4, max=25, message="Username must be between 4 and 25 characters.")])
first_name = StringField('', render_kw={'placeholder': 'First name'}, validators=[DataRequired(message="First name is required"), Length(min=1, max=30, message="First name must be between 1 and 30 characters.")])
last_name = StringField('', render_kw={'placeholder': 'Last name'}, validators=[DataRequired(message="Last name is required"), Length(min=1, max=30, message="Last name must be between 1 and 30 characters.")])
email = StringField('', render_kw={'placeholder': 'Email address'}, validators=[DataRequired(message="Email address is required"), Email(message="Invalid emal address"), latin_characters_only])
password = PasswordField('', validators=[
DataRequired(),
DataRequired(message="Password is required"),
Length(min=10, max=50, message='Password must be between 10 and 50 characters.'),
Regexp(re.compile(r'.*[A-Z].*'), message='Password must contain at least one uppercase letter.'),
Regexp(re.compile(r'.*[0-9].*'), message='Password must contain at least one digit.'),
Regexp(re.compile(r'.*[!@#$%^&*()_+=\-{}\[\]:;,<.>?].*'), message='Password must contain at least one special character.')
],
render_kw={'placeholder': 'Password'})
confirm = PasswordField('', render_kw={'placeholder': 'Repeat password'}, validators=[
DataRequired(),
DataRequired(message="Please confirm your password"),
EqualTo('password', message='Passwords must match'),
Length(min=10, max=50, message='Password must be between 10 and 50 characters.')
])
Expand Down Expand Up @@ -183,10 +183,10 @@ class PublishCommentForm(FlaskForm):

########### Contact Form ###########
class ContactForm(FlaskForm):
username = StringField('Name', validators=[DataRequired(), Length(min=4, max=25)],)
email = StringField('Email address', validators=[DataRequired(), Email(), latin_characters_only])
subject = StringField('Subject', validators=[DataRequired(), Length(min=4, max=25)])
message = TextAreaField('Message', validators=[DataRequired(), Length(min=10)])
username = StringField('Name', validators=[DataRequired(message="Name is required"), Length(min=4, max=25, message="Name must be between 4 and 25 characters.")],)
email = StringField('Email address', validators=[DataRequired(message="Email address is required."), Email(message="Invalid email address."), latin_characters_only])
subject = StringField('Subject', validators=[DataRequired(message="Subject is required"), Length(min=4, max=25, message="Subject must be between 4 and 25 characters.")])
message = TextAreaField('Message', validators=[DataRequired(message="Message is required"), Length(min=10, max=500, message="Message must be between 10 and 500 characters.")])
submit = SubmitField('Send Message')

########### User Profile Form - update user's profile ###########
Expand Down
16 changes: 6 additions & 10 deletions app/routes/quests_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from app.models import Quest, ReportedQuest, User, SubmitedSolution, UserAchievement, Achievement, Comment
from app.forms import QuestForm, PublishCommentForm, EditQuestForm, EditReportedQuestForm
# Import code runners
from app.code_runners import run_python, run_javascript, run_java, run_csharp
from app.code_runners import run_javascript
from app.code_runners.piston_api import code_runner
# Import the database instance
from app.database.db_init import db
Expand Down Expand Up @@ -152,10 +152,10 @@ def delete_comment(comment_id):
flash('Comment deletion failed!', 'danger')
return redirect(url_for('quests.open_curr_quest', quest_id=comment.quest_id))



# Handle quest edit from the Admin Panel
# Open quest edit from the Admin Panel
@bp_qst.route('/edit_quest', methods=['GET', 'POST'])
@login_required
@admin_required
def edit_quest_db():
form = EditQuestForm()
if request.method == 'POST':
Expand Down Expand Up @@ -264,7 +264,6 @@ def edit_reported_quest():
flash('Form validation failed!', 'danger')
return render_template('edit_quest.html', form=form)


# Open Quest for editing from the Admin Panel
@bp_qst.route('/edit_quest/<quest_id>', methods=['GET'])
@login_required
Expand All @@ -288,7 +287,6 @@ def open_edit_quest(quest_id):
flash('Quest not found!', 'danger')
return redirect(url_for('usr.open_admin_panel')), 404


# Open Reported Quest for editing from the Admin Panel
@bp_qst.route('/edit_reported_quest/<report_id>')
@login_required
Expand All @@ -312,7 +310,6 @@ def open_edit_reported_quest(report_id):
flash('Quest not found!', 'danger')
return redirect(url_for('usr.open_admin_panel')), 404


# Route to handle `Report Quest` Button
@bp_qst.route('/report_quest/<curr_quest_id>')
@login_required
Expand Down Expand Up @@ -357,7 +354,6 @@ def report_quest(curr_quest_id, report_reason='no reason'):
flash('Quest reported successfully! Administrator will review your report and will take actions shortly.', 'success')
return redirect(url_for('main.main_page', quest_id=curr_quest_id))


# Redirect to the table with all tasks. Change from template to real page!!!!
@bp_qst.route('/quests/<language>', methods=['GET'])
@login_required
Expand All @@ -377,7 +373,7 @@ def open_quests_table(language):

return render_template('quest_table.html', quests=all_quests, users=all_users, solved_quests=solved_quests, language=language)

# Open Quest for submitting. Change from template to real page!!!!
# Open a specific quest
@bp_qst.route('/quest/<quest_id>', methods=['GET'])
@login_required
def open_curr_quest(quest_id):
Expand All @@ -396,7 +392,7 @@ def open_curr_quest(quest_id):
user_role=user_role,
form=quest_post_form)

# Route to handle solution submission
# Submit Quest Solution
@bp_qst.route('/submit-solution', methods=['POST'])
@login_required
def submit_solution():
Expand Down
36 changes: 22 additions & 14 deletions app/routes/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def login():
return render_template('index.html', form=form)

# Route to handle the logout functionality
@bp.route('/logout')
@bp.route('/logout', methods=['GET', 'POST'])
@login_required
def logout():
user = current_user
Expand All @@ -61,7 +61,6 @@ def logout():
flash('You have been logged out.', 'success')
return redirect(url_for('main.login'))


# Handle the registration route
@bp.route('/register', methods=['GET', 'POST'])
def register():
Expand Down Expand Up @@ -110,7 +109,6 @@ def register():
flash(f"{getattr(form, field).label.text} - {error}", 'error')
return render_template('register.html', form=form)


########### Routes handling password reset functionality ###########
# Route to open the forgot password form
@bp.route('/forgot_password')
Expand Down Expand Up @@ -221,9 +219,9 @@ def update_new_password(form=None):
# Pass necessary parameters to the template in case of errors
return render_template('reset_password.html', form=form, token=form.token.data, user_id=form.user_id.data, username=form.username.data, expiration_time=form.expiration_time.data)

########### Routes handling main apge ###########
########### Routes handling main page ###########
# Open the main page
@bp.route('/home')
@bp.route('/home', methods=['GET'])
@login_required
def main_page():
user_count = User.query.count()
Expand All @@ -240,19 +238,19 @@ def main_page():
solutions_count=solutions_count)

# Check the number of online users. Function used by the websockets and client side script.
@bp.route('/get_online_users')
@bp.route('/get_online_users', methods=['GET'])
def get_online_users():
online_users = User.query.filter_by(user_online_status='Online').count()
return jsonify({'online_users': online_users})

# Route to open the about page
@bp.route('/about')
@bp.route('/about', methods=['GET'])
@login_required
def about():
return render_template('about.html')

# Route to open the contact page
@bp.route('/contact_us')
@bp.route('/contact_us', methods=['GET'])
@login_required
def contact():
contact_form = ContactForm()
Expand All @@ -264,15 +262,25 @@ def contact():
@login_required
def send_message():
contact_form = ContactForm()
user = contact_form.username.data
email = contact_form.email.data
subject = contact_form.subject.data
message = contact_form.message.data

if contact_form.validate_on_submit():
# Extract data only after validation succeeds
user = contact_form.username.data
email = contact_form.email.data
subject = contact_form.subject.data
message = contact_form.message.data

# Call function to send the email
send_contact_email(user, email, subject, message)

# Success feedback
flash('Thank you for contacting us. We will get back to you as soon as possible.', 'success')
return redirect(url_for('main.contact'))

# Error handling if form is not valid
flash('Error during sending the email.', 'error')
for error in contact_form.errors:
flash(f'{contact_form.errors[error][0]}', 'error')
for field, errors in contact_form.errors.items():
for error in errors:
flash(f'{field}: {error}', 'error')

return render_template('contact.html', form=contact_form)
15 changes: 14 additions & 1 deletion app/routes/user_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@

bp_usr = Blueprint('usr', __name__)

fields_flash_messages = {
'about_me': 'About me',
'first_name': 'First name',
'last_name': 'Last name',
'email': 'Email',
'facebook_profile': 'Facebook profile',
'instagram_profile': 'Instagram profile',
'github_profile': 'GitHub profile',
'discord_id': 'Discord ID',
'linked_in': 'LinkedIn',
'avatar': 'Avatar'
}

# Open the user profile page
@bp_usr.route('/my_profile', methods=['GET', 'POST'])
@login_required
Expand Down Expand Up @@ -78,7 +91,7 @@ def open_user_profile():
else:
for field, errors in form.errors.items():
for error in errors:
flash(f'{field}: {error}', 'danger')
flash(f'{fields_flash_messages[field]}: {error}', 'danger')
return redirect(url_for('usr.open_user_profile'))

if request.method == 'GET':
Expand Down
85 changes: 54 additions & 31 deletions app/routes/user_submit_quest_routes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import random, string, base64
import random, string, base64, json
from datetime import datetime
from flask import Blueprint, render_template, redirect, url_for, flash, current_app, request
from flask import Blueprint, render_template, redirect, url_for, flash, request, abort
from flask_login import login_required, current_user
# Import the database instance
from app.database.db_init import db
Expand All @@ -9,7 +9,7 @@
# Import the forms and models
from app.models import SubmitedQuest, Quest, User, Achievement, UserAchievement
# Import the forms
from app.forms import QuestSubmissionForm, QuestApprovalForm, EditQuestForm
from app.forms import QuestSubmissionForm, QuestApprovalForm
# Import admin_required decorator
from app.user_permission import admin_required
# Import the mail functions
Expand All @@ -27,7 +27,7 @@ def open_user_submit_quest():
return render_template('user_submit_quest.html', form=form)


# # Open User Submited Quest for editing from the Admin Panel
# Open User Submited Quest for editing from the Admin Panel
@bp_usq.route('/open_submited_quest/<quest_id>', methods=['GET'])
@login_required
@admin_required
Expand Down Expand Up @@ -242,7 +242,7 @@ def approve_submited_quest(quest_id):
@login_required
def post_comment():
submited_quest_id = request.form.get('submited_quest_id')
all_comments = eval(request.form.get('submited_quest_comments'))
all_comments = json.loads(request.form.get('submited_quest_comments'))
comment = request.form.get('submited_quest_comment')
user_id = current_user.user_id
user_role = current_user.user_role
Expand Down Expand Up @@ -272,24 +272,36 @@ def post_comment():
user_role=user_role,
user_id=user_id))


# Route to open submited quest for editing as regular user
@bp_usq.route('/edit_submited_quest/<quest_id>', methods=['GET'])
@login_required
def open_submited_quest_as_user(quest_id):
submited_quest = SubmitedQuest.query.filter_by(quest_id=quest_id).first()

# Throw 404 error if the user is not the author of the quest OR the user is not an admin
if current_user.username != submited_quest.quest_author and current_user.user_role != 'Admin':
abort(404)

form = QuestApprovalForm()
form.submited_quest_id.data = quest_id
form.submited_quest_name.data = submited_quest.quest_name
form.submited_quest_language.data = submited_quest.language
form.submited_quest_difficulty.data = submited_quest.difficulty
form.submited_quest_author.data = submited_quest.quest_author
form.submited_quest_date_added.data = submited_quest.date_added
form.submited_quest_condition.data = submited_quest.condition
form.submited_function_template.data = submited_quest.function_template
form.submited_quest_unitests.data = submited_quest.unit_tests
form.submited_quest_inputs.data = submited_quest.test_inputs
form.submited_quest_outputs.data = submited_quest.test_outputs

try:
if form.validate_on_submit():
form.submited_quest_id.data = quest_id
form.submited_quest_name.data = submited_quest.quest_name
form.submited_quest_language.data = submited_quest.language
form.submited_quest_difficulty.data = submited_quest.difficulty
form.submited_quest_author.data = submited_quest.quest_author
form.submited_quest_date_added.data = submited_quest.date_added
form.submited_quest_condition.data = submited_quest.condition
form.submited_function_template.data = submited_quest.function_template
form.submited_quest_unitests.data = submited_quest.unit_tests
form.submited_quest_inputs.data = submited_quest.test_inputs
form.submited_quest_outputs.data = submited_quest.test_outputs
except:
flash('Quest update failed! Check the fields and try again', 'danger')
return render_template('edit_submited_quest_as_user.html',
submited_quest=submited_quest,
form=form)

return render_template('edit_submited_quest_as_user.html',
submited_quest=submited_quest,
Expand All @@ -302,20 +314,31 @@ def update_submited_quest(quest_id):
quest_id = form.submited_quest_id.data
submited_quest = SubmitedQuest.query.filter_by(quest_id=quest_id).first()

if form.validate_on_submit():
submited_quest.quest_name = form.submited_quest_name.data
submited_quest.language = form.submited_quest_language.data
submited_quest.difficulty = form.submited_quest_difficulty.data
submited_quest.condition = form.submited_quest_condition.data
submited_quest.function_template = form.submited_function_template.data
submited_quest.unit_tests = form.submited_quest_unitests.data
submited_quest.test_inputs = form.submited_quest_inputs.data
submited_quest.test_outputs = form.submited_quest_outputs.data
submited_quest.last_modified = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

db.session.commit()
flash('Quest updated successfully!', 'success')
return redirect(url_for('usq.open_submited_quest_as_user', quest_id=quest_id))
# Check if the quest is pending
if submited_quest.status != 'Pending':
flash('You can only edit pending quests!', 'danger')
return redirect(url_for('main.main_page'))

try:
if form.validate_on_submit():
submited_quest.quest_name = form.submited_quest_name.data
submited_quest.language = form.submited_quest_language.data
submited_quest.difficulty = form.submited_quest_difficulty.data
submited_quest.condition = form.submited_quest_condition.data
submited_quest.function_template = form.submited_function_template.data
submited_quest.unit_tests = form.submited_quest_unitests.data
submited_quest.test_inputs = form.submited_quest_inputs.data
submited_quest.test_outputs = form.submited_quest_outputs.data
submited_quest.last_modified = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

db.session.commit()
flash('Quest updated successfully!', 'success')
return redirect(url_for('usq.open_submited_quest_as_user', quest_id=quest_id))
except:
flash('Quest update failed! Check the fields and try again', 'danger')
return render_template('edit_submited_quest_as_user.html',
submited_quest=submited_quest,
form=form)
else:
flash('Quest update failed! Check the fields and try again', 'danger')
return render_template('edit_submited_quest_as_user.html',
Expand Down
Loading

0 comments on commit 51115fe

Please sign in to comment.