Add admin panel, arbitrary queries

This commit is contained in:
David Hoppenbrouwers
2022-10-08 23:40:35 +02:00
parent 5773bce507
commit e3af03bbac
9 changed files with 290 additions and 10 deletions

View File

@@ -340,5 +340,52 @@ class DB:
# User already exists, probably # User already exists, probably
return False return False
def get_users(self):
return self._db().execute('''
select user_id, name, join_time, role, banned_until
from users
''',
)
def set_forum_name(self, forum_id, name):
return self.change_one('''
update forums
set name = ?
where forum_id = ?
''',
(name, forum_id)
)
def set_forum_description(self, forum_id, description):
return self.change_one('''
update forums
set description = ?
where forum_id = ?
''',
(description, forum_id)
)
def add_forum(self, name, description):
db = self._db()
db.execute('''
insert into forums(name, description)
values (?, ?)
''',
(name, description)
)
db.commit()
def change_one(self, query, values):
db = self._db()
c = db.cursor()
c.execute(query, values)
if c.rowcount > 0:
db.commit()
return True
return False
def query(self, q):
return self._db().execute(q)
def _db(self): def _db(self):
return sqlite3.connect(self.conn) return sqlite3.connect(self.conn)

65
main.py
View File

@@ -308,6 +308,67 @@ def register():
answer = answer, answer = answer,
) )
@app.route('/admin/')
def admin():
user = get_user()
if user is None:
return redirect(url_for('login'))
if not user.is_admin():
return '<h1>Forbidden</h1>', 403
return render_template(
'admin/index.html',
title = 'Admin panel',
forums = db.get_forums(),
users = db.get_users(),
)
@app.route('/admin/query/', methods = ['GET', 'POST'])
def admin_query():
user = get_user()
if user is None:
return redirect(url_for('login'))
if not user.is_admin():
return '<h1>Forbidden</h1>', 403
try:
rows = db.query(request.form['q']) if request.method == 'POST' else []
except Exception as e:
flash(e, 'error')
rows = []
return render_template(
'admin/query.html',
title = 'Query',
rows = rows,
)
@app.route('/admin/forum/<int:forum_id>/edit/<string:what>/', methods = ['POST'])
def admin_edit_forum(forum_id, what):
try:
if what == 'description':
res = db.set_forum_description(forum_id, request.form['description'].replace('\r', ''))
elif what == 'name':
res = db.set_forum_name(forum_id, request.form['name'])
else:
flash(f'Unknown property "{what}"', 'error')
res = None
if res is True:
flash(f'Updated {what}', 'success')
elif res is False:
flash(f'Failed to update {what}', 'error')
except Exception as e:
flash(e, 'error')
return redirect(url_for('admin'))
@app.route('/admin/forum/new/', methods = ['POST'])
def admin_new_forum():
try:
db.add_forum(request.form['name'], request.form['description'].replace('\r', ''))
flash('Added forum', 'success')
except Exception as e:
flash(str(e), 'error')
return redirect(url_for('admin'))
class Comment: class Comment:
def __init__(self, id, author_id, author, text, create_time, modify_time, parent_id): def __init__(self, id, author_id, author, text, create_time, modify_time, parent_id):
@@ -407,6 +468,9 @@ def utility_processor():
# This shouldn't be reachable, but it's still better to return something # This shouldn't be reachable, but it's still better to return something
return "incredibly long ago" return "incredibly long ago"
def format_time(t):
return datetime.utcfromtimestamp(t / 10 ** 9)
def minimd(text): def minimd(text):
# Replace angle brackets to prevent XSS # Replace angle brackets to prevent XSS
# Also replace ampersands to prevent surprises. # Also replace ampersands to prevent surprises.
@@ -420,6 +484,7 @@ def utility_processor():
return { return {
'format_since': format_since, 'format_since': format_since,
'format_time': format_time,
'minimd': minimd, 'minimd': minimd,
} }

View File

@@ -5,7 +5,8 @@ create table users (
email varchar(254), email varchar(254),
about text not null default '', about text not null default '',
join_time integer not null, join_time integer not null,
role integer not null default 0 role integer not null default 0,
banned_until integer
); );
create table threads ( create table threads (
@@ -34,10 +35,9 @@ create table comments (
); );
create table forums ( create table forums (
forum_id integer unique not null primary key autoincrement, forum_id integer unique not null primary key autoincrement,
name varchar(64) not null, name varchar(64) not null,
description text, description text not null default ''
allowed_roles_mask integer not null
); );
-- Both of these speed up searches significantly if there are many threads or comments. -- Both of these speed up searches significantly if there are many threads or comments.

45
templates/admin/base.html Normal file
View File

@@ -0,0 +1,45 @@
{# Don't use the default theme to emphasize this page is special -#}
<!doctype html>
<head>
<title>{{ title }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta content="utf-8" http-equiv="encoding">
<style>
body {
font-family: sans-serif;
}
.flash.success {
background-color: lightgreen;
border-radius: 5px;
padding: 8px;
}
.flash.error {
background-color: #ff4646;
border-radius: 5px;
padding: 8px;
}
table {
border-collapse: collapse;
width: 80%;
}
th, td {
border: 1px solid;
padding: 5px;
text-align: left;
}
textarea {
width: 95%;
}
</style>
</head>
<body>
<h1>{{ title }}</h1>
<p>
<a href="{{ url_for('admin') }}">Admin panel</a>
<a href="{{ url_for('index') }}">Home page</a>
</p>
{%- for category, msg in get_flashed_messages(True) -%}
<p class="flash {{ category }}">{{ msg }}</p>
{%- endfor %}
{%- block content %}{% endblock -%}
</body>

101
templates/admin/index.html Normal file
View File

@@ -0,0 +1,101 @@
{% extends 'admin/base.html' -%}
{% block content -%}
<h2>Query</h2>
<p>&#9888; Only use queries if you know what you're doing &#9888;</p>
<form action=query/ method=post>
<input type=text name=q placeholder="SELECT * from users">
<input type=submit value=Submit>
</form>
<h2>Configuration</h2>
<table>
<tr>
<td>Name</td>
<td>
<input type=text value="Agrepy">
<input type=submit value=Set>
</td>
</tr>
<tr>
<td>Registrations enabled</td>
<td>
<input type=checkbox checked>
<input type=submit value=Set>
</td>
</tr>
</table>
<h2>Forums</h2>
<table>
<tr>
<th>ID</th>
<th>Name</th>
<th>Description</th>
<th>Actions</th>
</tr>
{% for id, name, description, _, _, _ in forums %}
<tr>
<td>{{ id }}</td>
<td>
<form method=post action="forum/{{ id }}/edit/name/">
<input type=text name=name value="{{ name }}"</input>
<input type=submit value="Set name">
</form>
<td>
<form method=post action="forum/{{ id }}/edit/description/">
<textarea name=description>{{ description }}</textarea>
<input type=submit value="Set description">
</form>
</td>
<td><a href="#">Remove</a></td>
</tr>
{% endfor %}
</table>
<h3>Add forum</h3>
<form method=post action="forum/new/">
<table>
<tr>
<td>Name</td>
<td><input type=text name=name></td>
</tr>
<tr>
<td>Description</td>
<td><textarea name=description></textarea></td>
</tr>
</table>
<input type=submit value="Add forum">
</form>
<h2>Users</h2>
<table>
<tr>
<th>ID</th>
<th>Name</th>
<th>Join date</th>
<th>Role</th>
<th>Banned until</th>
<th>Actions</th>
</tr>
{%- for id, name, join_date, role, banned_until in users -%}
<tr>
<td>{{ id }}</td>
<td>{{ name }}</td>
<td>{{ format_time(join_date) }}</td>
<td>{{ ['user', 'moderator', 'admin'][role] if role >= 0 and role <= 2 else role }}</td>
<td>{{ '' if banned_until is none else format_time(banned_until) }}</td>
<td>
<form method=post action="user/{{ id }}/edit/ban/">
<input type=number name=days placeholder=days>
<input type=time name=time>
<input type=submit value=Ban>
</form>
<form method=post action="user/{{ id }}/edit/role/">
<select name=role>
<option value=0>user</option>
<option value=1>moderator</option>
<option value=2>admin</option>
</select>
<input type=submit value="Set role">
</form>
</td>
</tr>
{%- endfor -%}
</table>
{%- endblock %}

View File

@@ -0,0 +1,17 @@
{% extends 'admin/base.html' -%}
{% block content -%}
<form method=post>
<input type=text name=q placeholder="SELECT * from users">
<input type=submit value=Submit>
</form>
<table style="font-family:monospace;white-space:pre">
{%- for r in rows -%}
<tr>
{%- for c in r -%}
<td>{{ c }}</td>
{%- endfor -%}
</tr>
{%- endfor -%}
</table>
{%- endblock -%}

View File

@@ -12,6 +12,10 @@
{% if user is not none %} {% if user is not none %}
<a href="{{ url_for('user_edit') }}">{{ user.name }}</a> <a href="{{ url_for('user_edit') }}">{{ user.name }}</a>
<span>|</span> <span>|</span>
{% if user.is_admin() %}
<a href="{{ url_for('admin') }}">Admin panel</a>
<span>|</span>
{% endif %}
<a href="{{ url_for('logout') }}">Logout</a> <a href="{{ url_for('logout') }}">Logout</a>
{% else %} {% else %}
<a href="{{ url_for('register') }}">Register</a> <a href="{{ url_for('register') }}">Register</a>

View File

@@ -10,7 +10,7 @@
<tr> <tr>
<td> <td>
<p><a href="{{ url_for('forum', forum_id = id) }}"><b>{{ name }}</b></a></p> <p><a href="{{ url_for('forum', forum_id = id) }}"><b>{{ name }}</b></a></p>
<p>{{ description }}</p> <p>{{ minimd(description) | safe }}</p>
</td> </td>
{% if t_id %} {% if t_id %}
<td> <td>
@@ -21,6 +21,6 @@
<td>No threads</td> <td>No threads</td>
{% endif %} {% endif %}
</tr> </tr>
{% endfor %} {%- endfor -%}
</table> </table>
{% endblock %} {% endblock %}

View File

@@ -12,15 +12,16 @@ insert into users (name, password, email, join_time) values (
"bar@foo.baz", "bar@foo.baz",
0 0
); );
insert into users (name, password, join_time) values ( insert into users (name, password, join_time, role) values (
"bazzers", "bazzers",
-- e -- e
"$argon2id$v=19$m=65536,t=3,p=4$9v5fS2ktxTinNEbIGUOoFQ$LMdEuAuuTCJ7utOE88+nXn7o6R/DEKY8ZA6wV+YkVGQ", "$argon2id$v=19$m=65536,t=3,p=4$9v5fS2ktxTinNEbIGUOoFQ$LMdEuAuuTCJ7utOE88+nXn7o6R/DEKY8ZA6wV+YkVGQ",
0 0,
2
); );
insert into forums (name, description, allowed_roles_mask) insert into forums (name, description)
values ("Earth", "The totality of all space and time; all that is, has been, and will be.", 1); values ("Earth", "The totality of all space and time; all that is, has been, and will be.");
insert into threads (author_id, forum_id, create_time, modify_time, update_time, title, text) insert into threads (author_id, forum_id, create_time, modify_time, update_time, title, text)
values (1, 1, 0, 0, 0, "Hello, world!", values (1, 1, 0, 0, 0, "Hello, world!",