Merge pull request #8 from Fluffy-Bean/beta

Beta
This commit is contained in:
Michał Gdula 2023-06-24 13:55:23 +01:00 committed by GitHub
commit 6472d6969c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 122 additions and 80 deletions

View file

@ -11,3 +11,4 @@ Flask-Caching
libsass-bin libsass-bin
jsmin jsmin
cssmin cssmin
timeago

View file

@ -1,5 +1,3 @@
from random import randint
from flask import Flask, render_template, abort from flask import Flask, render_template, abort
from flask_assets import Bundle from flask_assets import Bundle
from werkzeug.exceptions import HTTPException from werkzeug.exceptions import HTTPException
@ -53,8 +51,6 @@ def error_page(err):
abort(500) abort(500)
return ( return (
render_template( render_template("error.html", error=err.code, msg=err.description),
"error.html", error=err.code, msg=err.description, image=str(randint(1, 3))
),
err.code, err.code,
) )

View file

@ -1,5 +1,4 @@
import uuid import uuid
import re
import os import os
from PIL import Image from PIL import Image
@ -29,9 +28,6 @@ def settings():
username = request.form.get("username", "").strip() username = request.form.get("username", "").strip()
email = request.form.get("email", "").strip() email = request.form.get("email", "").strip()
password = request.form.get("password", "").strip() password = request.form.get("password", "").strip()
user_regex = re.compile(USER_REGEX)
email_regex = re.compile(USER_EMAIL_REGEX)
error = [] error = []
user = Users.query.filter_by(username=current_user.username).first() user = Users.query.filter_by(username=current_user.username).first()
@ -42,7 +38,7 @@ def settings():
if "file" in request.files and request.files['file'].filename: if "file" in request.files and request.files['file'].filename:
picture = request.files["file"] picture = request.files["file"]
file_ext = picture.filename.split(".")[-1] file_ext = picture.filename.split(".")[-1].lower()
file_name = f"{user.id}.{file_ext}" file_name = f"{user.id}.{file_ext}"
if file_ext not in UPLOAD_EXTENSIONS: if file_ext not in UPLOAD_EXTENSIONS:
@ -51,6 +47,9 @@ def settings():
error.append(f"Picture must be less than {UPLOAD_EXTENSIONS / 1000000}MB!") error.append(f"Picture must be less than {UPLOAD_EXTENSIONS / 1000000}MB!")
image = Image.open(picture.stream) image = Image.open(picture.stream)
# Resizing gifs is more work than it's worth
if file_ext != "gif":
image_x, image_y = image.size image_x, image_y = image.size
image.thumbnail(( image.thumbnail((
min(image_x, UPLOAD_RESOLUTION), min(image_x, UPLOAD_RESOLUTION),
@ -66,16 +65,21 @@ def settings():
os.remove(os.path.join(UPLOAD_DIR, user.picture)) os.remove(os.path.join(UPLOAD_DIR, user.picture))
user.picture = file_name user.picture = file_name
if file_ext == "gif":
image.save(os.path.join(UPLOAD_DIR, file_name), save_all=True)
else:
image.save(os.path.join(UPLOAD_DIR, file_name)) image.save(os.path.join(UPLOAD_DIR, file_name))
image.close() image.close()
if username: if username:
if user_regex.match(username): if USER_REGEX.match(username):
user.username = username user.username = username
else: else:
error.append("Username is invalid!") error.append("Username is invalid!")
if email: if email:
if email_regex.match(email): if USER_EMAIL_REGEX.match(email):
user.email = email user.email = email
else: else:
error.append("Email is invalid!") error.append("Email is invalid!")

View file

@ -1,4 +1,3 @@
import re
import shortuuid import shortuuid
from flask import Blueprint, request, jsonify, send_from_directory from flask import Blueprint, request, jsonify, send_from_directory
@ -9,6 +8,9 @@ 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
from .config import ( from .config import (
GAME_VERSION,
GAME_VERSIONS,
GAME_DIFFICULTY,
GAME_DIFFICULTIES, GAME_DIFFICULTIES,
MAX_SEARCH_RESULTS, MAX_SEARCH_RESULTS,
USER_REGEX, USER_REGEX,
@ -49,8 +51,8 @@ def tokens():
@blueprint.route("/post", methods=["POST"]) @blueprint.route("/post", methods=["POST"])
def post(): def post():
session_key = request.form.get("session", "").strip() session_key = request.form.get("session", "").strip()
version = request.form.get("version", "alpha").strip() version = request.form.get("version", GAME_VERSION).strip()
difficulty = request.form.get("difficulty", 0) difficulty = request.form.get("difficulty", GAME_DIFFICULTY)
score = request.form.get("score", 0) score = request.form.get("score", 0)
if not session_key: if not session_key:
@ -66,6 +68,8 @@ def post():
if int(difficulty) not in GAME_DIFFICULTIES: if int(difficulty) not in GAME_DIFFICULTIES:
return "Invalid difficulty!" return "Invalid difficulty!"
if version not in GAME_VERSIONS:
return "Invalid version!"
# This is a fix for a bug in the game that we dunno how to actually fix # This is a fix for a bug in the game that we dunno how to actually fix
# if score < 10: # if score < 10:
# return "Score is impossible!" # return "Score is impossible!"
@ -110,9 +114,8 @@ def login():
username = request.form.get("username", "").strip() username = request.form.get("username", "").strip()
password = request.form.get("password", "").strip() password = request.form.get("password", "").strip()
device = request.form.get("device", "Unknown").strip() device = request.form.get("device", "Unknown").strip()
username_regex = re.compile(USER_REGEX)
if not username or not username_regex.match(username) or not password: if not username or not USER_REGEX.match(username) or not password:
return "Username or Password is incorrect!", 400 return "Username or Password is incorrect!", 400
user = Users.query.filter_by(username=username).first() user = Users.query.filter_by(username=username).first()

View file

@ -1,4 +1,3 @@
import re
import uuid import uuid
from flask import Blueprint, render_template, request, flash, redirect, url_for from flask import Blueprint, render_template, request, flash, redirect, url_for
@ -24,12 +23,10 @@ def register():
username = request.form.get("username", None).strip() username = request.form.get("username", None).strip()
password = request.form.get("password", None).strip() password = request.form.get("password", None).strip()
confirm = request.form.get("confirm", None).strip() confirm = request.form.get("confirm", None).strip()
username_regex = re.compile(USER_REGEX)
error = [] error = []
# Validate the form # Validate the form
if not username or not username_regex.match(username): if not username or not USER_REGEX.match(username):
error.append("Username is invalid! Must be alphanumeric, and can contain ._-") error.append("Username is invalid! Must be alphanumeric, and can contain ._-")
if not password or len(password) < 8: if not password or len(password) < 8:
error.append("Password is too short! Must be at least 8 characters long.") error.append("Password is too short! Must be at least 8 characters long.")
@ -61,11 +58,10 @@ def login():
# Get the form data # Get the form data
username = request.form.get("username", None).strip() username = request.form.get("username", None).strip()
password = request.form.get("password", None).strip() password = request.form.get("password", None).strip()
username_regex = re.compile(USER_REGEX)
error = [] error = []
# Validate the form # Validate the form
if not username or not username_regex.match(username) or not password: if not username or not USER_REGEX.match(username) or not password:
error.append("Username or Password is incorrect!") error.append("Username or Password is incorrect!")
user = Users.query.filter_by(username=username).first() user = Users.query.filter_by(username=username).first()

View file

@ -1,35 +1,41 @@
from os import getenv from os import getenv
import re
SECRET_KEY = getenv("FLASK_KEY")
UPLOAD_DIR = "/data/uploads" UPLOAD_DIR = "/data/uploads"
UPLOAD_EXTENSIONS = ["png", "jpg", "jpeg", "gif"] UPLOAD_EXTENSIONS = ["png", "jpg", "jpeg", "gif", "webp"]
UPLOAD_RESOLUTION = 512 UPLOAD_RESOLUTION = 512
UPLOAD_MAX_SIZE = 3 * 1024 * 1024 # 3MB UPLOAD_MAX_SIZE = 3 * 1024 * 1024 # 3MB
GAME_VERSION = "alpha" GAME_VERSION = "alpha"
GAME_VERSIONS = ["alpha"] GAME_DIFFICULTY = 0
GAME_DIFFICULTIES = [0, 1, 2, 3, 4]
USER_REGEX = r"\b[A-Za-z0-9._-]+\b" GAME_VERSIONS = {
USER_EMAIL_REGEX = r"[^@]+@[^@]+\.[^@]+" "alpha": "Alpha",
"alpha-expo": "Alpha (Expo Build)",
}
GAME_DIFFICULTIES = {
0: "Easy - Level 1",
1: "Easy - Level 2",
2: "Easy - Level 3",
3: "Medium",
4: "Hard",
}
USER_REGEX = re.compile(r"\b[A-Za-z0-9._-]+\b")
USER_EMAIL_REGEX = re.compile(r"[^@]+@[^@]+\.[^@]+")
MAX_TOP_SCORES = 15 MAX_TOP_SCORES = 15
MAX_SEARCH_RESULTS = 5 MAX_SEARCH_RESULTS = 5
# Postgres
SECRET_KEY = getenv("FLASK_KEY")
user = getenv("DB_USER") user = getenv("DB_USER")
password = getenv("DB_PASSWORD") password = getenv("DB_PASSWORD")
host = getenv("DB_HOST") host = getenv("DB_HOST")
db = getenv("DB_NAME") db = getenv("DB_NAME")
port = 5432
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{user}:{password}@{host}:5432/{db}" SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{user}:{password}@{host}:{port}/{db}"
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_POOL_RECYCLE = 621 SQLALCHEMY_POOL_RECYCLE = 621
"""
# SQLite
SECRET_KEY = "dev"
SQLALCHEMY_DATABASE_URI = "sqlite:///tfr.db"
"""

View file

@ -1,4 +1,5 @@
import datetime import datetime
import timeago
from flask import Blueprint from flask import Blueprint
@ -11,3 +12,8 @@ def format_result(dttm):
time = datetime.timedelta(seconds=int(dttm[0])) time = datetime.timedelta(seconds=int(dttm[0]))
microtime = dttm[1][:3] microtime = dttm[1][:3]
return f"{time}:{microtime}" return f"{time}:{microtime}"
@blueprint.app_template_filter()
def timesince(dttm):
return timeago.format(dttm, datetime.datetime.now())

Binary file not shown.

Before

Width:  |  Height:  |  Size: 570 KiB

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 708 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View file

@ -12,6 +12,7 @@
flex-direction: column flex-direction: column
border: 1px solid RGBA($white, 0.1) border: 1px solid RGBA($white, 0.1)
box-sizing: content-box
> img > img
height: 10rem height: 10rem
@ -67,7 +68,11 @@
gap: 0.5rem gap: 0.5rem
> .text-input > .text-input
margin: 0 !important margin: 0
> button
margin: 0 0 0 auto
width: auto
@media (max-width: 621px) @media (max-width: 621px)
.profile-settings .profile-settings
@ -80,3 +85,7 @@
> img > img
height: 13rem height: 13rem
width: 13rem width: 13rem
.other > button
margin: 0
width: 100%

View file

@ -90,9 +90,16 @@ body
padding: 0.5rem padding: 0.5rem
text-align: center text-align: center
&.player > a
color: RGB($secondary) color: RGB($white)
text-shadow: 0 0 5px RGBA($secondary, 0.7) text-decoration: none
&#you
color: RGB($primary)
text-shadow: 0 0 5px RGBA($primary, 0.4)
&:hover
text-decoration: underline
> i > i
padding: 0.25rem padding: 0.25rem

View file

@ -3,6 +3,6 @@
<div class="center-text"> <div class="center-text">
<h2>{{ error }}</h2> <h2>{{ error }}</h2>
<p>{{ msg }}</p> <p>{{ msg }}</p>
<image src="{{ url_for('static', filename='images/error/' + image + '.jpg') }}" alt="Error"> <image src="{{ url_for('static', filename='images/error.png') }}" alt="Error">
</div> </div>
{% endblock %} {% endblock %}

View file

@ -3,6 +3,8 @@
<h2>What is The Front Rooms?</h2> <h2>What is The Front Rooms?</h2>
<p>The Front Rooms is a game based on The Backrooms Genre of games.</p> <p>The Front Rooms is a game based on The Backrooms Genre of games.</p>
<img src="{{ url_for('static', filename='images/controls.png') }}" alt="Drawing of keyboard displaying controls" width="500" height="500">
<h2>Is my data secured?</h2> <h2>Is my data secured?</h2>
<p>Yes, all passwords and emails are hashed and salted, and at no point stored in plain text.</p> <p>Yes, all passwords and emails are hashed and salted, and at no point stored in plain text.</p>
{% endblock %} {% endblock %}

View file

@ -8,9 +8,9 @@
<div class="profile-settings"> <div class="profile-settings">
<div class="picture"> <div class="picture">
{% if current_user.picture %} {% if current_user.picture %}
<img src="{{ url_for('api.upload_dir', filename=current_user.picture) }}" alt="Profile picture"> <img src="{{ url_for('api.upload_dir', filename=current_user.picture) }}" alt="Profile picture" id="picture-preview">
{% else %} {% else %}
<img src="{{ url_for('static', filename='images/pfp.png') }}" alt="Profile picture"> <img src="{{ url_for('static', filename='images/pfp.png') }}" alt="Profile picture" id="picture-preview">
{% endif %} {% endif %}
<label for="profile-picture">Profile Picture</label> <label for="profile-picture">Profile Picture</label>
<input type="file" name="file" id="profile-picture"> <input type="file" name="file" id="profile-picture">
@ -60,9 +60,26 @@
</div> </div>
{% if not current_user.email %}
<script> <script>
{% if not current_user.email %}
addFlashMessage("No Email set. If you loose your account, it will not be possible to recover it!", "error") addFlashMessage("No Email set. If you loose your account, it will not be possible to recover it!", "error")
</script>
{% endif %} {% endif %}
// Adjusted from https://stackoverflow.com/a/3814285/14885829
document.getElementById('profile-picture').onchange = (event) => {
let tgt = event.target || window.event.srcElement,
files = tgt.files;
if (FileReader && files && files.length) {
let fr = new FileReader();
fr.onload = () => {
document.getElementById('picture-preview').src = fr.result;
}
fr.readAsDataURL(files[0]);
}
else {
addFlashMessage("Your browser could not show a preview of your profile picture!", "error")
}
}
</script>
{% endblock %} {% endblock %}

View file

@ -4,24 +4,23 @@
<nav> <nav>
<form method="GET" action="{{ url_for('views.index') }}" class="compact"> <form method="GET" action="{{ url_for('views.index') }}" class="compact">
<select name="diff" class="button"> <select name="diff" class="button">
<option value="0" {% if diff==0 %}selected{% endif %}>Level 1</option> {% for difficulty in config["GAME_DIFFICULTIES"] %}
<option value="1" {% if diff==1 %}selected{% endif %}>Level 2</option> <option value="{{ difficulty }}" {% if diff==difficulty %}selected{% endif %}>
<option value="2" {% if diff==2 %}selected{% endif %}>Level 3</option> {{ config["GAME_DIFFICULTIES"][difficulty] }}
<option value="3" {% if diff==3 %}selected{% endif %}>Normal</option> </option>
<option value="4" {% if diff==4 %}selected{% endif %}>Hard</option> {% endfor %}
</select> </select>
<select name="ver" class="button"> <select name="ver" class="button">
{% for game_version in config["GAME_VERSIONS"] %} {% for version in config["GAME_VERSIONS"] %}
<option <option value="{{ version }}" {% if ver==version %}selected{% endif %}>
value="{{ game_version }}" {{ config["GAME_VERSIONS"][version] }}
{% if ver==game_version %}selected{% endif %} </option>
>{{ game_version }}</option>
{% endfor %} {% endfor %}
</select> </select>
<span class="text-input"> <span class="text-input">
<label for="search">Username</label> <label for="search" style="min-width:auto">Username</label>
<input <input
type="text" type="text"
name="user" name="user"
@ -45,11 +44,8 @@
{% endif %} {% endif %}
<div class="other"> <div class="other">
<h2>{{ user.username }}</h2> <h2>{{ user.username }}</h2>
<p>Joined {{ user.joined_at.strftime('%Y-%m-%d') }}</p>
<hr> <hr>
<p>Joined {{ user.joined_at|timesince }}</p>
<p>Best score: {{ scores[0].score | format_result }}</p>
</div> </div>
</div> </div>
{% endif %} {% endif %}
@ -58,7 +54,7 @@
<div class="table"> <div class="table">
<table> <table>
<tr> <tr>
<th></th> <th>Pos</th>
<th>Name</th> <th>Name</th>
<th>Time Set</th> <th>Time Set</th>
<th>Submitted</th> <th>Submitted</th>
@ -74,14 +70,13 @@
{% else %} {% else %}
<td>{{ loop.index }}</td> <td>{{ loop.index }}</td>
{% endif %} {% endif %}
<td>
{% if score.users.id == current_user.id %} <a href="{{ url_for('views.index', user=score.users.username) }}"
<td class="player">{{ score.users.username }}</td> {% if score.users.id == current_user.id %}id="you"{% endif %}>
{% else %} {{ score.users.username }}
<td>{{ score.users.username }}</td> </a>
{% endif %} </td>
<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>
</tr> </tr>
{% endfor %} {% endfor %}
@ -90,7 +85,7 @@
{% else %} {% else %}
<div class="center-text"> <div class="center-text">
<h2>No scores</h2> <h2>No scores</h2>
<p>Go set some!</p> <p>We searched far and wide, but nothing was found</p>
</div> </div>
{% endif %} {% endif %}
{% endblock %} {% endblock %}