summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorubq323 <ubq323>2021-06-19 09:45:53 +0000
committerubq323 <ubq323>2021-06-19 09:45:53 +0000
commit3f001e130222c9bc2f0048d5941eeaed22940449 (patch)
tree2707d3b0b883df00de52415a2c0ad31ffdb8dd15
parentcb628d88e54267b9b46a1cfc37f24f0c5bb7f2cb (diff)
parentb887340ebc120923178ff20cc79f43d7bc19f62a (diff)
merge trunk into rss so we are not based on a very old versionrss
-rw-r--r--apioforum/__init__.py18
-rw-r--r--apioforum/admin.py14
-rw-r--r--apioforum/auth.py9
-rw-r--r--apioforum/db.py18
-rw-r--r--apioforum/forum.py12
-rw-r--r--apioforum/permissions.py43
-rw-r--r--apioforum/static/style.css61
-rw-r--r--apioforum/templates/admin/admin_page.html15
-rw-r--r--apioforum/templates/base.html17
-rw-r--r--apioforum/templates/common.html11
-rw-r--r--apioforum/templates/config_thread.html32
-rw-r--r--apioforum/templates/search_results.html18
-rw-r--r--apioforum/templates/user_settings.html28
-rw-r--r--apioforum/templates/view_forum.html34
-rw-r--r--apioforum/templates/view_thread.html18
-rw-r--r--apioforum/templates/view_user.html37
-rw-r--r--apioforum/thread.py58
-rw-r--r--apioforum/user.py65
18 files changed, 472 insertions, 36 deletions
diff --git a/apioforum/__init__.py b/apioforum/__init__.py
index 9a989cc..ab96d2a 100644
--- a/apioforum/__init__.py
+++ b/apioforum/__init__.py
@@ -1,7 +1,7 @@
# boilerplate boilerplate boilerplate
# yay
-from flask import Flask
+from flask import Flask, request
from .db import get_db
import os
@@ -19,6 +19,8 @@ def create_app():
from . import db
db.init_app(app)
+ from . import permissions
+ permissions.init_app(app)
from . import auth
app.register_blueprint(auth.bp)
@@ -33,6 +35,20 @@ def create_app():
app.jinja_env.filters["fuzzy"]=fuzzy
app.jinja_env.filters["rss_datetime"]=rss_datetime
+ from . import admin
+ app.register_blueprint(admin.bp)
+
+ from . import user
+ app.register_blueprint(user.bp)
+
+ @app.context_processor
+ def path_for_next():
+ p = request.path
+ if len(request.query_string) > 0:
+ p += "?" + request.query_string.decode("utf-8")
+ return dict(path_for_next=p)
+
+
app.add_url_rule("/",endpoint="index")
return app
diff --git a/apioforum/admin.py b/apioforum/admin.py
new file mode 100644
index 0000000..b11b735
--- /dev/null
+++ b/apioforum/admin.py
@@ -0,0 +1,14 @@
+from flask import (
+ Blueprint, render_template
+)
+from .db import get_db
+from .permissions import admin_required
+
+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)
diff --git a/apioforum/auth.py b/apioforum/auth.py
index 547f15e..dae7b03 100644
--- a/apioforum/auth.py
+++ b/apioforum/auth.py
@@ -5,6 +5,7 @@ from flask import (
from werkzeug.security import check_password_hash, generate_password_hash
from .db import get_db
import functools
+import datetime
bp = Blueprint("auth", __name__, url_prefix="/auth")
@@ -57,8 +58,8 @@ def register():
if err is None:
db.execute(
- "INSERT INTO users (username, password) VALUES (?,?);",
- (username,generate_password_hash(password))
+ "INSERT INTO users (username, password, joined) VALUES (?,?,?);",
+ (username,generate_password_hash(password),datetime.datetime.now())
)
db.commit()
flash("successfully created account")
@@ -81,14 +82,17 @@ def load_user():
username = session.get("user")
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):
@@ -112,3 +116,4 @@ def cool():
@login_required
def cooler():
return "bee"
+
diff --git a/apioforum/db.py b/apioforum/db.py
index c24aa0e..910118d 100644
--- a/apioforum/db.py
+++ b/apioforum/db.py
@@ -65,7 +65,25 @@ 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);
END;
+""",
+"""
+CREATE TABLE tags (
+ 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)
+);
+""",
+"""CREATE INDEX thread_tags_thread ON thread_tags (thread);""",
+"""ALTER TABLE users ADD COLUMN admin INT NOT NULL DEFAULT 0""",
"""
+ALTER TABLE users ADD COLUMN bio TEXT;
+ALTER TABLE users ADD COLUMN joined TIMESTAMP;
+""",
]
def init_db():
diff --git a/apioforum/forum.py b/apioforum/forum.py
index f3354c3..a7f4719 100644
--- a/apioforum/forum.py
+++ b/apioforum/forum.py
@@ -5,12 +5,14 @@ from flask import (
Blueprint, render_template, request,
g, redirect, url_for, flash, Response
)
+
from .db import get_db
from .mdrender import render
from .thread import post_jump
bp = Blueprint("forum", __name__, url_prefix="/")
+
@bp.route("/")
def view_forum():
db = get_db()
@@ -22,7 +24,15 @@ def view_forum():
GROUP BY threads.id
ORDER BY threads.updated DESC;
""").fetchall()
- return render_template("view_forum.html",threads=threads)
+ thread_tags = {}
+ #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 = ?;
+ """,(thread['id'],)).fetchall()
+ return render_template("view_forum.html",threads=threads,thread_tags=thread_tags)
@bp.route("/create_thread",methods=("GET","POST"))
def create_thread():
diff --git a/apioforum/permissions.py b/apioforum/permissions.py
new file mode 100644
index 0000000..816936c
--- /dev/null
+++ b/apioforum/permissions.py
@@ -0,0 +1,43 @@
+from flask import (
+ g, redirect, url_for, flash
+)
+import functools
+import click
+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
+
+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
+
+@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()
+
+def init_app(app):
+ app.cli.add_command(make_admin)
+ app.context_processor(lambda: dict(is_admin=is_admin()))
+
+
diff --git a/apioforum/static/style.css b/apioforum/static/style.css
index 141cb2e..c4a6d40 100644
--- a/apioforum/static/style.css
+++ b/apioforum/static/style.css
@@ -1,4 +1,4 @@
-body { font-family: sans-serif }
+body { font-family: sans-serif; word-wrap: break-word; }
:root {
--alternating-colour-even: hsl(0,0%,96%);
@@ -16,12 +16,12 @@ body { font-family: sans-serif }
}
.post:last-of-type { border-bottom: 1px solid black; }
-.post-heading {
- color: hsl(0,0%,25%);
- font-size: smaller;
+.post-heading { font-size: smaller; }
+.post-heading,.post-heading .username,.post-heading a:visited {
+ color: hsl(0,0%,25%);
}
.post-heading-em { font-weight: bold; }
-.post-content > * { margin-bottom: 8px; margin-top: 8px; }
+.post-content * { margin-bottom: 8px; margin-top: 8px; }
.post-content > *:first-child { margin-top: 2px }
.post-content > *:last-child { margin-bottom: 0}
.post-content { padding: 4px }
@@ -30,6 +30,31 @@ body { font-family: sans-serif }
.post-anchor-link { color: hsl(0,0%,25%); }
+.thread-top-bar, .user-top-bar {
+ margin-bottom: 4px;
+}
+
+.thread-top-bar-b {
+ float: right;
+ margin-right: -2px;
+}
+
+.thread-top-bar-b .tag {
+ font-size: .9rem;
+}
+
+.user_info {
+ border: 1px solid black;
+ background-color: var(--alternating-colour-even);
+ width: 100%;
+ padding: 4px;
+}
+.user_bio_quote { width: max-content; max-width: 100% }
+.user_bio_attribution { text-align: right; font-style: italic; }
+
+dt { font-weight: bold }
+
+
.un-col-1 { color: hsl(0, 100%, 30%) }
.un-col-2 { color: hsl(22.5, 100%, 30%) }
.un-col-3 { color: hsl(45.0, 100%, 30%) }
@@ -69,7 +94,7 @@ nav .links { display: flex; }
.threadlisting { display: contents }
.threadlistings {
display: grid;
- grid-template-columns: 2fr 0.8fr 1.2fr 1.2fr 0.8fr 0.4fr;
+ grid-template-columns: 2fr repeat(5,1fr) 0.4fr;
}
.threadlisting-part {
@@ -81,6 +106,9 @@ nav .links { display: flex; }
border-bottom: 1px solid black;
}
+ .only-small { display: none !important }
+
+
}
/* small screens */
@@ -101,6 +129,8 @@ nav .links { display: flex; }
border-right: 1px solid black;
border-bottom: 1px solid black;
}
+
+ .only-big { display: none !important }
}
@@ -137,11 +167,12 @@ nav .links { display: flex; }
margin-top: 5px;
}
-@media all and (min-width: 810px ) {
- main {
- width: 800px;
- margin: auto;
- }
+main {
+ max-width: 60ch;
+ margin: auto;
+}
+main.widemain {
+ max-width: 120ch;
}
blockquote {
@@ -152,4 +183,10 @@ blockquote {
.search-form {
display: inline-block;
-} \ No newline at end of file
+}
+
+.tag {
+ font-size: .75rem;
+ padding: 1px 3px;
+ border: 1px solid black;
+}
diff --git a/apioforum/templates/admin/admin_page.html b/apioforum/templates/admin/admin_page.html
new file mode 100644
index 0000000..f48c6c0
--- /dev/null
+++ b/apioforum/templates/admin/admin_page.html
@@ -0,0 +1,15 @@
+{% extends 'base.html' %}
+{% block header %}
+<h1>{% block title %}admin page{% endblock %}</h1>
+{% endblock %}
+
+{% block content %}
+<h2>admins</h2>
+<ul>
+ {% 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/base.html b/apioforum/templates/base.html
index ba96c91..3eb112e 100644
--- a/apioforum/templates/base.html
+++ b/apioforum/templates/base.html
@@ -19,20 +19,25 @@
<p><a href="{{url_for('index')}}">home</a></p>
{% if g.user %}
- <p>{{ g.user }}</p>
+ <p><a href="{{url_for('user.view_user', username=g.user)}}">{{g.user}}</a></p>
+
+ {% if is_admin %}
+ <p><a href="{{url_for('admin.admin_page')}}">admin</a></p>
+ {% endif %}
+
<p>
- <a href="{{ url_for('auth.logout',next=request.path) }}">
+ <a href="{{url_for('auth.logout',next=path_for_next)}}">
logout
</a>
</p>
{% else %}
<p>
- <a href="{{ url_for('auth.login',next=request.path) }}">
+ <a href="{{url_for('auth.login',next=path_for_next)}}">
login
</a>
</p>
<p>
- <a href="{{ url_for('auth.register',next=request.path) }}">
+ <a href="{{url_for('auth.register',next=path_for_next)}}">
register
</a>
</p>
@@ -49,10 +54,14 @@
<div class="flashmsg">{{ msg }}</div>
{% endfor %}
+ {% 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>
</html>
diff --git a/apioforum/templates/common.html b/apioforum/templates/common.html
index 33aee0b..c484a9d 100644
--- a/apioforum/templates/common.html
+++ b/apioforum/templates/common.html
@@ -1,8 +1,13 @@
+{% macro disp_user(username) -%}
+<a href="{{url_for('user.view_user',username=username)}}" class="username">{{username}}</a>
+{%- endmacro %}
+
{% macro disp_post(post, buttons=False) %}
<div class="post" id="post_{{post.id}}">
<div class="post-heading">
<span class="post-heading-a">
- <span class="post-heading-em">{{post.author}}</span> {{ts(post.created)}}
+ <span class="post-heading-em">{{disp_user(post.author)}}</span>
+ {{ts(post.created)}}
{% if post.edited %}
(edited {{ts(post.updated)}})
{% endif %}
@@ -26,3 +31,7 @@
{% macro ts(dt) -%}
<time title="{{dt.isoformat(' ')}}" datetime="{{dt.isoformat(' ')}}">{{dt | fuzzy}}</time>
{%- endmacro %}
+
+{% macro tag(the_tag) -%}
+<span class="tag" style="color: {{the_tag.text_colour}}; background-color: {{the_tag.bg_colour}}">{{the_tag.name}}</span>
+{%- endmacro %}
diff --git a/apioforum/templates/config_thread.html b/apioforum/templates/config_thread.html
new file mode 100644
index 0000000..973fbf5
--- /dev/null
+++ b/apioforum/templates/config_thread.html
@@ -0,0 +1,32 @@
+{% extends 'base.html' %}
+{% from 'common.html' import tag %}
+{% block header %}<h1>{% block title %}configure thread '{{thread.title}}'{% endblock %}</h1>{% endblock %}
+{% block content %}
+<form method="post">
+<fieldset>
+<legend>title</legend>
+<p>if you want to change the title of this thread, make sure you check the "change title?" box.</p>
+<label for="do_title">change title?</label>
+<input type="checkbox" id="do_title" name="do_title"><br>
+<label for="title">thread title</label>
+<input type="text" id="title" name="title" value="{{thread.title}}">
+</fieldset>
+<fieldset>
+<legend>tags</legend>
+<p>if you want to change the tags on this thread, make sure you check the "change tags?" box.</p>
+<label for="do_chtags">change tags?</label>
+<input type="checkbox" name="do_chtags" id="do_chtags"><br>
+<ul>
+ {% for the_tag in avail_tags %}
+ <li>
+ <input type="checkbox" id="tag_{{the_tag.id}}" name="tag_{{the_tag.id}}" {%- if the_tag.id in thread_tags %} checked{% endif %}>
+ <label for="tag_{{the_tag.id}}">#{{the_tag.id}} {{tag(the_tag)}}</label>
+ </li>
+ {% endfor %}
+</ul>
+</fieldset>
+<p>confirm changes?</p>
+<input type="submit" value="confirm">
+<a href="{{url_for('thread.view_thread',thread_id=thread.id)}}">cancel</a>
+</form>
+{% endblock %}
diff --git a/apioforum/templates/search_results.html b/apioforum/templates/search_results.html
index 7035e8f..4d0be2f 100644
--- a/apioforum/templates/search_results.html
+++ b/apioforum/templates/search_results.html
@@ -1,18 +1,30 @@
{% from 'common.html' import disp_post %}
{% extends 'base.html' %}
{% block header %}
-<h1>{%block title %}Results for {{query}}{% endblock %}</h1>
+<h1>{%block title %}results for {{query}}{% endblock %}</h1>
{% endblock %}
{%block content%}
<div class="results">
{% for result in results %}
{% if display_thread_id[loop.index0] %}
- <h3><a href="{{url_for('thread.view_thread', thread_id=result.thread)}}">{{ result.thread_title }}</a></h3>
+ {% 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 %}
- {% call disp_post(result, True) %}
+ {% call disp_post(result, False) %}
{{ rendered_posts[loop.index0] | safe}}
{% endcall %}
{% endfor %}
+
+ {% if results|length > 0 %}
+ </div>
+ {% else %}
+ <p>no results were found for '{{query}}'.</p>
+ {% endif %}
</div>
{% endblock %}
diff --git a/apioforum/templates/user_settings.html b/apioforum/templates/user_settings.html
new file mode 100644
index 0000000..ad93036
--- /dev/null
+++ b/apioforum/templates/user_settings.html
@@ -0,0 +1,28 @@
+{% extends 'base.html' %}
+{% block header %}<h1>{% block title %}user settings{% endblock %}</h1>{% endblock %}
+{% block content %}
+<form method="post">
+<fieldset>
+<legend>change password</legend>
+<p>if you want to change your password, make sure you check the "change password?" box.</p>
+<label for="do_chpass">change password?</label>
+<input type="checkbox" id="do_chpass" name="do_chpass"><br>
+<label for="password">current password</label>
+<input type="text" id="password" name="password"><br>
+<label for="new_password">new password</label>
+<input type="text" id="new_password" name="new_password">
+</fieldset>
+<fieldset>
+<legend>change bio</legend>
+<p>if you want to change your bio, make sure you check the "change bio?" box.</p>
+<label for="do_chbio">change bio?</label>
+<input type="checkbox" name="do_chbio" id="do_chbio"><br>
+<textarea class="new-post-box" name="bio" maxlength="4000">
+ {{- user.bio or "hail GEORGE" -}}
+</textarea>
+</fieldset>
+<p>confirm changes?</p>
+<input type="submit" value="confirm">
+<a href="{{url_for('user.view_user',username=user.username)}}">cancel</a>
+</form>
+{% endblock %}
diff --git a/apioforum/templates/view_forum.html b/apioforum/templates/view_forum.html
index aef01d6..59c594b 100644
--- a/apioforum/templates/view_forum.html
+++ b/apioforum/templates/view_forum.html
@@ -1,7 +1,8 @@
{% extends 'base.html' %}
-{% from 'common.html' import ts %}
+{% from 'common.html' import ts, tag, disp_user %}
{% block header %}<h1>{% block title %}apioforum{%endblock%}</h1>{%endblock%}
-{%block content%}
+{%block nmcontent%}
+<main class="widemain">
<p>welcome to the apioforum</p>
<p>forum rules: do not be a bad person. do not do bad things.</p>
{% if g.user %}
@@ -12,7 +13,10 @@
<div class="threadlistings">
<div class="threadlisting">
<div class="threadlisting-part threadlisting-part-title threadlisting-header">
- name
+ name<span class="only-small"> &amp; tags</span>
+ </div>
+ <div class="threadlisting-part threadlisting-part-tags threadlisting-header only-big">
+ tags
</div>
<div class="threadlisting-part threadlisting-part-creator threadlisting-header">
creator
@@ -24,21 +28,35 @@
last updated
</div>
<div class="threadlisting-part threadlisting-part-lastactivityby threadlisting-header">
- last activity by
+ last post by
</div>
<div class="threadlisting-part threadlisting-part-numreplies threadlisting-header">
- replies
+ posts
</div>
</div>
{%for thread in threads%}
<div class="threadlisting">
- <div class="threadlisting-part threadlisting-part-title"><a href="{{url_for('thread.view_thread',thread_id=thread.id)}}">{{thread.title}}</a></div>
- <div class="threadlisting-part threadlisting-part-creator">{{thread.creator}}</div>
+ <div class="threadlisting-part threadlisting-part-title"><a href="{{url_for('thread.view_thread',thread_id=thread.id)}}">{{thread.title}}</a>
+ {% if thread_tags[thread.id]|length > 0 %}
+ <span class="only-small">
+ {% for the_tag in thread_tags[thread.id] %}
+ {{tag(the_tag)}}
+ {% endfor %}
+ </span>
+ {%endif%}
+ </div>
+ <div class="threadlisting-part threadlisting-part-tags only-big">
+ {% for the_tag in thread_tags[thread.id] %}
+ {{tag(the_tag)}}
+ {% endfor %}
+ </div>
+ <div class="threadlisting-part threadlisting-part-creator">{{disp_user(thread.creator)}}</div>
<div class="threadlisting-part threadlisting-part-created">{{ts(thread.created)}}</div>
<div class="threadlisting-part threadlisting-part-updated">{{ts(thread.updated)}}</div>
- <div class="threadlisting-part threadlisting-part-lastactivityby">{{thread.last_user}}</div>
+ <div class="threadlisting-part threadlisting-part-lastactivityby">{{disp_user(thread.last_user)}}</div>
<div class="threadlisting-part threadlisting-part-numreplies">{{thread.num_replies}}</div>
</div>
{%endfor%}
</div>
+</main>
{%endblock%}
diff --git a/apioforum/templates/view_thread.html b/apioforum/templates/view_thread.html
index f38cf34..abd6aaa 100644
--- a/apioforum/templates/view_thread.html
+++ b/apioforum/templates/view_thread.html
@@ -1,10 +1,24 @@
-{% from 'common.html' import disp_post %}
+{% from 'common.html' import disp_post,tag %}
{% extends 'base.html' %}
{% block header %}
<h1>{%block title %}{{thread.title}}{% endblock %}</h1>
{% endblock %}
{%block content%}
+<div class="thread-top-bar">
+ <span class="thread-top-bar-a">
+ {% if g.user == thread.creator %}
+ <a class="actionbutton" href="{{url_for('thread.config_thread',thread_id=thread.id)}}">configure thread</a>
+ {% endif %}
+ </span>
+ &nbsp;
+ <span class="thread-top-bar-b">
+ {% for the_tag in tags %}
+ {{ tag(the_tag) }}
+ {% endfor %}
+ </span>
+</div>
+
<div class="posts">
{% for post in posts %}
{% call disp_post(post, True) %}
@@ -13,7 +27,7 @@
{% endfor %}
</div>
{% if g.user %}
-<form class="new-post" action="{{url_for('thread.create_post',thread_id=thread_id)}}" method="POST">
+<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>
<input type="submit" value="yes">
</form>
diff --git a/apioforum/templates/view_user.html b/apioforum/templates/view_user.html
new file mode 100644
index 0000000..f773978
--- /dev/null
+++ b/apioforum/templates/view_user.html
@@ -0,0 +1,37 @@
+{% from 'common.html' import disp_post,ts %}
+{% extends 'base.html' %}
+{% block header %}
+<h1>{%block title %}{{user.username|e}}{% endblock %}</h1>
+{% endblock %}
+
+{%block content%}
+<div class="user-top-bar">
+ {% if g.user == user.username %}
+ <a class="actionbutton" href="{{url_for('user.edit_user',username=user.username)}}">settings</a>
+ {% endif %}
+</div>
+<div class="user_info">
+ <div class="user_bio_quote">
+ <div class="user_bio">{{rendered_bio|safe}}</div>
+ <p class="user_bio_attribution">— {{user.username|e}}</p>
+ </div>
+ <dl>
+ <dt>joined</dt>
+ {% if user.joined %}
+ <dd>{{ts(user.joined)}} ago</dd>
+ {% else %}
+ <dd>a very long time ago</dd>
+ {% endif %}
+ </dl>
+</div>
+{% if posts %}
+ <h2>recent posts</h2>
+ <div class="user_posts">
+ {% for post in posts %}
+ {% call disp_post(post, False) %}
+ {{ rendered_posts[loop.index0] | safe}}
+ {% endcall %}
+ {% endfor %}
+ </div>
+{% endif %}
+{% endblock %}
diff --git a/apioforum/thread.py b/apioforum/thread.py
index 2c7366e..6f3386b 100644
--- a/apioforum/thread.py
+++ b/apioforum/thread.py
@@ -23,8 +23,12 @@ def view_thread(thread_id):
"SELECT * FROM posts WHERE thread = ? ORDER BY created ASC;",
(thread_id,)
).fetchall()
+ tags = db.execute(
+ """SELECT tags.* FROM tags
+ INNER JOIN thread_tags ON thread_tags.tag = tags.id
+ WHERE thread_tags.thread = ?""",(thread_id,)).fetchall()
rendered_posts = [render(q['content']) for q in posts]
- return render_template("view_thread.html",posts=posts,thread=thread,thread_id=thread_id,rendered_posts=rendered_posts)
+ return render_template("view_thread.html",posts=posts,thread=thread,rendered_posts=rendered_posts,tags=tags)
@bp.route("/<int:thread_id>/create_post", methods=("POST",))
def create_post(thread_id):
@@ -104,6 +108,56 @@ def edit_post(post_id):
else:
flash(err)
return render_template("edit_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 = db.execute("select * from tags order by id").fetchall()
+ err = None
+ if g.user is None:
+ err = "you need to be logged in to do that"
+ elif g.user != thread['creator']:
+ 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 'do_title' in request.form:
+ 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()
+ if 'do_chtags' in request.form:
+ changed = False
+ wanted_tags = []
+ for tagid in range(1,len(avail_tags)+1):
+ current = tagid in thread_tags
+ wanted = f'tag_{tagid}' in request.form
+ print(tagid, current, wanted)
+ 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)
+
@bp.route("/<int:thread_id>/rss")
def rss_feed(thread_id):
@@ -112,4 +166,4 @@ def rss_feed(thread_id):
items = db.execute("SELECT * FROM posts WHERE thread = ? ORDER BY updated DESC LIMIT 50", (thread_id,))
items = [ { **item, "rendered": render(item["content"]), "link": request.base_url + post_jump(item["thread"], item["id"]), "updated": item["updated"] or item["created"] } for item in items ]
return Response(render_template("rss.xml", description=f"Posts in thread {thread_id}", title=f'''{thread['title']} - Apioforum''',
- link=request.base_url.rstrip("/rss"), items=items), mimetype="text/xml") \ No newline at end of file
+ link=request.base_url.rstrip("/rss"), items=items), mimetype="text/xml")
diff --git a/apioforum/user.py b/apioforum/user.py
new file mode 100644
index 0000000..c4a6998
--- /dev/null
+++ b/apioforum/user.py
@@ -0,0 +1,65 @@
+# user pages
+
+from flask import (
+ Blueprint, render_template, abort, g, flash, redirect, url_for, request
+)
+
+from werkzeug.security import check_password_hash, generate_password_hash
+from .db import get_db
+from .mdrender import render
+
+bp = Blueprint("user", __name__, url_prefix="/user")
+
+
+@bp.route("/<username>")
+def view_user(username):
+ 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 WHERE author = ? ORDER BY created DESC LIMIT 25;",(username,)).fetchall()
+ rendered_posts = [render(post['content']) for post in posts]
+ return render_template("view_user.html",
+ user=user,
+ rendered_bio=render(user['bio'] or "hail GEORGE"),
+ posts=posts,
+ rendered_posts=rendered_posts)
+
+@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))
+
+ if request.method == "POST":
+ err = []
+ if 'do_chpass' in request.form:
+ 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 'do_chbio' in request.form:
+ 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)