From fd257e701f7948b18e17e46d232077dc605d9a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lera=20Elvo=C3=A9?= Date: Mon, 30 Jun 2025 12:28:10 +0300 Subject: [PATCH] start user page port --- app/__init__.py | 2 +- app/models.py | 36 +++++++++++++++++- app/routes/users.py | 72 ++++++++++++++++++++++++++++++++++- app/templates/base.html | 4 +- app/templates/users/user.html | 35 +++++++++++++++++ 5 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 app/templates/users/user.html diff --git a/app/__init__.py b/app/__init__.py index 8ff3903..5f2f3b2 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -84,6 +84,6 @@ def create_app(): @app.context_processor def inject_auth(): - return {"is_logged_in": is_logged_in, "get_active_user": get_active_user} + return {"is_logged_in": is_logged_in, "get_active_user": get_active_user, "active_user": get_active_user()} return app diff --git a/app/models.py b/app/models.py index 1711d1c..ecdadb1 100644 --- a/app/models.py +++ b/app/models.py @@ -1,4 +1,4 @@ -from .db import Model +from .db import Model, db from .constants import PermissionLevel class Users(Model): @@ -19,6 +19,40 @@ class Users(Model): def is_default_avatar(self): return self.avatar_id == 1 + def get_latest_posts(self): + q = """SELECT + posts.id, posts.created_at, post_history.content, post_history.edited_at, threads.title AS thread_title, topics.name as topic_name, threads.slug as thread_slug + FROM + posts + JOIN + post_history ON posts.current_revision_id = post_history.id + JOIN + threads ON posts.thread_id = threads.id + JOIN + topics ON threads.topic_id = topics.id + WHERE + posts.user_id = ? + ORDER BY posts.created_at DESC + LIMIT 10""" + return db.query(q, self.id) + + def get_post_stats(self): + q = """SELECT + COUNT(posts.id) AS post_count, + COUNT(DISTINCT threads.id) AS thread_count, + MAX(threads.title) FILTER (WHERE threads.created_at = latest.created_at) AS latest_thread_title, + MAX(threads.slug) FILTER (WHERE threads.created_at = latest.created_at) AS latest_thread_slug + FROM users + LEFT JOIN posts ON posts.user_id = users.id + LEFT JOIN threads ON threads.user_id = users.id + LEFT JOIN ( + SELECT user_id, MAX(created_at) AS created_at + FROM threads + GROUP BY user_id + ) latest ON latest.user_id = users.id + WHERE users.id = ?""" + return db.fetch_one(q, self.id) + class Topics(Model): table = "topics" diff --git a/app/routes/users.py b/app/routes/users.py index e83961a..83abbd5 100644 --- a/app/routes/users.py +++ b/app/routes/users.py @@ -1,6 +1,7 @@ from flask import ( - Blueprint, render_template, request, redirect, url_for, flash, session + Blueprint, render_template, request, redirect, url_for, flash, session, current_app ) +from functools import wraps from ..models import Users, Sessions from ..constants import InfoboxKind, PermissionLevel from ..auth import digest, verify @@ -42,12 +43,67 @@ def validate_username(username): return bool(re.fullmatch(pattern, username)) +def redirect_if_logged_in(*args, **kwargs): + def decorator(view_func): + @wraps(view_func) + def wrapper(*view_args, **view_kwargs): + if is_logged_in(): + # resolve callables + processed_kwargs = { + k: v() if callable(v) else v + for k, v in kwargs.items() + } + endpoint = args[0] if args else processed_kwargs.get("endpoint") + if endpoint.startswith("."): + blueprint = current_app.blueprints.get(view_func.__name__.split(".")[0]) + if blueprint: + endpoint = endpoint.lstrip(".") + return redirect(url_for(f"{blueprint.name}.{endpoint}", **processed_kwargs)) + return redirect(url_for(*args, **processed_kwargs)) + return view_func(*view_args, **view_kwargs) + return wrapper + return decorator + + +def login_required(view_func): + @wraps(view_func) + def wrapper(*args, **kwargs): + if not is_logged_in(): + return redirect(url_for("users.log_in")) + return view_func(*args, **kwargs) + return wrapper + + +def mod_only(*args, **kwargs): + def decorator(view_func): + @wraps(view_func) + def wrapper(*view_args, **view_kwargs): + if not get_active_user().is_mod(): + # resolve callables + processed_kwargs = { + k: v() if callable(v) else v + for k, v in kwargs.items() + } + endpoint = args[0] if args else processed_kwargs.get("endpoint") + if endpoint.startswith("."): + blueprint = current_app.blueprints.get(view_func.__name__.split(".")[0]) + if blueprint: + endpoint = endpoint.lstrip(".") + return redirect(url_for(f"{blueprint.name}.{endpoint}", **processed_kwargs)) + return redirect(url_for(*args, **processed_kwargs)) + return view_func(*view_args, **view_kwargs) + return wrapper + return decorator + + @bp.get("/log_in") +@redirect_if_logged_in(".page", username = lambda: get_active_user().username) def log_in(): return render_template("users/log_in.html") @bp.post("/log_in") +@redirect_if_logged_in(".page", username = lambda: get_active_user().username) def log_in_post(): target_user = Users.find({ "username": request.form['username'] @@ -68,11 +124,13 @@ def log_in_post(): @bp.get("/sign_up") +@redirect_if_logged_in(".page", username = lambda: get_active_user().username) def sign_up(): return render_template("users/sign_up.html") @bp.post("/sign_up") +@redirect_if_logged_in(".page", username = lambda: get_active_user().username) def sign_up_post(): username = request.form['username'] password = request.form['password'] @@ -112,19 +170,29 @@ def sign_up_post(): @bp.get("/") def page(username): - return "stub" + target_user = Users.find({"username": username}) + return render_template("users/user.html", target_user = target_user) @bp.get("//setings") +@login_required def settings(username): return "stub" @bp.get("//inbox") +@login_required def inbox(username): return "stub" @bp.get("/list") +@login_required +@mod_only(".page", username = lambda: get_active_user().username) def user_list(): return "stub" + + +@bp.post("/log_out") +def log_out(): + pass diff --git a/app/templates/base.html b/app/templates/base.html index 87787aa..e86a806 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -3,7 +3,7 @@ - {% if title %} + {% if self.title() %} Porom - {% block title %}{% endblock %} {% else %} Porom @@ -21,7 +21,7 @@ {% endwith %} {% block content %}{% endblock %}
- Porom commit + Pyrom commit
diff --git a/app/templates/users/user.html b/app/templates/users/user.html new file mode 100644 index 0000000..a645f85 --- /dev/null +++ b/app/templates/users/user.html @@ -0,0 +1,35 @@ +{% extends 'base.html' %} +{% block title %}{{ target_user.username }}'s profile{% endblock %} +{% block content %} +
+

{{ target_user.username }}'s profile

+ {% if active_user.id == target_user.id %} + + {% if active_user.is_guest() %} +

You are a guest. A Moderator needs to approve your account before you will be able to post.

+ {% endif %} + {% endif %} + {% if active_user and active_user.is_mod() and not target_user.is_system() %} +

Moderation controls

+ {% if target_user.is_guest() %} +

This user is a guest. They signed up on ...

+ {% else %} +

This user signed up on ... and was confirmed on ...

+ {% endif %} + {% endif %} +
+
+ {% with stats = target_user.get_post_stats() %} +
    +
  • Posts created: {{ stats.post_count }}
  • +
  • Threads started: {{ stats.thread_count }}
  • +
  • Latest started thread: {{ stats.latest_thread_title }} +
+ {% endwith %} +
+{% endblock %}