Compare commits
No commits in common. "a12fd0a90403378abcdef6586519d12266b46e9c" and "19bf98f5b5d9ef1000b008cac90d71be472c790a" have entirely different histories.
a12fd0a904
...
19bf98f5b5
@ -71,11 +71,9 @@ def create_app():
|
|||||||
from app.routes.app import bp as app_bp
|
from app.routes.app import bp as app_bp
|
||||||
from app.routes.topics import bp as topics_bp
|
from app.routes.topics import bp as topics_bp
|
||||||
from app.routes.users import bp as users_bp
|
from app.routes.users import bp as users_bp
|
||||||
from app.routes.mod import bp as mod_bp
|
|
||||||
app.register_blueprint(app_bp)
|
app.register_blueprint(app_bp)
|
||||||
app.register_blueprint(topics_bp)
|
app.register_blueprint(topics_bp)
|
||||||
app.register_blueprint(users_bp)
|
app.register_blueprint(users_bp)
|
||||||
app.register_blueprint(mod_bp)
|
|
||||||
|
|
||||||
app.config['SESSION_COOKIE_SECURE'] = True
|
app.config['SESSION_COOKIE_SECURE'] = True
|
||||||
|
|
||||||
@ -85,14 +83,10 @@ def create_app():
|
|||||||
|
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
def inject_constants():
|
def inject_constants():
|
||||||
commit = ""
|
|
||||||
with open('.git/refs/heads/main') as f:
|
|
||||||
commit = f.read().strip()
|
|
||||||
return {
|
return {
|
||||||
"InfoboxIcons": InfoboxIcons,
|
"InfoboxIcons": InfoboxIcons,
|
||||||
"InfoboxHTMLClass": InfoboxHTMLClass,
|
"InfoboxHTMLClass": InfoboxHTMLClass,
|
||||||
"InfoboxKind": InfoboxKind,
|
"InfoboxKind": InfoboxKind,
|
||||||
"__commit": commit,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
|
46
app/db.py
46
app/db.py
@ -4,12 +4,13 @@ from flask import current_app
|
|||||||
|
|
||||||
class DB:
|
class DB:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self._transaction_depth = 0
|
||||||
self._connection = None
|
self._connection = None
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def _get_connection(self):
|
def _get_connection(self):
|
||||||
if self._connection:
|
if self._connection and self._transaction_depth > 0:
|
||||||
yield self._connection
|
yield self._connection
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -20,24 +21,48 @@ class DB:
|
|||||||
try:
|
try:
|
||||||
yield conn
|
yield conn
|
||||||
finally:
|
finally:
|
||||||
|
if self._transaction_depth == 0:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def transaction(self):
|
def transaction(self):
|
||||||
"""Transaction context."""
|
"""Transaction context."""
|
||||||
tr_connection = sqlite3.connect(current_app.config["DB_PATH"])
|
self.begin()
|
||||||
tr_connection.row_factory = sqlite3.Row
|
|
||||||
tr_connection.execute("PRAGMA FOREIGN_KEYS = 1")
|
|
||||||
tr_connection.execute("BEGIN")
|
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
tr_connection.execute("COMMIT")
|
self.commit()
|
||||||
except Exception:
|
except Exception:
|
||||||
tr_connection.execute("ROLLBACK")
|
self.rollback()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def begin(self):
|
||||||
|
"""Begins a new transaction."""
|
||||||
|
if self._transaction_depth == 0:
|
||||||
|
if not self._connection:
|
||||||
|
self._connection = sqlite3.connect(current_app.config["DB_PATH"])
|
||||||
|
self._connection.row_factory = sqlite3.Row
|
||||||
|
self._connection.execute("PRAGMA FOREIGN_KEYS = 1")
|
||||||
|
self._connection.execute("BEGIN")
|
||||||
|
self._transaction_depth += 1
|
||||||
|
|
||||||
|
|
||||||
|
def commit(self):
|
||||||
|
"""Commits the current transaction."""
|
||||||
|
if self._transaction_depth > 0:
|
||||||
|
self._transaction_depth -= 1
|
||||||
|
if self._transaction_depth == 0:
|
||||||
|
self._connection.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def rollback(self):
|
||||||
|
"""Rolls back the current transaction."""
|
||||||
|
if self._transaction_depth > 0:
|
||||||
|
self._transaction_depth = 0
|
||||||
|
self._connection.rollback()
|
||||||
|
|
||||||
|
|
||||||
def query(self, sql, *args):
|
def query(self, sql, *args):
|
||||||
"""Executes a query and returns a list of dictionaries."""
|
"""Executes a query and returns a list of dictionaries."""
|
||||||
with self._get_connection() as conn:
|
with self._get_connection() as conn:
|
||||||
@ -184,13 +209,6 @@ class Model:
|
|||||||
return result["c"] if result else 0
|
return result["c"] if result else 0
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def select(cls, sel = "*"):
|
|
||||||
qb = db.QueryBuilder(cls.table).select(sel)
|
|
||||||
result = qb.all()
|
|
||||||
return result if result else []
|
|
||||||
|
|
||||||
|
|
||||||
def update(self, data):
|
def update(self, data):
|
||||||
qb = db.QueryBuilder(self.table)\
|
qb = db.QueryBuilder(self.table)\
|
||||||
.where({"id": self._data["id"]})
|
.where({"id": self._data["id"]})
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
from flask import (
|
|
||||||
Blueprint, render_template, request, redirect, url_for
|
|
||||||
)
|
|
||||||
from .users import login_required, mod_only
|
|
||||||
from ..models import Users
|
|
||||||
from ..db import db, DB
|
|
||||||
bp = Blueprint("mod", __name__, url_prefix = "/mod/")
|
|
||||||
|
|
||||||
@bp.get("/sort-topics")
|
|
||||||
@login_required
|
|
||||||
@mod_only("topics.all_topics")
|
|
||||||
def sort_topics():
|
|
||||||
topics = db.query("SELECT * FROM topics ORDER BY sort_order ASC")
|
|
||||||
return render_template("mod/sort-topics.html", topics = topics)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/sort-topics")
|
|
||||||
@login_required
|
|
||||||
@mod_only("topics.all_topics")
|
|
||||||
def sort_topics_post():
|
|
||||||
with db.transaction():
|
|
||||||
for topic_id, new_order in request.form.items():
|
|
||||||
db.execute("UPDATE topics SET sort_order = ? WHERE id = ?", new_order, topic_id)
|
|
||||||
|
|
||||||
return redirect(url_for(".sort_topics"))
|
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/user-list")
|
|
||||||
@login_required
|
|
||||||
@mod_only("users.page", username = lambda: get_active_user().username)
|
|
||||||
def user_list():
|
|
||||||
users = Users.select()
|
|
||||||
return render_template("mod/user-list.html", users = users)
|
|
@ -69,44 +69,3 @@ def topic(slug):
|
|||||||
current_page = page,
|
current_page = page,
|
||||||
page_count = page_count
|
page_count = page_count
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/<slug>/edit")
|
|
||||||
@login_required
|
|
||||||
@mod_only(".topic", slug = lambda slug: slug)
|
|
||||||
def edit(slug):
|
|
||||||
topic = Topics.find({"slug": slug})
|
|
||||||
if not topic:
|
|
||||||
return "no"
|
|
||||||
return render_template("topics/edit.html", topic=topic)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/<slug>/edit")
|
|
||||||
@login_required
|
|
||||||
@mod_only(".topic", slug = lambda slug: slug)
|
|
||||||
def edit_post(slug):
|
|
||||||
topic = Topics.find({"slug": slug})
|
|
||||||
if not topic:
|
|
||||||
return "no"
|
|
||||||
|
|
||||||
topic.update({
|
|
||||||
"name": request.form.get('name', default = topic.name).strip(),
|
|
||||||
"description": request.form.get('description', default = topic.description),
|
|
||||||
"is_locked": int(request.form.get("is_locked", default = topic.is_locked)),
|
|
||||||
})
|
|
||||||
|
|
||||||
return redirect(url_for("topics.topic", slug=slug))
|
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/<slug>/delete")
|
|
||||||
@login_required
|
|
||||||
@mod_only(".topic", slug = lambda slug: slug)
|
|
||||||
def delete(slug):
|
|
||||||
topic = Topics.find({"slug": slug})
|
|
||||||
if not topic:
|
|
||||||
return "no"
|
|
||||||
|
|
||||||
topic.delete()
|
|
||||||
|
|
||||||
flash("Topic deleted.", InfoboxKind.INFO)
|
|
||||||
return redirect(url_for("topics.all_topics"))
|
|
||||||
|
@ -50,7 +50,7 @@ def redirect_if_logged_in(*args, **kwargs):
|
|||||||
if is_logged_in():
|
if is_logged_in():
|
||||||
# resolve callables
|
# resolve callables
|
||||||
processed_kwargs = {
|
processed_kwargs = {
|
||||||
k: v(**view_kwargs) if callable(v) else v
|
k: v() if callable(v) else v
|
||||||
for k, v in kwargs.items()
|
for k, v in kwargs.items()
|
||||||
}
|
}
|
||||||
endpoint = args[0] if args else processed_kwargs.get("endpoint")
|
endpoint = args[0] if args else processed_kwargs.get("endpoint")
|
||||||
@ -81,7 +81,7 @@ def mod_only(*args, **kwargs):
|
|||||||
if not get_active_user().is_mod():
|
if not get_active_user().is_mod():
|
||||||
# resolve callables
|
# resolve callables
|
||||||
processed_kwargs = {
|
processed_kwargs = {
|
||||||
k: v(**view_kwargs) if callable(v) else v
|
k: v() if callable(v) else v
|
||||||
for k, v in kwargs.items()
|
for k, v in kwargs.items()
|
||||||
}
|
}
|
||||||
endpoint = args[0] if args else processed_kwargs.get("endpoint")
|
endpoint = args[0] if args else processed_kwargs.get("endpoint")
|
||||||
@ -186,6 +186,13 @@ def inbox(username):
|
|||||||
return "stub"
|
return "stub"
|
||||||
|
|
||||||
|
|
||||||
|
@bp.get("/list")
|
||||||
|
@login_required
|
||||||
|
@mod_only(".page", username = lambda: get_active_user().username)
|
||||||
|
def user_list():
|
||||||
|
return "stub"
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/log_out")
|
@bp.post("/log_out")
|
||||||
def log_out():
|
def log_out():
|
||||||
pass
|
pass
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
<footer class="darkbg">
|
<footer class="darkbg">
|
||||||
<span>Pyrom commit <a href="{{ "https://git.poto.cafe/yagich/porom/commit/" + __commit }}">{{ __commit[:8] }}</a></span>
|
<span>Pyrom commit</span>
|
||||||
</footer>
|
</footer>
|
||||||
<script src="/static/js/copy-code.js"></script>
|
<script src="/static/js/copy-code.js"></script>
|
||||||
<script src="/static/js/ui.js"></script>
|
<script src="/static/js/ui.js"></script>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<a href="{{ url_for("users.inbox", username = user.username) }}">Inbox</a>
|
<a href="{{ url_for("users.inbox", username = user.username) }}">Inbox</a>
|
||||||
{% if user.is_mod() %}
|
{% if user.is_mod() %}
|
||||||
•
|
•
|
||||||
<a href="{{ url_for("mod.user_list") }}">User list</a>
|
<a href="{{ url_for("users.user_list") }}">User list</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% block content %}
|
|
||||||
<div class="darkbg settings-container">
|
|
||||||
<h1>Change topics order</h1>
|
|
||||||
<p>Drag topic titles to reoder them. Press submit when done. The topics will appear to users in the order set here.</p>
|
|
||||||
<form method="post" id=topics-container>
|
|
||||||
{% for topic in topics %}
|
|
||||||
<div draggable="true" class="draggable-topic" ondragover="dragOver(event)" ondragstart="dragStart(event)" ondragend="dragEnd()">
|
|
||||||
<div class="thread-title">{{ topic['name'] }}</div>
|
|
||||||
<div>{{ topic.description }}</div>
|
|
||||||
<input type="hidden" name="{{ topic['id'] }}" value="{{ topic['sort-order'] }}" class="topic-input">
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
<input type=submit value="Save order">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<script src="/static/js/sort-topics.js"></script>
|
|
||||||
{% endblock %}
|
|
@ -1,10 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% block content %}
|
|
||||||
<div class="darkbg settings-container">
|
|
||||||
<ul>
|
|
||||||
{% for user in users %}
|
|
||||||
<li><a href="{{url_for("users.page", username=user['username'])}}">{{user['username']}}</a>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -1,16 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% block title %}creating a topic{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
<div class="darkbg settings-container">
|
|
||||||
<h1>Editing topic {{ topic['name'] }}</h1>
|
|
||||||
<form method="post">
|
|
||||||
<label for=name>Name</label>
|
|
||||||
<input type="text" name="name" id="name" required value="{{ topic['name'] }}"><br>
|
|
||||||
<label for="description">Description</label>
|
|
||||||
<textarea id="description" name="description" required rows=5>{{ topic['description'] }}</textarea><br>
|
|
||||||
<input type="submit" value="Save changes">
|
|
||||||
<a class="linkbutton warn" href={{ url_for("topics.topic", slug=topic['slug'] )}}>Cancel</a><br>
|
|
||||||
<i> Note: to preserve history, you cannot change the topic URL.</i>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -7,12 +7,6 @@
|
|||||||
<span>{{topic['description']}}</span>
|
<span>{{topic['description']}}</span>
|
||||||
<div>
|
<div>
|
||||||
{% if active_user and active_user.is_mod() %}
|
{% if active_user and active_user.is_mod() %}
|
||||||
<a class="linkbutton" href="{{url_for("topics.edit", slug=topic['slug'])}}">Edit topic</a>
|
|
||||||
<form class="modform" method="post" action="{{url_for("topics.edit", slug=topic['slug']) }}">
|
|
||||||
<input type="hidden" name="is_locked" value="{{ (not topic.is_locked) | int }}">
|
|
||||||
<input class="warn" type="submit" id="lock" value="{{"Unlock topic" if topic['is_locked'] else "Lock topic"}}">
|
|
||||||
</form>
|
|
||||||
<button type="button" class="critical" id="topic-delete-dialog-open">Delete</button>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@ -58,17 +52,4 @@
|
|||||||
<nav id="bottomnav">
|
<nav id="bottomnav">
|
||||||
{{ pager(current_page = current_page, page_count = page_count) }}
|
{{ pager(current_page = current_page, page_count = page_count) }}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<dialog id="delete-dialog">
|
|
||||||
<div class="delete-dialog-inner">
|
|
||||||
Are you sure you want to delete this topic?
|
|
||||||
<span>
|
|
||||||
<button id=topic-delete-dialog-close>Cancel</button>
|
|
||||||
<button class="critical" form=topic-delete-form>Delete</button>
|
|
||||||
<form id="topic-delete-form" method="post" action="{{ url_for("topics.delete", slug = topic.slug) }}"></form>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</dialog>
|
|
||||||
|
|
||||||
<script src="/static/js/topic.js"></script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
{% from 'common/macros.html' import timestamp %}
|
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<nav class="darkbg">
|
<nav class="darkbg">
|
||||||
<h1 class="thread-title">All topics</h1>
|
<h1 class="thread-title">All topics</h1>
|
||||||
{% if active_user and active_user.is_mod() %}
|
{% if active_user and active_user.is_mod() %}
|
||||||
<a class="linkbutton" href={{ url_for("topics.create") }}>Create new topic</a>
|
<a class="linkbutton" href={{ url_for("topics.create") }}>Create new topic</a>
|
||||||
<a class="linkbutton" href={{ url_for("mod.sort_topics") }}>Sort topics</a>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</nav>
|
</nav>
|
||||||
{% if topic_list | length == 0 %}
|
{% if topic_list | length == 0 %}
|
||||||
@ -18,12 +16,12 @@
|
|||||||
{{ topic['description'] }}
|
{{ topic['description'] }}
|
||||||
{% if topic['latest_thread_username'] %}
|
{% if topic['latest_thread_username'] %}
|
||||||
<span>
|
<span>
|
||||||
Latest thread: <a href="{{ url_for("threads.thread", slug=topic['latest_thread_slug'])}}">{{topic['latest_thread_title']}}</a> by <a href="{{url_for("users.page", username=topic['latest_thread_username'])}}">{{topic['latest_thread_username']}}</a> on {{ timestamp(topic['latest_thread_created_at']) }}
|
Latest thread: <a href="{{ url_for("threads.thread", slug=topic['latest_thread_slug'])}}">{{topic['latest_thread_title']}}</a> by <a href="{{url_for("users.page", username=topic['latest_thread_username'])}}">{{topic['latest_thread_username']}}</a> on ...
|
||||||
</span>
|
</span>
|
||||||
{% if topic['id'] in active_threads %}
|
{% if topic['id'] in active_threads %}
|
||||||
{% with thread=active_threads[topic['id']] %}
|
{% with thread=active_threads[topic['id']] %}
|
||||||
<span>
|
<span>
|
||||||
Latest post in: <a href="{{ url_for("threads.thread", slug=thread['thread_slug'])}}">{{ thread['thread_title'] }}</a> by <a href="{{ url_for("users.page", username=thread['username'])}}">{{ thread['username'] }}</a> at <a href="">{{ timestamp(thread['post_created_at']) }}</a>
|
Latest post in: <a href="{{ url_for("threads.thread", slug=thread['thread_slug'])}}">{{ thread['thread_title'] }}</a> by <a href="{{ url_for("users.page", username=thread['username'])}}">{{ thread['username'] }}</a> on ...
|
||||||
</span>
|
</span>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
Loading…
Reference in New Issue
Block a user