Compare commits

..

25 commits
master ... 2025

Author SHA1 Message Date
warren yun
4e90cbe150 sponsor changes 2025-01-02 19:25:03 -05:00
Warren Yun
605c8b3406 registration updates 2024-12-03 15:33:38 -05:00
ngolp
8ab8101214 basic implementation of github issue #28 (modifying user details right from admin dashboard). Went with first name, last name, school, and phone number, since the user can change the shirt size and special accomodations on their own. 2024-10-31 13:55:01 -04:00
ngolp
368f8b4de6 Hacker table on admin dashboard can now be sorted by double clicking the headers. Spent a bit trying to implement this manually before learning about the sortable class. The more you know! 2024-10-31 13:55:01 -04:00
ngolp
edffbb72fb Basic implementation of searching by email. I'd like to expand this to searching by other column values as well if necessary, and I still need to add sorting. Also, "registered users" isn't perfectly centered (its pushed off just a bit by the search button). This commit is mainly so I can save my progress. 2024-10-31 13:55:01 -04:00
ngolp
3ec089c749 missed that bit in the github issue where Cara wanted it to redirect to login page. quick fix 2024-10-31 13:54:50 -04:00
ngolp
8076d8571d graceful handling of database integrity violations in registration 2024-10-31 13:54:50 -04:00
Cara Salter
fd96be4752 open registration 2024-10-31 13:34:40 -04:00
Cara Salter
13119288d4 Add MLH logistics checkbox 2024-10-31 13:18:38 -04:00
Cara Salter
3441982734 use .env for config from now on
can't believe it took us this long
2024-10-31 13:10:09 -04:00
Cara Salter
2f8998c971 Update migrations and homepage for 2025 2024-10-31 13:09:26 -04:00
ngolp
a46a6b2831 Changed back the SQL query in /mail route 2024-10-22 20:11:34 -04:00
ngolp
44673d1b07 See Github Issue #10. Added separation of admins and users on admin dashboard.
Refactored a bit of the original admin.home endpoint into a helper function to accompany two routes that use the same template without having to copy+paste code.
2024-10-22 20:11:34 -04:00
warren yun
e9446b97c7 update user schema 2024-10-22 17:56:14 -04:00
warren yun
0268f70f9b more fields 2024-10-22 17:51:44 -04:00
warren yun
7defb52a56 countries 2024-10-20 22:15:12 -04:00
warren yun
5a3c43e98b schools dropdown 2024-10-20 21:55:25 -04:00
warren yun
2b0b2912f3 countdown (hot) 2024-10-18 23:51:22 -04:00
warren yun
e89b2ab7b4 modified theme 2024-09-30 22:09:25 -04:00
warren yun
16f08f7750 playing around with styling and items 2024-09-17 20:01:38 -04:00
Cara Salter
d0240d15d8
Merge pull request #33 from wpi-acm/event-date-time-fields
Event Modals & Split date/time fields
2024-06-05 09:21:09 -04:00
Cara Salter
f5eb90b73d
Make unauth handler redirect to login 2024-06-03 18:15:18 -04:00
Cara Salter
a3c89dd478
Support editing event category in modals 2024-06-03 18:12:39 -04:00
Cara Salter
7fc06bde11
Enable editing/creating/deleting events
Wholly through modals, yay!
2024-06-02 12:58:07 -04:00
Cara Salter
e28912b997
Input event modal and create some supporting infra
update form to split date/time as well
2024-06-02 12:30:40 -04:00
10 changed files with 141 additions and 34 deletions

View file

@ -11,8 +11,7 @@ class Config():
SQLALCHEMY_DATABASE_URI = dotenv_values().get("SQLALCHEMY_DATABASE_URI") or "postgresql://localhost/goathacks"
MAX_BEFORE_WAITLIST = int(dotenv_values().get("MAX_BEFORE_WAITLIST") or 1)
MCE_API_KEY = dotenv_values().get("MCE_API_KEY")
MAX_BEFORE_WAITLIST = dotenv_values().get("MAX_BEFORE_WAITLIST") or 1
SECRET_KEY = dotenv_values().get("SECRET_KEY") or "bad-key-change-me"
UPLOAD_FOLDER = dotenv_values().get("UPLOAD_FOLDER") or "./uploads/"

View file

@ -4,6 +4,8 @@ from flask_mail import Message
from goathacks.models import User
from sqlalchemy.exc import IntegrityError
bp = Blueprint("admin", __name__, url_prefix="/admin")
from goathacks import db, mail as app_mail
@ -221,6 +223,44 @@ def hackers():
users = User.query.all()
return User.create_json_output(users)
@bp.route("/updateHacker", methods=["POST"])
@login_required
def updateHacker():
if not current_user.is_admin:
return redirect(url_for("dashboard.home"))
# get params from json
hacker_id = request.json['hacker_id']
change_field = request.json['change_field']
new_val = request.json['new_val']
# find the user in db
user = User.query.filter_by(id=hacker_id).one()
if user is None:
return {"status": "error", "msg": "user not found"}
# update the hacker depending on change_field
match change_field:
case "first_name":
user.first_name = new_val
case "last_name":
user.last_name = new_val
case "school":
user.school = new_val
case "phone":
user.phone = new_val
try:
db.session.commit()
except IntegrityError as err:
db.session.rollback()
flash("Could not update user information for user " + hacker_id)
return {"status": "error"}
return {"status": "success"}
import json
import csv
from io import StringIO

View file

@ -1,4 +1,4 @@
from flask import Blueprint, current_app, flash, jsonify, redirect, render_template, request, url_for
from flask import Blueprint, current_app, flash, jsonify, render_template, request
from flask_login import current_user, login_required
from werkzeug.utils import secure_filename
@ -47,17 +47,9 @@ def resume():
filename = current_user.first_name.lower() + '_' + current_user.last_name.lower() + '_' + str(
current_user.id) + '.' + resume.filename.split('.')[-1].lower()
filename = secure_filename(filename)
if not os.path.exists(current_app.config['UPLOAD_FOLDER']):
try:
os.makedirs(current_app.config['UPLOAD_FOLDER'])
except Exception:
flash("Error saving resume. Contact acm-sysadmin@wpi.edu")
return redirect(url_for("dashboard.home"))
resume.save(os.path.join(current_app.config['UPLOAD_FOLDER'], filename))
flash("Resume uploaded!")
return redirect(url_for("dashboard.home"))
flash("Something went wrong. If this keeps happening, contact hack@wpi.edu for assistance")
return redirect(url_for("dashboard.home"))
return 'Resume uploaded! <a href="/dashboard">Return to dashboard</a>'
return "Something went wrong. If this keeps happening, contact hack@wpi.edu for assistance"
def allowed_file(filename):

View file

@ -6,6 +6,7 @@ from goathacks.registration.forms import LoginForm, PwResetForm, RegisterForm, R
from werkzeug.security import check_password_hash, generate_password_hash
from flask_mail import Message
import ulid
from sqlalchemy.exc import IntegrityError
from goathacks import db, mail as app_mail
from goathacks.models import PwResetRequest, User
@ -60,8 +61,20 @@ def register():
dietary_restrictions=dietary_restrictions,
newsletter=newsletter
)
db.session.add(user)
db.session.commit()
#try to add the user to the database, checking for duplicate users
try:
db.session.add(user)
db.session.commit()
except IntegrityError as err:
db.session.rollback()
if "duplicate key value violates unique constraint" in str(err):
flash("User with email " + email + " already exists.")
else:
flash("An unknown error occurred.")
return redirect(url_for("registration.login"))
#user successfully registered, so login
flask_login.login_user(user)
if waitlisted:
@ -129,6 +142,7 @@ def reset():
user_id=user.id,
expires=datetime.now() + timedelta(minutes=30)
)
db.session.add(r)
db.session.commit()

View file

@ -22,8 +22,8 @@ class RegisterForm(FlaskForm):
gender = SelectField("Gender", choices=[("F", "Female"), ("M", "Male"),
("NB", "Non-binary/Other")],
widget=widgets.Select())
country = SelectField("Country", choices=[(country.split(",")[0], country.split(",")[0]) for country in countries_list], widget=widgets.Select())
newsletter = BooleanField("Subscribe to the MLH newsletter?")
country = SelectField("Country of Origin", choices=[(country.split(",")[0], country.split(",")[0]) for country in countries_list], widget=widgets.Select())
newsletter = BooleanField("'I authorize MLH to send me occasional emails about relevant events, career opportunities, and community announcements.")
agree_coc = BooleanField("I confirm that I have read and agree to the Code of Conduct", validators=[DataRequired()])
logistics = BooleanField("I authorize you to share my application/registration with Major League Hacking for event administration, ranking, and MLH administration in-line with the MLH privacy policy.I further agree to the terms of both the MLH Contest Terms and Conditions and the MLH Privacy Policy.", validators=[DataRequired()])

View file

@ -1,4 +1,4 @@
Worcester Polytechnic Institute
21st Century Cyber Charter School
Aalto University
Aarhus University
@ -2198,7 +2198,6 @@ Woodbridge High School - London
"Woodbridge High School - Woodbridge, NJ"
"Woodbridge High School - Woodbridge, ON"
"Woodbridge High School - Woodbridge, VA"
Worcester Polytechnic Institute
Worcester State University
World Communications Charter School
Wright State University

View file

@ -14,8 +14,22 @@
{% block app_content %}
<div class="card text-center">
<div class="card-body">
<h1 class="h3 mb-3 fw-normal">Registered Users</h1>
<table id="hackers" class="table table-striped">
<div style="display:flex;flex-wrap:nowrap;align-items:center;">
<div class="dropdown">
<a href="#" class="btn btn-primary dropdown-toggle"
data-bs-toggle="dropdown">Search<span
class="caret"></span></a>
<ul class="dropdown-menu">
<input style="padding:5px;margin-left:10px;margin-right:10px;" type="text" id="searchbox" name="searchbox-text" placeholder="Search By Email" onkeyup="filterHackers()"/>
</ul>
</div>
<!-- TODO: get "Registered Users" properly centered -->
<h1 style="flex-grow:1;justify-content:center;" class="h3 mb-3 fw-normal">Registered Users</h1>
</div>
<table id="hackers" class="table table-striped sortable">
<thead>
<tr>
<th>Options</th>
@ -79,16 +93,71 @@
<td>{{ hacker.id }}</td>
<td>{{ hacker.last_login }}</td>
<td>{{ hacker.email }}</td>
<td>{{ hacker.first_name + ' ' + hacker.last_name }}</td>
<td>{{ hacker.phone }}</td>
<td>
<div style="display: flex; justify-content: flex-start;">
<input style="padding:5px; margin-left:10px; margin-right:10px;width:fit-content;max-width:100px;" type="text" id="{{hacker.id}}-namebox-first" placeholder="first_name" value="{{hacker.first_name}}" onchange="updateHacker(this.id, 'first_name', this.value)"/>
<input style="padding:5px; margin-left:10px; margin-right:10px;width:fit-content;max-width:100px;" type="text" id="{{hacker.id}}-namebox-last" placeholder="last_name" value="{{hacker.last_name}}" onchange="updateHacker(this.id, 'last_name', this.value)"/>
</div>
</td>
<td>
<input style="padding:5px; margin-left:10px; margin-right:10px;width:fit-content;max-width:100px;" type="text" id="{{hacker.id}}-phonebox" placeholder="phone" value="{{hacker.phone}}" onchange="updateHacker(this.id, 'phone', this.value)"/>
</td>
<td>{{ hacker.shirt_size }}</td>
<td>{{ hacker.accomodations }}</td>
<td>{{ hacker.school }}</td>
<td>
<input style="padding:5px; margin-left:10px; margin-right:10px;width:fit-content;max-width:75px;" type="text" id="{{hacker.id}}-schoolbox" placeholder="school" value="{{hacker.school}}" onchange="updateHacker(this.id, 'school', this.value)"/>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<script type="text/javascript">
function filterHackers() {
//get hacker table and searchbox info
const input = document.getElementById("searchbox").value.toLowerCase();
const hackertable = document.getElementById("hackers");
let rows = hackertable.getElementsByTagName("tr");
//iterate over all rows
for(let i = 1; i < rows.length; i++) {
//get the email
const cells = rows[i].getElementsByTagName("td");
const emailCell = cells[6];
//if there is an email, display or dont display based on searchbox
if(emailCell) {
const emailText = emailCell.textContent.toLowerCase();
if(!emailText.includes(input)) {
rows[i].style.display = "none";
}
else {
rows[i].style.display = "";
}
}
}
}
function updateHacker(id, change_field, new_val) {
//tell backend to update a specific field for a hacker
const headers = [
["Content-Type", "application/json"],
];
let body = {
"hacker_id": id.substr(0, id.indexOf('-')),
"change_field": change_field,
"new_val": new_val,
}
//send the post request, and report the error if there is one
fetch('/admin/updateHacker', {method: 'POST', body: JSON.stringify(body), headers: headers}).catch((err) => {
window.alert("Error updating user - see console for details");
console.log(err);
})
}
</script>
{% endblock %}

View file

@ -15,12 +15,6 @@
<p class="card-text">Let us know if you have any questions by sending
them to <a href="mailto:hack@wpi.edu">hack@wpi.edu</a></p>
{% if not current_user.waitlisted and config['DISCORD_LINK'] %}
<p>Make sure to join our Discord to get the latest updates!</p>
<button type="button" class="btn btn-primary mb-3"><a href="{{ config['DISCORD_LINK']
}}" class="link-light">Discord</a></button>
{% endif %}
<div class="row center justify-content-center">
<form method="post">
{{ form.csrf_token() }}
@ -39,7 +33,7 @@
</div>
<hr/>
<div class="row center justify-content-center">
<form method="post" action="{{url_for('dashboard.resume')}}"
<form method="post" action={{url_for('dashboard.resume')}}"
enctype="multipart/form-data">
{{ resform.csrf_token() }}
<p><b>If you'd like, add your resume to send to

@ -1 +1 @@
Subproject commit db2a7a865f9b3865fa2180b1b53b1c2d2640be81
Subproject commit 261ffb2bab157162c86f60cdd4b22723565c903e

View file

@ -102,7 +102,7 @@ privacy policy. I further agree to the terms of both the <a
</div>
<div class="form-check mb-3">
{{ form.newsletter }}
Subscribe to the MLH newsletter?
I authorize MLH to send me occasional emails about relevant events, career opportunities, and community announcements.
</div>
{{ render_field(form.submit) }}
</form>