mirror of
https://github.com/Fluffy-Bean/GameExpo23.git
synced 2025-05-14 14:22:16 +00:00
commit
2a1c83a44e
12 changed files with 164 additions and 34 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
Pillow
|
||||||
Gunicorn
|
Gunicorn
|
||||||
psycopg2-binary
|
psycopg2-binary
|
||||||
shortuuid
|
shortuuid
|
||||||
|
|
|
@ -1,11 +1,20 @@
|
||||||
import uuid
|
import uuid
|
||||||
import re
|
import re
|
||||||
|
import os
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
from flask import Blueprint, request, render_template, flash, redirect, url_for
|
from flask import Blueprint, request, render_template, flash, redirect, url_for
|
||||||
from flask_login import login_required, current_user, logout_user
|
from flask_login import login_required, current_user, logout_user
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
|
|
||||||
from .config import USER_REGEX, USER_EMAIL_REGEX
|
from .config import (
|
||||||
|
USER_REGEX,
|
||||||
|
USER_EMAIL_REGEX,
|
||||||
|
UPLOAD_EXTENSIONS,
|
||||||
|
UPLOAD_MAX_SIZE,
|
||||||
|
UPLOAD_DIR,
|
||||||
|
UPLOAD_RESOLUTION,
|
||||||
|
)
|
||||||
from .models import Users, Sessions, Scores, ProfileTags, PasswordReset
|
from .models import Users, Sessions, Scores, ProfileTags, PasswordReset
|
||||||
from .extensions import db
|
from .extensions import db
|
||||||
|
|
||||||
|
@ -29,7 +38,36 @@ def settings():
|
||||||
|
|
||||||
if not check_password_hash(user.password, password):
|
if not check_password_hash(user.password, password):
|
||||||
flash("Password is incorrect!", "error")
|
flash("Password is incorrect!", "error")
|
||||||
return redirect(url_for('account.settings'))
|
return redirect(url_for("account.settings"))
|
||||||
|
|
||||||
|
if "file" in request.files and request.files['file'].filename:
|
||||||
|
picture = request.files["file"]
|
||||||
|
file_ext = picture.filename.split(".")[-1]
|
||||||
|
file_name = f"{user.id}.{file_ext}"
|
||||||
|
|
||||||
|
if file_ext not in UPLOAD_EXTENSIONS:
|
||||||
|
error.append("Picture is not a valid image!")
|
||||||
|
if picture.content_length > UPLOAD_MAX_SIZE:
|
||||||
|
error.append(f"Picture must be less than {UPLOAD_EXTENSIONS / 1000000}MB!")
|
||||||
|
|
||||||
|
image = Image.open(picture.stream)
|
||||||
|
image_x, image_y = image.size
|
||||||
|
image.thumbnail((
|
||||||
|
min(image_x, UPLOAD_RESOLUTION),
|
||||||
|
min(image_y, UPLOAD_RESOLUTION)
|
||||||
|
))
|
||||||
|
|
||||||
|
if error:
|
||||||
|
for err in error:
|
||||||
|
flash(err, "error")
|
||||||
|
return redirect(url_for("account.settings"))
|
||||||
|
|
||||||
|
if user.picture:
|
||||||
|
os.remove(os.path.join(UPLOAD_DIR, user.picture))
|
||||||
|
|
||||||
|
user.picture = file_name
|
||||||
|
image.save(os.path.join(UPLOAD_DIR, file_name))
|
||||||
|
image.close()
|
||||||
|
|
||||||
if username:
|
if username:
|
||||||
if user_regex.match(username):
|
if user_regex.match(username):
|
||||||
|
@ -79,7 +117,9 @@ def password_reset():
|
||||||
if not check_password_hash(user.password, current):
|
if not check_password_hash(user.password, current):
|
||||||
error.append("Current password is incorrect!")
|
error.append("Current password is incorrect!")
|
||||||
if len(new) < 8:
|
if len(new) < 8:
|
||||||
error.append("New password is too short! Must be at least 8 characters long.")
|
error.append(
|
||||||
|
"New password is too short! Must be at least 8 characters long."
|
||||||
|
)
|
||||||
if new != confirm:
|
if new != confirm:
|
||||||
error.append("New passwords do not match!")
|
error.append("New passwords do not match!")
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import re
|
import re
|
||||||
import shortuuid
|
import shortuuid
|
||||||
|
|
||||||
from flask import Blueprint, request, jsonify
|
from flask import Blueprint, request, jsonify, send_from_directory
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
from werkzeug.security import check_password_hash
|
from werkzeug.security import check_password_hash
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
from .models import Scores, Sessions, Users
|
from .models import Scores, Sessions, Users
|
||||||
from .extensions import db
|
from .extensions import db
|
||||||
|
@ -11,12 +12,19 @@ from .config import (
|
||||||
GAME_DIFFICULTIES,
|
GAME_DIFFICULTIES,
|
||||||
MAX_SEARCH_RESULTS,
|
MAX_SEARCH_RESULTS,
|
||||||
USER_REGEX,
|
USER_REGEX,
|
||||||
|
UPLOAD_DIR,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
blueprint = Blueprint("api", __name__, url_prefix="/api")
|
blueprint = Blueprint("api", __name__, url_prefix="/api")
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route("/uploads/<filename>", methods=["GET"])
|
||||||
|
def upload_dir(filename):
|
||||||
|
filename = secure_filename(filename)
|
||||||
|
return send_from_directory(UPLOAD_DIR, filename)
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/tokens", methods=["POST"])
|
@blueprint.route("/tokens", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def tokens():
|
def tokens():
|
||||||
|
@ -58,6 +66,10 @@ def post():
|
||||||
|
|
||||||
if int(difficulty) not in GAME_DIFFICULTIES:
|
if int(difficulty) not in GAME_DIFFICULTIES:
|
||||||
return "Invalid difficulty!"
|
return "Invalid difficulty!"
|
||||||
|
# This is a fix for a bug in the game that we dunno how to actually fix
|
||||||
|
# Yupeeeeeeeeee
|
||||||
|
if score < 10:
|
||||||
|
return "Score is impossible!"
|
||||||
|
|
||||||
session_data = Sessions.query.filter_by(auth_key=session_key).first()
|
session_data = Sessions.query.filter_by(auth_key=session_key).first()
|
||||||
if not session_data:
|
if not session_data:
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
import os
|
from os import getenv
|
||||||
|
|
||||||
|
|
||||||
|
UPLOAD_DIR = "/data/uploads"
|
||||||
|
UPLOAD_EXTENSIONS = ["png", "jpg", "jpeg", "gif"]
|
||||||
|
UPLOAD_RESOLUTION = 512
|
||||||
|
UPLOAD_MAX_SIZE = 3 * 1024 * 1024 # 3MB
|
||||||
|
|
||||||
GAME_VERSION = "alpha"
|
GAME_VERSION = "alpha"
|
||||||
GAME_VERSIONS = ["alpha"]
|
GAME_VERSIONS = ["alpha"]
|
||||||
GAME_DIFFICULTIES = [0, 1, 2, 3, 4]
|
GAME_DIFFICULTIES = [0, 1, 2, 3, 4]
|
||||||
|
@ -12,12 +17,12 @@ MAX_TOP_SCORES = 15
|
||||||
MAX_SEARCH_RESULTS = 5
|
MAX_SEARCH_RESULTS = 5
|
||||||
|
|
||||||
# Postgres
|
# Postgres
|
||||||
SECRET_KEY = os.getenv("FLASK_KEY")
|
SECRET_KEY = getenv("FLASK_KEY")
|
||||||
|
|
||||||
user = os.getenv("DB_USER")
|
user = getenv("DB_USER")
|
||||||
password = os.getenv("DB_PASSWORD")
|
password = getenv("DB_PASSWORD")
|
||||||
host = os.getenv("DB_HOST")
|
host = getenv("DB_HOST")
|
||||||
db = os.getenv("DB_NAME")
|
db = getenv("DB_NAME")
|
||||||
|
|
||||||
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{user}:{password}@{host}:5432/{db}"
|
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{user}:{password}@{host}:5432/{db}"
|
||||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
|
|
BIN
TFR/server/static/images/pfp.png
Normal file
BIN
TFR/server/static/images/pfp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 MiB |
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
background-color: rgba($black, 0.5)
|
background-color: rgba($black, 0.5)
|
||||||
border-radius: 2px
|
border-radius: 2px
|
||||||
border: 1px solid RGBA(var(--white),0.05)
|
border: 1px solid RGBA($white, 0.05)
|
||||||
|
|
||||||
> h2
|
> h2
|
||||||
margin: 0 0 0.2rem 0
|
margin: 0 0 0.2rem 0
|
||||||
|
@ -28,3 +28,59 @@
|
||||||
display: flex
|
display: flex
|
||||||
flex-direction: column
|
flex-direction: column
|
||||||
gap: 0.5rem
|
gap: 0.5rem
|
||||||
|
|
||||||
|
.account-block
|
||||||
|
margin-bottom: 1rem
|
||||||
|
padding: 1rem
|
||||||
|
|
||||||
|
display: flex
|
||||||
|
flex-direction: row
|
||||||
|
gap: 1rem
|
||||||
|
|
||||||
|
background-color: rgba($black, 0.5)
|
||||||
|
border-radius: 2px
|
||||||
|
border: 1px solid RGBA($white, 0.05)
|
||||||
|
|
||||||
|
> img
|
||||||
|
height: 10rem
|
||||||
|
width: 10rem
|
||||||
|
|
||||||
|
background-color: RGBA($white, 0.02)
|
||||||
|
border: 1px solid RGBA($white, 0.1)
|
||||||
|
border-radius: 2px
|
||||||
|
|
||||||
|
object-fit: cover
|
||||||
|
|
||||||
|
.other
|
||||||
|
width: 100%
|
||||||
|
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
justify-content: flex-start
|
||||||
|
|
||||||
|
gap: 0.5rem
|
||||||
|
|
||||||
|
h2
|
||||||
|
margin: 0
|
||||||
|
font-size: 1.3rem
|
||||||
|
color: RGB($white)
|
||||||
|
|
||||||
|
p
|
||||||
|
margin: 0
|
||||||
|
font-size: 1rem
|
||||||
|
color: RGB($white)
|
||||||
|
|
||||||
|
hr
|
||||||
|
margin: 0
|
||||||
|
width: 100%
|
||||||
|
border: 0 solid transparent
|
||||||
|
border-bottom: 1px solid RGBA($white, 0.1)
|
||||||
|
|
||||||
|
@media (max-width: 621px)
|
||||||
|
.account-block
|
||||||
|
flex-direction: column
|
||||||
|
|
||||||
|
> img
|
||||||
|
margin: 0 auto
|
||||||
|
width: 15rem
|
||||||
|
height: 15rem
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
|
|
||||||
> label
|
> label
|
||||||
padding: 0.5rem 0.7rem
|
padding: 0.5rem 0.7rem
|
||||||
min-width: 6rem
|
min-width: 7rem
|
||||||
|
|
||||||
text-decoration: none
|
text-decoration: none
|
||||||
text-align: end
|
text-align: end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.profile-settings
|
.profile-settings
|
||||||
display: flex
|
display: flex
|
||||||
flex-direction: row
|
flex-direction: row
|
||||||
gap: 0.5rem
|
gap: 1rem
|
||||||
|
|
||||||
.picture
|
.picture
|
||||||
margin: 0
|
margin: 0
|
||||||
|
@ -11,6 +11,8 @@
|
||||||
display: flex
|
display: flex
|
||||||
flex-direction: column
|
flex-direction: column
|
||||||
|
|
||||||
|
border: 1px solid RGBA($white, 0.1)
|
||||||
|
|
||||||
> img
|
> img
|
||||||
height: 10rem
|
height: 10rem
|
||||||
width: 10rem
|
width: 10rem
|
||||||
|
@ -59,16 +61,17 @@
|
||||||
|
|
||||||
.other
|
.other
|
||||||
width: 100%
|
width: 100%
|
||||||
height: 100%
|
|
||||||
|
|
||||||
display: flex
|
display: flex
|
||||||
flex-direction: column
|
flex-direction: column
|
||||||
justify-content: flex-start
|
justify-content: space-between
|
||||||
gap: 0.5rem
|
gap: 0.5rem
|
||||||
|
|
||||||
|
> .text-input
|
||||||
|
margin: 0 !important
|
||||||
|
|
||||||
@media (max-width: 621px)
|
@media (max-width: 621px)
|
||||||
.profile-settings
|
.profile-settings
|
||||||
flex-direction: column
|
flex-direction: column
|
||||||
gap: 1rem
|
|
||||||
|
|
||||||
.picture
|
.picture
|
||||||
margin: 0 auto
|
margin: 0 auto
|
||||||
|
|
|
@ -4,20 +4,27 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<h2 style="margin-bottom: 1rem;">Profile Settings</h2>
|
<h2 style="margin-bottom: 1rem;">Profile Settings</h2>
|
||||||
<form action="{{ url_for('account.settings') }}" method="POST">
|
<form action="{{ url_for('account.settings') }}" method="POST" enctype="multipart/form-data">
|
||||||
<div class="profile-settings">
|
<div class="profile-settings">
|
||||||
<div class="picture">
|
<div class="picture">
|
||||||
<img src="{{ url_for('static', filename='images/error/2.jpg') }}" alt="Profile picture">
|
{% if current_user.picture %}
|
||||||
|
<img src="{{ url_for('api.upload_dir', filename=current_user.picture) }}" alt="Profile picture">
|
||||||
|
{% else %}
|
||||||
|
<img src="{{ url_for('static', filename='images/pfp.png') }}" alt="Profile picture">
|
||||||
|
{% endif %}
|
||||||
<label for="profile-picture">Profile Picture</label>
|
<label for="profile-picture">Profile Picture</label>
|
||||||
<input type="file" name="picture" id="profile-picture">
|
<input type="file" name="file" id="profile-picture">
|
||||||
</div>
|
</div>
|
||||||
<div class="other">
|
<div class="other">
|
||||||
{{ text(id="profile-username", name="username", value=current_user.username) }}
|
{{ text(id="profile-username", name="username", value=current_user.username) }}
|
||||||
{{ text(id="profile-email", name="email") }}
|
{{ text(id="profile-email", name="email") }}
|
||||||
{{ text(id="profile-password", name="password", type="password", required=True, minlength=8) }}
|
{{ text(id="profile-password", name="password", type="password", required=True, minlength=8) }}
|
||||||
|
|
||||||
|
<span style="height: 100%"></span>
|
||||||
|
|
||||||
|
<button type="submit" class="button primary">Save changes</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="button primary">Save changes</button>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,20 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if user %}
|
{% if user %}
|
||||||
<div class="block">
|
<div class="account-block">
|
||||||
<h2 style="margin-bottom: 0;">{{ user.username }}</h2>
|
{% if user.picture %}
|
||||||
|
<img src="{{ url_for('api.upload_dir', filename=user.picture) }}" alt="Profile picture">
|
||||||
|
{% else %}
|
||||||
|
<img src="{{ url_for('static', filename='images/pfp.png') }}" alt="Profile picture">
|
||||||
|
{% endif %}
|
||||||
|
<div class="other">
|
||||||
|
<h2>{{ user.username }}</h2>
|
||||||
|
<p>Joined {{ user.joined_at.strftime('%Y-%m-%d') }}</p>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<p>Best score: {{ scores[0].score | format_result }}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -50,7 +62,6 @@
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Time Set</th>
|
<th>Time Set</th>
|
||||||
<th>Submitted</th>
|
<th>Submitted</th>
|
||||||
<!-- <th>Game</th> -->
|
|
||||||
</tr>
|
</tr>
|
||||||
{% for score in scores %}
|
{% for score in scores %}
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -72,7 +83,6 @@
|
||||||
|
|
||||||
<td>{{ score.score | format_result }}</td>
|
<td>{{ score.score | format_result }}</td>
|
||||||
<td>{{ score.scored_at.strftime('%Y-%m-%d') }}</td>
|
<td>{{ score.scored_at.strftime('%Y-%m-%d') }}</td>
|
||||||
<!-- <td>{{ score.version }}</td> -->
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -19,7 +19,7 @@ def index():
|
||||||
scores = db.session.query(Scores).filter_by(difficulty=diff_arg)
|
scores = db.session.query(Scores).filter_by(difficulty=diff_arg)
|
||||||
|
|
||||||
subquery = (
|
subquery = (
|
||||||
db.session.query(Scores.user_id, func.min(Scores.score).label('min'))
|
db.session.query(Scores.user_id, func.min(Scores.score).label("min"))
|
||||||
.group_by(Scores.user_id)
|
.group_by(Scores.user_id)
|
||||||
.subquery()
|
.subquery()
|
||||||
)
|
)
|
||||||
|
@ -28,9 +28,8 @@ def index():
|
||||||
scores = scores.filter_by(version=ver_arg)
|
scores = scores.filter_by(version=ver_arg)
|
||||||
|
|
||||||
if not user_arg:
|
if not user_arg:
|
||||||
scores = (
|
scores = scores.join(subquery, Scores.user_id == subquery.c.user_id).filter(
|
||||||
scores.join(subquery, Scores.user_id == subquery.c.user_id)
|
Scores.score == subquery.c.min
|
||||||
.filter(Scores.score == subquery.c.min)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
user = Users.query.filter_by(username=user_arg).first()
|
user = Users.query.filter_by(username=user_arg).first()
|
||||||
|
@ -42,11 +41,7 @@ def index():
|
||||||
scores = scores.order_by(Scores.score.asc()).limit(MAX_TOP_SCORES).all()
|
scores = scores.order_by(Scores.score.asc()).limit(MAX_TOP_SCORES).all()
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"views/scores.html",
|
"views/scores.html", scores=scores, diff=int(diff_arg), ver=ver_arg, user=user
|
||||||
scores=scores,
|
|
||||||
diff=int(diff_arg),
|
|
||||||
ver=ver_arg,
|
|
||||||
user=user
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ services:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- ./TFR/storage/migrations:/data/migrations
|
- ./TFR/storage/migrations:/data/migrations
|
||||||
|
- ./TFR/storage/uploads:/data/uploads
|
||||||
environment:
|
environment:
|
||||||
FLASK_KEY: ${THE_FRONT_ROOMS_SECRETE_KEY}
|
FLASK_KEY: ${THE_FRONT_ROOMS_SECRETE_KEY}
|
||||||
DB_USER: ${POSTGRES_USER}
|
DB_USER: ${POSTGRES_USER}
|
||||||
|
|
Loading…
Add table
Reference in a new issue