add user settings

This commit is contained in:
Lera Elvoé 2025-07-02 02:21:53 +03:00
parent bd556d102b
commit dde1139eed
Signed by: yagich
SSH Key Fingerprint: SHA256:6xjGb6uA7lAVcULa7byPEN//rQ0wPoG+UzYVMfZnbvc
5 changed files with 158 additions and 4 deletions

View File

@ -57,6 +57,9 @@ def create_app():
app.config["SECRET_KEY"] = os.getenv("FLASK_SECRET_KEY") 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) os.makedirs(os.path.dirname(app.config["DB_PATH"]), exist_ok = True)
with app.app_context(): with app.app_context():
from .schema import create as create_tables from .schema import create as create_tables

View File

@ -7,6 +7,9 @@ class Users(Model):
def get_avatar_url(self): def get_avatar_url(self):
return Avatars.find({"id": self.avatar_id}).file_path 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): def is_guest(self):
return self.permission == PermissionLevel.GUEST.value return self.permission == PermissionLevel.GUEST.value

View File

@ -3,16 +3,50 @@ from flask import (
) )
from functools import wraps from functools import wraps
from ..db import db 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 ..constants import InfoboxKind, PermissionLevel
from ..auth import digest, verify from ..auth import digest, verify
from wand.image import Image
from wand.exceptions import WandException
import secrets import secrets
import time import time
import re import re
import os
bp = Blueprint("users", __name__, url_prefix = "/users/") 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(): def is_logged_in():
return "pyrom_session_key" in session return "pyrom_session_key" in session
@ -197,10 +231,92 @@ def page(username):
return render_template("users/user.html", target_user = target_user) return render_template("users/user.html", target_user = target_user)
@bp.get("/<username>/setings") @bp.get("/<username>/settings")
@login_required @login_required
def settings(username): 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('/<username>/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('/<username>/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('/<username>/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") @bp.post("/log_out")

View File

@ -76,7 +76,7 @@
{{babycode_editor_component(ta_name, prefill = prefill)}} {{babycode_editor_component(ta_name, prefill = prefill)}}
{% if not cancel_url %} {% if not cancel_url %}
<span> <span>
<input type="checkbox" id="subscribe" name="subscribe" {{ "checked" if session['subscribe_by_default'] else "" }}> <input type="checkbox" id="subscribe" name="subscribe" {{ "checked" if session.get('subscribe_by_default', default=true) else "" }}>
<label for="subscribe">Subscribe to thread</label> <label for="subscribe">Subscribe to thread</label>
</span> </span>
{% endif %} {% endif %}

View File

@ -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() %}
<div class='darkbg settings-container'>
<h1>User settings</h1>
<form class='avatar-form' method='post' action='{{ url_for('users.set_avatar', username=active_user.username) }}' enctype='multipart/form-data'>
<span>Set avatar (1mb max)</span>
<img src='{{ active_user.get_avatar_url() }}'>
<input id='file' type='file' name='avatar' accept='image/*' required>
<div>
<input type='submit' value='Upload avatar' {{ 'disabled' if disable_avatar else '' }}>
<input type='submit' value='Clear avatar' formaction='{{ url_for('users.clear_avatar', username=active_user.username) }}' formnovalidate {{ 'disabled' if active_user.is_default_avatar() else '' }}>
</div>
</form>
<form method='post'>
<label for='topic_sort_by'>Sort threads by:</label>
<select id='topic_sort_by' name='topic_sort_by'>
<option value='activity' {{ 'selected' if session['sort_by'] == 'activity' else '' }}>Latest activity</option>
<option value='thread' {{ 'selected' if session['sort_by'] == 'thread' else '' }}>Thread creation date</option>
</select>
<label for='status'>Status</label>
<input type='text' id='status' name='status' value='{{ active_user.status }}' maxlength=100 placeholder='Will be shown under your name. Max 100 characters.'>
<label for='babycode-content'>Signature</label>
{{ babycode_editor_component(ta_name='signature', prefill=active_user.signature_original_markup, ta_placeholder='Will be shown under each of your posts', optional=true) }}
<input autocomplete='off' type='checkbox' id='subscribe_by_default' {{ 'checked' if session.get('subscribe_by_default', default=true) else '' }}>
<label for='subscribe_by_default'>Subscribe to thread by default when responding</label><br>
<input type='submit' value='Save settings'>
</form>
</div>
{% endblock %}