Compare commits

..

No commits in common. "bd556d102b7a0043195225b1a18366395c2eb670" and "2345830074b246af1cebec08a999f9dce5dfd300" have entirely different histories.

8 changed files with 22 additions and 415 deletions

View File

@ -105,11 +105,4 @@ def create_app():
def ts_datetime(ts, format):
return datetime.utcfromtimestamp(ts or int(time.time())).strftime(format)
@app.template_filter("pluralize")
def pluralize(number, singular = "", plural = "s"):
if number == 1:
return singular
return plural
return app

View File

@ -4,9 +4,6 @@ from .constants import PermissionLevel
class Users(Model):
table = "users"
def get_avatar_url(self):
return Avatars.find({"id": self.avatar_id}).file_path
def is_guest(self):
return self.permission == PermissionLevel.GUEST.value
@ -56,18 +53,6 @@ class Users(Model):
WHERE users.id = ?"""
return db.fetch_one(q, self.id)
def get_all_subscriptions(self):
q = """
SELECT threads.title AS thread_title, threads.slug AS thread_slug
FROM
threads
JOIN
subscriptions ON subscriptions.thread_id = threads.id
WHERE
subscriptions.user_id = ?"""
return db.query(q, self.id)
class Topics(Model):
table = "topics"

View File

@ -1,10 +1,9 @@
from flask import (
Blueprint, render_template, request, redirect, url_for, flash
Blueprint, render_template, request, redirect, url_for
)
from .users import login_required, mod_only, get_active_user, is_logged_in
from ..db import db
from ..models import Threads, Topics, Posts, Subscriptions
from ..constants import InfoboxKind
from ..models import Threads, Topics, Posts
from .posts import create_post
from slugify import slugify
import math
@ -40,17 +39,7 @@ def thread(slug):
topic = Topics.find({"id": thread.topic_id})
other_topics = Topics.select()
is_subscribed = False
if is_logged_in():
subscription = Subscriptions.find({
'thread_id': thread.id,
'user_id': get_active_user().id,
})
if subscription:
subscription.update({
'last_seen': int(time.time())
})
is_subscribed = True
#TODO: subscription last seen
return render_template(
"threads/thread.html",
@ -60,7 +49,6 @@ def thread(slug):
posts = posts,
topic = topic,
topics = other_topics,
is_subscribed = is_subscribed,
)
@ -119,94 +107,18 @@ def create_form():
@bp.post("/<slug>/lock")
@login_required
def lock(slug):
user = get_active_user()
thread = Threads.find({'slug': slug})
if not ((thread.user_id == user.id) or user.is_mod()):
return 'no'
target_op = request.form.get('target_op')
thread.update({
'is_locked': target_op
})
return redirect(url_for('.thread', slug=slug))
pass
@bp.post("/<slug>/sticky")
@login_required
@mod_only(".thread", slug = lambda slug: slug)
def sticky(slug):
user = get_active_user()
thread = Threads.find({'slug': slug})
if not ((thread.user_id == user.id) or user.is_mod()):
return 'no'
target_op = request.form.get('target_op')
thread.update({
'is_stickied': target_op
})
return redirect(url_for('.thread', slug=slug))
pass
@bp.post("/<slug>/move")
@login_required
@mod_only(".thread", slug = lambda slug: slug)
def move(slug):
user = get_active_user()
new_topic_id = request.form.get('new_topic_id', default=None)
if new_topic_id is None:
flash('Thread is already in this topic.', InfoboxKind.ERROR)
return redirect(url_for('.thread', slug=slug))
new_topic = Topics.find({
'id': new_topic_id
})
if not new_topic:
return 'no'
thread = Threads.find({
'slug': slug
})
if not thread:
return 'no'
if new_topic.id == thread.topic_id:
flash('Thread is already in this topic.', InfoboxKind.ERROR)
return redirect(url_for('.thread', slug=slug))
old_topic = Topics.find({'id': thread.topic_id})
thread.update({'topic_id': new_topic_id})
flash(f'Topic moved from "{old_topic.name}" to "{new_topic.name}".', InfoboxKind.INFO)
return redirect(url_for('.thread', slug=slug))
@bp.post("/<slug>/subscribe")
@login_required
def subscribe(slug):
user = get_active_user()
thread = Threads.find({'slug': slug})
if not thread:
return 'no'
subscription = Subscriptions.find({
'user_id': user.id,
'thread_id': thread.id,
})
if request.form['subscribe'] == 'subscribe':
if subscription:
subscription.delete()
Subscriptions.create({
'user_id': user.id,
'thread_id': thread.id,
'last_seen': int(time.time()),
})
elif request.form['subscribe'] == 'unsubscribe':
if not subscription:
return 'no'
subscription.delete()
elif request.form['subscribe'] == 'read':
if not subscription:
return 'no'
subscription.update({
'last_seen': int(time.time())
})
last_visible_post = request.form.get('last_visible_post', default=None)
if last_visible_post is not None:
return redirect(url_for('.thread', slug=thread.slug, after=last_visible_post))
else:
return redirect(url_for('users.inbox', username=user.username))
pass

View File

@ -2,8 +2,7 @@ from flask import (
Blueprint, render_template, request, redirect, url_for, flash, session, current_app
)
from functools import wraps
from ..db import db
from ..models import Users, Sessions, Subscriptions
from ..models import Users, Sessions
from ..constants import InfoboxKind, PermissionLevel
from ..auth import digest, verify
import secrets
@ -97,28 +96,6 @@ def mod_only(*args, **kwargs):
return decorator
def admin_only(*args, **kwargs):
def decorator(view_func):
@wraps(view_func)
def wrapper(*view_args, **view_kwargs):
if not get_active_user().is_admin():
# resolve callables
processed_kwargs = {
k: v(**view_kwargs) 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():
@ -203,160 +180,12 @@ def settings(username):
return "stub"
@bp.post("/log_out")
@login_required
def log_out():
user = get_active_user()
session_obj = Sessions.find({"key": session['pyrom_session_key']})
session_obj.delete()
session.clear()
return redirect(url_for(".log_in"))
@bp.post("/confirm_user/<user_id>")
@login_required
@mod_only("topics.all_topics")
def confirm_user(user_id):
target_user = Users.find({"id": user_id})
if not target_user:
return "no"
if int(target_user.permission) > PermissionLevel.GUEST.value:
return "no"
target_user.update({
"permission": PermissionLevel.USER.value,
"confirmed_on": int(time.time()),
})
return redirect(url_for(".page", username=target_user.username))
@bp.post("/mod_user/<user_id>")
@login_required
@admin_only("topics.all_topics")
def mod_user(user_id):
target_user = Users.find({"id": user_id})
if not target_user:
return "no"
if target_user.is_mod():
return "no"
target_user.update({
"permission": PermissionLevel.MODERATOR.value,
})
return redirect(url_for(".page", username=target_user.username))
@bp.post("/demod_user/<user_id>")
@login_required
@admin_only("topics.all_topics")
def demod_user(user_id):
target_user = Users.find({"id": user_id})
if not target_user:
return "no"
if not target_user.is_mod():
return "no"
target_user.update({
"permission": PermissionLevel.USER.value,
})
return redirect(url_for(".page", username=target_user.username))
@bp.post("/guest_user/<user_id>")
@login_required
@admin_only("topics.all_topics")
def guest_user(user_id):
target_user = Users.find({"id": user_id})
if not target_user:
return "no"
if target_user.is_mod():
return "no"
target_user.update({
"permission": PermissionLevel.GUEST.value,
})
return redirect(url_for(".page", username=target_user.username))
@bp.get("/<username>/inbox")
@login_required
def inbox(username):
user = get_active_user()
if username != user.username:
return redirect(url_for(".inbox", username = user.username))
return "stub"
new_posts = []
subscription = Subscriptions.find({"user_id": user.id})
all_subscriptions = None
total_unreads_count = None
if subscription:
all_subscriptions = user.get_all_subscriptions()
q = """
WITH thread_metadata AS (
SELECT
posts.thread_id, threads.slug AS thread_slug, threads.title AS thread_title, COUNT(*) AS unread_count, MAX(posts.created_at) AS newest_post_time
FROM
posts
LEFT JOIN
threads ON threads.id = posts.thread_id
LEFT JOIN
subscriptions ON subscriptions.thread_id = posts.thread_id
WHERE subscriptions.user_id = ? AND posts.created_at > subscriptions.last_seen
GROUP BY posts.thread_id
)
SELECT
tm.thread_id, tm.thread_slug, tm.thread_title, tm.unread_count, tm.newest_post_time,
posts.id, posts.created_at, post_history.content, post_history.edited_at, users.username, users.status, avatars.file_path AS avatar_path, posts.thread_id, users.id AS user_id, post_history.original_markup, users.signature_rendered
FROM
thread_metadata tm
JOIN
posts ON posts.thread_id = tm.thread_id
JOIN
post_history ON posts.current_revision_id = post_history.id
JOIN
users ON posts.user_id = users.id
LEFT JOIN
threads ON threads.id = posts.thread_id
LEFT JOIN
avatars ON users.avatar_id = avatars.id
LEFT JOIN
subscriptions ON subscriptions.thread_id = posts.thread_id
WHERE
subscriptions.user_id = ? AND posts.created_at > subscriptions.last_seen
ORDER BY
tm.newest_post_time DESC, posts.created_at ASC"""
new_posts_raw = db.query(q, user.id, user.id)
current_thread_id = None
current_thread_group = None
total_unreads_count = 0
for row in new_posts_raw:
if row['thread_id'] != current_thread_id:
current_thread_group = {
'thread_id': row['thread_id'],
'thread_title': row['thread_title'],
'unread_count': row['unread_count'],
'thread_slug': row['thread_slug'],
'newest_post_time': row['newest_post_time'],
'posts': [],
}
total_unreads_count += int(row['unread_count'])
new_posts.append(current_thread_group)
current_thread_id = row['thread_id']
current_thread_group['posts'].append({
'id': row['id'],
'created_at': row['created_at'],
'content': row['content'],
'edited_at': row['edited_at'],
'username': row['username'],
'status': row['status'],
'avatar_path': row['avatar_path'],
'thread_id': row['thread_id'],
'user_id': row['user_id'],
'original_markup': row['original_markup'],
'signature_rendered': row['signature_rendered']
})
return render_template("users/inbox.html", new_posts = new_posts, total_unreads_count = total_unreads_count, all_subscriptions = all_subscriptions)
@bp.post("/log_out")
def log_out():
pass

View File

@ -89,7 +89,7 @@
</form>
{% endmacro %}
{% macro full_post(post, render_sig = True, is_latest = False, editing = False, active_user = None, no_reply = false) %}
{% macro full_post(post, render_sig = True, is_latest = False, editing = False, active_user = None) %}
{% set postclass = "post" %}
{% if editing %}
{% set postclass = postclass + " editing" %}

View File

@ -20,21 +20,16 @@
</span>
<div>
{% if can_subscribe %}
<form class="modform" action="{{ url_for('threads.subscribe', slug=thread.slug) }}" method="post">
<input type='hidden' name='last_visible_post' value='{{posts[-1].id}}'>
<input type='hidden' name='subscribe' value='{{ 'unsubscribe' if is_subscribed else 'subscribe' }}'>
<input type='submit' value='{{ 'Unsubscribe' if is_subscribed else 'Subscribe' }}'>
</form>
{% endif %}
{% if can_lock %}
<form class="modform" action="{{ url_for("threads.lock", slug=thread.slug) }}" method="post">
<input type=hidden name='target_op' value="{{ (not thread.is_locked) | int }}">
<input type=hidden value="{{ (not thread.is_locked) | int }}">
<input class="warn" type="submit" value="{{"Unlock thread" if thread.is_locked else "Lock thread"}}">
</form>
{% endif %}
{% if active_user.is_mod() %}
<form class="modform" action="{{ url_for("threads.sticky", slug=thread.slug) }}" method="post">
<input type=hidden name='target_op' value="{{ (not thread.is_stickied) | int }}">
<input type=hidden value="{{ (not thread.is_stickied) | int }}">
<input class="warn" type="submit" value="{{"Unsticky thread" if thread.is_stickied else "Sticky thread"}}">
</form>
<form class="modform" action="{{ url_for("threads.move", slug=thread.slug) }}" method="post">

View File

@ -1,51 +0,0 @@
{% from "common/macros.html" import timestamp, full_post %}
{% extends "base.html" %}
{% block title %}inbox{% endblock %}
{% block content %}
<div class="inbox-container">
{% if all_subscriptions is none %}
You have no subscriptions.<br>
{% else %}
Your subscriptions:
<ul>
{% for sub in all_subscriptions %}
<li>
<a href=" {{ url_for("threads.thread", slug=sub.thread_slug) }} ">{{ sub.thread_title }}</a>
<form class="modform" method="post" action="{{ url_for("threads.subscribe", slug = sub.thread_slug) }}">
<input type="hidden" name="subscribe" value="unsubscribe">
<input class="warn" type="submit" value="Unsubscribe">
</form>
</li>
{% endfor %}
</ul>
{% endif %}
{% if not new_posts %}
You have no unread posts.
{% else %}
You have {{ total_unreads_count }} unread post{{(total_unreads_count | int) | pluralize }}:
{% for thread in new_posts %}
<div class="accordion">
<div class="accordion-header">
<button type="button" class="accordion-toggle"></button>
{% set latest_post_id = thread.posts[-1].id %}
{% set unread_posts_text = " (" + (thread.unread_count | string) + (" unread post" | pluralize) %}
<a class="accordion-title" href="{{ url_for("threads.thread", slug=latest_post_slug, after=latest_post_id, _anchor="post-" + (latest_post_id | string)) }}" title="Jump to latest post">{{thread.thread_title + unread_posts_text}}, latest at {{ timestamp(thread.newest_post_time) }})</a>
<form class="modform" method="post" action="{{ url_for("threads.subscribe", slug = thread.thread_slug) }}">
<input type="hidden" name="subscribe" value="read">
<input type="submit" value="Mark thread as Read">
</form>
<form class="modform" method="post" action="{{ url_for("threads.subscribe", slug = thread.thread_slug) }}">
<input type="hidden" name="subscribe" value="unsubscribe">
<input class="warn" type="submit" value="Unsubscribe">
</form>
</div>
<div class="accordion-content">
{% for post in thread.posts %}
{{ full_post(post, no_reply = true) }}
{% endfor %}
</div>
</div>
{% endfor %}
{% endif %}
</div>
{% endblock %}

View File

@ -19,74 +19,18 @@
<h1 class="thread-title">Moderation controls</h1>
{% if target_user.is_guest() %}
<p>This user is a guest. They signed up on {{ timestamp(target_user['created_at']) }}</p>
<form class="modform" method="post" action="{{ url_for("users.confirm_user", user_id=target_user.id) }}">
<input type="submit" value="Confirm user">
</form>
{% else %}
<p>This user signed up on {{ timestamp(target_user['created_at']) }} and was confirmed on {{ timestamp(target_user['confirmed_on']) }}</p>
{% if (target_user.permission | int) < (active_user.permission | int) %}
<form class="modform" method="post" action="{{ url_for("users.guest_user", user_id=target_user.id) }}">
<input class="warn" type="submit" value="Demote user to guest (soft ban)">
</form>
{% endif %}
{% if active_user.is_admin() and not target_user.is_mod() %}
<form class="modform" method="post" action="{{ url_for("users.mod_user", user_id=target_user.id) }}">
<input class="warn" type="submit" value="Promote user to moderator">
</form>
{% elif target_user.is_mod() and (target_user.permission | int) < (active_user.permission | int) %}
<form class="modform" method="post" action="{{ url_for("users.demod_user", user_id=target_user.id) }}">
<input class="critical" type="submit" value="Demote user to regular user">
</form>
{% endif %}
{% endif %}
{% endif %}
</div>
<div class="user-info">
<div class="user-page-usercard">
<div class="usercard-inner">
<img class="avatar" src="{{ target_user.get_avatar_url() }}">
<strong class="big">{{ target_user.username }}</strong>
{% if target_user.status %}
<em class="user-status">{{ target_user.status }}</em>
{% endif %}
{% if target_user.signature_rendered %}
Signature:
<div>{{ target_user.signature_rendered | safe }}</div>
{% endif %}
</div>
</div>
<div class="user-page-stats">
{% with stats = target_user.get_post_stats() %}
<ul class="user-stats-list">
<li>Permission: {{ target_user.permission }}</li>
<li>Posts created: {{ stats.post_count }}</li>
<li>Threads started: {{ stats.thread_count }}</li>
{% if stats.latest_thread_title %}
<li>Latest started thread: <a href="{{ url_for("threads.thread", slug = stats.latest_thread_slug) }}">{{ stats.latest_thread_title }}</a>
{% endif %}
</ul>
{% endwith %}
Latest posts:
{% with posts = target_user.get_latest_posts() %}
<div class="user-page-posts">
{% for post in posts %}
<div class="post-content-container">
<div class="post-info">
<a href="{{ url_for("threads.thread", slug=post.thread_slug, after=post.id) }}" title="permalink"><i>
{% if (post.edited_at | int) > (post.created_at | int) %}
Edited on {{ timestamp(post.edited_at) }}
{% else %}
Posted on {{ timestamp(post.edited_at) }}
{% endif %}
</i></a>
</div>
<div class="post-content wider user-page-post-preview">
<div class="post-inner">{{ post.content | safe }}</div>
</div>
</div>
{% endfor %}
</div>
{% endwith %}
</div>
<div>
{% with stats = target_user.get_post_stats() %}
<ul>
<li>Posts created: {{ stats.post_count }}</li>
<li>Threads started: {{ stats.thread_count }}</li>
<li>Latest started thread: {{ stats.latest_thread_title }}
</ul>
{% endwith %}
</div>
{% endblock %}