From dde1139eed2584105e6f795964e3a9bacaa9979f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lera=20Elvo=C3=A9?= Date: Wed, 2 Jul 2025 02:21:53 +0300 Subject: [PATCH] add user settings --- app/__init__.py | 3 + app/models.py | 3 + app/routes/users.py | 122 +++++++++++++++++++++++++++++- app/templates/common/macros.html | 2 +- app/templates/users/settings.html | 32 ++++++++ 5 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 app/templates/users/settings.html diff --git a/app/__init__.py b/app/__init__.py index 7c38839..598d51b 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -57,6 +57,9 @@ def create_app(): app.config["SECRET_KEY"] = os.getenv("FLASK_SECRET_KEY") + app.config['AVATAR_UPLOAD_PATH'] = 'data/static/avatars/' + app.config['MAX_CONTENT_LENGTH'] = 1000 * 1000 + os.makedirs(os.path.dirname(app.config["DB_PATH"]), exist_ok = True) with app.app_context(): from .schema import create as create_tables diff --git a/app/models.py b/app/models.py index fb27b6c..c92bf7a 100644 --- a/app/models.py +++ b/app/models.py @@ -7,6 +7,9 @@ class Users(Model): def get_avatar_url(self): return Avatars.find({"id": self.avatar_id}).file_path + def is_default_avatar(self): + return int(Avatars.find({'id': self.avatar_id}).id) == 1 + def is_guest(self): return self.permission == PermissionLevel.GUEST.value diff --git a/app/routes/users.py b/app/routes/users.py index 61fccbb..854c6f9 100644 --- a/app/routes/users.py +++ b/app/routes/users.py @@ -3,16 +3,50 @@ from flask import ( ) from functools import wraps from ..db import db -from ..models import Users, Sessions, Subscriptions +from ..lib.babycode import babycode_to_html +from ..models import Users, Sessions, Subscriptions, Avatars from ..constants import InfoboxKind, PermissionLevel from ..auth import digest, verify +from wand.image import Image +from wand.exceptions import WandException import secrets import time import re +import os bp = Blueprint("users", __name__, url_prefix = "/users/") +def validate_and_create_avatar(input_image, filename): + try: + with Image(blob=input_image) as img: + img.strip() + img.gravity = 'center' + + width, height = img.width, img.height + min_dim = min(width, height) + if min_dim > 256: + ratio = 256.0 / min_dim + new_width = int(width * ratio) + new_height = int(height * ratio) + img.resize(new_width, new_height) + + width, height = img.width, img.height + crop_size = min(width, height) + x_offset = (width - crop_size) // 2 + y_offset = (height - crop_size) // 2 + img.crop(left=x_offset, top=y_offset, + width=crop_size, height=crop_size) + + img.resize(256, 256) + img.format = 'webp' + img.compression_quality = 85 + img.save(filename=filename) + return True + except WandException: + return False + + def is_logged_in(): return "pyrom_session_key" in session @@ -197,10 +231,92 @@ def page(username): return render_template("users/user.html", target_user = target_user) -@bp.get("//setings") +@bp.get("//settings") @login_required def settings(username): - return "stub" + target_user = Users.find({'username': username}) + if target_user.id != get_active_user().id: + return redirect('.settings', username = get_active_user().username) + + return render_template('users/settings.html') + + +@bp.post('//settings') +@login_required +def settings_form(username): + # we silently ignore the passed username + # and grab the correct user from the session + user = get_active_user() + topic_sort_by = request.form.get('topic_sort_by', default='activity') + if topic_sort_by == 'activity' or topic_sort_by == 'thread': + sort_by = session['sort_by'] = topic_sort_by + status = request.form.get('status', default="")[:100] + original_sig = request.form.get('signature', default='') + rendered_sig = babycode_to_html(original_sig) + session['subscribe_by_default'] = request.form.get('subscribe_by_default', default='on') == 'on' + + user.update({ + 'status': status, + 'signature_original_markup': original_sig, + 'signature_rendered': rendered_sig, + }) + flash('Settings updated.', InfoboxKind.INFO) + return redirect(url_for('.settings', username=user.username)) + + +@bp.post('//set_avatar') +@login_required +def set_avatar(username): + user = get_active_user() + if user.is_guest(): + return 'no' + if 'avatar' not in request.files: + return 'no!...' + + file = request.files['avatar'] + + if file.filename == '': + return 'no..?' + + file_bytes = file.read() + + now = int(time.time()) + filename = f"u{user.id}d{now}.webp" + output_path = os.path.join(current_app.config['AVATAR_UPLOAD_PATH'], filename) + proxied_filename = f"/static/avatars/{filename}" + res = validate_and_create_avatar(file_bytes, output_path) + if res: + flash('Avatar updated.', InfoboxKind.INFO) + avatar = Avatars.create({ + 'file_path': proxied_filename, + 'uploaded_at': now, + }) + old_avatar = Avatars.find({'id': user.avatar_id}) + user.update({'avatar_id': avatar.id}) + if int(old_avatar.id) != 1: + # delete old avi, but not default + filename = os.path.join(current_app.config['AVATAR_UPLOAD_PATH'], os.path.basename(old_avatar.file_path)) + os.remove(filename) + old_avatar.delete() + return redirect(url_for('.settings', username=user.username)) + else: + return 'uhhhh no' + + +@bp.post('//clear_avatar') +@login_required +def clear_avatar(username): + user = get_active_user() + if user.is_default_avatar(): + return 'no' + + old_avatar = Avatars.find({'id': user.avatar_id}) + user.update({'avatar_id': 1}) + # delete old avi + filename = os.path.join(current_app.config['AVATAR_UPLOAD_PATH'], os.path.basename(old_avatar.file_path)) + os.remove(filename) + old_avatar.delete() + return redirect(url_for('.settings', username=user.username)) @bp.post("/log_out") diff --git a/app/templates/common/macros.html b/app/templates/common/macros.html index 303879c..675d255 100644 --- a/app/templates/common/macros.html +++ b/app/templates/common/macros.html @@ -76,7 +76,7 @@ {{babycode_editor_component(ta_name, prefill = prefill)}} {% if not cancel_url %} - + {% endif %} diff --git a/app/templates/users/settings.html b/app/templates/users/settings.html new file mode 100644 index 0000000..ada346d --- /dev/null +++ b/app/templates/users/settings.html @@ -0,0 +1,32 @@ +{% from 'common/macros.html' import babycode_editor_component %} +{% extends 'base.html' %} +{% block title %}settings{% endblock %} +{% block content %} +{% set disable_avatar = not is_logged_in() %} +
+

User settings

+
+ Set avatar (1mb max) + + +
+ + +
+
+
+ + + + + + {{ babycode_editor_component(ta_name='signature', prefill=active_user.signature_original_markup, ta_placeholder='Will be shown under each of your posts', optional=true) }} + +
+ +
+
+{% endblock %}