Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
6d28c8a
Merge pull request #196 from karastoyanov/development
karastoyanov Jul 25, 2024
6f4e55f
footer fixed
karastoyanov Jul 25, 2024
0870e0c
frontend avatar updated
karastoyanov Jul 25, 2024
382200d
guild homepage section
karastoyanov Jul 26, 2024
eb45083
create guild form
karastoyanov Jul 27, 2024
4e7ad96
create guild backend implemented
karastoyanov Jul 27, 2024
2a3e377
create guild form updated
karastoyanov Jul 27, 2024
a6d1916
mongo transaction create guild implemented
karastoyanov Jul 27, 2024
a64afde
View guilds list page implemented
karastoyanov Jul 27, 2024
07741c5
Guild List section implemented
karastoyanov Jul 27, 2024
356b5bb
guild list updated
karastoyanov Jul 27, 2024
67304e6
guilds list table implemented
karastoyanov Jul 27, 2024
10dba81
Merge pull request #202 from karastoyanov/main
karastoyanov Jul 29, 2024
2654ad9
guild list update
karastoyanov Jul 29, 2024
cb68808
Merge pull request #203 from karastoyanov/main
karastoyanov Jul 29, 2024
a000617
navbar fixed
karastoyanov Jul 29, 2024
7d0cd99
navbar update
karastoyanov Jul 30, 2024
078e912
homepage update
karastoyanov Jul 30, 2024
f24a8d3
homepage fixed
karastoyanov Jul 30, 2024
c884774
quest page fixed
karastoyanov Aug 1, 2024
290dfa8
navbar cursor pointer fix
karastoyanov Aug 1, 2024
24c4756
homepage fix
karastoyanov Aug 1, 2024
154f179
user_profile update
karastoyanov Aug 2, 2024
1f86aa2
user profile info update
karastoyanov Aug 2, 2024
cae60cd
social links updated
karastoyanov Aug 3, 2024
b5477c0
user profile update
karastoyanov Aug 3, 2024
c6f37b1
user stats updated
karastoyanov Aug 3, 2024
c5d4f86
user profile tables implemented
karastoyanov Aug 3, 2024
62e8acc
user update form updated
karastoyanov Aug 3, 2024
f5a5cc7
update avatar form
karastoyanov Aug 3, 2024
cc2d900
user profile form update
karastoyanov Aug 3, 2024
8c39bf9
Merge branch 'bootstrap-migr' into 199-add-guild-functionality-and-du…
karastoyanov Aug 3, 2024
d18f925
Merge pull request #204 from karastoyanov/199-add-guild-functionality…
karastoyanov Aug 3, 2024
24c61f1
user profile view reworked
karastoyanov Aug 3, 2024
545c798
user profile bio
karastoyanov Aug 3, 2024
c1272d9
about me field implemented
karastoyanov Aug 3, 2024
bc8eda9
Merge pull request #207 from karastoyanov/bootstrap-migr
karastoyanov Aug 4, 2024
4b66061
contact form fixed
karastoyanov Aug 4, 2024
ff84f06
user submit quest form update
karastoyanov Aug 4, 2024
b8d76f3
edin submited quest pages update
karastoyanov Aug 4, 2024
329d396
view solution page update
karastoyanov Aug 4, 2024
48f06af
login form update
karastoyanov Aug 4, 2024
79ed0e4
Merge pull request #208 from karastoyanov/bootstrap-migr
karastoyanov Aug 4, 2024
c9abc6d
python gen task
karastoyanov Aug 4, 2024
2854468
version 1.3.14 deployment prep
karastoyanov Aug 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ FROM karastoyanov/skill-forge-baseimage

# Image Labels. Update values for each build
LABEL Name="Skill-Forge"
LABEL Version=1.3.13
LABEL Version=1.3.14
LABEL Release="pre-release"
LABEL ReleaseDate="28.07.2024"
LABEL ReleaseDate="04.08.2024"
LABEL Description="Skill Forge is a open-source platform for learning and practicing programming languages."
LABEL Maintainer="Aleksandar Karastoyanov <karastoqnov.alexadar@gmail.com>"
LABEL License="GNU GPL v3.0 license"
Expand Down
7 changes: 4 additions & 3 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

from flask import Flask, redirect, url_for, render_template, flash, jsonify
from flask import Flask, redirect, url_for, render_template, flash, jsonify, send_file
from config import Config
from flask_migrate import Migrate
from flask_bcrypt import Bcrypt
Expand All @@ -13,7 +13,7 @@
from app.models import User
# Import scheduler
from apscheduler.schedulers.background import BackgroundScheduler
import atexit
import atexit, base64, io

migrate = Migrate()
bcrypt = Bcrypt()
Expand All @@ -37,12 +37,13 @@ def create_app():
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1)

# Import routes
from app.routes import routes, user_routes, user_submited_solutions, quests_routes, user_submit_quest_routes
from app.routes import routes, user_routes, user_submited_solutions, quests_routes, user_submit_quest_routes, guild_routes
app.register_blueprint(routes.bp)
app.register_blueprint(user_routes.bp_usr)
app.register_blueprint(user_submit_quest_routes.bp_usq)
app.register_blueprint(user_submited_solutions.bp_uss)
app.register_blueprint(quests_routes.bp_qst)
app.register_blueprint(guild_routes.bp_guild)

# Import websockets
# Define ping timeout and ping interval (in seconds)
Expand Down
13 changes: 10 additions & 3 deletions app/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ class ContactForm(FlaskForm):

########### User Profile Form - update user's profile ###########
class UserProfileForm(FlaskForm):
about_me = TextAreaField('About Me', validators=[Optional()])
first_name = StringField('First Name', validators=[Optional()])
last_name = StringField('Last Name', validators=[Optional()])
email = StringField('Email', validators=[Optional(), Email(), latin_characters_only])
Expand All @@ -199,9 +200,8 @@ class UserProfileForm(FlaskForm):
discord_id = StringField('Discord ID', validators=[Optional()])
linked_in = StringField('LinkedIn', validators=[Optional()])
avatar = FileField('Upload Avatar', name="update_avatar", validators=[Optional()])
submit = SubmitField('Save Changes', name="submit")
submit = SubmitField('Update Profile', name="submit")


########### Submit Quest Form - as a Regular User ###########
class QuestSubmissionForm(FlaskForm):
quest_name = StringField('Quest Name', validators=[DataRequired(), Length(max=100)])
Expand Down Expand Up @@ -239,4 +239,11 @@ class QuestApprovalForm(FlaskForm):
approve = SubmitField('Approve Quest', render_kw={'value': 'approve'})
request_changes = SubmitField('Request Changes', render_kw={'value': 'request-changes'})
reject = SubmitField('Reject Quest', render_kw={'value': 'reject'})
save_changes = SubmitField('Save Changes', render_kw={'value': 'save-changes'})
save_changes = SubmitField('Save Changes', render_kw={'value': 'save-changes'})

########### Create New Guild Form ###########
class CreateGuildForm(FlaskForm):
name = StringField('Guild Name', validators=[DataRequired(), Length(min=5, max=50)])
description = TextAreaField('Description', validators=[Optional(), Length(min=10, max=500)])
avatar = FileField('Avatar')
submit = SubmitField('Create Guild')
23 changes: 23 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,20 @@ class User(UserMixin, db.Model):
github_profile = db.Column(db.String(120), default="")
discord_id = db.Column(db.String(120), default="")
linked_in = db.Column(db.String(120), default="")
about_me = db.Column(db.Text, default="", nullable=True)
is_banned = db.Column(db.Boolean, default=lambda: False)
ban_date = db.Column(db.DateTime, nullable=True)
ban_reason = db.Column(db.String(120), default=" ", nullable=True)
user_online_status = db.Column(db.String(10), default="Offline", nullable=True)
last_status_update = db.Column(db.DateTime, default=datetime.now(), nullable=True)
guild_id = db.Column(db.String(20), db.ForeignKey('guilds.guild_id'), nullable=True)

# Define the relationship with the UserAchievement model
achievements = db.relationship('UserAchievement')
# Define the relationship with the Comment model
comments = db.relationship('Comment', back_populates='user')
# Define the relationship with the Guild model
guild = db.relationship('Guild', back_populates='members', foreign_keys=[guild_id])

def __init__(self, username, first_name, last_name, password, email, avatar=None):
self.username = username
Expand Down Expand Up @@ -224,3 +228,22 @@ class SubmitedSolution(db.Model):

# Define the relationship between the user_submited_solutions and coding_quests table.
coding_quest = db.relationship('Quest')

########### Define the Guild model ###########
class Guild(db.Model):
__tablename__ = 'guilds'
guild_id = db.Column(db.String(20), primary_key=True, nullable=False)
guild_name = db.Column(db.String(100), unique=True, nullable=False)
guild_avatar = db.Column(db.LargeBinary, default=None)
description = db.Column(db.String(200), nullable=True)
guild_master_id = db.Column(db.String(20), db.ForeignKey('users.user_id'), nullable=False)
points = db.Column(db.Integer, default=0)
level = db.Column(db.Integer, default=1)
dungeons_completed = db.Column(db.Integer, default=0)
date_created = db.Column(db.DateTime, default=datetime.now(), nullable=False)
guild_members_count = db.Column(db.Integer, default=1)

# Define the relationship between the Guild model and the User model
members = db.relationship('User', back_populates='guild', foreign_keys=[User.guild_id])
# Define the relationship with the User model for guild master
guild_master = db.relationship('User', foreign_keys=[guild_master_id])
5 changes: 4 additions & 1 deletion app/routes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@
from .user_routes import bp_usr

# Import routes from user submited solutions routes
from .user_submited_solutions import bp_uss
from .user_submited_solutions import bp_uss

# Import routes from guild routes
from .guild_routes import bp_guild
100 changes: 100 additions & 0 deletions app/routes/guild_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import random, string, base64, json, os
from datetime import datetime
from flask import Blueprint, redirect, url_for, request, render_template, jsonify, flash, abort, send_file
from flask_login import login_required, current_user
# Import the forms and models
from app.models import Guild
from app.forms import CreateGuildForm
# Import code runners
from app.code_runners import run_python, run_javascript, run_java, run_csharp
# Import the database instance
from app.database.db_init import db
from sqlalchemy.orm import joinedload
# Import MongoDB transactions functions
from app.database.mongodb_transactions import mongo_transaction
# Import admin_required decorator
from app.user_permission import admin_required
import io
import base64

bp_guild = Blueprint('guilds', __name__)

# Redirect to create new guild page
@bp_guild.route('/create_guild', methods=['GET'])
def open_create_guild():
form = CreateGuildForm()
return render_template('guild_templates/create_guild.html', form=form)

# Redirect to the guilds list page
@bp_guild.route('/guilds', methods=['GET'])
def open_guilds_list():
guilds = Guild.query.all()
return render_template('guild_templates/guilds_list.html', guilds=guilds)

# Handle the guild avatar image requests
@bp_guild.route('/guilds/avatar/<guild_id>')
def get_guild_avatar(guild_id):
guild = Guild.query.filter_by(guild_id=guild_id).first_or_404()
if guild.guild_avatar:
img_data = guild.guild_avatar
else:
# Return a default image if no avatar is set
with open('app/static/images/default-guild-avatar.png', 'rb') as f:
img_data = f.read()
return send_file(io.BytesIO(img_data), mimetype='image/jpeg')

# Create new guild
@bp_guild.route('/guilds/create', methods=['GET', 'POST'])
@login_required
def create_new_guild():
form = CreateGuildForm()
if form.validate_on_submit():
existing_guild = Guild.query.filter_by(guild_name=form.name.data).first()
if existing_guild:
flash('This guild name is already taken. Please choose a different name.', 'error')
return render_template('create_guild.html', form=form)

if form.avatar.data:
guild_avatar = form.avatar.data.read()
else:
with open('app/static/images/default-guild-avatar.png', 'rb') as f:
guild_avatar = f.read()

# Generate a random guild ID
while True:
# Generate a random 7-digit number
random_digits = random.randint(1000000, 9999999)
guild_id = f"GD-{random_digits}"
# Check if this ID already exists in the database
existing_guild = Guild.query.filter_by(guild_id=guild_id).first()
if not existing_guild:
break


# Create new guild
guild = Guild(
guild_id=guild_id,
guild_name=form.name.data,
description=form.description.data,
guild_master_id=current_user.user_id,
guild_avatar=guild_avatar)

db.session.add(guild)
db.session.commit()

mongo_transaction(
'guild_create',
action=f'Guild {form.name.data} created by {current_user.username}',
user_id=current_user.user_id,
username=current_user.username,
timestamp=datetime.now().strftime('%Y-%m-%d %H:%M:%S')
)
flash(f'Guild {form.name.data} created successfully!', 'success')
return redirect(url_for('guilds.create_new_guild'))
else:
# Print form errors as flash messages
for field, errors in form.errors.items():
for error in errors:
flash(f"Error in the {getattr(form, field).label.text} field - {error}", 'error')

return render_template('guild_templates/create_guild.html', form=form)
1 change: 0 additions & 1 deletion app/routes/quests_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from app.database.mongodb_transactions import mongo_transaction
# Import admin_required decorator
from app.user_permission import admin_required
import logging


bp_qst = Blueprint('quests', __name__)
Expand Down
4 changes: 2 additions & 2 deletions app/routes/user_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def open_user_profile():

if form.validate_on_submit():
if 'submit' in request.form:
user.about_me = form.about_me.data
user.first_name = form.first_name.data
user.last_name = form.last_name.data
user.email = form.email.data
Expand Down Expand Up @@ -61,6 +62,7 @@ def open_user_profile():
return redirect(url_for('usr.open_user_profile'))

if request.method == 'GET':
form.about_me.data = user.about_me
form.first_name.data = user.first_name
form.last_name.data = user.last_name
form.email.data = user.email
Expand Down Expand Up @@ -130,7 +132,6 @@ def edit_user_db():
db.session.commit()
return redirect(url_for('usr.open_admin_panel'))


# Route to handle the user profile (self-open)
@bp_usr.route('/user_profile/<username>', methods=['GET'])
@login_required
Expand Down Expand Up @@ -173,7 +174,6 @@ def open_user_profile_view(username):
# Handle the case where the user is not found
return abort(404)


# Redirect to the Admin Panel (Admin Role in the database is needed)
@bp_usr.route('/admin_panel')
@login_required
Expand Down
Loading