diff options
author | ubq323 <ubq323> | 2021-06-19 09:45:53 +0000 |
---|---|---|
committer | ubq323 <ubq323> | 2021-06-19 09:45:53 +0000 |
commit | 3f001e130222c9bc2f0048d5941eeaed22940449 (patch) | |
tree | 2707d3b0b883df00de52415a2c0ad31ffdb8dd15 | |
parent | cb628d88e54267b9b46a1cfc37f24f0c5bb7f2cb (diff) | |
parent | b887340ebc120923178ff20cc79f43d7bc19f62a (diff) |
merge trunk into rss so we are not based on a very old versionrss
-rw-r--r-- | apioforum/__init__.py | 18 | ||||
-rw-r--r-- | apioforum/admin.py | 14 | ||||
-rw-r--r-- | apioforum/auth.py | 9 | ||||
-rw-r--r-- | apioforum/db.py | 18 | ||||
-rw-r--r-- | apioforum/forum.py | 12 | ||||
-rw-r--r-- | apioforum/permissions.py | 43 | ||||
-rw-r--r-- | apioforum/static/style.css | 61 | ||||
-rw-r--r-- | apioforum/templates/admin/admin_page.html | 15 | ||||
-rw-r--r-- | apioforum/templates/base.html | 17 | ||||
-rw-r--r-- | apioforum/templates/common.html | 11 | ||||
-rw-r--r-- | apioforum/templates/config_thread.html | 32 | ||||
-rw-r--r-- | apioforum/templates/search_results.html | 18 | ||||
-rw-r--r-- | apioforum/templates/user_settings.html | 28 | ||||
-rw-r--r-- | apioforum/templates/view_forum.html | 34 | ||||
-rw-r--r-- | apioforum/templates/view_thread.html | 18 | ||||
-rw-r--r-- | apioforum/templates/view_user.html | 37 | ||||
-rw-r--r-- | apioforum/thread.py | 58 | ||||
-rw-r--r-- | apioforum/user.py | 65 |
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"> & 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> + + <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) |