summaryrefslogtreecommitdiffhomepage
path: root/apioforum/forum.py
diff options
context:
space:
mode:
Diffstat (limited to 'apioforum/forum.py')
-rw-r--r--apioforum/forum.py244
1 files changed, 230 insertions, 14 deletions
diff --git a/apioforum/forum.py b/apioforum/forum.py
index e03ca01..cae4eab 100644
--- a/apioforum/forum.py
+++ b/apioforum/forum.py
@@ -3,14 +3,15 @@
from flask import (
Blueprint, render_template, request,
- g, redirect, url_for, flash
+ g, redirect, url_for, flash, abort
)
from .db import get_db
from .mdrender import render
-
+from .roles import get_forum_roles,has_permission,is_bureaucrat,get_user_role, permissions as role_permissions
from sqlite3 import OperationalError
import datetime
+import functools
bp = Blueprint("forum", __name__, url_prefix="/")
@@ -43,10 +44,45 @@ def forum_path(forum_id):
ancestors.reverse()
return ancestors
-@bp.route("/<int:forum_id>")
-def view_forum(forum_id):
+def forum_route(relative_path, **kwargs):
+ def decorator(f):
+ path = "/<int:forum_id>"
+ if relative_path != "":
+ path += "/" + relative_path
+
+ @bp.route(path, **kwargs)
+ @functools.wraps(f)
+ def wrapper(forum_id, *args, **kwargs):
+ db = get_db()
+ forum = db.execute("SELECT * FROM forums WHERE id = ?",
+ (forum_id,)).fetchone()
+ if forum == None:
+ abort(404)
+ return f(forum, *args, **kwargs)
+
+ return decorator
+
+def requires_permission(permission):
+ def decorator(f):
+ @functools.wraps(f)
+ def wrapper(forum, *args, **kwargs):
+ if not has_permission(forum['id'], g.user, permission):
+ abort(403)
+ return f(forum, *args, **kwargs)
+ return wrapper
+ return decorator
+
+def requires_bureaucrat(f):
+ @functools.wraps(f)
+ def wrapper(forum, *args, **kwargs):
+ if not is_bureaucrat(forum['id'], g.user):
+ abort(403)
+ return f(forum, *args, **kwargs)
+ return wrapper
+
+@forum_route("")
+def view_forum(forum):
db = get_db()
- forum = db.execute("SELECT * FROM forums WHERE id = ?",(forum_id,)).fetchone()
threads = db.execute(
"""SELECT
threads.id, threads.title, threads.creator, threads.created,
@@ -54,17 +90,18 @@ def view_forum(forum_id):
most_recent_posts.created as mrp_created,
most_recent_posts.author as mrp_author,
most_recent_posts.id as mrp_id,
- most_recent_posts.content as mrp_content
+ most_recent_posts.content as mrp_content,
+ most_recent_posts.deleted as mrp_deleted
FROM threads
INNER JOIN most_recent_posts ON most_recent_posts.thread = threads.id
INNER JOIN number_of_posts ON number_of_posts.thread = threads.id
WHERE threads.forum = ?
ORDER BY threads.updated DESC;
- """,(forum_id,)).fetchall()
+ """,(forum['id'],)).fetchall()
thread_tags = {}
thread_polls = {}
- avail_tags = get_avail_tags(forum_id)
+ avail_tags = get_avail_tags(forum['id'])
#todo: somehow optimise this
for thread in threads:
@@ -101,10 +138,10 @@ def view_forum(forum_id):
subforums_rows = db.execute("""
SELECT max(threads.updated) as updated, forums.* FROM forums
LEFT OUTER JOIN threads ON threads.forum=forums.id
- WHERE parent = ?
+ WHERE parent = ? AND unlisted = 0
GROUP BY forums.id
ORDER BY name ASC
- """,(forum_id,)).fetchall()
+ """,(forum['id'],)).fetchall()
subforums = []
for s in subforums_rows:
a={}
@@ -112,20 +149,27 @@ def view_forum(forum_id):
if a['updated'] is not None:
a['updated'] = datetime.datetime.fromisoformat(a['updated'])
subforums.append(a)
+
+ bureaucrats = db.execute("""
+ SELECT user FROM role_assignments
+ WHERE role = 'bureaucrat' AND forum = ?
+ """,(forum['id'],)).fetchall()
+ bureaucrats = [b[0] for b in bureaucrats]
return render_template("view_forum.html",
forum=forum,
subforums=subforums,
threads=threads,
thread_tags=thread_tags,
+ bureaucrats=bureaucrats,
thread_polls=thread_polls,
avail_tags=avail_tags,
)
-@bp.route("/<int:forum_id>/create_thread",methods=("GET","POST"))
-def create_thread(forum_id):
+@forum_route("create_thread",methods=("GET","POST"))
+def create_thread(forum):
db = get_db()
- forum = db.execute("SELECT * FROM forums WHERE id = ?",(forum_id,)).fetchone()
+ forum = db.execute("SELECT * FROM forums WHERE id = ?",(forum['id'],)).fetchone()
if forum is None:
flash("that forum doesn't exist")
return redirect(url_for('index'))
@@ -145,7 +189,7 @@ def create_thread(forum_id):
cur = db.cursor()
cur.execute(
"INSERT INTO threads (title,creator,created,updated,forum) VALUES (?,?,current_timestamp,current_timestamp,?);",
- (title,g.user,forum_id)
+ (title,g.user,forum['id'])
)
thread_id = cur.lastrowid
cur.execute(
@@ -159,6 +203,178 @@ def create_thread(forum_id):
return render_template("create_thread.html")
+@forum_route("roles",methods=("GET","POST"))
+@requires_bureaucrat
+def edit_roles(forum):
+ db = get_db()
+ role_configs = db.execute(
+ "SELECT * FROM role_config WHERE forum = ? ORDER BY ID ASC",
+ (forum['id'],)).fetchall()
+
+ if request.method == "POST":
+ for config in role_configs:
+ if 'roleconfig_' + config['role'] in request.form:
+ for p in role_permissions:
+ permission_setting =\
+ f"perm_{config['role']}_{p}" in request.form
+ db.execute(f"""
+ UPDATE role_config SET {p} = ?
+ WHERE forum = ? AND role = ?;
+ """,
+ (permission_setting,forum['id'], config['role']))
+ db.commit()
+ flash('roles sucessfully enroled')
+ return redirect(url_for('forum.view_forum',forum_id=forum['id']))
+
+ role_config_roles = [c['role'] for c in role_configs]
+ other_roles = [role for role in get_forum_roles(forum['id']) if not role in role_config_roles]
+
+ return render_template("edit_permissions.html",
+ forum=forum,
+ role_configs=role_configs,
+ other_roles=other_roles
+ )
+
+@forum_route("roles/new",methods=["POST"])
+def add_role(forum):
+ name = request.form['role'].strip()
+ if not all(c in (" ","-","_") or c.isalnum() for c in name) \
+ or len(name) > 32:
+ flash("role name must contain no special characters")
+ return redirect(url_for('forum.edit_roles',forum_id=forum['id']))
+ if name == "bureaucrat":
+ flash("cannot configure permissions for bureaucrat")
+ return redirect(url_for('forum.edit_roles',forum_id=forum['id']))
+
+ db = get_db()
+
+ existing_config = db.execute("""
+ SELECT * FROM role_config WHERE forum = ? AND role = ?
+ """,(forum['id'],name)).fetchone()
+ if not existing_config:
+ db.execute("INSERT INTO role_config (forum,role) VALUES (?,?)",
+ (forum['id'],name))
+ db.commit()
+ return redirect(url_for('forum.edit_roles',forum_id=forum['id']))
+
+@forum_route("role",methods=["GET","POST"])
+@requires_permission("p_approve")
+def view_user_role(forum):
+ if request.method == "POST":
+ return redirect(url_for( 'forum.edit_user_role',
+ username=request.form['user'],forum_id=forum['id']))
+ else:
+ return render_template("role_assignment.html",forum=forum)
+
+@forum_route("role/<username>",methods=["GET","POST"])
+@requires_permission("p_approve")
+def edit_user_role(forum, username):
+ db = get_db()
+ if request.method == "POST":
+ user = db.execute("SELECT * FROM users WHERE username = ?;",(username,)).fetchone()
+ if user == None:
+ return redirect(url_for('forum.edit_user_role',
+ username=username,forum_id=forum['id']))
+ role = request.form['role']
+ if role not in get_forum_roles(forum['id']) and role != "" and role != "bureaucrat":
+ flash("no such role")
+ return redirect(url_for('forum.edit_user_role',
+ username=username,forum_id=forum['id']))
+ if not is_bureaucrat(forum['id'],g.user) and role != "approved" and role != "":
+ # only bureaucrats can assign arbitrary roles
+ abort(403)
+ existing = db.execute(
+ "SELECT * FROM role_assignments WHERE user = ? AND forum = ?;",
+ (username,forum['id'])).fetchone()
+ if existing:
+ db.execute("DELETE FROM role_assignments WHERE user = ? AND forum = ?;",(username,forum['id']))
+ if role != "":
+ db.execute(
+ "INSERT INTO role_assignments (user,role,forum) VALUES (?,?,?);",
+ (username,role,forum['id']))
+ db.commit()
+ flash("role assigned assignedly")
+ return redirect(url_for('forum.view_forum',forum_id=forum['id']))
+ else:
+ user = db.execute("SELECT * FROM users WHERE username = ?;",(username,)).fetchone()
+ if user == None:
+ return render_template("role_assignment.html",
+ forum=forum,user=username,invalid_user=True)
+ r = db.execute(
+ "SELECT role FROM role_assignments WHERE user = ? AND forum = ?;",
+ (username,forum['id'])).fetchone()
+ if not r:
+ assigned_role = ""
+ else:
+ assigned_role = r[0]
+ role = get_user_role(forum['id'], username)
+ if is_bureaucrat(forum['id'], g.user):
+ roles = get_forum_roles(forum['id'])
+ roles.remove("other")
+ roles.add("bureaucrat")
+ else:
+ roles = ["approved"]
+ return render_template("role_assignment.html",
+ forum=forum,user=username,role=role,
+ assigned_role=assigned_role,forum_roles=roles)
+
+def forum_config_page(forum, create=False):
+ db = get_db()
+ if request.method == "POST":
+ name = request.form["name"]
+ desc = request.form["description"]
+ unlisted = "unlisted" in request.form
+ if len(name) > 100 or len(name.strip()) == 0:
+ flash("invalid name")
+ return redirect(url_for('forum.edit_forum',forum_id=forum['id']))
+ elif len(desc) > 6000:
+ flash("invalid description")
+ return redirect(url_for('forum.edit_forum',forum_id=forum['id']))
+ if not create:
+ db.execute("UPDATE forums SET name = ?, description = ?, unlisted = ? WHERE id = ?",
+ (name,desc,forum['id']))
+ fid = forum['id']
+ else:
+ cur = db.cursor()
+ cur.execute(
+ "INSERT INTO forums (name,description,parent,unlisted) VALUES (?,?,?,?)",
+ (name,desc,forum['id'],unlisted))
+ new = cur.lastrowid
+ # creator becomes bureaucrat of new forum
+ db.execute("INSERT INTO role_assignments (role,user,forum) VALUES (?,?,?)",
+ ("bureaucrat",g.user,new))
+ fid = new
+ db.commit()
+ return redirect(url_for('forum.view_forum',forum_id=fid))
+ else:
+ if create:
+ name = ""
+ desc = ""
+ else:
+ name = forum['name']
+ desc = forum['description']
+ cancel_link = url_for('forum.view_forum',forum_id=forum['id'])
+ return render_template("edit_forum.html",create=create,
+ name=name,description=desc,cancel_link=cancel_link)
+
+@forum_route("edit",methods=["GET","POST"])
+@requires_bureaucrat
+def edit_forum(forum):
+ return forum_config_page(forum)
+
+@forum_route("create",methods=["GET","POST"])
+@requires_permission("p_create_subforum")
+def create_forum(forum):
+ return forum_config_page(forum,create=True)
+
+@forum_route("unlisted")
+@requires_bureaucrat
+def view_unlisted(forum):
+ db = get_db()
+ unlisted = db.execute(
+ "SELECT * FROM forums WHERE unlisted = 1 AND parent = ?",(forum['id'],))
+ return render_template('view_unlisted.html',forum=forum,unlisted=unlisted)
+
@bp.route("/search")
def search():
db = get_db()