Skip to content

Commit 51115fe

Browse files
authored
Merge pull request #286 from Skill-Forge-Project/development
Skill Forge v1.4.5 patch release
2 parents 95f1fe7 + a71e4ba commit 51115fe

File tree

14 files changed

+157
-100
lines changed

14 files changed

+157
-100
lines changed

Dockerfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Use the base image containing Python 3.8, NodeJS, npm, mono-complete compiler and java compiler
2-
FROM python:latest
2+
FROM python:3.12.7
33

44
# Image Labels. Update values for each build
55
LABEL Name="Skill-Forge"
6-
LABEL Version="1.4.4"
6+
LABEL Version="1.4.5"
77
LABEL Release="public"
8-
LABEL ReleaseDate="08.10.2024"
8+
LABEL ReleaseDate="13.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: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,20 @@ class LoginForm(FlaskForm):
4848

4949
########### Register Form ###########
5050
class RegistrationForm(FlaskForm):
51-
username = StringField('', render_kw={'placeholder': 'Username'}, validators=[DataRequired(), Length(min=4, max=25)])
52-
first_name = StringField('', render_kw={'placeholder': 'First name'}, validators=[DataRequired(), Length(min=1, max=30)])
53-
last_name = StringField('', render_kw={'placeholder': 'Last name'}, validators=[DataRequired(), Length(min=1, max=30)])
54-
email = StringField('', render_kw={'placeholder': 'Email address'}, validators=[DataRequired(), Email(), latin_characters_only])
51+
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.")])
52+
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.")])
53+
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.")])
54+
email = StringField('', render_kw={'placeholder': 'Email address'}, validators=[DataRequired(message="Email address is required"), Email(message="Invalid emal address"), latin_characters_only])
5555
password = PasswordField('', validators=[
56-
DataRequired(),
56+
DataRequired(message="Password is required"),
5757
Length(min=10, max=50, message='Password must be between 10 and 50 characters.'),
5858
Regexp(re.compile(r'.*[A-Z].*'), message='Password must contain at least one uppercase letter.'),
5959
Regexp(re.compile(r'.*[0-9].*'), message='Password must contain at least one digit.'),
6060
Regexp(re.compile(r'.*[!@#$%^&*()_+=\-{}\[\]:;,<.>?].*'), message='Password must contain at least one special character.')
6161
],
6262
render_kw={'placeholder': 'Password'})
6363
confirm = PasswordField('', render_kw={'placeholder': 'Repeat password'}, validators=[
64-
DataRequired(),
64+
DataRequired(message="Please confirm your password"),
6565
EqualTo('password', message='Passwords must match'),
6666
Length(min=10, max=50, message='Password must be between 10 and 50 characters.')
6767
])
@@ -183,10 +183,10 @@ class PublishCommentForm(FlaskForm):
183183

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

192192
########### User Profile Form - update user's profile ###########

app/routes/quests_routes.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from app.models import Quest, ReportedQuest, User, SubmitedSolution, UserAchievement, Achievement, Comment
77
from app.forms import QuestForm, PublishCommentForm, EditQuestForm, EditReportedQuestForm
88
# Import code runners
9-
from app.code_runners import run_python, run_javascript, run_java, run_csharp
9+
from app.code_runners import run_javascript
1010
from app.code_runners.piston_api import code_runner
1111
# Import the database instance
1212
from app.database.db_init import db
@@ -152,10 +152,10 @@ def delete_comment(comment_id):
152152
flash('Comment deletion failed!', 'danger')
153153
return redirect(url_for('quests.open_curr_quest', quest_id=comment.quest_id))
154154

155-
156-
157-
# Handle quest edit from the Admin Panel
155+
# Open quest edit from the Admin Panel
158156
@bp_qst.route('/edit_quest', methods=['GET', 'POST'])
157+
@login_required
158+
@admin_required
159159
def edit_quest_db():
160160
form = EditQuestForm()
161161
if request.method == 'POST':
@@ -264,7 +264,6 @@ def edit_reported_quest():
264264
flash('Form validation failed!', 'danger')
265265
return render_template('edit_quest.html', form=form)
266266

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

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

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

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

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

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

399-
# Route to handle solution submission
395+
# Submit Quest Solution
400396
@bp_qst.route('/submit-solution', methods=['POST'])
401397
@login_required
402398
def submit_solution():

app/routes/routes.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def login():
4949
return render_template('index.html', form=form)
5050

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

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

113-
114112
########### Routes handling password reset functionality ###########
115113
# Route to open the forgot password form
116114
@bp.route('/forgot_password')
@@ -221,9 +219,9 @@ def update_new_password(form=None):
221219
# Pass necessary parameters to the template in case of errors
222220
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)
223221

224-
########### Routes handling main apge ###########
222+
########### Routes handling main page ###########
225223
# Open the main page
226-
@bp.route('/home')
224+
@bp.route('/home', methods=['GET'])
227225
@login_required
228226
def main_page():
229227
user_count = User.query.count()
@@ -240,19 +238,19 @@ def main_page():
240238
solutions_count=solutions_count)
241239

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

248246
# Route to open the about page
249-
@bp.route('/about')
247+
@bp.route('/about', methods=['GET'])
250248
@login_required
251249
def about():
252250
return render_template('about.html')
253251

254252
# Route to open the contact page
255-
@bp.route('/contact_us')
253+
@bp.route('/contact_us', methods=['GET'])
256254
@login_required
257255
def contact():
258256
contact_form = ContactForm()
@@ -264,15 +262,25 @@ def contact():
264262
@login_required
265263
def send_message():
266264
contact_form = ContactForm()
267-
user = contact_form.username.data
268-
email = contact_form.email.data
269-
subject = contact_form.subject.data
270-
message = contact_form.message.data
265+
271266
if contact_form.validate_on_submit():
267+
# Extract data only after validation succeeds
268+
user = contact_form.username.data
269+
email = contact_form.email.data
270+
subject = contact_form.subject.data
271+
message = contact_form.message.data
272+
273+
# Call function to send the email
272274
send_contact_email(user, email, subject, message)
275+
276+
# Success feedback
273277
flash('Thank you for contacting us. We will get back to you as soon as possible.', 'success')
274278
return redirect(url_for('main.contact'))
279+
280+
# Error handling if form is not valid
275281
flash('Error during sending the email.', 'error')
276-
for error in contact_form.errors:
277-
flash(f'{contact_form.errors[error][0]}', 'error')
282+
for field, errors in contact_form.errors.items():
283+
for error in errors:
284+
flash(f'{field}: {error}', 'error')
285+
278286
return render_template('contact.html', form=contact_form)

app/routes/user_routes.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@
2020

2121
bp_usr = Blueprint('usr', __name__)
2222

23+
fields_flash_messages = {
24+
'about_me': 'About me',
25+
'first_name': 'First name',
26+
'last_name': 'Last name',
27+
'email': 'Email',
28+
'facebook_profile': 'Facebook profile',
29+
'instagram_profile': 'Instagram profile',
30+
'github_profile': 'GitHub profile',
31+
'discord_id': 'Discord ID',
32+
'linked_in': 'LinkedIn',
33+
'avatar': 'Avatar'
34+
}
35+
2336
# Open the user profile page
2437
@bp_usr.route('/my_profile', methods=['GET', 'POST'])
2538
@login_required
@@ -78,7 +91,7 @@ def open_user_profile():
7891
else:
7992
for field, errors in form.errors.items():
8093
for error in errors:
81-
flash(f'{field}: {error}', 'danger')
94+
flash(f'{fields_flash_messages[field]}: {error}', 'danger')
8295
return redirect(url_for('usr.open_user_profile'))
8396

8497
if request.method == 'GET':

app/routes/user_submit_quest_routes.py

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import random, string, base64
1+
import random, string, base64, json
22
from datetime import datetime
3-
from flask import Blueprint, render_template, redirect, url_for, flash, current_app, request
3+
from flask import Blueprint, render_template, redirect, url_for, flash, request, abort
44
from flask_login import login_required, current_user
55
# Import the database instance
66
from app.database.db_init import db
@@ -9,7 +9,7 @@
99
# Import the forms and models
1010
from app.models import SubmitedQuest, Quest, User, Achievement, UserAchievement
1111
# Import the forms
12-
from app.forms import QuestSubmissionForm, QuestApprovalForm, EditQuestForm
12+
from app.forms import QuestSubmissionForm, QuestApprovalForm
1313
# Import admin_required decorator
1414
from app.user_permission import admin_required
1515
# Import the mail functions
@@ -27,7 +27,7 @@ def open_user_submit_quest():
2727
return render_template('user_submit_quest.html', form=form)
2828

2929

30-
# # Open User Submited Quest for editing from the Admin Panel
30+
# Open User Submited Quest for editing from the Admin Panel
3131
@bp_usq.route('/open_submited_quest/<quest_id>', methods=['GET'])
3232
@login_required
3333
@admin_required
@@ -242,7 +242,7 @@ def approve_submited_quest(quest_id):
242242
@login_required
243243
def post_comment():
244244
submited_quest_id = request.form.get('submited_quest_id')
245-
all_comments = eval(request.form.get('submited_quest_comments'))
245+
all_comments = json.loads(request.form.get('submited_quest_comments'))
246246
comment = request.form.get('submited_quest_comment')
247247
user_id = current_user.user_id
248248
user_role = current_user.user_role
@@ -272,24 +272,36 @@ def post_comment():
272272
user_role=user_role,
273273
user_id=user_id))
274274

275-
276275
# Route to open submited quest for editing as regular user
277276
@bp_usq.route('/edit_submited_quest/<quest_id>', methods=['GET'])
278277
@login_required
279278
def open_submited_quest_as_user(quest_id):
280279
submited_quest = SubmitedQuest.query.filter_by(quest_id=quest_id).first()
280+
281+
# Throw 404 error if the user is not the author of the quest OR the user is not an admin
282+
if current_user.username != submited_quest.quest_author and current_user.user_role != 'Admin':
283+
abort(404)
284+
281285
form = QuestApprovalForm()
282-
form.submited_quest_id.data = quest_id
283-
form.submited_quest_name.data = submited_quest.quest_name
284-
form.submited_quest_language.data = submited_quest.language
285-
form.submited_quest_difficulty.data = submited_quest.difficulty
286-
form.submited_quest_author.data = submited_quest.quest_author
287-
form.submited_quest_date_added.data = submited_quest.date_added
288-
form.submited_quest_condition.data = submited_quest.condition
289-
form.submited_function_template.data = submited_quest.function_template
290-
form.submited_quest_unitests.data = submited_quest.unit_tests
291-
form.submited_quest_inputs.data = submited_quest.test_inputs
292-
form.submited_quest_outputs.data = submited_quest.test_outputs
286+
287+
try:
288+
if form.validate_on_submit():
289+
form.submited_quest_id.data = quest_id
290+
form.submited_quest_name.data = submited_quest.quest_name
291+
form.submited_quest_language.data = submited_quest.language
292+
form.submited_quest_difficulty.data = submited_quest.difficulty
293+
form.submited_quest_author.data = submited_quest.quest_author
294+
form.submited_quest_date_added.data = submited_quest.date_added
295+
form.submited_quest_condition.data = submited_quest.condition
296+
form.submited_function_template.data = submited_quest.function_template
297+
form.submited_quest_unitests.data = submited_quest.unit_tests
298+
form.submited_quest_inputs.data = submited_quest.test_inputs
299+
form.submited_quest_outputs.data = submited_quest.test_outputs
300+
except:
301+
flash('Quest update failed! Check the fields and try again', 'danger')
302+
return render_template('edit_submited_quest_as_user.html',
303+
submited_quest=submited_quest,
304+
form=form)
293305

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

305-
if form.validate_on_submit():
306-
submited_quest.quest_name = form.submited_quest_name.data
307-
submited_quest.language = form.submited_quest_language.data
308-
submited_quest.difficulty = form.submited_quest_difficulty.data
309-
submited_quest.condition = form.submited_quest_condition.data
310-
submited_quest.function_template = form.submited_function_template.data
311-
submited_quest.unit_tests = form.submited_quest_unitests.data
312-
submited_quest.test_inputs = form.submited_quest_inputs.data
313-
submited_quest.test_outputs = form.submited_quest_outputs.data
314-
submited_quest.last_modified = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
315-
316-
db.session.commit()
317-
flash('Quest updated successfully!', 'success')
318-
return redirect(url_for('usq.open_submited_quest_as_user', quest_id=quest_id))
317+
# Check if the quest is pending
318+
if submited_quest.status != 'Pending':
319+
flash('You can only edit pending quests!', 'danger')
320+
return redirect(url_for('main.main_page'))
321+
322+
try:
323+
if form.validate_on_submit():
324+
submited_quest.quest_name = form.submited_quest_name.data
325+
submited_quest.language = form.submited_quest_language.data
326+
submited_quest.difficulty = form.submited_quest_difficulty.data
327+
submited_quest.condition = form.submited_quest_condition.data
328+
submited_quest.function_template = form.submited_function_template.data
329+
submited_quest.unit_tests = form.submited_quest_unitests.data
330+
submited_quest.test_inputs = form.submited_quest_inputs.data
331+
submited_quest.test_outputs = form.submited_quest_outputs.data
332+
submited_quest.last_modified = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
333+
334+
db.session.commit()
335+
flash('Quest updated successfully!', 'success')
336+
return redirect(url_for('usq.open_submited_quest_as_user', quest_id=quest_id))
337+
except:
338+
flash('Quest update failed! Check the fields and try again', 'danger')
339+
return render_template('edit_submited_quest_as_user.html',
340+
submited_quest=submited_quest,
341+
form=form)
319342
else:
320343
flash('Quest update failed! Check the fields and try again', 'danger')
321344
return render_template('edit_submited_quest_as_user.html',

0 commit comments

Comments
 (0)