aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--apioforum/__init__.py140
-rw-r--r--apioforum/admin.py8
-rw-r--r--apioforum/auth.py186
-rw-r--r--apioforum/csscolors.py296
-rw-r--r--apioforum/db.py238
-rw-r--r--apioforum/forum.py860
-rw-r--r--apioforum/fuzzy.py50
-rw-r--r--apioforum/mdrender.py62
-rw-r--r--apioforum/permissions.py48
-rw-r--r--apioforum/read.py16
-rw-r--r--apioforum/roles.py154
-rw-r--r--apioforum/static/style.css112
-rw-r--r--apioforum/templates/admin/admin_page.html6
-rw-r--r--apioforum/templates/auth/login.html22
-rw-r--r--apioforum/templates/auth/register.html22
-rw-r--r--apioforum/templates/base.html48
-rw-r--r--apioforum/templates/common.html50
-rw-r--r--apioforum/templates/config_thread.html22
-rw-r--r--apioforum/templates/create_thread.html12
-rw-r--r--apioforum/templates/edit_forum.html6
-rw-r--r--apioforum/templates/search_results.html34
-rw-r--r--apioforum/templates/view_forum.html4
-rw-r--r--apioforum/templates/view_thread.html116
-rw-r--r--apioforum/thread.py746
-rw-r--r--apioforum/user.py122
-rw-r--r--apioforum/util.py18
-rw-r--r--apioforum/webhooks.py342
27 files changed, 1870 insertions, 1870 deletions
diff --git a/apioforum/__init__.py b/apioforum/__init__.py
index a7d70c0..8a917c9 100644
--- a/apioforum/__init__.py
+++ b/apioforum/__init__.py
@@ -6,73 +6,73 @@ from .db import get_db
import os
def create_app():
- app = Flask(__name__, instance_relative_config=True)
- app.config.from_mapping(
- SECRET_KEY="dev",
- DATABASE=os.path.join(app.instance_path, 'database.db'),
- )
- app.config.from_pyfile("config.py",silent=True)
- try:
- os.makedirs(app.instance_path)
- except OSError:
- pass
-
- app.jinja_env.trim_blocks = True
- app.jinja_env.lstrip_blocks = True
-
- from . import db
- db.init_app(app)
- from . import permissions
- permissions.init_app(app)
-
- from . import auth
- app.register_blueprint(auth.bp)
-
- from . import forum
- app.register_blueprint(forum.bp)
-
- from . import thread
- app.register_blueprint(thread.bp)
-
- from . import admin
- app.register_blueprint(admin.bp)
-
- from . import user
- app.register_blueprint(user.bp)
-
- from .fuzzy import fuzzy
- app.jinja_env.filters['fuzzy']=fuzzy
-
- from .util import gen_colour
- app.jinja_env.filters['gen_colour']=gen_colour
-
- @app.context_processor
- def path_for_next():
- p = request.path
- if len(request.query_string) > 0 and not p.startswith("/auth"):
- p += "?" + request.query_string.decode("utf-8")
- return dict(path_for_next=p)
-
- app.jinja_env.globals.update(forum_path=forum.forum_path)
- app.jinja_env.globals.update(post_jump=thread.post_jump)
- from .roles import has_permission, is_bureaucrat, get_user_role
- app.jinja_env.globals.update(
- has_permission=has_permission,
- is_bureaucrat=is_bureaucrat,
- get_user_role=get_user_role)
-
- from .mdrender import render
- @app.template_filter('md')
- def md_render(s):
- return render(s)
-
- @app.errorhandler(404)
- def not_found(e):
- return render_template('err/404.html'), 404
- @app.errorhandler(403)
- def forbidden(e):
- return render_template('err/403.html'), 403
-
- app.add_url_rule("/",endpoint="index")
-
- return app
+ app = Flask(__name__, instance_relative_config=True)
+ app.config.from_mapping(
+ SECRET_KEY="dev",
+ DATABASE=os.path.join(app.instance_path, 'database.db'),
+ )
+ app.config.from_pyfile("config.py",silent=True)
+ try:
+ os.makedirs(app.instance_path)
+ except OSError:
+ pass
+
+ app.jinja_env.trim_blocks = True
+ app.jinja_env.lstrip_blocks = True
+
+ from . import db
+ db.init_app(app)
+ from . import permissions
+ permissions.init_app(app)
+
+ from . import auth
+ app.register_blueprint(auth.bp)
+
+ from . import forum
+ app.register_blueprint(forum.bp)
+
+ from . import thread
+ app.register_blueprint(thread.bp)
+
+ from . import admin
+ app.register_blueprint(admin.bp)
+
+ from . import user
+ app.register_blueprint(user.bp)
+
+ from .fuzzy import fuzzy
+ app.jinja_env.filters['fuzzy']=fuzzy
+
+ from .util import gen_colour
+ app.jinja_env.filters['gen_colour']=gen_colour
+
+ @app.context_processor
+ def path_for_next():
+ p = request.path
+ if len(request.query_string) > 0 and not p.startswith("/auth"):
+ p += "?" + request.query_string.decode("utf-8")
+ return dict(path_for_next=p)
+
+ app.jinja_env.globals.update(forum_path=forum.forum_path)
+ app.jinja_env.globals.update(post_jump=thread.post_jump)
+ from .roles import has_permission, is_bureaucrat, get_user_role
+ app.jinja_env.globals.update(
+ has_permission=has_permission,
+ is_bureaucrat=is_bureaucrat,
+ get_user_role=get_user_role)
+
+ from .mdrender import render
+ @app.template_filter('md')
+ def md_render(s):
+ return render(s)
+
+ @app.errorhandler(404)
+ def not_found(e):
+ return render_template('err/404.html'), 404
+ @app.errorhandler(403)
+ def forbidden(e):
+ return render_template('err/403.html'), 403
+
+ app.add_url_rule("/",endpoint="index")
+
+ return app
diff --git a/apioforum/admin.py b/apioforum/admin.py
index b11b735..f96b0c8 100644
--- a/apioforum/admin.py
+++ b/apioforum/admin.py
@@ -1,5 +1,5 @@
from flask import (
- Blueprint, render_template
+ Blueprint, render_template
)
from .db import get_db
from .permissions import admin_required
@@ -9,6 +9,6 @@ bp = Blueprint("admin",__name__,url_prefix="/admin")
@bp.route("/")
@admin_required
def admin_page():
- db = get_db()
- admins = db.execute("select * from users where admin > 0;").fetchall()
- return render_template("admin/admin_page.html",admins=admins)
+ db = get_db()
+ admins = db.execute("select * from users where admin > 0;").fetchall()
+ return render_template("admin/admin_page.html",admins=admins)
diff --git a/apioforum/auth.py b/apioforum/auth.py
index 8864fc0..93aafdd 100644
--- a/apioforum/auth.py
+++ b/apioforum/auth.py
@@ -1,6 +1,6 @@
from flask import (
- Blueprint, session, request, url_for, render_template, redirect,
- flash, g
+ Blueprint, session, request, url_for, render_template, redirect,
+ flash, g
)
from werkzeug.security import check_password_hash, generate_password_hash
from .db import get_db
@@ -9,121 +9,121 @@ import functools
bp = Blueprint("auth", __name__, url_prefix="/auth")
def get_next():
- return request.args.get('next',url_for('index'))
+ return request.args.get('next',url_for('index'))
@bp.route("/login",methods=('GET','POST'))
def login():
- if request.method == "POST":
- username = request.form["username"]
- password = request.form["password"]
- db = get_db()
- err = None
- user = db.execute(
- "SELECT password FROM users WHERE username = ?;",(username,)
- ).fetchone()
- if not username:
- err = "username required"
- elif not password:
- err = "password required"
- elif user is None or not check_password_hash(user['password'], password):
- err = "invalid login"
+ if request.method == "POST":
+ username = request.form["username"]
+ password = request.form["password"]
+ db = get_db()
+ err = None
+ user = db.execute(
+ "SELECT password FROM users WHERE username = ?;",(username,)
+ ).fetchone()
+ if not username:
+ err = "username required"
+ elif not password:
+ err = "password required"
+ elif user is None or not check_password_hash(user['password'], password):
+ err = "invalid login"
- if err is None:
- session.clear()
- session['user'] = username
- if 'keep_logged_in' in request.form:
- session['keep_logged_in']=True
- session.permanent = True
- flash("logged in successfully")
- return redirect(get_next())
+ if err is None:
+ session.clear()
+ session['user'] = username
+ if 'keep_logged_in' in request.form:
+ session['keep_logged_in']=True
+ session.permanent = True
+ flash("logged in successfully")
+ return redirect(get_next())
- flash(err)
-
- return render_template("auth/login.html")
+ flash(err)
+
+ return render_template("auth/login.html")
@bp.route("/register", methods=("GET","POST"))
def register():
- if request.method == "POST":
- username = request.form["username"]
- password = request.form["password"]
- db = get_db()
- err = None
- if not username:
- err = "Username required"
- elif not password:
- err = "Password required"
- elif db.execute(
- "SELECT 1 FROM users WHERE username = ?;", (username,)
- ).fetchone() is not None:
- err = f"User {username} is already registered."
- elif len(username) > 20:
- err = "username can't be longer than 20 characters"
- elif not username.isalnum():
- err = "username must be alphanumeric"
+ if request.method == "POST":
+ username = request.form["username"]
+ password = request.form["password"]
+ db = get_db()
+ err = None
+ if not username:
+ err = "Username required"
+ elif not password:
+ err = "Password required"
+ elif db.execute(
+ "SELECT 1 FROM users WHERE username = ?;", (username,)
+ ).fetchone() is not None:
+ err = f"User {username} is already registered."
+ elif len(username) > 20:
+ err = "username can't be longer than 20 characters"
+ elif not username.isalnum():
+ err = "username must be alphanumeric"
- if err is None:
- db.execute(
- "INSERT INTO users (username, password, joined) VALUES (?,?,current_timestamp);",
- (username,generate_password_hash(password))
- )
- db.commit()
- session['user'] = username
- if 'keep_logged_in' in request.form:
- session['keep_logged_in'] = True
- session.permanent = True
- flash("successfully created account")
- return redirect(get_next())
+ if err is None:
+ db.execute(
+ "INSERT INTO users (username, password, joined) VALUES (?,?,current_timestamp);",
+ (username,generate_password_hash(password))
+ )
+ db.commit()
+ session['user'] = username
+ if 'keep_logged_in' in request.form:
+ session['keep_logged_in'] = True
+ session.permanent = True
+ flash("successfully created account")
+ return redirect(get_next())
- flash(err)
-
- return render_template("auth/register.html")
+ flash(err)
+
+ return render_template("auth/register.html")
@bp.route("/logout")
def logout():
- session.clear()
- flash("logged out successfully")
- return redirect(get_next())
+ session.clear()
+ flash("logged out successfully")
+ return redirect(get_next())
@bp.before_app_request
def load_user():
- username = session.get("user")
- if session.get("keep_logged_in",False):
- session.permanent = True
- if username is None:
- g.user = None
- g.user_info = None
- else:
- row = get_db().execute(
- "SELECT * FROM users WHERE username = ?;", (username,)
- ).fetchone()
- if row is None:
- g.user = None
- g.user_info = None
- else:
- g.user = row['username']
- g.user_info = row
-
+ username = session.get("user")
+ if session.get("keep_logged_in",False):
+ session.permanent = True
+ if username is None:
+ g.user = None
+ g.user_info = None
+ else:
+ row = get_db().execute(
+ "SELECT * FROM users WHERE username = ?;", (username,)
+ ).fetchone()
+ if row is None:
+ g.user = None
+ g.user_info = None
+ else:
+ g.user = row['username']
+ g.user_info = row
+
def login_required(view):
- @functools.wraps(view)
- def wrapped(**kwargs):
- print(g.user)
- if g.user is None:
- return redirect(url_for("auth.login"))
- return view(**kwargs)
- return wrapped
+ @functools.wraps(view)
+ def wrapped(**kwargs):
+ print(g.user)
+ if g.user is None:
+ return redirect(url_for("auth.login"))
+ return view(**kwargs)
+ return wrapped
@bp.route("/cool")
def cool():
- user = session.get("user")
- if user is None:
- return "you are not logged in"
- else:
- return f"you are logged in as {user}"
+ user = session.get("user")
+ if user is None:
+ return "you are not logged in"
+ else:
+ return f"you are logged in as {user}"
@bp.route("/cooler")
@login_required
def cooler():
- return "bee"
+ return "bee"
diff --git a/apioforum/csscolors.py b/apioforum/csscolors.py
index 25bdfdd..c8c096d 100644
--- a/apioforum/csscolors.py
+++ b/apioforum/csscolors.py
@@ -1,150 +1,150 @@
csscolors = [
- "black",
- "silver",
- "gray",
- "white",
- "maroon",
- "red",
- "purple",
- "fuchsia",
- "green",
- "lime",
- "olive",
- "yellow",
- "navy",
- "blue",
- "teal",
- "aqua",
- "orange",
- "aliceblue",
- "antiquewhite",
- "aquamarine",
- "azure",
- "beige",
- "bisque",
- "blanchedalmond",
- "blueviolet",
- "brown",
- "burlywood",
- "cadetblue",
- "chartreuse",
- "chocolate",
- "coral",
- "cornflowerblue",
- "cornsilk",
- "crimson",
- "cyan",
- "darkblue",
- "darkcyan",
- "darkgoldenrod",
- "darkgray",
- "darkgreen",
- "darkgrey",
- "darkkhaki",
- "darkmagenta",
- "darkolivegreen",
- "darkorange",
- "darkorchid",
- "darkred",
- "darksalmon",
- "darkseagreen",
- "darkslateblue",
- "darkslategray",
- "darkslategrey",
- "darkturquoise",
- "darkviolet",
- "deeppink",
- "deepskyblue",
- "dimgray",
- "dimgrey",
- "dodgerblue",
- "firebrick",
- "floralwhite",
- "forestgreen",
- "gainsboro",
- "ghostwhite",
- "gold",
- "goldenrod",
- "greenyellow",
- "grey",
- "honeydew",
- "hotpink",
- "indianred",
- "indigo",
- "ivory",
- "khaki",
- "lavender",
- "lavenderblush",
- "lawngreen",
- "lemonchiffon",
- "lightblue",
- "lightcoral",
- "lightcyan",
- "lightgoldenrodyellow",
- "lightgray",
- "lightgreen",
- "lightgrey",
- "lightpink",
- "lightsalmon",
- "lightseagreen",
- "lightskyblue",
- "lightslategray",
- "lightslategrey",
- "lightsteelblue",
- "lightyellow",
- "limegreen",
- "linen",
- "magenta",
- "mediumaquamarine",
- "mediumblue",
- "mediumorchid",
- "mediumpurple",
- "mediumseagreen",
- "mediumslateblue",
- "mediumspringgreen",
- "mediumturquoise",
- "mediumvioletred",
- "midnightblue",
- "mintcream",
- "mistyrose",
- "moccasin",
- "navajowhite",
- "oldlace",
- "olivedrab",
- "orangered",
- "orchid",
- "palegoldenrod",
- "palegreen",
- "paleturquoise",
- "palevioletred",
- "papayawhip",
- "peachpuff",
- "peru",
- "pink",
- "plum",
- "powderblue",
- "rosybrown",
- "royalblue",
- "saddlebrown",
- "salmon",
- "sandybrown",
- "seagreen",
- "seashell",
- "sienna",
- "skyblue",
- "slateblue",
- "slategray",
- "slategrey",
- "snow",
- "springgreen",
- "steelblue",
- "tan",
- "thistle",
- "tomato",
- "turquoise",
- "violet",
- "wheat",
- "whitesmoke",
- "yellowgreen",
- "rebeccapurple"
+ "black",
+ "silver",
+ "gray",
+ "white",
+ "maroon",
+ "red",
+ "purple",
+ "fuchsia",
+ "green",
+ "lime",
+ "olive",
+ "yellow",
+ "navy",
+ "blue",
+ "teal",
+ "aqua",
+ "orange",
+ "aliceblue",
+ "antiquewhite",
+ "aquamarine",
+ "azure",
+ "beige",
+ "bisque",
+ "blanchedalmond",
+ "blueviolet",
+ "brown",
+ "burlywood",
+ "cadetblue",
+ "chartreuse",
+ "chocolate",
+ "coral",
+ "cornflowerblue",
+ "cornsilk",
+ "crimson",
+ "cyan",
+ "darkblue",
+ "darkcyan",
+ "darkgoldenrod",
+ "darkgray",
+ "darkgreen",
+ "darkgrey",
+ "darkkhaki",
+ "darkmagenta",
+ "darkolivegreen",
+ "darkorange",
+ "darkorchid",
+ "darkred",
+ "darksalmon",
+ "darkseagreen",
+ "darkslateblue",
+ "darkslategray",
+ "darkslategrey",
+ "darkturquoise",
+ "darkviolet",
+ "deeppink",
+ "deepskyblue",
+ "dimgray",
+ "dimgrey",
+ "dodgerblue",
+ "firebrick",
+ "floralwhite",
+ "forestgreen",
+ "gainsboro",
+ "ghostwhite",
+ "gold",
+ "goldenrod",
+ "greenyellow",
+ "grey",
+ "honeydew",
+ "hotpink",
+ "indianred",
+ "indigo",
+ "ivory",
+ "khaki",
+ "lavender",
+ "lavenderblush",
+ "lawngreen",
+ "lemonchiffon",
+ "lightblue",
+ "lightcoral",
+ "lightcyan",
+ "lightgoldenrodyellow",
+ "lightgray",
+ "lightgreen",
+ "lightgrey",
+ "lightpink",
+ "lightsalmon",
+ "lightseagreen",
+ "lightskyblue",
+ "lightslategray",
+ "lightslategrey",
+ "lightsteelblue",
+ "lightyellow",
+ "limegreen",
+ "linen",
+ "magenta",
+ "mediumaquamarine",
+ "mediumblue",
+ "mediumorchid",
+ "mediumpurple",
+ "mediumseagreen",
+ "mediumslateblue",
+ "mediumspringgreen",
+ "mediumturquoise",
+ "mediumvioletred",
+ "midnightblue",
+ "mintcream",
+ "mistyrose",
+ "moccasin",
+ "navajowhite",
+ "oldlace",
+ "olivedrab",
+ "orangered",
+ "orchid",
+ "palegoldenrod",
+ "palegreen",
+ "paleturquoise",
+ "palevioletred",
+ "papayawhip",
+ "peachpuff",
+ "peru",
+ "pink",
+ "plum",
+ "powderblue",
+ "rosybrown",
+ "royalblue",
+ "saddlebrown",
+ "salmon",
+ "sandybrown",
+ "seagreen",
+ "seashell",
+ "sienna",
+ "skyblue",
+ "slateblue",
+ "slategray",
+ "slategrey",
+ "snow",
+ "springgreen",
+ "steelblue",
+ "tan",
+ "thistle",
+ "tomato",
+ "turquoise",
+ "violet",
+ "wheat",
+ "whitesmoke",
+ "yellowgreen",
+ "rebeccapurple"
]
diff --git a/apioforum/db.py b/apioforum/db.py
index 2d06a13..620749f 100644
--- a/apioforum/db.py
+++ b/apioforum/db.py
@@ -4,40 +4,40 @@ from flask import current_app, g
from flask.cli import with_appcontext
def get_db():
- if 'db' not in g:
- g.db = sqlite3.connect(
- current_app.config['DATABASE'],
- detect_types=sqlite3.PARSE_DECLTYPES
- )
- g.db.row_factory = sqlite3.Row
- g.db.execute("PRAGMA foreign_keys = ON;")
- return g.db
+ if 'db' not in g:
+ g.db = sqlite3.connect(
+ current_app.config['DATABASE'],
+ detect_types=sqlite3.PARSE_DECLTYPES
+ )
+ g.db.row_factory = sqlite3.Row
+ g.db.execute("PRAGMA foreign_keys = ON;")
+ return g.db
def close_db(e=None):
- db = g.pop('db', None)
- if db is not None:
- db.close()
+ db = g.pop('db', None)
+ if db is not None:
+ db.close()
migrations = [
"""
CREATE TABLE users (
- username TEXT PRIMARY KEY,
- password TEXT NOT NULL
+ username TEXT PRIMARY KEY,
+ password TEXT NOT NULL
);""",
"""
CREATE TABLE threads (
- id INTEGER PRIMARY KEY,
- title TEXT NOT NULL,
- creator TEXT NOT NULL REFERENCES users(username),
- created TIMESTAMP NOT NULL,
- updated TIMESTAMP NOT NULL
+ id INTEGER PRIMARY KEY,
+ title TEXT NOT NULL,
+ creator TEXT NOT NULL REFERENCES users(username),
+ created TIMESTAMP NOT NULL,
+ updated TIMESTAMP NOT NULL
);
CREATE TABLE posts (
- id INTEGER PRIMARY KEY,
- content TEXT,
- thread INTEGER NOT NULL REFERENCES threads(id),
- author TEXT NOT NULL REFERENCES users(username),
- created TIMESTAMP NOT NULL
+ id INTEGER PRIMARY KEY,
+ content TEXT,
+ thread INTEGER NOT NULL REFERENCES threads(id),
+ author TEXT NOT NULL REFERENCES users(username),
+ created TIMESTAMP NOT NULL
);
CREATE INDEX posts_thread_idx ON posts (thread);
@@ -48,34 +48,34 @@ ALTER TABLE posts ADD COLUMN updated TIMESTAMP;
""",
"""
CREATE VIRTUAL TABLE posts_fts USING fts5(
- content,
- content=posts,
- content_rowid=id,
- tokenize='porter unicode61 remove_diacritics 2'
+ content,
+ content=posts,
+ content_rowid=id,
+ tokenize='porter unicode61 remove_diacritics 2'
);
INSERT INTO posts_fts (rowid, content) SELECT id, content FROM posts;
CREATE TRIGGER posts_ai AFTER INSERT ON posts BEGIN
- INSERT INTO posts_fts(rowid, content) VALUES (new.id, new.content);
+ INSERT INTO posts_fts(rowid, content) VALUES (new.id, new.content);
END;
CREATE TRIGGER posts_ad AFTER DELETE ON posts BEGIN
- INSERT INTO posts_fts(posts_fts, rowid, content) VALUES('delete', old.id, old.content);
+ INSERT INTO posts_fts(posts_fts, rowid, content) VALUES('delete', old.id, old.content);
END;
CREATE TRIGGER posts_au AFTER UPDATE ON posts BEGIN
- INSERT INTO posts_fts(posts_fts, rowid, content) VALUES('delete', old.id, old.content);
- INSERT INTO posts_fts(rowid, content) VALUES (new.id, new.content);
+ INSERT INTO posts_fts(posts_fts, rowid, content) VALUES('delete', old.id, old.content);
+ INSERT INTO posts_fts(rowid, content) VALUES (new.id, new.content);
END;
""",
"""
CREATE TABLE tags (
- id INTEGER PRIMARY KEY,
- name TEXT NOT NULL,
- text_colour TEXT NOT NULL,
- bg_colour TEXT NOT NULL
+ id INTEGER PRIMARY KEY,
+ name TEXT NOT NULL,
+ text_colour TEXT NOT NULL,
+ bg_colour TEXT NOT NULL
);
CREATE TABLE thread_tags (
- thread INTEGER NOT NULL REFERENCES threads(id),
- tag INTEGER NOT NULL REFERENCES tags(id)
+ thread INTEGER NOT NULL REFERENCES threads(id),
+ tag INTEGER NOT NULL REFERENCES tags(id)
);
""",
"""CREATE INDEX thread_tags_thread ON thread_tags (thread);""",
@@ -86,59 +86,59 @@ ALTER TABLE users ADD COLUMN joined TIMESTAMP;
""",
"""
CREATE TABLE polls (
- id INTEGER PRIMARY KEY,
- title TEXT NOT NULL
+ id INTEGER PRIMARY KEY,
+ title TEXT NOT NULL
);
ALTER TABLE threads ADD COLUMN poll INTEGER REFERENCES polls(id);
CREATE TABLE poll_options (
- poll INTEGER NOT NULL REFERENCES polls(id),
- text TEXT NOT NULL,
- option_idx INTEGER NOT NULL,
- PRIMARY KEY ( poll, option_idx )
+ poll INTEGER NOT NULL REFERENCES polls(id),
+ text TEXT NOT NULL,
+ option_idx INTEGER NOT NULL,
+ PRIMARY KEY ( poll, option_idx )
);
CREATE TABLE votes (
- id INTEGER PRIMARY KEY,
- user TEXT NOT NULL REFERENCES users(username),
- poll INTEGER NOT NULL,
- option_idx INTEGER,
- time TIMESTAMP NOT NULL,
- current INTEGER NOT NULL,
- is_retraction INTEGER,
- CHECK (is_retraction OR (option_idx NOT NULL)),
- FOREIGN KEY ( poll, option_idx ) REFERENCES poll_options(poll, option_idx)
+ id INTEGER PRIMARY KEY,
+ user TEXT NOT NULL REFERENCES users(username),
+ poll INTEGER NOT NULL,
+ option_idx INTEGER,
+ time TIMESTAMP NOT NULL,
+ current INTEGER NOT NULL,
+ is_retraction INTEGER,
+ CHECK (is_retraction OR (option_idx NOT NULL)),
+ FOREIGN KEY ( poll, option_idx ) REFERENCES poll_options(poll, option_idx)
);
ALTER TABLE posts ADD COLUMN vote INTEGER REFERENCES votes(id);
""",
"""
CREATE VIEW vote_counts AS
- SELECT poll, option_idx, count(*) AS num FROM votes WHERE current GROUP BY option_idx,poll;
+ SELECT poll, option_idx, count(*) AS num FROM votes WHERE current GROUP BY option_idx,poll;
""",
"""
CREATE TABLE forums (
- id INTEGER PRIMARY KEY,
- name TEXT NOT NULL,
- parent INTEGER REFERENCES forums(id),
- description TEXT
+ id INTEGER PRIMARY KEY,
+ name TEXT NOT NULL,
+ parent INTEGER REFERENCES forums(id),
+ description TEXT
);
INSERT INTO forums (name,parent,description) values ('apioforum',null,
- 'welcome to the apioforum\n\n' ||
- 'forum rules: do not be a bad person. do not do bad things.');
+ 'welcome to the apioforum\n\n' ||
+ 'forum rules: do not be a bad person. do not do bad things.');
PRAGMA foreign_keys = off;
BEGIN TRANSACTION;
CREATE TABLE threads_new (
- id INTEGER PRIMARY KEY,
- title TEXT NOT NULL,
- creator TEXT NOT NULL,
- created TIMESTAMP NOT NULL,
- updated TIMESTAMP NOT NULL,
- forum NOT NULL REFERENCES forums(id),
- poll INTEGER REFERENCES polls(id)
+ id INTEGER PRIMARY KEY,
+ title TEXT NOT NULL,
+ creator TEXT NOT NULL,
+ created TIMESTAMP NOT NULL,
+ updated TIMESTAMP NOT NULL,
+ forum NOT NULL REFERENCES forums(id),
+ poll INTEGER REFERENCES polls(id)
);
INSERT INTO threads_new (id,title,creator,created,updated,forum)
- SELECT id,title,creator,created,updated,1 FROM threads;
+ SELECT id,title,creator,created,updated,1 FROM threads;
DROP TABLE threads;
ALTER TABLE threads_new RENAME TO threads;
COMMIT;
@@ -146,14 +146,14 @@ PRAGMA foreign_keys = on;
""",
"""
CREATE VIEW most_recent_posts AS
- SELECT max(id), * FROM posts GROUP BY thread;
+ SELECT max(id), * FROM posts GROUP BY thread;
CREATE VIEW number_of_posts AS
- SELECT thread, count(*) AS num_replies FROM posts GROUP BY thread;
+ SELECT thread, count(*) AS num_replies FROM posts GROUP BY thread;
""",
"""
CREATE VIEW total_vote_counts AS
- SELECT poll, count(*) AS total_votes FROM votes WHERE current AND NOT is_retraction GROUP BY poll;
+ SELECT poll, count(*) AS total_votes FROM votes WHERE current AND NOT is_retraction GROUP BY poll;
""",
"""
PRAGMA foreign_keys = off;
@@ -173,19 +173,19 @@ PRAGMA foreign_keys = on;
""",
"""
CREATE TABLE role_config (
- role TEXT NOT NULL,
- forum NOT NULL REFERENCES forums(id),
- id INTEGER PRIMARY KEY,
+ role TEXT NOT NULL,
+ forum NOT NULL REFERENCES forums(id),
+ id INTEGER PRIMARY KEY,
- p_create_threads INT NOT NULL DEFAULT 1,
- p_reply_threads INT NOT NULL DEFAULT 1,
- p_view_threads INT NOT NULL DEFAULT 1,
- p_manage_threads INT NOT NULL DEFAULT 0,
- p_delete_posts INT NOT NULL DEFAULT 0,
- p_vote INT NOT NULL DEFAULT 1,
- p_create_polls INT NOT NULL DEFAULT 1,
- p_approve INT NOT NULL DEFAULT 0,
- p_create_subforum INT NOT NULL DEFAULT 0
+ p_create_threads INT NOT NULL DEFAULT 1,
+ p_reply_threads INT NOT NULL DEFAULT 1,
+ p_view_threads INT NOT NULL DEFAULT 1,
+ p_manage_threads INT NOT NULL DEFAULT 0,
+ p_delete_posts INT NOT NULL DEFAULT 0,
+ p_vote INT NOT NULL DEFAULT 1,
+ p_create_polls INT NOT NULL DEFAULT 1,
+ p_approve INT NOT NULL DEFAULT 0,
+ p_create_subforum INT NOT NULL DEFAULT 0
);
INSERT INTO role_config (role,forum) VALUES ("approved",1);
@@ -193,9 +193,9 @@ INSERT INTO role_config (role,forum) VALUES ("other",1);
""",
"""
CREATE TABLE role_assignments (
- user NOT NULL REFERENCES users(username),
- forum NOT NULL REFERENCES forums(id),
- role TEXT NOT NULL
+ user NOT NULL REFERENCES users(username),
+ forum NOT NULL REFERENCES forums(id),
+ role TEXT NOT NULL
);
""",
"""
@@ -209,36 +209,36 @@ ALTER TABLE role_config ADD COLUMN p_view_forum INT NOT NULL DEFAULT 1;
""",
"""
CREATE TABLE webhooks (
- id INTEGER PRIMARY KEY,
- type TEXT NOT NULL,
- url TEXT NOT NULL,
- forum INTEGER NOT NULL REFERENCES forums(id)
+ id INTEGER PRIMARY KEY,
+ type TEXT NOT NULL,
+ url TEXT NOT NULL,
+ forum INTEGER NOT NULL REFERENCES forums(id)
);""",
"""
CREATE VIEW public_forums AS
- SELECT f.id as id,
- COALESCE(r.p_view_forum,1) as public
- FROM forums f
- LEFT JOIN role_config r ON
- r.forum = f.id AND r.role='other';
+ SELECT f.id as id,
+ COALESCE(r.p_view_forum,1) as public
+ FROM forums f
+ LEFT JOIN role_config r ON
+ r.forum = f.id AND r.role='other';
CREATE VIEW forum_thread_of_post AS
- SELECT p.id as p_id, t.id as t_id, f.id as f_id
- FROM posts p
- JOIN threads t on p.thread = t.id
- JOIN forums f on t.forum = f.id;
+ SELECT p.id as p_id, t.id as t_id, f.id as f_id
+ FROM posts p
+ JOIN threads t on p.thread = t.id
+ JOIN forums f on t.forum = f.id;
CREATE VIEW public_posts AS
- SELECT p.id AS id,
- b.public AS public
- FROM posts p
- JOIN forum_thread_of_post h ON p.id=h.p_id
- JOIN public_forums b ON b.id=h.f_id;
+ SELECT p.id AS id,
+ b.public AS public
+ FROM posts p
+ JOIN forum_thread_of_post h ON p.id=h.p_id
+ JOIN public_forums b ON b.id=h.f_id;
""",
"""
CREATE TABLE read (
- user NOT NULL REFERENCES users(username),
- forum REFERENCES forums(id),
- thread REFERENCES threads(id),
- time TIMESTAMP NOT NULL
+ user NOT NULL REFERENCES users(username),
+ forum REFERENCES forums(id),
+ thread REFERENCES threads(id),
+ time TIMESTAMP NOT NULL
);
ALTER TABLE forums ADD COLUMN updated TIMESTAMP;
""",
@@ -246,22 +246,22 @@ ALTER TABLE forums ADD COLUMN updated TIMESTAMP;
def init_db():
- db = get_db()
- version = db.execute("PRAGMA user_version;").fetchone()[0]
- for i in range(version, len(migrations)):
- db.executescript(migrations[i])
- db.execute(f"PRAGMA user_version = {i+1}")
- db.commit()
- click.echo(f"migration {i}")
+ db = get_db()
+ version = db.execute("PRAGMA user_version;").fetchone()[0]
+ for i in range(version, len(migrations)):
+ db.executescript(migrations[i])
+ db.execute(f"PRAGMA user_version = {i+1}")
+ db.commit()
+ click.echo(f"migration {i}")
@click.command("migrate")
@with_appcontext
def migrate_command():
- """update database scheme etc"""
- init_db()
- click.echo("ok")
+ """update database scheme etc"""
+ init_db()
+ click.echo("ok")
def init_app(app):
- app.teardown_appcontext(close_db)
- app.cli.add_command(migrate_command)
+ app.teardown_appcontext(close_db)
+ app.cli.add_command(migrate_command)
diff --git a/apioforum/forum.py b/apioforum/forum.py
index b86fcc9..ce90853 100644
--- a/apioforum/forum.py
+++ b/apioforum/forum.py
@@ -3,8 +3,8 @@
# ^ aha we never removed this. we should keep it. it is funny.
from flask import (
- Blueprint, render_template, request,
- g, redirect, url_for, flash, abort
+ Blueprint, render_template, request,
+ g, redirect, url_for, flash, abort
)
from .db import get_db
@@ -23,484 +23,484 @@ bp = Blueprint("forum", __name__, url_prefix="/")
@bp.route("/")
def not_actual_index():
- return redirect("/1")
+ return redirect("/1")
def get_avail_tags(forum_id):
- db = get_db()
- tags = db.execute("""
- WITH RECURSIVE fs AS
- (SELECT * FROM forums WHERE id = ?
- UNION ALL
- SELECT forums.* FROM forums, fs WHERE fs.parent=forums.id)
- SELECT * FROM tags
- WHERE tags.forum in (SELECT id FROM fs)
- ORDER BY id;
- """,(forum_id,)).fetchall()
- return tags
+ db = get_db()
+ tags = db.execute("""
+ WITH RECURSIVE fs AS
+ (SELECT * FROM forums WHERE id = ?
+ UNION ALL
+ SELECT forums.* FROM forums, fs WHERE fs.parent=forums.id)
+ SELECT * FROM tags
+ WHERE tags.forum in (SELECT id FROM fs)
+ ORDER BY id;
+ """,(forum_id,)).fetchall()
+ return tags
def forum_path(forum_id):
- db = get_db()
- ancestors = db.execute("""
- WITH RECURSIVE fs AS
- (SELECT * FROM forums WHERE id = ?
- UNION ALL
- SELECT forums.* FROM forums, fs WHERE fs.parent=forums.id)
- SELECT * FROM fs;
- """,(forum_id,)).fetchall()
- ancestors.reverse()
- return ancestors
+ db = get_db()
+ ancestors = db.execute("""
+ WITH RECURSIVE fs AS
+ (SELECT * FROM forums WHERE id = ?
+ UNION ALL
+ SELECT forums.* FROM forums, fs WHERE fs.parent=forums.id)
+ SELECT * FROM fs;
+ """,(forum_id,)).fetchall()
+ ancestors.reverse()
+ return ancestors
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 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 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
+ @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
def set_updated(forum_id):
- db = get_db()
- ancestors = db.execute("""
- WITH RECURSIVE fs AS
- (SELECT * FROM forums WHERE id = ?
- UNION ALL
- SELECT forums.* FROM forums, fs WHERE fs.parent=forums.id)
- SELECT * FROM fs;
- """,(forum_id,)).fetchall()
- for f in ancestors:
- db.execute("UPDATE forums SET updated = current_timestamp WHERE id = ?;", (f['id'],))
- db.commit()
+ db = get_db()
+ ancestors = db.execute("""
+ WITH RECURSIVE fs AS
+ (SELECT * FROM forums WHERE id = ?
+ UNION ALL
+ SELECT forums.* FROM forums, fs WHERE fs.parent=forums.id)
+ SELECT * FROM fs;
+ """,(forum_id,)).fetchall()
+ for f in ancestors:
+ db.execute("UPDATE forums SET updated = current_timestamp WHERE id = ?;", (f['id'],))
+ db.commit()
@forum_route("",pagination=True)
@requires_permission("p_view_forum", login_required=False)
def view_forum(forum,page=1):
- db = get_db()
-
- sortby = request.args.get("sortby","ad")
- try:
- sortby_dir = {'d':'DESC','a':'ASC'}[sortby[1]]
- sortby_by = {'a':'threads.updated','c':'threads.created'}[sortby[0]]
- except KeyError:
- return redirect(url_for('forum.view_forum',forum_id=forum['id']))
-
- avail_tags = get_avail_tags(forum['id'])
-
- tagfilter = request.args.get("tagfilter",None)
- if tagfilter == "":
- tagfilter = None
- tagfilter_clause = ""
- tagfilter_tag = None
- if tagfilter is not None:
- try:
- tagfilter = int(tagfilter)
- except ValueError:
- flash(f'invalid tag id "{tagfilter}"')
- return redirect(url_for('forum.view_forum',forum_id=forum['id']))
- else:
- # there is no risk of sql injection because
- # we just checked it is an int
- tagfilter_clause = f"AND thread_tags.tag = {tagfilter}"
- for the_tag in avail_tags:
- if the_tag['id'] == tagfilter:
- tagfilter_tag = the_tag
- break
- else:
- flash("that tag doesn't exist or isn't available here")
- return redirect(url_for('forum.view_forum',forum_id=forum['id']))
-
-
- threads = db.execute(
- f"""SELECT
- threads.id, threads.title, threads.creator, threads.created,
- threads.updated, threads.poll, number_of_posts.num_replies,
- 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.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
- LEFT OUTER JOIN thread_tags ON threads.id = thread_tags.thread
- WHERE threads.forum = ? {tagfilter_clause}
- GROUP BY threads.id
- ORDER BY {sortby_by} {sortby_dir}
- LIMIT ? OFFSET ?;
- """,(
- forum['id'],
- THREADS_PER_PAGE,
- (page-1)*THREADS_PER_PAGE,
- )).fetchall()
-
- num_threads = db.execute(f"""
- SELECT count(*) AS count FROM threads
- LEFT OUTER JOIN thread_tags ON threads.id = thread_tags.thread
- WHERE threads.forum = ? {tagfilter_clause};
- """,(forum['id'],)).fetchone()['count']
- max_pageno = math.ceil(num_threads/THREADS_PER_PAGE)
- if page < 1:
- abort(404)
- elif page > max_pageno and (max_pageno > 0 or page != 1):
- abort(404)
-
- thread_tags = {}
- thread_polls = {}
-
-
- #todo: somehow optimise this
- for thread in threads:
- thread_tags[thread['id']] = db.execute(
- """SELECT tags.* FROM tags
- INNER JOIN thread_tags ON thread_tags.tag = tags.id
- WHERE thread_tags.thread = ?
- ORDER BY tags.id;
- """,(thread['id'],)).fetchall()
-
- if thread['poll'] is not None:
- # todo: make this not be duplicated from thread.py
- poll_row= db.execute("""
- SELECT polls.*,total_vote_counts.total_votes FROM polls
- LEFT OUTER JOIN total_vote_counts ON polls.id = total_vote_counts.poll
- WHERE polls.id = ?;
- """,(thread['poll'],)).fetchone()
- options = db.execute("""
- SELECT poll_options.*, vote_counts.num
- FROM poll_options
- LEFT OUTER JOIN vote_counts ON poll_options.poll = vote_counts.poll
- AND poll_options.option_idx = vote_counts.option_idx
- WHERE poll_options.poll = ?
- ORDER BY option_idx asc;
- """,(poll_row['id'],)).fetchall()
-
- poll = {}
- poll.update(poll_row)
- poll['options'] = options
- poll['total_votes']=poll['total_votes'] or 0
- thread_polls[thread['id']]=poll
-
-
- subforums_rows = db.execute("""
- SELECT max(threads.updated) as updated, forums.* FROM forums
- LEFT OUTER JOIN threads ON threads.forum=forums.id
- WHERE parent = ? AND unlisted = 0
- GROUP BY forums.id
- ORDER BY name ASC
- """,(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'])
- 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]
-
-
- if g.user != None:
- db.execute("DELETE FROM read WHERE user = ? AND forum = ?;", (g.user, forum['id']))
- db.execute("INSERT INTO read (user,forum,time) VALUES (?,?,current_timestamp);", (g.user, forum['id']))
- db.commit()
-
-
- 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,
- current_sortby=sortby,
- tagfilter_tag=tagfilter_tag,
- is_read=read.is_read,
- )
+ db = get_db()
+
+ sortby = request.args.get("sortby","ad")
+ try:
+ sortby_dir = {'d':'DESC','a':'ASC'}[sortby[1]]
+ sortby_by = {'a':'threads.updated','c':'threads.created'}[sortby[0]]
+ except KeyError:
+ return redirect(url_for('forum.view_forum',forum_id=forum['id']))
+
+ avail_tags = get_avail_tags(forum['id'])
+
+ tagfilter = request.args.get("tagfilter",None)
+ if tagfilter == "":
+ tagfilter = None
+ tagfilter_clause = ""
+ tagfilter_tag = None
+ if tagfilter is not None:
+ try:
+ tagfilter = int(tagfilter)
+ except ValueError:
+ flash(f'invalid tag id "{tagfilter}"')
+ return redirect(url_for('forum.view_forum',forum_id=forum['id']))
+ else:
+ # there is no risk of sql injection because
+ # we just checked it is an int
+ tagfilter_clause = f"AND thread_tags.tag = {tagfilter}"
+ for the_tag in avail_tags:
+ if the_tag['id'] == tagfilter:
+ tagfilter_tag = the_tag
+ break
+ else:
+ flash("that tag doesn't exist or isn't available here")
+ return redirect(url_for('forum.view_forum',forum_id=forum['id']))
+
+
+ threads = db.execute(
+ f"""SELECT
+ threads.id, threads.title, threads.creator, threads.created,
+ threads.updated, threads.poll, number_of_posts.num_replies,
+ 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.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
+ LEFT OUTER JOIN thread_tags ON threads.id = thread_tags.thread
+ WHERE threads.forum = ? {tagfilter_clause}
+ GROUP BY threads.id
+ ORDER BY {sortby_by} {sortby_dir}
+ LIMIT ? OFFSET ?;
+ """,(
+ forum['id'],
+ THREADS_PER_PAGE,
+ (page-1)*THREADS_PER_PAGE,
+ )).fetchall()
+
+ num_threads = db.execute(f"""
+ SELECT count(*) AS count FROM threads
+ LEFT OUTER JOIN thread_tags ON threads.id = thread_tags.thread
+ WHERE threads.forum = ? {tagfilter_clause};
+ """,(forum['id'],)).fetchone()['count']
+ max_pageno = math.ceil(num_threads/THREADS_PER_PAGE)
+ if page < 1:
+ abort(404)
+ elif page > max_pageno and (max_pageno > 0 or page != 1):
+ abort(404)
+
+ thread_tags = {}
+ thread_polls = {}
+
+
+ #todo: somehow optimise this
+ for thread in threads:
+ thread_tags[thread['id']] = db.execute(
+ """SELECT tags.* FROM tags
+ INNER JOIN thread_tags ON thread_tags.tag = tags.id
+ WHERE thread_tags.thread = ?
+ ORDER BY tags.id;
+ """,(thread['id'],)).fetchall()
+
+ if thread['poll'] is not None:
+ # todo: make this not be duplicated from thread.py
+ poll_row= db.execute("""
+ SELECT polls.*,total_vote_counts.total_votes FROM polls
+ LEFT OUTER JOIN total_vote_counts ON polls.id = total_vote_counts.poll
+ WHERE polls.id = ?;
+ """,(thread['poll'],)).fetchone()
+ options = db.execute("""
+ SELECT poll_options.*, vote_counts.num
+ FROM poll_options
+ LEFT OUTER JOIN vote_counts ON poll_options.poll = vote_counts.poll
+ AND poll_options.option_idx = vote_counts.option_idx
+ WHERE poll_options.poll = ?
+ ORDER BY option_idx asc;
+ """,(poll_row['id'],)).fetchall()
+
+ poll = {}
+ poll.update(poll_row)
+ poll['options'] = options
+ poll['total_votes']=poll['total_votes'] or 0
+ thread_polls[thread['id']]=poll
+
+
+ subforums_rows = db.execute("""
+ SELECT max(threads.updated) as updated, forums.* FROM forums
+ LEFT OUTER JOIN threads ON threads.forum=forums.id
+ WHERE parent = ? AND unlisted = 0
+ GROUP BY forums.id
+ ORDER BY name ASC
+ """,(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'])
+ 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]
+
+
+ if g.user != None:
+ db.execute("DELETE FROM read WHERE user = ? AND forum = ?;", (g.user, forum['id']))
+ db.execute("INSERT INTO read (user,forum,time) VALUES (?,?,current_timestamp);", (g.user, forum['id']))
+ db.commit()
+
+
+ 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,
+ current_sortby=sortby,
+ tagfilter_tag=tagfilter_tag,
+ is_read=read.is_read,
+ )
@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()
- if forum is None:
- flash("that forum doesn't exist")
- return redirect(url_for('index'))
-
- if g.user is None:
- flash("you need to be logged in to create a thread")
- return redirect(url_for('index'))
-
- if request.method == "POST":
- title = request.form['title']
- content = request.form['content']
- err = None
- if len(title.strip()) == 0 or len(content.strip()) == 0:
- err = "title and content can't be empty"
-
- if err is None:
- cur = db.cursor()
- cur.execute(
- "INSERT INTO threads (title,creator,created,updated,forum) VALUES (?,?,current_timestamp,current_timestamp,?);",
- (title,g.user,forum['id'])
- )
- thread_id = cur.lastrowid
- cur.execute(
- "INSERT INTO posts (thread,created,author,content) VALUES (?,current_timestamp,?,?);",
- (thread_id,g.user,content)
- )
- db.commit()
- set_updated(forum['id'])
-
- from . import webhooks
- thread = db.execute("select * from threads where id = ?",(thread_id,)).fetchone()
- webhooks.do_webhooks_thread(forum['id'],thread)
- return redirect(url_for('thread.view_thread',thread_id=thread_id))
- flash(err)
-
-
- return render_template("create_thread.html")
+ db = get_db()
+ 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'))
+
+ if g.user is None:
+ flash("you need to be logged in to create a thread")
+ return redirect(url_for('index'))
+
+ if request.method == "POST":
+ title = request.form['title']
+ content = request.form['content']
+ err = None
+ if len(title.strip()) == 0 or len(content.strip()) == 0:
+ err = "title and content can't be empty"
+
+ if err is None:
+ cur = db.cursor()
+ cur.execute(
+ "INSERT INTO threads (title,creator,created,updated,forum) VALUES (?,?,current_timestamp,current_timestamp,?);",
+ (title,g.user,forum['id'])
+ )
+ thread_id = cur.lastrowid
+ cur.execute(
+ "INSERT INTO posts (thread,created,author,content) VALUES (?,current_timestamp,?,?);",
+ (thread_id,g.user,content)
+ )
+ db.commit()
+ set_updated(forum['id'])
+
+ from . import webhooks
+ thread = db.execute("select * from threads where id = ?",(thread_id,)).fetchone()
+ webhooks.do_webhooks_thread(forum['id'],thread)
+ return redirect(url_for('thread.view_thread',thread_id=thread_id))
+ flash(err)
+
+
+ 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
- )
+ 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']))
+ 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)
+ 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)
+ 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)
+ 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)
+ 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)
+ 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)
+# 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()
- query = request.args["q"]
- try:
- results = db.execute("""
- SELECT posts.id, highlight(posts_fts, 0, '<mark>', '</mark>') AS
- content, posts.thread, posts.author, posts.created, posts.edited,
- posts.updated, threads.title AS thread_title
- FROM posts_fts
- JOIN posts ON posts_fts.rowid = posts.id
- JOIN threads ON threads.id = posts.thread
- JOIN public_posts ON public_posts.id = posts.id
- WHERE posts_fts MATCH ? AND public_posts.public
- ORDER BY rank
- LIMIT 50
- """, (query,)).fetchall()
- except OperationalError as e:
- print(e)
- flash('your search query was malformed.')
- return redirect(url_for("forum.not_actual_index"))
-
- display_thread_id = [ True ] * len(results)
- last_thread = None
- for ix, result in enumerate(results):
- if result["thread"] == last_thread:
- display_thread_id[ix] = False
- last_thread = result["thread"]
- return render_template("search_results.html", results=results, query=query, display_thread_id=display_thread_id)
+ db = get_db()
+ query = request.args["q"]
+ try:
+ results = db.execute("""
+ SELECT posts.id, highlight(posts_fts, 0, '<mark>', '</mark>') AS
+ content, posts.thread, posts.author, posts.created, posts.edited,
+ posts.updated, threads.title AS thread_title
+ FROM posts_fts
+ JOIN posts ON posts_fts.rowid = posts.id
+ JOIN threads ON threads.id = posts.thread
+ JOIN public_posts ON public_posts.id = posts.id
+ WHERE posts_fts MATCH ? AND public_posts.public
+ ORDER BY rank
+ LIMIT 50
+ """, (query,)).fetchall()
+ except OperationalError as e:
+ print(e)
+ flash('your search query was malformed.')
+ return redirect(url_for("forum.not_actual_index"))
+
+ display_thread_id = [ True ] * len(results)
+ last_thread = None
+ for ix, result in enumerate(results):
+ if result["thread"] == last_thread:
+ display_thread_id[ix] = False
+ last_thread = result["thread"]
+ return render_template("search_results.html", results=results, query=query, display_thread_id=display_thread_id)
diff --git a/apioforum/fuzzy.py b/apioforum/fuzzy.py
index 8396b8f..6edb649 100644
--- a/apioforum/fuzzy.py
+++ b/apioforum/fuzzy.py
@@ -1,35 +1,35 @@
# fuzzy datetime things
units = (
- ("y", "year","years",365*24*60*60), # leap years aren't real
- ("d", "day","days",24*60*60),
- ("h", "hour","hours",60*60),
- ("m", "minute","minutes",60),
- ("s", "second","seconds",1),
+ ("y", "year","years",365*24*60*60), # leap years aren't real
+ ("d", "day","days",24*60*60),
+ ("h", "hour","hours",60*60),
+ ("m", "minute","minutes",60),
+ ("s", "second","seconds",1),
)
from datetime import datetime, timedelta, timezone
def fuzzy(seconds, ago=False):
- if isinstance(seconds, timedelta):
- seconds = seconds.total_seconds()
- elif isinstance(seconds, datetime):
- seconds = (seconds.replace(tzinfo=timezone.utc) - datetime.now(tz=timezone.utc)).total_seconds()
+ if isinstance(seconds, timedelta):
+ seconds = seconds.total_seconds()
+ elif isinstance(seconds, datetime):
+ seconds = (seconds.replace(tzinfo=timezone.utc) - datetime.now(tz=timezone.utc)).total_seconds()
- components_used = 0
- fmt = "{}"
- buf = ""
- if ago:
- fmt = "in {}" if seconds > 0 else "{} ago"
- elif seconds > 0: fmt = "in {}"
- seconds = abs(seconds)
- for short, _, _, unit_length in units:
- if seconds >= unit_length:
- components_used += 1
- qty = seconds // unit_length
- buf += str(int(qty)) + short
- seconds -= qty * unit_length
- if components_used == 2: break
- if not buf: return "now"
+ components_used = 0
+ fmt = "{}"
+ buf = ""
+ if ago:
+ fmt = "in {}" if seconds > 0 else "{} ago"
+ elif seconds > 0: fmt = "in {}"
+ seconds = abs(seconds)
+ for short, _, _, unit_length in units:
+ if seconds >= unit_length:
+ components_used += 1
+ qty = seconds // unit_length
+ buf += str(int(qty)) + short
+ seconds -= qty * unit_length
+ if components_used == 2: break
+ if not buf: return "now"
- return fmt.format(buf)
+ return fmt.format(buf)
diff --git a/apioforum/mdrender.py b/apioforum/mdrender.py
index d9b8ea1..5a50661 100644
--- a/apioforum/mdrender.py
+++ b/apioforum/mdrender.py
@@ -2,26 +2,26 @@ import bleach
from .csscolors import csscolors
allowed_tags = [
- 'p',
- 'h1',
- 'h2',
- 'h3',
- 'h4',
- 'h5',
- 'h6',
- 'pre',
- 'del',
- 'ins',
- 'mark',
- 'img',
- 'marquee',
- 'pulsate',
- 'sup','sub',
- 'table','thead','tbody','tr','th','td',
- 'details','summary',
- 'hr',
- 'br',
-
+ 'p',
+ 'h1',
+ 'h2',
+ 'h3',
+ 'h4',
+ 'h5',
+ 'h6',
+ 'pre',
+ 'del',
+ 'ins',
+ 'mark',
+ 'img',
+ 'marquee',
+ 'pulsate',
+ 'sup','sub',
+ 'table','thead','tbody','tr','th','td',
+ 'details','summary',
+ 'hr',
+ 'br',
+
]
@@ -31,9 +31,9 @@ allowed_tags += ("mark" + c for c in csscolors)
allowed_attributes = bleach.sanitizer.ALLOWED_ATTRIBUTES.copy()
allowed_attributes.update(
- img=['src','alt','title'],
- ol=['start'],
- details=['open'],
+ img=['src','alt','title'],
+ ol=['start'],
+ details=['open'],
)
allowed_tags.extend(bleach.sanitizer.ALLOWED_TAGS)
@@ -42,14 +42,14 @@ cleaner = bleach.sanitizer.Cleaner(tags=allowed_tags,attributes=allowed_attribut
import markdown
md = markdown.Markdown(extensions=[
- 'pymdownx.tilde',
- 'pymdownx.caret',
- 'fenced_code',
- 'tables',
- 'pymdownx.details',
+ 'pymdownx.tilde',
+ 'pymdownx.caret',
+ 'fenced_code',
+ 'tables',
+ 'pymdownx.details',
])
def render(text):
- text = md.reset().convert(text)
- text = cleaner.clean(text)
- return text
+ text = md.reset().convert(text)
+ text = cleaner.clean(text)
+ return text
diff --git a/apioforum/permissions.py b/apioforum/permissions.py
index 816936c..f705d08 100644
--- a/apioforum/permissions.py
+++ b/apioforum/permissions.py
@@ -1,5 +1,5 @@
from flask import (
- g, redirect, url_for, flash
+ g, redirect, url_for, flash
)
import functools
import click
@@ -7,37 +7,37 @@ from flask.cli import with_appcontext
from .db import get_db
def is_admin():
- if g.user_info is None:
- return False
- else:
- return g.user_info['admin'] > 0
+ if g.user_info is None:
+ return False
+ else:
+ return g.user_info['admin'] > 0
def admin_required(view):
- @functools.wraps(view)
- def wrapped(**kwargs):
- if is_admin():
- return view(**kwargs)
- else:
- flash("you must be an admin to do that")
- return redirect(url_for("index"))
- return wrapped
+ @functools.wraps(view)
+ def wrapped(**kwargs):
+ if is_admin():
+ return view(**kwargs)
+ else:
+ flash("you must be an admin to do that")
+ return redirect(url_for("index"))
+ return wrapped
@click.command("make_admin")
@click.argument("username")
@with_appcontext
def make_admin(username):
- """makes a user an admin user"""
- db = get_db()
- cur = db.cursor()
- cur.execute("UPDATE users SET admin = 1 WHERE username = ?",(username,))
- if cur.rowcount == 0:
- click.echo("no such user found")
- else:
- click.echo("ok")
- db.commit()
+ """makes a user an admin user"""
+ db = get_db()
+ cur = db.cursor()
+ cur.execute("UPDATE users SET admin = 1 WHERE username = ?",(username,))
+ if cur.rowcount == 0:
+ click.echo("no such user found")
+ else:
+ click.echo("ok")
+ db.commit()
def init_app(app):
- app.cli.add_command(make_admin)
- app.context_processor(lambda: dict(is_admin=is_admin()))
+ app.cli.add_command(make_admin)
+ app.context_processor(lambda: dict(is_admin=is_admin()))
diff --git a/apioforum/read.py b/apioforum/read.py
index 289b40e..b09a1b2 100644
--- a/apioforum/read.py
+++ b/apioforum/read.py
@@ -2,11 +2,11 @@ from flask import g
from .db import get_db
def is_read(type, id):
- if g.user == None:
- return False
- db = get_db()
- read = db.execute("SELECT * FROM read WHERE user = ? AND " + type + " = ?;", (g.user, id)).fetchone()
- if read == None:
- return False
- updated = db.execute("SELECT * FROM " + type + "s WHERE id = ?;", (id,)).fetchone()['updated']
- return updated == None or read['time'] >= updated
+ if g.user == None:
+ return False
+ db = get_db()
+ read = db.execute("SELECT * FROM read WHERE user = ? AND " + type + " = ?;", (g.user, id)).fetchone()
+ if read == None:
+ return False
+ updated = db.execute("SELECT * FROM " + type + "s WHERE id = ?;", (id,)).fetchone()['updated']
+ return updated == None or read['time'] >= updated
diff --git a/apioforum/roles.py b/apioforum/roles.py
index aa1d239..42ce21c 100644
--- a/apioforum/roles.py
+++ b/apioforum/roles.py
@@ -3,95 +3,95 @@ from .db import get_db
from .permissions import is_admin
permissions = [
- "p_create_threads",
- "p_reply_threads",
- "p_manage_threads",
- "p_delete_posts",
- "p_view_threads",
- "p_vote",
- "p_create_polls",
- "p_approve",
- "p_create_subforum",
- "p_view_forum"
+ "p_create_threads",
+ "p_reply_threads",
+ "p_manage_threads",
+ "p_delete_posts",
+ "p_view_threads",
+ "p_vote",
+ "p_create_polls",
+ "p_approve",
+ "p_create_subforum",
+ "p_view_forum"
]
def get_role_config(forum_id, role):
- db = get_db()
+ db = get_db()
- fid = forum_id
- the = None
- while the == None and fid != None:
- the = db.execute("""
- SELECT * FROM role_config
- WHERE forum = ? AND role = ?;
- """, (fid,role)).fetchone()
- fid = db.execute("""
- SELECT * FROM forums WHERE id = ?
- """,(fid,)).fetchone()['parent']
- if the == None:
- if role == "other":
- raise(RuntimeError(
- "unable to find permissions for role 'other', " +
- "which should have associated permissions in all contexts."))
- else:
- return get_role_config(forum_id, "other")
- return the
+ fid = forum_id
+ the = None
+ while the == None and fid != None:
+ the = db.execute("""
+ SELECT * FROM role_config
+ WHERE forum = ? AND role = ?;
+ """, (fid,role)).fetchone()
+ fid = db.execute("""
+ SELECT * FROM forums WHERE id = ?
+ """,(fid,)).fetchone()['parent']
+ if the == None:
+ if role == "other":
+ raise(RuntimeError(
+ "unable to find permissions for role 'other', " +
+ "which should have associated permissions in all contexts."))
+ else:
+ return get_role_config(forum_id, "other")
+ return the
def get_user_role(forum_id, username):
- db = get_db()
- user = db.execute('SELECT * FROM users WHERE username = ?',
- (username,)).fetchone()
- if user == None: return "other"
- if user['admin']: return "bureaucrat"
-
- fid = forum_id
- the = None
- while fid != None:
- r = db.execute("""
- SELECT * FROM role_assignments
- WHERE forum = ? AND user = ?;
- """,(fid,username)).fetchone()
- # the user's role is equal to the role assignnment of the closest
- # ancestor unless the user's role is "bureaucrat" in any ancestor
- # in which case, the users role is "bureaucrat"
- if the == None or (r and r['role'] == "bureaucrat"):
- the = r
- fid = db.execute("""
- SELECT * FROM forums WHERE id = ?
- """,(fid,)).fetchone()['parent']
- return the['role'] if the != None else 'other'
+ db = get_db()
+ user = db.execute('SELECT * FROM users WHERE username = ?',
+ (username,)).fetchone()
+ if user == None: return "other"
+ if user['admin']: return "bureaucrat"
+
+ fid = forum_id
+ the = None
+ while fid != None:
+ r = db.execute("""
+ SELECT * FROM role_assignments
+ WHERE forum = ? AND user = ?;
+ """,(fid,username)).fetchone()
+ # the user's role is equal to the role assignnment of the closest
+ # ancestor unless the user's role is "bureaucrat" in any ancestor
+ # in which case, the users role is "bureaucrat"
+ if the == None or (r and r['role'] == "bureaucrat"):
+ the = r
+ fid = db.execute("""
+ SELECT * FROM forums WHERE id = ?
+ """,(fid,)).fetchone()['parent']
+ return the['role'] if the != None else 'other'
def get_forum_roles(forum_id):
- db = get_db()
+ db = get_db()
- ancestors = db.execute("""
- WITH RECURSIVE fs AS
- (SELECT * FROM forums WHERE id = ?
- UNION ALL
- SELECT forums.* FROM forums, fs WHERE fs.parent=forums.id)
- SELECT * FROM fs;
- """,(forum_id,)).fetchall()
- configs = []
- for a in ancestors:
- configs += db.execute("""
- SELECT * FROM role_config WHERE forum = ?
- """,(a['id'],)).fetchall()
- return set(r['role'] for r in configs)
+ ancestors = db.execute("""
+ WITH RECURSIVE fs AS
+ (SELECT * FROM forums WHERE id = ?
+ UNION ALL
+ SELECT forums.* FROM forums, fs WHERE fs.parent=forums.id)
+ SELECT * FROM fs;
+ """,(forum_id,)).fetchall()
+ configs = []
+ for a in ancestors:
+ configs += db.execute("""
+ SELECT * FROM role_config WHERE forum = ?
+ """,(a['id'],)).fetchall()
+ return set(r['role'] for r in configs)
def has_permission(forum_id, username, permission, login_required=True):
- db = get_db()
- forum = db.execute("SELECT * FROM forums WHERE id = ?",(forum_id,)).fetchone()
- user = db.execute('SELECT * FROM users WHERE username = ?',
- (username,)).fetchone() if username else None
+ db = get_db()
+ forum = db.execute("SELECT * FROM forums WHERE id = ?",(forum_id,)).fetchone()
+ user = db.execute('SELECT * FROM users WHERE username = ?',
+ (username,)).fetchone() if username else None
- if forum['unlisted'] and not (user and user['admin']): return False
- if username == None and login_required: return False
+ if forum['unlisted'] and not (user and user['admin']): return False
+ if username == None and login_required: return False
- role = get_user_role(forum_id, username) if username else "other"
- if role == "bureaucrat": return True
- config = get_role_config(forum_id, role)
- return config[permission]
+ role = get_user_role(forum_id, username) if username else "other"
+ if role == "bureaucrat": return True
+ config = get_role_config(forum_id, role)
+ return config[permission]
def is_bureaucrat(forum_id, user):
- if user == None: return False
- return get_user_role(forum_id, user) == "bureaucrat"
+ if user == None: return False
+ return get_user_role(forum_id, user) == "bureaucrat"
diff --git a/apioforum/static/style.css b/apioforum/static/style.css
index c77ec69..63c7347 100644
--- a/apioforum/static/style.css
+++ b/apioforum/static/style.css
@@ -1,17 +1,17 @@
body { font-family: sans-serif; word-wrap: break-word; }
:root {
- --alternating-colour-even: hsl(0, 0%, 96%);
- --alternating-colour-odd: hsl(0, 0%, 91%);
- --dark-colour: black;
+ --alternating-colour-even: hsl(0, 0%, 96%);
+ --alternating-colour-odd: hsl(0, 0%, 91%);
+ --dark-colour: black;
--gray: darkgray;
- --light-colour: white;
- --username-colour: hsl(0, 0%, 25%);
- --read-colour: hsl(0, 0%, 30%);
- --red: red;
- --yellow: yellow;
- --blue: blue;
- --visited: #552a8b;
+ --light-colour: white;
+ --username-colour: hsl(0, 0%, 25%);
+ --read-colour: hsl(0, 0%, 30%);
+ --red: red;
+ --yellow: yellow;
+ --blue: blue;
+ --visited: #552a8b;
}
a { color: var(--blue) }
@@ -21,10 +21,10 @@ a:visited { color: var(--visited); }
.post:nth-child(even) { background-color: var(--alternating-colour-even) }
.post:nth-child(odd) { background-color: var(--alternating-colour-odd) }
.post {
- border-left: 1px solid var(--dark-colour);
- border-right: 1px solid var(--dark-colour);
- border-top: 1px solid var(--dark-colour);
- width: 100%;
+ border-left: 1px solid var(--dark-colour);
+ border-right: 1px solid var(--dark-colour);
+ border-top: 1px solid var(--dark-colour);
+ width: 100%;
box-sizing: border-box;
}
.post:last-of-type { border-bottom: 1px solid var(--dark-colour); }
@@ -34,8 +34,8 @@ a:visited { color: var(--visited); }
color: var(--username-colour);
}
.username,nav#navbar .username{
- font-weight: bold;
- text-decoration: underline;
+ font-weight: bold;
+ text-decoration: underline;
}
.post-heading-em { font-weight: bold; }
.post-content * { margin-bottom: 8px; margin-top: 8px; }
@@ -52,9 +52,9 @@ a:visited { color: var(--visited); }
div.deleted-post {
color:var(--light-colour);
background-color: var(--dark-colour) !important;
- border-left: 1px solid var(--dark-colour);
- border-right: 1px solid var(--dark-colour);
- border-top: 1px solid var(--dark-colour);
+ border-left: 1px solid var(--dark-colour);
+ border-right: 1px solid var(--dark-colour);
+ border-top: 1px solid var(--dark-colour);
}
.deleted-post > .post-heading > * {
color: hsl(0,0%,85%);
@@ -95,22 +95,22 @@ div.deleted-post {
}
.thread-top-bar, .user-top-bar {
- margin-bottom: 4px;
+ margin-bottom: 4px;
}
.thread-top-bar-b {
- float: right;
- margin-right: -2px;
+ float: right;
+ margin-right: -2px;
}
.thread-top-bar-b .tag {
- font-size: .9rem;
+ font-size: .9rem;
}
.user_info {
- border: 1px solid var(--dark-colour);
+ border: 1px solid var(--dark-colour);
background-color: var(--alternating-colour-even);
- width: 100%;
+ width: 100%;
padding: 4px;
}
.user_bio_quote { width: max-content; max-width: 100% }
@@ -195,9 +195,9 @@ nav#pages .pageno { align-self: center; }
.listing:nth-child(odd) { background-color: var(--alternating-colour-odd) }
.listing {
- border-left: 1px solid var(--dark-colour);
- border-right: 1px solid var(--dark-colour);
- border-top: 1px solid var(--dark-colour);
+ border-left: 1px solid var(--dark-colour);
+ border-right: 1px solid var(--dark-colour);
+ border-top: 1px solid var(--dark-colour);
padding: 10px;
}
.listing:last-of-type { border-bottom: 1px solid var(--dark-colour); }
@@ -273,12 +273,12 @@ nav#pages .pageno { align-self: center; }
}
.actionbutton::before {
- content: "[";
- color: var(--gray);
+ content: "[";
+ color: var(--gray);
}
.actionbutton::after {
- content: "]";
- color: var(--gray);
+ content: "]";
+ color: var(--gray);
}
.actionbutton,.actionbutton:visited {
color:var(--blue);
@@ -286,32 +286,32 @@ nav#pages .pageno { align-self: center; }
}
.new-post-box, .forum-desc-box {
- height:20em;
- resize:vertical;
- width:100%;
- border:1px solid var(--dark-colour);
- margin-top: 5px;
+ height:20em;
+ resize:vertical;
+ width:100%;
+ border:1px solid var(--dark-colour);
+ margin-top: 5px;
}
#polloptions {
display: block;
resize: vertical;
- border:1px solid var(--dark-colour);
- margin-top: 5px;
+ border:1px solid var(--dark-colour);
+ margin-top: 5px;
height: 5em;
width: 100%;
font-family: sans-serif;
}
main {
- max-width: 80ch;
- margin: auto;
+ max-width: 80ch;
+ margin: auto;
}
blockquote {
- margin-left: 10px;
- padding-left: 10px;
- border-left: 3px solid var(--gray);
+ margin-left: 10px;
+ padding-left: 10px;
+ border-left: 3px solid var(--gray);
}
label { user-select: none; }
@@ -321,23 +321,23 @@ fieldset { margin-bottom: 15px; }
.warning { color: var(--red); font-weight: bold }
.inline-form {
- display: inline-block;
+ display: inline-block;
}
.tag {
- font-size: .75rem;
- padding: 1px 3px;
- border: 1px solid var(--dark-colour);
+ font-size: .75rem;
+ padding: 1px 3px;
+ border: 1px solid var(--dark-colour);
white-space: nowrap;
}
.md table {
- border: 1px solid var(--gray);
- border-collapse: collapse;
+ border: 1px solid var(--gray);
+ border-collapse: collapse;
}
.md table td,.md table th {
- border: 1px solid var(--gray);
- padding: 4px;
+ border: 1px solid var(--gray);
+ padding: 4px;
}
.role-input, .name-input { width: 12ch; }
@@ -345,16 +345,16 @@ fieldset { margin-bottom: 15px; }
.thing-id { color: var(--gray); font-size: smaller; font-weight: normal; }
.breadcrumbs {
- list-style: none;
+ list-style: none;
}
.breadcrumbs li {
- display: inline;
+ display: inline;
}
.breadcrumbs li+li::before {
- content: "/\00a0";
- padding: 8px;
+ content: "/\00a0";
+ padding: 8px;
}
textarea {
diff --git a/apioforum/templates/admin/admin_page.html b/apioforum/templates/admin/admin_page.html
index f48c6c0..fb558bf 100644
--- a/apioforum/templates/admin/admin_page.html
+++ b/apioforum/templates/admin/admin_page.html
@@ -6,9 +6,9 @@
{% block content %}
<h2>admins</h2>
<ul>
- {% for admin in admins %}
- <li>{{admin.username}}</li>
- {% endfor %}
+ {% for admin in admins %}
+ <li>{{admin.username}}</li>
+ {% endfor %}
</ul>
<p>this page will have more things on it later, probably</p>
{% endblock %}
diff --git a/apioforum/templates/auth/login.html b/apioforum/templates/auth/login.html
index 89f490f..361650f 100644
--- a/apioforum/templates/auth/login.html
+++ b/apioforum/templates/auth/login.html
@@ -1,20 +1,20 @@
{% extends "base.html" %}
{% block header %}
- <h1>{% block title %}login{% endblock %}</h1>
+ <h1>{% block title %}login{% endblock %}</h1>
{% endblock %}
{% block content %}
<p>log in using an existing account here. if you don't already have an account, <a href="{{url_for('auth.register')}}">register</a> first instead.</p>
<form method="post">
- <label for="username">username</label>
- <input name="username" id="username" required>
- <br>
- <label for="password">password</label>
- <input type="password" name="password" id="password" required>
- <br>
- <input type="checkbox" name="keep_logged_in" id="keep_logged_in">
- <label for="keep_logged_in">keep me logged in</label>
- <br>
- <input type="submit" value="login">
+ <label for="username">username</label>
+ <input name="username" id="username" required>
+ <br>
+ <label for="password">password</label>
+ <input type="password" name="password" id="password" required>
+ <br>
+ <input type="checkbox" name="keep_logged_in" id="keep_logged_in">
+ <label for="keep_logged_in">keep me logged in</label>
+ <br>
+ <input type="submit" value="login">
</form>
{% endblock %}
diff --git a/apioforum/templates/auth/register.html b/apioforum/templates/auth/register.html
index 082a95b..ed09c56 100644
--- a/apioforum/templates/auth/register.html
+++ b/apioforum/templates/auth/register.html
@@ -1,20 +1,20 @@
{% extends "base.html" %}
{% block header %}
- <h1>{% block title %}register{% endblock %}</h1>
+ <h1>{% block title %}register{% endblock %}</h1>
{% endblock %}
{% block content %}
<p>create a new account here. if you already have an account, <a href="{{url_for('auth.login')}}">login</a> instead.</p>
<form method="post">
- <label for="username">username</label>
- <input name="username" id="username" maxlength="20" required>
- <br>
- <label for="password">password</label>
- <input type="password" name="password" id="password" required>
- <br>
- <input type="checkbox" name="keep_logged_in" id="keep_logged_in">
- <label for="keep_logged_in">keep me logged in</label>
- <br>
- <input type="submit" value="register">
+ <label for="username">username</label>
+ <input name="username" id="username" maxlength="20" required>
+ <br>
+ <label for="password">password</label>
+ <input type="password" name="password" id="password" required>
+ <br>
+ <input type="checkbox" name="keep_logged_in" id="keep_logged_in">
+ <label for="keep_logged_in">keep me logged in</label>
+ <br>
+ <input type="submit" value="register">
</form>
{% endblock %}
diff --git a/apioforum/templates/base.html b/apioforum/templates/base.html
index a202300..ed0a195 100644
--- a/apioforum/templates/base.html
+++ b/apioforum/templates/base.html
@@ -2,15 +2,15 @@
{% from 'common.html' import disp_user with context %}
<!DOCTYPE html>
<html>
- <head>
- <title>{%block title %}{% endblock %}</title>
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <link rel="stylesheet" href="/static/style.css">
- <link rel="stylesheet" href="/static/md-colors.css">
- <link rel="icon" href="//gh0.pw/i/a.ico">
- </head>
- <body>
- <nav aria-label="main" id="navbar">
+ <head>
+ <title>{%block title %}{% endblock %}</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <link rel="stylesheet" href="/static/style.css">
+ <link rel="stylesheet" href="/static/md-colors.css">
+ <link rel="icon" href="//gh0.pw/i/a.ico">
+ </head>
+ <body>
+ <nav aria-label="main" id="navbar">
<p style="font-family: monospace;"><b><red>ap</red><orange>i</orange><yellow>o</yellow><green>f</green><blue>o</blue><indigo>r</indigo><violet>um</violet></b>&trade;</p>
<form class="inline-form" action="/search">
<input type="search" placeholder="query" name="q">
@@ -27,7 +27,7 @@
{% if is_admin %}
<p><a href="{{url_for('admin.admin_page')}}">admin</a></p>
{% endif %}
-
+
<p>
<a href="{{url_for('auth.logout',next=path_for_next)}}">
logout
@@ -47,24 +47,24 @@
{% endif %}
</div>
- </nav>
+ </nav>
- <div class="header">
- {% block header %}{% endblock %}
- </div>
+ <div class="header">
+ {% block header %}{% endblock %}
+ </div>
- {% for msg in get_flashed_messages() %}
- <div class="flashmsg">{{ msg }}</div>
- {% endfor %}
+ {% for msg in get_flashed_messages() %}
+ <div class="flashmsg">{{ msg }}</div>
+ {% endfor %}
- {% block nmcontent %}
- <main>
- {%block content %}{% endblock %}
- </main>
- {% endblock %}
- <script>/* bees */</script>
+ {% block nmcontent %}
+ <main>
+ {%block content %}{% endblock %}
+ </main>
+ {% endblock %}
+ <script>/* bees */</script>
<!-- citrons was here -->
<!-- Complete hybridisation of various species of wild duck gene pools could result in the extinction of many indigenous waterfowl. -->
- </body>
+ </body>
</html>
diff --git a/apioforum/templates/common.html b/apioforum/templates/common.html
index fae4b7c..a24272b 100644
--- a/apioforum/templates/common.html
+++ b/apioforum/templates/common.html
@@ -4,8 +4,8 @@
{% macro disp_post(post, buttons=False, forum=None, footer=None) %}
<div class="post {% if post.deleted %}deleted-post{% endif %}" id="post_{{post.id}}">
- <div class="post-heading">
- <span class="post-heading-a">
+ <div class="post-heading">
+ <span class="post-heading-a">
{% if not post.deleted %}
{{disp_user(post.author)}}
{% else %}
@@ -27,12 +27,12 @@
{{ts(post.created)}}
- {% if post.edited %}
- (edited {{ts(post.updated)}})
- {% endif %}
- </span>
- <span class="post-heading-b">
- {% if buttons and not post.deleted %}
+ {% if post.edited %}
+ (edited {{ts(post.updated)}})
+ {% endif %}
+ </span>
+ <span class="post-heading-b">
+ {% if buttons and not post.deleted %}
{% if post.author == g.user %}
<a class="actionbutton"
href="{{url_for('thread.edit_post',post_id=post.id)}}">edit</a>
@@ -41,25 +41,25 @@
<a class="actionbutton"
href="{{url_for('thread.delete_post',post_id=post.id)}}">delete</a>
{% endif %}
- <a class="actionbutton"
- href="{{url_for('thread.view_post',post_id=post.id)}}">src</a>
- {% endif %}
-
+ <a class="actionbutton"
+ href="{{url_for('thread.view_post',post_id=post.id)}}">src</a>
+ {% endif %}
+
<a class="post-anchor-link" href="{{post_jump(post.id)}}">#{{post.id}}</a>
- </span>
- </div>
- <div class="post-content md">
+ </span>
+ </div>
+ <div class="post-content md">
{% if not post.deleted %}
{{ post.content|md|safe }}
{% else %}
this post never existed.
{% endif %}
- </div>
- {% if footer %}
- <div class="post-footer">
- {{ footer }}
- </div>
- {% endif %}
+ </div>
+ {% if footer %}
+ <div class="post-footer">
+ {{ footer }}
+ </div>
+ {% endif %}
</div>
{% endmacro %}
@@ -79,7 +79,7 @@
{% if href is not none -%}
href="{{href}}"
{%- endif -%}>
- {{-the_tag.name-}}
+ {{-the_tag.name-}}
</{{el}}>
{%- endmacro %}
@@ -123,9 +123,9 @@
<text text-anchor="middle" dominant-baseline="middle" x="11%" y="55%" fill="black" style="font-size:15px">no votes</text>
{% else %}
{% for opt in poll.options %}
- {% set opt_count = opt.num or 0 %}
- {% set colour = (loop.index|string + opt.text)|gen_colour %}
- {% if opt_count != 0 %}
+ {% set opt_count = opt.num or 0 %}
+ {% set colour = (loop.index|string + opt.text)|gen_colour %}
+ {% if opt_count != 0 %}
{% set percentage = 100*(opt_count/total_votes) %}
{# todo: do this in css somehow #}
{% if opt.text|length > 10 %}
diff --git a/apioforum/templates/config_thread.html b/apioforum/templates/config_thread.html
index 0795ccc..1debe76 100644
--- a/apioforum/templates/config_thread.html
+++ b/apioforum/templates/config_thread.html
@@ -33,22 +33,22 @@
{% if thread.poll is none %}
<h2>create poll</h2>
<form method="post" action="{{url_for('thread.create_poll',thread_id=thread.id)}}">
- <fieldset>
- <legend>create poll</legend>
- <label for="polltitle">question title</label>
- <input type="title" id="polltitle" name="polltitle">
- <br>
- <label for="polloptions">options (one per line)</label>
- <textarea name="polloptions" id="polloptions"></textarea>
- </fieldset>
- <p>important: once a poll is created, you will not be able to modify it except to delete it entirely</p>
- <input type="submit" value="create">
+ <fieldset>
+ <legend>create poll</legend>
+ <label for="polltitle">question title</label>
+ <input type="title" id="polltitle" name="polltitle">
+ <br>
+ <label for="polloptions">options (one per line)</label>
+ <textarea name="polloptions" id="polloptions"></textarea>
+ </fieldset>
+ <p>important: once a poll is created, you will not be able to modify it except to delete it entirely</p>
+ <input type="submit" value="create">
</form>
{% else %}
<h2>delete poll</h2>
<p>there is already a poll attached to this thread. you can delete it, which will allow you to create a new one, but this will erase all existing votes and data for the current poll.</p>
<form action="{{url_for('thread.delete_poll',thread_id=thread.id)}}" method="post">
- <input type="submit" value="confirm: delete poll">
+ <input type="submit" value="confirm: delete poll">
</form>
{% endif %}
{% endif %}
diff --git a/apioforum/templates/create_thread.html b/apioforum/templates/create_thread.html
index 04b4f42..90d1410 100644
--- a/apioforum/templates/create_thread.html
+++ b/apioforum/templates/create_thread.html
@@ -5,11 +5,11 @@
{% block content %}
<form method="POST">
- <label for="title">thread title</label>
- <input name="title" id="title">
- <br>
- <label for="content">thread content</label>
- <textarea name="content" id="content" class="new-post-box" placeholder="thread content here"></textarea>
- <input type="submit" value="create">
+ <label for="title">thread title</label>
+ <input name="title" id="title">
+ <br>
+ <label for="content">thread content</label>
+ <textarea name="content" id="content" class="new-post-box" placeholder="thread content here"></textarea>
+ <input type="submit" value="create">
</form>
{% endblock %}
diff --git a/apioforum/templates/edit_forum.html b/apioforum/templates/edit_forum.html
index f165676..3c07d1a 100644
--- a/apioforum/templates/edit_forum.html
+++ b/apioforum/templates/edit_forum.html
@@ -5,10 +5,10 @@
{% block content %}
<form method="POST">
- <label for="name">forum name</label>
+ <label for="name">forum name</label>
<input name="name" id="name" value="{{name}}" placeholder="apioforum" required maxlength="100"/>
- <br>
- <label for="description">forum description (markdown enabled)</label>
+ <br>
+ <label for="description">forum description (markdown enabled)</label>
<textarea
name="description"
id="description"
diff --git a/apioforum/templates/search_results.html b/apioforum/templates/search_results.html
index fe016ab..a55dc8a 100644
--- a/apioforum/templates/search_results.html
+++ b/apioforum/templates/search_results.html
@@ -6,23 +6,23 @@
{%block content%}
<div class="results">
- {% for result in results %}
- {% if display_thread_id[loop.index0] %}
- {% if loop.index0 != 0 %}
- </div>
- {% endif %}
- <h3><a href="{{url_for('thread.view_thread', thread_id=result.thread)}}">
- {{result.thread_title}}
- </a></h3>
- <div class="posts">
- {% endif %}
- {{ disp_post(result, False) }}
- {% endfor %}
+ {% for result in results %}
+ {% if display_thread_id[loop.index0] %}
+ {% if loop.index0 != 0 %}
+ </div>
+ {% endif %}
+ <h3><a href="{{url_for('thread.view_thread', thread_id=result.thread)}}">
+ {{result.thread_title}}
+ </a></h3>
+ <div class="posts">
+ {% endif %}
+ {{ disp_post(result, False) }}
+ {% endfor %}
- {% if results|length > 0 %}
- </div>
- {% else %}
- <p>no results were found for '{{query}}'.</p>
- {% endif %}
+ {% if results|length > 0 %}
+ </div>
+ {% else %}
+ <p>no results were found for '{{query}}'.</p>
+ {% endif %}
</div>
{% endblock %}
diff --git a/apioforum/templates/view_forum.html b/apioforum/templates/view_forum.html
index d37a018..7732b21 100644
--- a/apioforum/templates/view_forum.html
+++ b/apioforum/templates/view_forum.html
@@ -110,8 +110,8 @@ you do not have permission to create threads in this forum
{% for the_tag in avail_tags %}
<input type="radio" id="tagfilter-{{the_tag.id}}"
- name="tagfilter" value="{{the_tag.id}}"
- {% if tagfilter_tag.id == the_tag.id %}checked{% endif %}>
+ name="tagfilter" value="{{the_tag.id}}"
+ {% if tagfilter_tag.id == the_tag.id %}checked{% endif %}>
<label for="tagfilter-{{the_tag.id}}">
{{tag(the_tag)}}
</label>
diff --git a/apioforum/templates/view_thread.html b/apioforum/templates/view_thread.html
index 6e881cb..fa707a6 100644
--- a/apioforum/templates/view_thread.html
+++ b/apioforum/templates/view_thread.html
@@ -9,93 +9,93 @@
{% if poll %}
<p>{{poll.title}}</p>
<ol>
- {%for opt in poll.options%}
- <li value="{{opt.option_idx}}"><i>{{opt.text}}</i>: {{opt.num or 0}} votes</li>
- {%endfor%}
+ {%for opt in poll.options%}
+ <li value="{{opt.option_idx}}"><i>{{opt.text}}</i>: {{opt.num or 0}} votes</li>
+ {%endfor%}
</ol>
{{ vote_meter(poll) }}
{% endif %}
<div class="thread-top-bar">
- <span class="thread-top-bar-a">
- {% if g.user == thread.creator or has_permission(thread.forum, g.user, "p_manage_threads") %}
- <a class="actionbutton" href="{{url_for('thread.config_thread',thread_id=thread.id)}}">configure thread</a>
- {% endif %}
+ <span class="thread-top-bar-a">
+ {% if g.user == thread.creator or has_permission(thread.forum, g.user, "p_manage_threads") %}
+ <a class="actionbutton" href="{{url_for('thread.config_thread',thread_id=thread.id)}}">configure thread</a>
+ {% endif %}
{% if has_permission(thread.forum, g.user, "p_delete_posts") %}
- <a class="actionbutton" href="{{url_for('thread.delete_thread',thread_id=thread.id)}}">delete thread</a>
+ <a class="actionbutton" href="{{url_for('thread.delete_thread',thread_id=thread.id)}}">delete thread</a>
{% endif %}
- </span>
- &nbsp;
- <span class="thread-top-bar-b">
- {% for the_tag in tags %}
- {{ tag(the_tag) }}
- {% endfor %}
- </span>
+ </span>
+ &nbsp;
+ <span class="thread-top-bar-b">
+ {% for the_tag in tags %}
+ {{ tag(the_tag) }}
+ {% endfor %}
+ </span>
</div>
{{ pagination_nav(page,max_pageno,'thread.view_thread',thread_id=thread.id) }}
<div class="posts">
- {% for post in posts %}
- {% if votes[post.id] %}
+ {% for post in posts %}
+ {% if votes[post.id] %}
- {% set vote = votes[post.id] %}
- {% set option_idx = vote.option_idx %}
-
- {# this is bad but it's going to get refactored anyway #}
- {% set footer %}
- {% if vote.is_retraction %}
+ {% set vote = votes[post.id] %}
+ {% set option_idx = vote.option_idx %}
+
+ {# this is bad but it's going to get refactored anyway #}
+ {% set footer %}
+ {% if vote.is_retraction %}
{% if not post.deleted %}
{{post.author}} retracted their vote
{% else %}
this post retracted a vote
{% endif %}
- {% else %}
- {% set option = poll.options[option_idx-1] %}
- {% if vote.current %}
- {{post.author}} votes for {{option_idx}}: {{option.text}}
- {% else %}
+ {% else %}
+ {% set option = poll.options[option_idx-1] %}
+ {% if vote.current %}
+ {{post.author}} votes for {{option_idx}}: {{option.text}}
+ {% else %}
{% if not post.deleted %}
{{post.author}} voted for {{option_idx}}: {{option.text}}, but later changed their vote
{% else %}
this post presented a vote that was later changed
{% endif %}
- {% endif %}
- {% endif %}
+ {% endif %}
+ {% endif %}
- {% endset %}
+ {% endset %}
- {{ disp_post(post, forum=thread.forum, buttons=True, footer=footer) }}
-
- {% else %}
- {{ disp_post(post, forum=thread.forum, buttons=True) }}
- {% endif %}
- {% endfor %}
+ {{ disp_post(post, forum=thread.forum, buttons=True, footer=footer) }}
+
+ {% else %}
+ {{ disp_post(post, forum=thread.forum, buttons=True) }}
+ {% endif %}
+ {% endfor %}
</div>
{% if g.user and has_permission(thread.forum, g.user, "p_reply_threads") %}
<form class="new-post" action="{{url_for('thread.create_post',thread_id=thread.id)}}" method="POST">
- <textarea class="new-post-box" placeholder="your post here..." name="content"></textarea>
- {% if poll and has_permission(thread.forum, g.user, "p_vote") %}
- <fieldset>
- <legend>poll: {{poll.title}}</legend>
- <p>if you want, you can submit a vote along with this post. if you have previously voted
- on this poll, your previous vote will be changed</p>
+ <textarea class="new-post-box" placeholder="your post here..." name="content"></textarea>
+ {% if poll and has_permission(thread.forum, g.user, "p_vote") %}
+ <fieldset>
+ <legend>poll: {{poll.title}}</legend>
+ <p>if you want, you can submit a vote along with this post. if you have previously voted
+ on this poll, your previous vote will be changed</p>
- <input type="radio" id="dontvote" name="poll" value="dontvote" checked>
- <label for="dontvote">do not submit any vote at the moment</label>
+ <input type="radio" id="dontvote" name="poll" value="dontvote" checked>
+ <label for="dontvote">do not submit any vote at the moment</label>
- {% if has_voted %}
- <br>
- <input type="radio" id="retractvote" name="poll" value="retractvote">
- <label for="retractvote">clear current vote</label>
- {% endif %}
+ {% if has_voted %}
+ <br>
+ <input type="radio" id="retractvote" name="poll" value="retractvote">
+ <label for="retractvote">clear current vote</label>
+ {% endif %}
- {% for opt in poll.options %}
- <br>
- <input type="radio" id="option_{{opt.option_idx}}" name="poll" value="{{opt.option_idx}}">
- <label for="option_{{opt.option_idx}}">#{{opt.option_idx}} - {{opt.text}}</label>
- {% endfor %}
- </fieldset>
- {% endif %}
- <input type="submit" value="yes">
+ {% for opt in poll.options %}
+ <br>
+ <input type="radio" id="option_{{opt.option_idx}}" name="poll" value="{{opt.option_idx}}">
+ <label for="option_{{opt.option_idx}}">#{{opt.option_idx}} - {{opt.text}}</label>
+ {% endfor %}
+ </fieldset>
+ {% endif %}
+ <input type="submit" value="yes">
</form>
{% elif g.user %}
<p>you do not have permission to reply to this thread</p>
diff --git a/apioforum/thread.py b/apioforum/thread.py
index fdd72e1..9352afb 100644
--- a/apioforum/thread.py
+++ b/apioforum/thread.py
@@ -3,8 +3,8 @@
import itertools, math
from flask import (
- Blueprint, render_template, abort, request, g, redirect,
- url_for, flash, jsonify
+ Blueprint, render_template, abort, request, g, redirect,
+ url_for, flash, jsonify
)
from .db import get_db
from .roles import has_permission
@@ -17,403 +17,403 @@ bp = Blueprint("thread", __name__, url_prefix="/thread")
POSTS_PER_PAGE = 28
def which_page(post_id,return_thread_id=False):
- # on which page lieth the post in question?
- # forget not that page numbers employeth a system that has a base of 1.
- # the
- # we need impart the knowledgf e into ourselves pertaining to the
- # number of things
- # before the thing
- # yes
-
- db = get_db()
- # ASSUMES THAT post ids are consecutive and things
- # this is probably a reasonable assumption
-
- thread_id = db.execute('select thread from posts where id = ?',(post_id,)).fetchone()['thread']
-
- number_of_things_before_the_thing = db.execute('select count(*) as c, thread as t from posts where thread = ? and id < ?;',(thread_id,post_id)).fetchone()['c']
-
-
- page = 1+math.floor(number_of_things_before_the_thing/POSTS_PER_PAGE)
- if return_thread_id:
- return page, thread_id
- else:
- return page
+ # on which page lieth the post in question?
+ # forget not that page numbers employeth a system that has a base of 1.
+ # the
+ # we need impart the knowledgf e into ourselves pertaining to the
+ # number of things
+ # before the thing
+ # yes
+
+ db = get_db()
+ # ASSUMES THAT post ids are consecutive and things
+ # this is probably a reasonable assumption
+
+ thread_id = db.execute('select thread from posts where id = ?',(post_id,)).fetchone()['thread']
+
+ number_of_things_before_the_thing = db.execute('select count(*) as c, thread as t from posts where thread = ? and id < ?;',(thread_id,post_id)).fetchone()['c']
+
+
+ page = 1+math.floor(number_of_things_before_the_thing/POSTS_PER_PAGE)
+ if return_thread_id:
+ return page, thread_id
+ else:
+ return page
def post_jump(post_id,*,external=False):
- page,thread_id=which_page(post_id,True)
- return url_for("thread.view_thread",thread_id=thread_id,page=page,_external=external)+"#post_"+str(post_id)
+ page,thread_id=which_page(post_id,True)
+ return url_for("thread.view_thread",thread_id=thread_id,page=page,_external=external)+"#post_"+str(post_id)
@bp.route("/<int:thread_id>")
@bp.route("/<int:thread_id>/page/<int:page>")
def view_thread(thread_id,page=1):
- db = get_db()
- thread = db.execute("SELECT * FROM threads WHERE id = ?;",(thread_id,)).fetchone()
- if thread is None:
- abort(404)
- if not has_permission(thread['forum'], g.user, "p_view_threads", False):
- abort(403)
-
- num_posts = db.execute("SELECT count(*) as count FROM posts WHERE posts.thread = ?",(thread_id,)).fetchone()['count']
- max_pageno = math.ceil(num_posts/POSTS_PER_PAGE)
- if page < 1:
- abort(404)
- elif page > max_pageno and (max_pageno > 0 or page != 1):
- abort(404)
-
- posts = db.execute("""
- SELECT * FROM posts
- WHERE posts.thread = ?
- ORDER BY created ASC
- LIMIT ? OFFSET ?;
- """,(
- thread_id,
- POSTS_PER_PAGE,
- (page-1)*POSTS_PER_PAGE,
- )).fetchall()
-
- tags = db.execute(
- """SELECT tags.* FROM tags
- INNER JOIN thread_tags ON thread_tags.tag = tags.id
- WHERE thread_tags.thread = ?
- ORDER BY tags.id""",(thread_id,)).fetchall()
- poll = None
- votes = None
- if thread['poll'] is not None:
- poll_row= db.execute("""
- SELECT polls.*,total_vote_counts.total_votes FROM polls
- LEFT OUTER JOIN total_vote_counts ON polls.id = total_vote_counts.poll
- WHERE polls.id = ?;
- """,(thread['poll'],)).fetchone()
- options = db.execute("""
- SELECT poll_options.*, vote_counts.num
- FROM poll_options
- LEFT OUTER JOIN vote_counts ON poll_options.poll = vote_counts.poll
- AND poll_options.option_idx = vote_counts.option_idx
- WHERE poll_options.poll = ?
- ORDER BY option_idx asc;
- """,(poll_row['id'],)).fetchall()
- poll = {}
- poll.update(poll_row)
- poll['options'] = options
- votes = {}
- # todo: optimise this somehow
- for post in posts:
- if post['vote'] is not None:
- votes[post['id']] = db.execute("SELECT * FROM votes WHERE id = ?",(post['vote'],)).fetchone()
-
- if g.user is None or poll is None:
- has_voted = None
- else:
- v = db.execute("SELECT * FROM votes WHERE poll = ? AND user = ? AND current AND NOT is_retraction;",(poll['id'],g.user)).fetchone()
- has_voted = v is not None
-
- if g.user != None:
- db.execute("DELETE FROM read WHERE user = ? AND thread = ?;", (g.user, thread_id))
- db.execute("INSERT INTO read (user,thread,time) VALUES (?,?,current_timestamp);", (g.user, thread_id))
- db.commit()
-
- return render_template(
- "view_thread.html",
- posts=posts,
- thread=thread,
- tags=tags,
- poll=poll,
- votes=votes,
- has_voted=has_voted,
- page=page,
- max_pageno=max_pageno,
- )
+ db = get_db()
+ thread = db.execute("SELECT * FROM threads WHERE id = ?;",(thread_id,)).fetchone()
+ if thread is None:
+ abort(404)
+ if not has_permission(thread['forum'], g.user, "p_view_threads", False):
+ abort(403)
+
+ num_posts = db.execute("SELECT count(*) as count FROM posts WHERE posts.thread = ?",(thread_id,)).fetchone()['count']
+ max_pageno = math.ceil(num_posts/POSTS_PER_PAGE)
+ if page < 1:
+ abort(404)
+ elif page > max_pageno and (max_pageno > 0 or page != 1):
+ abort(404)
+
+ posts = db.execute("""
+ SELECT * FROM posts
+ WHERE posts.thread = ?
+ ORDER BY created ASC
+ LIMIT ? OFFSET ?;
+ """,(
+ thread_id,
+ POSTS_PER_PAGE,
+ (page-1)*POSTS_PER_PAGE,
+ )).fetchall()
+
+ tags = db.execute(
+ """SELECT tags.* FROM tags
+ INNER JOIN thread_tags ON thread_tags.tag = tags.id
+ WHERE thread_tags.thread = ?
+ ORDER BY tags.id""",(thread_id,)).fetchall()
+ poll = None
+ votes = None
+ if thread['poll'] is not None:
+ poll_row= db.execute("""
+ SELECT polls.*,total_vote_counts.total_votes FROM polls
+ LEFT OUTER JOIN total_vote_counts ON polls.id = total_vote_counts.poll
+ WHERE polls.id = ?;
+ """,(thread['poll'],)).fetchone()
+ options = db.execute("""
+ SELECT poll_options.*, vote_counts.num
+ FROM poll_options
+ LEFT OUTER JOIN vote_counts ON poll_options.poll = vote_counts.poll
+ AND poll_options.option_idx = vote_counts.option_idx
+ WHERE poll_options.poll = ?
+ ORDER BY option_idx asc;
+ """,(poll_row['id'],)).fetchall()
+ poll = {}
+ poll.update(poll_row)
+ poll['options'] = options
+ votes = {}
+ # todo: optimise this somehow
+ for post in posts:
+ if post['vote'] is not None:
+ votes[post['id']] = db.execute("SELECT * FROM votes WHERE id = ?",(post['vote'],)).fetchone()
+
+ if g.user is None or poll is None:
+ has_voted = None
+ else:
+ v = db.execute("SELECT * FROM votes WHERE poll = ? AND user = ? AND current AND NOT is_retraction;",(poll['id'],g.user)).fetchone()
+ has_voted = v is not None
+
+ if g.user != None:
+ db.execute("DELETE FROM read WHERE user = ? AND thread = ?;", (g.user, thread_id))
+ db.execute("INSERT INTO read (user,thread,time) VALUES (?,?,current_timestamp);", (g.user, thread_id))
+ db.commit()
+
+ return render_template(
+ "view_thread.html",
+ posts=posts,
+ thread=thread,
+ tags=tags,
+ poll=poll,
+ votes=votes,
+ has_voted=has_voted,
+ page=page,
+ max_pageno=max_pageno,
+ )
def register_vote(thread,pollval):
- if pollval is None or pollval == 'dontvote':
- return
-
- is_retraction = pollval == 'retractvote'
-
- if is_retraction:
- option_idx = None
- else:
- option_idx = int(pollval)
-
- db = get_db()
- cur = db.cursor()
- cur.execute("""
- UPDATE votes
- SET current = 0
- WHERE poll = ? AND user = ?;
- """,(thread['poll'],g.user))
-
- cur.execute("""
- INSERT INTO votes (user,poll,option_idx,time,current,is_retraction)
- VALUES (?,?,?,current_timestamp,1,?);
- """,(g.user,thread['poll'],option_idx,is_retraction))
- vote_id = cur.lastrowid
- return vote_id
+ if pollval is None or pollval == 'dontvote':
+ return
+
+ is_retraction = pollval == 'retractvote'
+
+ if is_retraction:
+ option_idx = None
+ else:
+ option_idx = int(pollval)
+
+ db = get_db()
+ cur = db.cursor()
+ cur.execute("""
+ UPDATE votes
+ SET current = 0
+ WHERE poll = ? AND user = ?;
+ """,(thread['poll'],g.user))
+
+ cur.execute("""
+ INSERT INTO votes (user,poll,option_idx,time,current,is_retraction)
+ VALUES (?,?,?,current_timestamp,1,?);
+ """,(g.user,thread['poll'],option_idx,is_retraction))
+ vote_id = cur.lastrowid
+ return vote_id
@bp.route("/<int:thread_id>/create_poll",methods=["POST"])
def create_poll(thread_id):
- fail = redirect(url_for('thread.config_thread',thread_id=thread_id))
- success = redirect(url_for('thread.view_thread',thread_id=thread_id))
- err = None
- db = get_db()
- thread = db.execute('select * from threads where id = ?',(thread_id,)).fetchone()
-
- polltitle = request.form.get('polltitle','').strip()
- polloptions = [q.strip() for q in request.form.get('polloptions','').split("\n") if len(q.strip()) > 0]
-
- if thread is None:
- err = "that thread does not exist"
- elif g.user is None:
- err = "you need to be logged in to do that"
- elif g.user != thread['creator'] and \
- not has_permission(thread['forum'],g.user,"p_manage_threads"):
- err = "you can only create polls on threads that you own"
- elif thread['poll'] is not None:
- err = "a poll already exists for that thread"
- elif not len(polltitle) > 0:
- err = "poll title can't be empty"
- elif len(polloptions) < 2:
- err = "you must provide at least 2 options"
- elif not has_permission(thread['forum'], g.user, "p_create_polls"):
- err = "you do not have permission to do that"
-
- if err is not None:
- flash(err)
- return fail
- else:
- cur = db.cursor()
- cur.execute("INSERT INTO polls (title) VALUES (?)",(polltitle,))
- pollid = cur.lastrowid
- cur.execute("UPDATE threads SET poll = ? WHERE threads.id = ?",(pollid,thread_id))
- cur.executemany(
- "INSERT INTO poll_options (poll,option_idx,text) VALUES (?,?,?)",
- zip(itertools.repeat(pollid),itertools.count(1),polloptions)
- )
- db.commit()
- flash("poll created successfully")
- return success
+ fail = redirect(url_for('thread.config_thread',thread_id=thread_id))
+ success = redirect(url_for('thread.view_thread',thread_id=thread_id))
+ err = None
+ db = get_db()
+ thread = db.execute('select * from threads where id = ?',(thread_id,)).fetchone()
+
+ polltitle = request.form.get('polltitle','').strip()
+ polloptions = [q.strip() for q in request.form.get('polloptions','').split("\n") if len(q.strip()) > 0]
+
+ if thread is None:
+ err = "that thread does not exist"
+ elif g.user is None:
+ err = "you need to be logged in to do that"
+ elif g.user != thread['creator'] and \
+ not has_permission(thread['forum'],g.user,"p_manage_threads"):
+ err = "you can only create polls on threads that you own"
+ elif thread['poll'] is not None:
+ err = "a poll already exists for that thread"
+ elif not len(polltitle) > 0:
+ err = "poll title can't be empty"
+ elif len(polloptions) < 2:
+ err = "you must provide at least 2 options"
+ elif not has_permission(thread['forum'], g.user, "p_create_polls"):
+ err = "you do not have permission to do that"
+
+ if err is not None:
+ flash(err)
+ return fail
+ else:
+ cur = db.cursor()
+ cur.execute("INSERT INTO polls (title) VALUES (?)",(polltitle,))
+ pollid = cur.lastrowid
+ cur.execute("UPDATE threads SET poll = ? WHERE threads.id = ?",(pollid,thread_id))
+ cur.executemany(
+ "INSERT INTO poll_options (poll,option_idx,text) VALUES (?,?,?)",
+ zip(itertools.repeat(pollid),itertools.count(1),polloptions)
+ )
+ db.commit()
+ flash("poll created successfully")
+ return success
@bp.route("/<int:thread_id>/delete_poll",methods=["POST"])
def delete_poll(thread_id):
- fail = redirect(url_for('thread.config_thread',thread_id=thread_id))
- success = redirect(url_for('thread.view_thread',thread_id=thread_id))
- err = None
- db = get_db()
- thread = db.execute('select * from threads where id = ?',(thread_id,)).fetchone()
-
- if thread is None:
- err = "that thread does not exist"
- elif g.user is None:
- err = "you need to be logged in to do that"
- elif g.user != thread['creator'] and not \
- has_permission(thread['forum'], g.user, "p_manage_threads"):
- err = "you can only delete polls on threads that you own"
- elif thread['poll'] is None:
- err = "there is no poll to delete on this thread"
-
- if err is not None:
- flash(err)
- return fail
- else:
- pollid = thread['poll']
- db.execute("UPDATE posts SET vote = NULL WHERE thread = ?",(thread_id,)) # this assumes only max one poll per thread
- db.execute("DELETE FROM votes WHERE poll = ?",(pollid,))
- db.execute("DELETE FROM poll_options WHERE poll = ?",(pollid,))
- db.execute("UPDATE THREADS set poll = NULL WHERE id = ?",(thread_id,))
- db.execute("DELETE FROM polls WHERE id = ?",(pollid,))
- db.commit()
- flash("poll deleted successfully")
- return success
-
+ fail = redirect(url_for('thread.config_thread',thread_id=thread_id))
+ success = redirect(url_for('thread.view_thread',thread_id=thread_id))
+ err = None
+ db = get_db()
+ thread = db.execute('select * from threads where id = ?',(thread_id,)).fetchone()
+
+ if thread is None:
+ err = "that thread does not exist"
+ elif g.user is None:
+ err = "you need to be logged in to do that"
+ elif g.user != thread['creator'] and not \
+ has_permission(thread['forum'], g.user, "p_manage_threads"):
+ err = "you can only delete polls on threads that you own"
+ elif thread['poll'] is None:
+ err = "there is no poll to delete on this thread"
+
+ if err is not None:
+ flash(err)
+ return fail
+ else:
+ pollid = thread['poll']
+ db.execute("UPDATE posts SET vote = NULL WHERE thread = ?",(thread_id,)) # this assumes only max one poll per thread
+ db.execute("DELETE FROM votes WHERE poll = ?",(pollid,))
+ db.execute("DELETE FROM poll_options WHERE poll = ?",(pollid,))
+ db.execute("UPDATE THREADS set poll = NULL WHERE id = ?",(thread_id,))
+ db.execute("DELETE FROM polls WHERE id = ?",(pollid,))
+ db.commit()
+ flash("poll deleted successfully")
+ return success
+
@bp.route("/<int:thread_id>/create_post", methods=("POST",))
def create_post(thread_id):
- if g.user is None:
- flash("you need to log in before you can post")
- db = get_db()
- content = request.form['content']
- thread = db.execute("SELECT * FROM threads WHERE id = ?;",(thread_id,)).fetchone()
- if len(content.strip()) == 0:
- flash("you cannot post an empty message")
- elif not thread:
- flash("that thread does not exist")
- elif not has_permission(thread['forum'], g.user, "p_reply_threads"):
- flash("you do not have permission to do this")
- elif not has_permission(thread['forum'], g.user, "p_view_threads"):
- flash("you do not have permission to do this")
- elif not has_permission(thread['forum'], g.user, "p_vote") \
- and 'poll' in request.form:
- flash("you do not have permission to do this")
- else:
- vote_id = None
- if thread['poll'] is not None:
- pollval = request.form.get('poll')
- try:
- vote_id = register_vote(thread,pollval)
- except ValueError:
- flash("invalid poll form value")
- return redirect(url_for('thread.view_thread',thread_id=thread_id))
-
- cur = db.cursor()
- cur.execute("""
- INSERT INTO posts (thread,author,content,created,vote)
- VALUES (?,?,?,current_timestamp,?);
- """,(thread_id,g.user,content,vote_id))
- post_id = cur.lastrowid
- cur.execute(
- "UPDATE threads SET updated = current_timestamp WHERE id = ?;",
- (thread_id,)
- )
- db.commit()
- forum.set_updated(thread['forum'])
- post = db.execute("select * from posts where id = ?",(post_id,)).fetchone()
- webhooks.do_webhooks_post(thread['forum'],post)
- flash("post posted postfully")
- return redirect(post_jump(post_id))
- return redirect(url_for('thread.view_thread',thread_id=thread_id))
+ if g.user is None:
+ flash("you need to log in before you can post")
+ db = get_db()
+ content = request.form['content']
+ thread = db.execute("SELECT * FROM threads WHERE id = ?;",(thread_id,)).fetchone()
+ if len(content.strip()) == 0:
+ flash("you cannot post an empty message")
+ elif not thread:
+ flash("that thread does not exist")
+ elif not has_permission(thread['forum'], g.user, "p_reply_threads"):
+ flash("you do not have permission to do this")
+ elif not has_permission(thread['forum'], g.user, "p_view_threads"):
+ flash("you do not have permission to do this")
+ elif not has_permission(thread['forum'], g.user, "p_vote") \
+ and 'poll' in request.form:
+ flash("you do not have permission to do this")
+ else:
+ vote_id = None
+ if thread['poll'] is not None:
+ pollval = request.form.get('poll')
+ try:
+ vote_id = register_vote(thread,pollval)
+ except ValueError:
+ flash("invalid poll form value")
+ return redirect(url_for('thread.view_thread',thread_id=thread_id))
+
+ cur = db.cursor()
+ cur.execute("""
+ INSERT INTO posts (thread,author,content,created,vote)
+ VALUES (?,?,?,current_timestamp,?);
+ """,(thread_id,g.user,content,vote_id))
+ post_id = cur.lastrowid
+ cur.execute(
+ "UPDATE threads SET updated = current_timestamp WHERE id = ?;",
+ (thread_id,)
+ )
+ db.commit()
+ forum.set_updated(thread['forum'])
+ post = db.execute("select * from posts where id = ?",(post_id,)).fetchone()
+ webhooks.do_webhooks_post(thread['forum'],post)
+ flash("post posted postfully")
+ return redirect(post_jump(post_id))
+ return redirect(url_for('thread.view_thread',thread_id=thread_id))
@bp.route("/delete_post/<int:post_id>", methods=["GET","POST"])
def delete_post(post_id):
- db = get_db()
- post = db.execute("SELECT * FROM posts WHERE id = ?",(post_id,)).fetchone()
- thread = db.execute("SELECT * FROM threads WHERE id = ?",(post['thread'],)).fetchone()
- if post is None:
- flash("that post doesn't exist")
- return redirect("/")
- if post['author'] != g.user and not has_permission(thread['forum'], g.user, "p_delete_posts"):
- flash("you do not have permission to do that")
- return redirect(url_for("thread.view_thread",thread_id=post["thread"]))
- if request.method == "POST":
- db.execute("""
- UPDATE posts SET
- content = '',
- deleted = 1
- WHERE id = ?""",(post_id,))
- db.commit()
- flash("post deleted deletedly")
- return redirect(url_for("thread.view_thread",thread_id=post["thread"]))
- else:
- return render_template("delete_post.html",post=post)
-
+ db = get_db()
+ post = db.execute("SELECT * FROM posts WHERE id = ?",(post_id,)).fetchone()
+ thread = db.execute("SELECT * FROM threads WHERE id = ?",(post['thread'],)).fetchone()
+ if post is None:
+ flash("that post doesn't exist")
+ return redirect("/")
+ if post['author'] != g.user and not has_permission(thread['forum'], g.user, "p_delete_posts"):
+ flash("you do not have permission to do that")
+ return redirect(url_for("thread.view_thread",thread_id=post["thread"]))
+ if request.method == "POST":
+ db.execute("""
+ UPDATE posts SET
+ content = '',
+ deleted = 1
+ WHERE id = ?""",(post_id,))
+ db.commit()
+ flash("post deleted deletedly")
+ return redirect(url_for("thread.view_thread",thread_id=post["thread"]))
+ else:
+ return render_template("delete_post.html",post=post)
+
@bp.route("/delete_thread/<int:thread_id>", methods=["GET","POST"])
def delete_thread(thread_id):
- db = get_db()
- thread = db.execute("SELECT * FROM threads WHERE id = ?",(thread_id,)).fetchone()
- if thread is None:
- flash("that thread doesn't exist")
- return redirect("/")
- if not has_permission(thread['forum'], g.user, "p_delete_posts"):
- flash("you do not have permission to do that")
- return redirect(url_for("thread.view_thread",thread_id=thread_id))
- if request.method == "POST":
- db.execute("DELETE FROM posts WHERE thread = ?",(thread_id,))
- db.execute("DELETE FROM threads WHERE id = ?",(thread_id,))
- db.commit()
- flash("thread deleted deletedly")
- return redirect(url_for("forum.view_forum",forum_id=thread['forum']))
- else:
- count = db.execute("SELECT num_replies FROM number_of_posts WHERE thread = ?",
- (thread_id,)).fetchone()[0]
- return render_template("delete_thread.html",thread=thread,post_count=count)
-
+ db = get_db()
+ thread = db.execute("SELECT * FROM threads WHERE id = ?",(thread_id,)).fetchone()
+ if thread is None:
+ flash("that thread doesn't exist")
+ return redirect("/")
+ if not has_permission(thread['forum'], g.user, "p_delete_posts"):
+ flash("you do not have permission to do that")
+ return redirect(url_for("thread.view_thread",thread_id=thread_id))
+ if request.method == "POST":
+ db.execute("DELETE FROM posts WHERE thread = ?",(thread_id,))
+ db.execute("DELETE FROM threads WHERE id = ?",(thread_id,))
+ db.commit()
+ flash("thread deleted deletedly")
+ return redirect(url_for("forum.view_forum",forum_id=thread['forum']))
+ else:
+ count = db.execute("SELECT num_replies FROM number_of_posts WHERE thread = ?",
+ (thread_id,)).fetchone()[0]
+ return render_template("delete_thread.html",thread=thread,post_count=count)
+
@bp.route("/edit_post/<int:post_id>",methods=["GET","POST"])
def edit_post(post_id):
- db = get_db()
- post = db.execute("SELECT * FROM posts WHERE id = ?",(post_id,)).fetchone()
- if post is None:
- flash("that post doesn't exist")
- return redirect(url_for('index'))
-
- if post['author'] != g.user:
- flash("you can only edit posts that you created")
- return redirect(url_for("thread.view_thread",thread_id=post['thread']))
- # note: i am writing this while i am very tired, so probably
- # come back and test this properly later
- if request.method == "POST":
- err = None
- newcontent = request.form['newcontent']
- if len(newcontent.strip()) == 0:
- err="post contents can't be empty"
- print(err)
- if err is None:
- db.execute(
- "UPDATE posts SET content = ?, edited = 1, updated = current_timestamp WHERE id = ?",(newcontent,post_id))
- db.commit()
- flash("post edited editiously")
- return redirect(post_jump(post_id))
- else:
- flash(err)
- return render_template("edit_post.html",post=post)
+ db = get_db()
+ post = db.execute("SELECT * FROM posts WHERE id = ?",(post_id,)).fetchone()
+ if post is None:
+ flash("that post doesn't exist")
+ return redirect(url_for('index'))
+
+ if post['author'] != g.user:
+ flash("you can only edit posts that you created")
+ return redirect(url_for("thread.view_thread",thread_id=post['thread']))
+ # note: i am writing this while i am very tired, so probably
+ # come back and test this properly later
+ if request.method == "POST":
+ err = None
+ newcontent = request.form['newcontent']
+ if len(newcontent.strip()) == 0:
+ err="post contents can't be empty"
+ print(err)
+ if err is None:
+ db.execute(
+ "UPDATE posts SET content = ?, edited = 1, updated = current_timestamp WHERE id = ?",(newcontent,post_id))
+ db.commit()
+ flash("post edited editiously")
+ return redirect(post_jump(post_id))
+ else:
+ flash(err)
+ return render_template("edit_post.html",post=post)
@bp.route("/view_post/<int:post_id>")
def view_post(post_id):
- db = get_db()
- post = db.execute("""
- SELECT p.*,h.f_id FROM posts p
- INNER JOIN forum_thread_of_post h ON p.id = h.p_id
- WHERE p.id = ?;
- """,(post_id,)).fetchone()
-
- if post is None:
- abort(404)
-
- if not has_permission(post['f_id'], g.user, "p_view_threads", False):
- abort(403)
-
- return render_template("view_post.html",post=post)
-
-
-
+ db = get_db()
+ post = db.execute("""
+ SELECT p.*,h.f_id FROM posts p
+ INNER JOIN forum_thread_of_post h ON p.id = h.p_id
+ WHERE p.id = ?;
+ """,(post_id,)).fetchone()
+
+ if post is None:
+ abort(404)
+
+ if not has_permission(post['f_id'], g.user, "p_view_threads", False):
+ abort(403)
+
+ return render_template("view_post.html",post=post)
+
+
+
@bp.route("/<int:thread_id>/config",methods=["GET","POST"])
def config_thread(thread_id):
- db = get_db()
- thread = db.execute("select * from threads where id = ?",(thread_id,)).fetchone()
- thread_tags = [r['tag'] for r in db.execute("select tag from thread_tags where thread = ?",(thread_id,)).fetchall()]
- avail_tags = get_avail_tags(thread['forum'])
- err = None
- if g.user is None:
- err = "you need to be logged in to do that"
- elif not has_permission(thread['forum'], g.user, "p_view_threads"):
- err = "you do not have permission to do that"
- elif g.user != thread['creator'] and not has_permission(thread['forum'], g.user, "p_manage_threads"):
- err = "you can only configure threads that you own"
-
- if err is not None:
- flash(err)
- return redirect(url_for("thread.view_thread",thread_id=thread_id))
-
- if request.method == "POST":
- err = []
- if request.form['title'] != thread['title']:
- title = request.form['title']
- if len(title.strip()) == 0:
- err.append("title can't be empty")
- else:
- db.execute("update threads set title = ? where id = ?;",(title,thread_id))
- flash("title updated successfully")
- db.commit()
- changed = False
- for avail_tag in avail_tags:
- tagid = avail_tag['id']
- current = tagid in thread_tags
- wanted = f'tag_{tagid}' in request.form
- if wanted and not current:
- db.execute("insert into thread_tags (thread, tag) values (?,?)",(thread_id,tagid))
- changed = True
- elif current and not wanted:
- db.execute("delete from thread_tags where thread = ? and tag = ?",(thread_id,tagid))
- changed = True
- if changed:
- db.commit()
- flash("tags updated successfully")
-
- if len(err) > 0:
- for e in err:
- flash(e)
- else:
- return redirect(url_for("thread.view_thread",thread_id=thread_id))
-
-
- return render_template("config_thread.html", thread=thread,thread_tags=thread_tags,avail_tags=avail_tags)
-
+ db = get_db()
+ thread = db.execute("select * from threads where id = ?",(thread_id,)).fetchone()
+ thread_tags = [r['tag'] for r in db.execute("select tag from thread_tags where thread = ?",(thread_id,)).fetchall()]
+ avail_tags = get_avail_tags(thread['forum'])
+ err = None
+ if g.user is None:
+ err = "you need to be logged in to do that"
+ elif not has_permission(thread['forum'], g.user, "p_view_threads"):
+ err = "you do not have permission to do that"
+ elif g.user != thread['creator'] and not has_permission(thread['forum'], g.user, "p_manage_threads"):
+ err = "you can only configure threads that you own"
+
+ if err is not None:
+ flash(err)
+ return redirect(url_for("thread.view_thread",thread_id=thread_id))
+
+ if request.method == "POST":
+ err = []
+ if request.form['title'] != thread['title']:
+ title = request.form['title']
+ if len(title.strip()) == 0:
+ err.append("title can't be empty")
+ else:
+ db.execute("update threads set title = ? where id = ?;",(title,thread_id))
+ flash("title updated successfully")
+ db.commit()
+ changed = False
+ for avail_tag in avail_tags:
+ tagid = avail_tag['id']
+ current = tagid in thread_tags
+ wanted = f'tag_{tagid}' in request.form
+ if wanted and not current:
+ db.execute("insert into thread_tags (thread, tag) values (?,?)",(thread_id,tagid))
+ changed = True
+ elif current and not wanted:
+ db.execute("delete from thread_tags where thread = ? and tag = ?",(thread_id,tagid))
+ changed = True
+ if changed:
+ db.commit()
+ flash("tags updated successfully")
+
+ if len(err) > 0:
+ for e in err:
+ flash(e)
+ else:
+ return redirect(url_for("thread.view_thread",thread_id=thread_id))
+
+
+ return render_template("config_thread.html", thread=thread,thread_tags=thread_tags,avail_tags=avail_tags)
+
diff --git a/apioforum/user.py b/apioforum/user.py
index 8fb59ab..cead2fe 100644
--- a/apioforum/user.py
+++ b/apioforum/user.py
@@ -2,7 +2,7 @@
POSTS_PER_PAGE = 28
from flask import (
- Blueprint, render_template, abort, g, flash, redirect, url_for, request
+ Blueprint, render_template, abort, g, flash, redirect, url_for, request
)
from werkzeug.security import check_password_hash, generate_password_hash
@@ -10,72 +10,72 @@ from .db import get_db
import math
bp = Blueprint("user", __name__, url_prefix="/user")
-
+
@bp.route("/<username>")
@bp.route("/<username>/page/<int:page>")
def view_user(username, page=1):
- if page < 1:
- abort(400)
-
- db = get_db()
- user = db.execute("SELECT * FROM users WHERE username = ?;",(username,)).fetchone()
+ if page < 1:
+ abort(400)
+
+ db = get_db()
+ user = db.execute("SELECT * FROM users WHERE username = ?;",(username,)).fetchone()
- if user is None:
- abort(404)
- posts = db.execute("""
- SELECT * FROM posts
- JOIN public_posts ON public_posts.id = posts.id
- WHERE author = ? AND deleted = 0 AND public_posts.public
- ORDER BY created DESC
- LIMIT ? OFFSET ?;""",(username,POSTS_PER_PAGE,(page-1)*POSTS_PER_PAGE,)).fetchall()
- num_posts = db.execute("""
- SELECT count(*) as count FROM posts
- JOIN public_posts ON public_posts.id = posts.id
- WHERE author = ? AND public_posts.public;
- """,(username,)).fetchone()['count']
- max_pageno = math.ceil(num_posts/POSTS_PER_PAGE)
- return render_template(
- "view_user.html",
- user=user,
- posts=posts,
- page=page,
- max_pageno=max_pageno,
- )
+ if user is None:
+ abort(404)
+ posts = db.execute("""
+ SELECT * FROM posts
+ JOIN public_posts ON public_posts.id = posts.id
+ WHERE author = ? AND deleted = 0 AND public_posts.public
+ ORDER BY created DESC
+ LIMIT ? OFFSET ?;""",(username,POSTS_PER_PAGE,(page-1)*POSTS_PER_PAGE,)).fetchall()
+ num_posts = db.execute("""
+ SELECT count(*) as count FROM posts
+ JOIN public_posts ON public_posts.id = posts.id
+ WHERE author = ? AND public_posts.public;
+ """,(username,)).fetchone()['count']
+ max_pageno = math.ceil(num_posts/POSTS_PER_PAGE)
+ return render_template(
+ "view_user.html",
+ user=user,
+ posts=posts,
+ page=page,
+ max_pageno=max_pageno,
+ )
@bp.route("/<username>/edit", methods=["GET","POST"])
def edit_user(username):
- db = get_db()
- user = db.execute("SELECT * FROM users WHERE username = ?;",(username,)).fetchone()
- if user is None:
- abort(404)
- if username != g.user:
- flash("you cannot modify other people")
- return redirect(url_for("user.view_user",username=username))
+ db = get_db()
+ user = db.execute("SELECT * FROM users WHERE username = ?;",(username,)).fetchone()
+ if user is None:
+ abort(404)
+ if username != g.user:
+ flash("you cannot modify other people")
+ return redirect(url_for("user.view_user",username=username))
- if request.method == "POST":
- err = []
- if len(request.form['new_password']) > 0:
- if not check_password_hash(user['password'],request.form['password']):
- err.append("entered password does not match current password")
- else:
- db.execute("update users set password = ? where username = ?",
- (generate_password_hash(request.form["new_password"]), username))
- db.commit()
- flash("password changed changefully")
- if request.form['bio'] != user['bio']:
- if len(request.form['bio'].strip()) == 0:
- err.append("please submit nonempty bio")
- elif len(request.form['bio']) > 4500:
- err.append("bio is too long!!")
- else:
- db.execute("update users set bio = ? where username = ?", (request.form['bio'], username))
- db.commit()
- flash("bio updated successfully")
+ if request.method == "POST":
+ err = []
+ if len(request.form['new_password']) > 0:
+ if not check_password_hash(user['password'],request.form['password']):
+ err.append("entered password does not match current password")
+ else:
+ db.execute("update users set password = ? where username = ?",
+ (generate_password_hash(request.form["new_password"]), username))
+ db.commit()
+ flash("password changed changefully")
+ if request.form['bio'] != user['bio']:
+ if len(request.form['bio'].strip()) == 0:
+ err.append("please submit nonempty bio")
+ elif len(request.form['bio']) > 4500:
+ err.append("bio is too long!!")
+ else:
+ db.execute("update users set bio = ? where username = ?", (request.form['bio'], username))
+ db.commit()
+ flash("bio updated successfully")
- if len(err) > 0:
- for e in err:
- flash(e)
- else:
- return redirect(url_for("user.view_user",username=username))
-
- return render_template("user_settings.html",user=user)
+ if len(err) > 0:
+ for e in err:
+ flash(e)
+ else:
+ return redirect(url_for("user.view_user",username=username))
+
+ return render_template("user_settings.html",user=user)
diff --git a/apioforum/util.py b/apioforum/util.py
index 64bdf20..8f836d1 100644
--- a/apioforum/util.py
+++ b/apioforum/util.py
@@ -5,12 +5,12 @@ import hashlib
# same algorithm as xep-0392
def gen_colour(s):
- b=s.encode("utf-8")
- h=hashlib.sha1(b)
- two_bytes=h.digest()[:2]
- val = int.from_bytes(two_bytes, 'little')
- angle = 360*(val/65536)
- col = hsluv.hsluv_to_hex([angle, 80, 70])
- return col
-
-
+ b=s.encode("utf-8")
+ h=hashlib.sha1(b)
+ two_bytes=h.digest()[:2]
+ val = int.from_bytes(two_bytes, 'little')
+ angle = 360*(val/65536)
+ col = hsluv.hsluv_to_hex([angle, 80, 70])
+ return col
+
+
diff --git a/apioforum/webhooks.py b/apioforum/webhooks.py
index f7387be..a09f951 100644
--- a/apioforum/webhooks.py
+++ b/apioforum/webhooks.py
@@ -6,211 +6,211 @@ from flask import url_for, flash
def abridge(text,maxlen=20):
- if len(text) > maxlen+3:
- return text[:maxlen]+"..."
- else:
- return text
+ if len(text) > maxlen+3:
+ return text[:maxlen]+"..."
+ else:
+ return text
webhook_types = {}
def webhook_type(t):
- def inner(cls):
- webhook_types[t] = cls
- return cls
- return inner
+ def inner(cls):
+ webhook_types[t] = cls
+ return cls
+ return inner
class WebhookType(abc.ABC):
- def __init__(self, url, wh_id):
- self.url = url
- self.wh_id = wh_id
+ def __init__(self, url, wh_id):
+ self.url = url
+ self.wh_id = wh_id
- @abc.abstractmethod
- def on_new_thread(self,thread):
- pass
- @abc.abstractmethod
- def on_new_post(self,post):
- pass
+ @abc.abstractmethod
+ def on_new_thread(self,thread):
+ pass
+ @abc.abstractmethod
+ def on_new_post(self,post):
+ pass
def get_webhooks(forum_id):
- db = get_db()
- # todo inheritance (if needed)
- webhooks = db.execute("select * from webhooks where webhooks.forum = ?;",(forum_id,)).fetchall()
-
- for wh in webhooks:
- wh_type = wh['type']
- if wh_type not in webhook_types:
- print(f"unknown webhook type {wh_type}")
- continue
- wh_url = wh['url']
- wo = webhook_types[wh_type](wh_url, wh['id'])
- yield wo
+ db = get_db()
+ # todo inheritance (if needed)
+ webhooks = db.execute("select * from webhooks where webhooks.forum = ?;",(forum_id,)).fetchall()
+
+ for wh in webhooks:
+ wh_type = wh['type']
+ if wh_type not in webhook_types:
+ print(f"unknown webhook type {wh_type}")
+ continue
+ wh_url = wh['url']
+ wo = webhook_types[wh_type](wh_url, wh['id'])
+ yield wo
def do_webhooks_thread(forum_id,thread):
- for wh in get_webhooks(forum_id):
- try:
- wh.on_new_thread(thread)
- except Exception as e:
- #raise e
- flash(f"error executing webhook with id {wh.wh_id}")
+ for wh in get_webhooks(forum_id):
+ try:
+ wh.on_new_thread(thread)
+ except Exception as e:
+ #raise e
+ flash(f"error executing webhook with id {wh.wh_id}")
def do_webhooks_post(forum_id,post):
- for wh in get_webhooks(forum_id):
- try:
- wh.on_new_post(post)
- except Exception as e:
- #raise e
- flash(f"error executing webhook with id {wh.wh_id}")
+ for wh in get_webhooks(forum_id):
+ try:
+ wh.on_new_post(post)
+ except Exception as e:
+ #raise e
+ flash(f"error executing webhook with id {wh.wh_id}")
@webhook_type("fake")
class FakeWebhook(WebhookType):
- def on_new_post(self, post):
- print(f'fake wh {self.url} post {post["id"]}')
- def on_new_thread(self, thread):
- print(f'fake wh {self.url} thread {thread["id"]}')
+ def on_new_post(self, post):
+ print(f'fake wh {self.url} post {post["id"]}')
+ def on_new_thread(self, thread):
+ print(f'fake wh {self.url} thread {thread["id"]}')
@webhook_type("discord")
class DiscordWebhook(WebhookType):
- def send(self,payload):
- headers = {
- "User-Agent":"apioforum (https://g.gh0.pw/apioforum, v0.0)",
- "Content-Type":"application/json",
- }
- req = urllib.request.Request(
- self.url,
- json.dumps(payload).encode("utf-8"),
- headers
- )
- # todo: read response and things
- urllib.request.urlopen(req)
- #try:
- # res = urllib.request.urlopen(req)
- #except urllib.error.HTTPError as e:
- # print(f"error {e.code} {e.read()}")
- #else:
- # print(f"succ {res.read()}")
-
- @staticmethod
- def field(name,value):
- return {"name":name,"value":value,"inline":True}
-
- def on_new_thread(self,thread):
- f = self.field
- db = get_db()
- forum = db.execute("select * from forums where id = ?",(thread['forum'],)).fetchone()
- username = thread['creator']
- userpage = url_for('user.view_user',username=username,_external=True)
-
- forumpage = url_for('forum.view_forum',forum_id=forum['id'],_external=True)
-
- post = db.execute("select * from posts where thread = ? order by id asc limit 1",(thread['id'],)).fetchone()
-
- payload = {
- "username":"apioforum",
- "avatar_url":"https://d.gh0.pw/lib/exe/fetch.php?media=wiki:logo.png",
- "embeds":[
- {
- "title":"new thread: "+thread['title'],
- "description":abridge(post['content']),
- "url": url_for('thread.view_thread',thread_id=thread['id'],_external=True),
- "color": 0xff00ff,
- "fields":[
- f('author',f"[{username}]({userpage})"),
- f('forum',f"[{forum['name']}]({forumpage})"),
- ],
- "footer":{
- "text":thread['created'].isoformat(' '),
- },
- },
- ],
- }
- self.send(payload)
-
- def on_new_post(self,post):
- from .thread import post_jump
- f = self.field
- db = get_db()
-
- thread = db.execute("select * from threads where id = ?",(post['thread'],)).fetchone()
- threadpage = url_for('thread.view_thread',thread_id=thread['id'],_external=True)
-
- forum = db.execute("select * from forums where id = ?",(thread['forum'],)).fetchone()
- forumpage = url_for('forum.view_forum',forum_id=forum['id'],_external=True)
-
- username = post['author']
- userpage = url_for('user.view_user',username=username,_external=True)
-
- payload = {
- "username":"apioforum",
- "avatar_url":"https://d.gh0.pw/lib/exe/fetch.php?media=wiki:logo.png",
- "embeds":[
- {
- "title":"re: "+thread['title'],
- "description":abridge(post['content']),
- "url": post_jump(post['id'],external=True),
- "color": 0x00ffff,
- "fields":[
- f('author',f"[{username}]({userpage})"),
- f('thread',f"[{thread['title']}]({threadpage})"),
- f('forum',f"[{forum['name']}]({forumpage})"),
- ],
- "footer":{
- "text":post['created'].isoformat(' '),
- },
- },
- ],
- }
- self.send(payload)
+ def send(self,payload):
+ headers = {
+ "User-Agent":"apioforum (https://g.gh0.pw/apioforum, v0.0)",
+ "Content-Type":"application/json",
+ }
+ req = urllib.request.Request(
+ self.url,
+ json.dumps(payload).encode("utf-8"),
+ headers
+ )
+ # todo: read response and things
+ urllib.request.urlopen(req)
+ #try:
+ # res = urllib.request.urlopen(req)
+ #except urllib.error.HTTPError as e:
+ # print(f"error {e.code} {e.read()}")
+ #else:
+ # print(f"succ {res.read()}")
+
+ @staticmethod
+ def field(name,value):
+ return {"name":name,"value":value,"inline":True}
+
+ def on_new_thread(self,thread):
+ f = self.field
+ db = get_db()
+ forum = db.execute("select * from forums where id = ?",(thread['forum'],)).fetchone()
+ username = thread['creator']
+ userpage = url_for('user.view_user',username=username,_external=True)
+
+ forumpage = url_for('forum.view_forum',forum_id=forum['id'],_external=True)
+
+ post = db.execute("select * from posts where thread = ? order by id asc limit 1",(thread['id'],)).fetchone()
+
+ payload = {
+ "username":"apioforum",
+ "avatar_url":"https://d.gh0.pw/lib/exe/fetch.php?media=wiki:logo.png",
+ "embeds":[
+ {
+ "title":"new thread: "+thread['title'],
+ "description":abridge(post['content']),
+ "url": url_for('thread.view_thread',thread_id=thread['id'],_external=True),
+ "color": 0xff00ff,
+ "fields":[
+ f('author',f"[{username}]({userpage})"),
+ f('forum',f"[{forum['name']}]({forumpage})"),
+ ],
+ "footer":{
+ "text":thread['created'].isoformat(' '),
+ },
+ },
+ ],
+ }
+ self.send(payload)
+
+ def on_new_post(self,post):
+ from .thread import post_jump
+ f = self.field
+ db = get_db()
+
+ thread = db.execute("select * from threads where id = ?",(post['thread'],)).fetchone()
+ threadpage = url_for('thread.view_thread',thread_id=thread['id'],_external=True)
+
+ forum = db.execute("select * from forums where id = ?",(thread['forum'],)).fetchone()
+ forumpage = url_for('forum.view_forum',forum_id=forum['id'],_external=True)
+
+ username = post['author']
+ userpage = url_for('user.view_user',username=username,_external=True)
+
+ payload = {
+ "username":"apioforum",
+ "avatar_url":"https://d.gh0.pw/lib/exe/fetch.php?media=wiki:logo.png",
+ "embeds":[
+ {
+ "title":"re: "+thread['title'],
+ "description":abridge(post['content']),
+ "url": post_jump(post['id'],external=True),
+ "color": 0x00ffff,
+ "fields":[
+ f('author',f"[{username}]({userpage})"),
+ f('thread',f"[{thread['title']}]({threadpage})"),
+ f('forum',f"[{forum['name']}]({forumpage})"),
+ ],
+ "footer":{
+ "text":post['created'].isoformat(' '),
+ },
+ },
+ ],
+ }
+ self.send(payload)
@webhook_type("apionet")
class ApionetWebhook(WebhookType):
- MAXMSGLEN = 420
+ MAXMSGLEN = 420
- # using 'url' as path of socket to send to.
- def send(self,payload):
- import socket
- s = socket.socket(socket.AF_UNIX,socket.SOCK_DGRAM)
- s.sendto(payload.encode("utf-8"),self.url)
- s.close()
+ # using 'url' as path of socket to send to.
+ def send(self,payload):
+ import socket
+ s = socket.socket(socket.AF_UNIX,socket.SOCK_DGRAM)
+ s.sendto(payload.encode("utf-8"),self.url)
+ s.close()
- def append_url(self,rest,url):
- ub = url.encode("utf-8")
- max_rlen = self.MAXMSGLEN - len(ub) - 1
-
- while len(rest.encode("utf-8")) > max_rlen:
- # chop off characters until short enough
- rest = rest[:-1]
+ def append_url(self,rest,url):
+ ub = url.encode("utf-8")
+ max_rlen = self.MAXMSGLEN - len(ub) - 1
+
+ while len(rest.encode("utf-8")) > max_rlen:
+ # chop off characters until short enough
+ rest = rest[:-1]
- return rest + " " + url
+ return rest + " " + url
- def on_new_thread(self,thread):
- # copy paste from above. the great refactor will fix this all
- db = get_db()
- forum = db.execute("select * from forums where id = ?",(thread['forum'],)).fetchone()
- username = thread['creator'][:30]
- post = db.execute("select * from posts where thread = ? order by id asc limit 1",(thread['id'],)).fetchone()
- url = url_for('thread.view_thread',thread_id=thread['id'],_external=True)
+ def on_new_thread(self,thread):
+ # copy paste from above. the great refactor will fix this all
+ db = get_db()
+ forum = db.execute("select * from forums where id = ?",(thread['forum'],)).fetchone()
+ username = thread['creator'][:30]
+ post = db.execute("select * from posts where thread = ? order by id asc limit 1",(thread['id'],)).fetchone()
+ url = url_for('thread.view_thread',thread_id=thread['id'],_external=True)
- p = ""
- if forum['id'] != 1:
- p = f" in {forum['name']}"
- payload = f"new thread{p}: [{abridge(thread['title'],50)}] {username}: {abridge(post['content'],75)}"
- self.send(self.append_url(payload,"("+url+")"))
+ p = ""
+ if forum['id'] != 1:
+ p = f" in {forum['name']}"
+ payload = f"new thread{p}: [{abridge(thread['title'],50)}] {username}: {abridge(post['content'],75)}"
+ self.send(self.append_url(payload,"("+url+")"))
- def on_new_post(self,post):
- from .thread import post_jump
- db = get_db()
+ def on_new_post(self,post):
+ from .thread import post_jump
+ db = get_db()
- thread = db.execute("select * from threads where id = ?",(post['thread'],)).fetchone()
- forum = db.execute("select * from forums where id = ?",(thread['forum'],)).fetchone()
- username = post['author'][:30]
+ thread = db.execute("select * from threads where id = ?",(post['thread'],)).fetchone()
+ forum = db.execute("select * from forums where id = ?",(thread['forum'],)).fetchone()
+ username = post['author'][:30]
- url = post_jump(post['id'],external=True)
+ url = post_jump(post['id'],external=True)
- payload = f"[{abridge(thread['title'],50)}] {username}: {abridge(post['content'],75)}"
- self.send(self.append_url(payload,"("+url+")"))
+ payload = f"[{abridge(thread['title'],50)}] {username}: {abridge(post['content'],75)}"
+ self.send(self.append_url(payload,"("+url+")"))