finished dev version

This commit is contained in:
2026-01-12 14:01:18 +01:00
parent 79c011a217
commit 462f8a688b
8 changed files with 111 additions and 41 deletions

View File

@@ -5,8 +5,10 @@ class Participant(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False) name = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False) email = db.Column(db.String(120), unique=True, nullable=False)
num_emails_sent = db.Column(db.Integer, default=0)
looking_for_team = db.Column(db.Boolean, default=False) looking_for_team = db.Column(db.Boolean, default=False)
confirmed = db.Column(db.Boolean, default=False) confirmed = db.Column(db.Boolean, default=False)
on_waitlist = db.Column(db.Boolean, default=False)
discord_token = db.Column(db.String(16), unique=True) discord_token = db.Column(db.String(16), unique=True)
discord_user_id = db.Column(db.String(50)) discord_user_id = db.Column(db.String(50))

View File

@@ -9,14 +9,31 @@ bp = Blueprint('main', __name__)
def get_confirmed_count(): def get_confirmed_count():
return Participant.query.filter_by(confirmed=True).count() return Participant.query.filter_by(confirmed=True).count()
def send_verification_email(user_email): def add_user(name, email, looking_for_team, on_waitlist = False):
new_user = Participant(name=name, email=email, looking_for_team=looking_for_team)
if on_waitlist:
new_user.on_waitlist = on_waitlist
db.session.add(new_user)
db.session.commit()
return new_user
def send_verification_email(user):
s = URLSafeTimedSerializer(current_app.config['SECRET_KEY']) s = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
token = s.dumps(user_email, salt='email-confirm-h4tum-ctf-2025') token = s.dumps(user.email, salt='email-confirm-h4tum-ctf-2025')
link = url_for('main.verify_email', token=token, _external=True) link = url_for('main.verify_email', token=token, _external=True)
logo = url_for('static', filename='h4tum_white.png', _external=True) logo = url_for('static', filename='h4tum_white.png', _external=True)
msg = Message('Verify your CTF Registration', recipients=[user_email]) msg = Message('Verify your CTF Registration', recipients=[user.email])
msg.html = render_template("email.html", user_email=user_email, link=link, logo_url=logo) msg.html = render_template("email.html", user_name=user.name, link=link, logo_url=logo)
mail.send(msg)
user.num_emails_sent += 1
db.session.commit()
return True
def send_waitlist_email(user):
msg = Message('h4tum CTF 2026 Registration', recipients=[user.email])
logo = url_for('static', filename='h4tum_white.png', _external=True)
msg.html = render_template("waitlist_email.html", logo_url=logo, user_name=user.name)
mail.send(msg) mail.send(msg)
@bp.route('/', methods=['GET', 'POST']) @bp.route('/', methods=['GET', 'POST'])
@@ -35,28 +52,34 @@ def index():
flash(f"Error: You must use a @{tum_domain} email address.", "error") flash(f"Error: You must use a @{tum_domain} email address.", "error")
return redirect(url_for('main.index')) return redirect(url_for('main.index'))
if confirmed_count >= max_spots:
flash("Sorry, the event is full!", "error")
return redirect(url_for('main.index'))
existing_user = Participant.query.filter_by(email=email).first() existing_user = Participant.query.filter_by(email=email).first()
if existing_user: if existing_user:
if existing_user.confirmed: if existing_user.confirmed:
flash("You are already registered and confirmed!", "info") flash("You are already registered and confirmed!", "info")
elif existing_user.on_waitlist:
flash("You are already on the waitlist", "info")
else: else:
if existing_user.num_emails_sent < 5:
flash("Confirmation email resent. Please check your inbox.", "info") flash("Confirmation email resent. Please check your inbox.", "info")
send_verification_email(email) send_verification_email(existing_user)
else:
flash("Email limit reached", "error")
return redirect(url_for('main.index')) return redirect(url_for('main.index'))
else:
new_user = add_user(name, email, looking_for_team)
new_user = Participant(name=name, email=email, looking_for_team=looking_for_team) if confirmed_count < max_spots:
db.session.add(new_user) send_verification_email(new_user)
db.session.commit()
send_verification_email(email)
flash("Registration started! Check your email to confirm your spot.", "success") flash("Registration started! Check your email to confirm your spot.", "success")
else:
new_user.on_waitlist = True
db.session.commit()
send_waitlist_email(new_user)
flash("The event is already full!\nYou have been placed on the waitlist", "info")
return redirect(url_for('main.index')) return redirect(url_for('main.index'))
return render_template('index.html', spots_left=spots_left) return render_template('index.html', spots_left=spots_left, max_spots=max_spots)
@bp.route('/verify/<token>') @bp.route('/verify/<token>')
def verify_email(token): def verify_email(token):

View File

@@ -45,6 +45,10 @@ footer {
margin: 0; margin: 0;
} }
.status {
text-align: center;
}
h1 { font-size: 1.5rem; word-wrap: break-word; } h1 { font-size: 1.5rem; word-wrap: break-word; }

View File

@@ -20,7 +20,7 @@
<tr> <tr>
<td align="center" style="padding-bottom: 30px; font-size: 16px; line-height: 1.5; text-align: center;"> <td align="center" style="padding-bottom: 30px; font-size: 16px; line-height: 1.5; text-align: center;">
Thanks for registering <br> Thanks for registering <br>
<strong style="color: #00ff00;">{{ user_email }}</strong><br><br> <strong style="color: #00ff00;">{{ user_name }}</strong><br><br>
To secure your spot at the event, please confirm your attendance by clicking the button below. To secure your spot at the event, please confirm your attendance by clicking the button below.
</td> </td>
</tr> </tr>

View File

@@ -13,9 +13,9 @@
<hr style="border-color: #333; margin: 30px 0;"></hr> <hr style="border-color: #333; margin: 30px 0;"></hr>
<h1>> Prizes</h1> <h1>> Prizes</h1>
<ul> <ul>
<li>1st Place: 100€</li> <li>1st Place: 150€</li>
<li>2nd Place: 75</li> <li>2nd Place: 110</li>
<li>3rd Place: 50</li> <li>3rd Place: 75€</li>
</ul> </ul>
<hr style="border-color: #333; margin: 30px 0;"></hr> <hr style="border-color: #333; margin: 30px 0;"></hr>
<h1>> Format</h1> <h1>> Format</h1>
@@ -76,10 +76,14 @@
{% endwith %} {% endwith %}
<div class="status"> <div class="status">
<h2>Spots Remaining: {{ spots_left }} / 84</h2> {% if spots_left > 0 %}
<h2>Spots Remaining: {{ spots_left }} / {{max_spots}}</h2>
{% else %}
<h2>Register for Waitlist</h2>
<p>This event is currently full. Join the queue to be notified of openings.</p>
{%endif %}
</div> </div>
{% if spots_left > 0 %}
<form method="POST"> <form method="POST">
<label>Name</label> <label>Name</label>
<input type="text" name="name" required> <input type="text" name="name" required>
@@ -94,8 +98,5 @@
<button type="submit">INITIALIZE_REGISTRATION</button> <button type="submit">INITIALIZE_REGISTRATION</button>
</form> </form>
{% else %}
<h2 style="color:red">EVENT FULL</h2>
{% endif %}
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -15,6 +15,7 @@
<li>If something feels unintentionally easy, please report it.</li> <li>If something feels unintentionally easy, please report it.</li>
<li>Do not share flags or solutions with other teams. We will not tolerate flag sharing.</li> <li>Do not share flags or solutions with other teams. We will not tolerate flag sharing.</li>
<li>Each team may register only once, and each person may belong to only one team.</li> <li>Each team may register only once, and each person may belong to only one team.</li>
<li>A team has at maximum four members</li>
<li>Only TUM Students are permitted to participate in the CTF.</li> <li>Only TUM Students are permitted to participate in the CTF.</li>
<li>Attacking personnel, including participants and non-participants, or the competition system itself is forbidden.</li> <li>Attacking personnel, including participants and non-participants, or the competition system itself is forbidden.</li>
<li>Every participant has to follow the Code of Conduct.</li> <li>Every participant has to follow the Code of Conduct.</li>

View File

@@ -0,0 +1,39 @@
<html>
<body style="margin: 0; padding: 0; background-color: #1a1a1a; font-family: 'Courier New', Courier, monospace; color: #ffffff;">
<table width="100%" border="0" cellspacing="0" cellpadding="0" style="background-color: #1a1a1a; padding: 20px;">
<tr>
<td align="center">
<table width="100%" border="0" cellspacing="0" cellpadding="0" style="max-width: 600px; background-color: #222222; border: 1px solid #00ff00; padding: 30px;">
<tr>
<td align="center" style="padding-bottom: 20px;">
<img src="{{ logo_url }}" alt="Club Logo" width="150" style="display: block; border: 0;">
</td>
</tr>
<tr>
<td align="center" style="padding-bottom: 20px;">
<h1 style="color: #00ff00; font-size: 24px; margin: 0; text-transform: uppercase; text-align: center;">Registration Received</h1>
</td>
</tr>
<tr>
<td align="center" style="padding-bottom: 30px; font-size: 16px; line-height: 1.5; text-align: center;">
Thanks for registering <br>
<strong style="color: #00ff00;">{{ user_name }}</strong><br><br>
At this time, the event has reached maximum capacity. Your name has been placed on a waiting list; we will contact you in the event of any cancellations.
</td>
</tr>
<tr>
<td align="center" style="padding-top: 20px; border-top: 1px solid #333333; font-size: 12px; color: #888888; text-align: center;">
If you didn't request this, you can safely ignore this email.
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

2
run.py
View File

@@ -3,4 +3,4 @@ from app import create_app
app = create_app() app = create_app()
if __name__ == '__main__': if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0') app.run()