Compare commits
25 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4e90cbe150 | ||
![]() |
605c8b3406 | ||
![]() |
8ab8101214 | ||
![]() |
368f8b4de6 | ||
![]() |
edffbb72fb | ||
![]() |
3ec089c749 | ||
![]() |
8076d8571d | ||
![]() |
fd96be4752 | ||
![]() |
13119288d4 | ||
![]() |
3441982734 | ||
![]() |
2f8998c971 | ||
![]() |
a46a6b2831 | ||
![]() |
44673d1b07 | ||
![]() |
e9446b97c7 | ||
![]() |
0268f70f9b | ||
![]() |
7defb52a56 | ||
![]() |
5a3c43e98b | ||
![]() |
2b0b2912f3 | ||
![]() |
e89b2ab7b4 | ||
![]() |
16f08f7750 | ||
![]() |
d0240d15d8 | ||
![]() |
f5eb90b73d | ||
![]() |
a3c89dd478 | ||
![]() |
7fc06bde11 | ||
![]() |
e28912b997 |
10 changed files with 141 additions and 34 deletions
|
@ -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/"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
#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()
|
||||
|
||||
|
|
|
@ -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()])
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 %}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue