Add profile picture uploading

This commit is contained in:
Michał Gdula 2023-06-23 12:35:16 +01:00
parent 361d76f530
commit fdac4dc402
8 changed files with 73 additions and 22 deletions

View file

@ -1,3 +1,4 @@
Pillow
Gunicorn Gunicorn
psycopg2-binary psycopg2-binary
shortuuid shortuuid

View file

@ -1,11 +1,19 @@
import uuid import uuid
import re import re
import os
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_RESOLUTION,
UPLOAD_MAX_SIZE,
UPLOAD_DIR,
)
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 +37,30 @@ 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 is too large! Must be less than {UPLOAD_EXTENSIONS / 1000000}MB!"
)
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
picture.save(os.path.join(UPLOAD_DIR, file_name))
if username: if username:
if user_regex.match(username): if user_regex.match(username):
@ -79,7 +110,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!")

View file

@ -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:

View file

@ -1,6 +1,11 @@
import os from os import getenv
UPLOAD_DIR = "/data/uploads"
UPLOAD_EXTENSIONS = ["png", "jpg", "jpeg", "gif"]
UPLOAD_RESOLUTION = (512, 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View file

@ -4,12 +4,16 @@
{% 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) }}

View file

@ -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
) )

View file

@ -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}