diff options
Diffstat (limited to 'apioforum/forum.py')
-rw-r--r-- | apioforum/forum.py | 262 |
1 files changed, 246 insertions, 16 deletions
diff --git a/apioforum/forum.py b/apioforum/forum.py index 3a5dbe3..87d4022 100644 --- a/apioforum/forum.py +++ b/apioforum/forum.py @@ -1,5 +1,6 @@ # view threads in a forum # currently there is only ever one forum however +# ^ aha we never removed this. we should keep it. it is funny. from flask import ( Blueprint, render_template, request, @@ -8,10 +9,12 @@ from flask import ( 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 .permissions import is_admin from sqlite3 import OperationalError import datetime import math +import functools THREADS_PER_PAGE = 20 @@ -46,13 +49,53 @@ def forum_path(forum_id): ancestors.reverse() return ancestors -@bp.route("/<int:forum_id>") -@bp.route("/<int:forum_id>/page/<int:page>") -def view_forum(forum_id,page=1): +def forum_route(relative_path, pagination=False, **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) + + if pagination: + wrapper = bp.route(path+"/page/<int:page>", **kwargs)(wrapper) + + return decorator + +def requires_permission(permission, login_required=True): + def decorator(f): + @functools.wraps(f) + def wrapper(forum, *args, **kwargs): + if not has_permission(forum['id'],g.user,permission,login_required): + abort(403) + return f(forum, *args, **kwargs) + return wrapper + return decorator + +def requires_bureaucrat(f): + @functools.wraps(f) + @requires_permission("p_view_forum") + def wrapper(forum, *args, **kwargs): + if not is_bureaucrat(forum['id'], g.user): + abort(403) + return f(forum, *args, **kwargs) + return wrapper + + +@forum_route("",pagination=True) +@requires_permission("p_view_forum", login_required=False) +def view_forum(forum,page=1): if page < 1: abort(400) 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, @@ -60,7 +103,8 @@ def view_forum(forum_id,page=1): 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 @@ -68,19 +112,19 @@ def view_forum(forum_id,page=1): ORDER BY threads.updated DESC LIMIT ? OFFSET ?; """,( - forum_id, + forum['id'], THREADS_PER_PAGE, (page-1)*THREADS_PER_PAGE, )).fetchall() # XXX: update this when thread filtering happens - num_threads = db.execute("SELECT count(*) AS count FROM threads WHERE threads.forum = ?",(forum_id,)).fetchone()['count'] + num_threads = db.execute("SELECT count(*) AS count FROM threads WHERE threads.forum = ?",(forum['id'],)).fetchone()['count'] max_pageno = math.ceil(num_threads/THREADS_PER_PAGE) 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: @@ -117,33 +161,43 @@ def view_forum(forum_id,page=1): 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={} a.update(s) if a['updated'] is not None: a['updated'] = datetime.datetime.fromisoformat(a['updated']) - subforums.append(a) + if has_permission(a['id'],g.user,"p_view_forum",login_required=False): + 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, max_pageno=max_pageno, page=page, ) -@bp.route("/<int:forum_id>/create_thread",methods=("GET","POST")) -def create_thread(forum_id): +@forum_route("create_thread",methods=("GET","POST")) +@requires_permission("p_create_threads") +@requires_permission("p_view_forum") +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')) @@ -163,7 +217,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( @@ -177,6 +231,182 @@ 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 'delete_' + config['role'] in request.form: + db.execute( + "DELETE FROM role_config WHERE forum = ? AND role = ?", + (forum['id'],config['role'])) + elif '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"]) +@requires_bureaucrat +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"] + 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 = ? WHERE id = ?", + (name,desc,forum['id'])) + fid = forum['id'] + else: + cur = db.cursor() + cur.execute( + "INSERT INTO forums (name,description,parent) VALUES (?,?,?)", + (name,desc,forum['id'])) + 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") +#def view_unlisted(forum): +# if not is_admin: abort(403) # why doesn't this fucking work +# 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() |