First implementation of comments

This commit is contained in:
2026-02-19 12:46:43 +00:00
parent a68033df6e
commit 82def98e7e
6 changed files with 127 additions and 4 deletions

View File

@@ -16,6 +16,7 @@ from .content import ContentArea
from .contact import ContactForm
from .storage import LocalStorage
from .links import Links
from .comments import *
app = Flask(__name__)
@@ -29,6 +30,7 @@ projects = ContentArea(
app.register_blueprint(projects, url_prefix='/projects')
app.register_blueprint(ContactForm('contact', __name__), url_prefix='/contact')
app.register_blueprint(Links(path.join(md_path, 'links.json'), 'links', __name__), url_prefix='/links')
app.register_blueprint(Approval(path.join(projects.md_directory.uri, 'comments.db'), 'comments', __name__), url_prefix='/comments')
class DiscordLogger(logging.Handler):
''' Simple logging handler to send a message to Discord '''
@@ -140,7 +142,7 @@ def sitemap():
url = ET.SubElement(root, 'url')
ET.SubElement(url, 'loc').text = base_url + route
ET.SubElement(url, 'lastmod').text = date
for article in projects.get_all_posts():
for article in projects.get_live_posts():
if 'link' in article.metadata:
continue
url = ET.SubElement(root, 'url')

View File

@@ -0,0 +1,60 @@
#!/usr/bin/python3
import sqlite3
from flask import Blueprint, Response, request
from uuid import uuid4
from requests import post
from os import environ
class PostComments():
def __init__(self, post_id: str, db_path: str):
self.__db_path = db_path
self.__post_id = post_id
self._webhook = environ['DISCORD_WEBHOOK']
with sqlite3.connect(db_path) as db:
cursor = db.cursor()
cursor.execute("CREATE TABLE IF NOT EXISTS comments (name TEXT, comment TEXT, date INT, post_id TEXT, approved BOOL, key TEXT)")
self.comments = cursor.execute(
"SELECT name, comment, date FROM comments WHERE approved = 1 AND post_id = ? ORDER BY date DESC",
(post_id,)
).fetchall()
def send_to_discord(self, name: str, comment: str, comment_id: int, key: str):
''' Send the message '''
message_to_send = f'New comment from {name}\n\n{comment}'
if len(message_to_send) > 2000:
chars_to_lose = len(message_to_send) - 1900
message_to_send = message_to_send[-chars_to_lose:]
message_to_send += f'\n\n[Approve](https://jakecharman.co.uk/comments/approve/{comment_id}?key={key})'
post(self._webhook, data={'content': message_to_send}, timeout=30)
def make_comment(self, name: str, comment: str):
with sqlite3.connect(self.__db_path) as db:
key = str(uuid4())
cursor = db.cursor()
cursor.execute(
"INSERT INTO comments (name, comment, date, post_id, approved, key) VALUES (?, ?, datetime('now'), ?, 0, ?)",
(name, comment, self.__post_id, key)
)
db.commit()
self.send_to_discord(name, comment, cursor.lastrowid, key)
return cursor.lastrowid
class Approval(Blueprint):
def __init__(self, db_path: str, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__db_path = db_path
self.add_url_rule('/approve/<comment_id>', view_func=self.approve)
def approve(self, comment_id: str):
with sqlite3.connect(self.__db_path) as db:
cursor = db.cursor()
key = cursor.execute("SELECT key FROM comments WHERE rowid = ?", (comment_id,)).fetchone()[0]
if request.args.get('key') == key:
with sqlite3.connect(self.__db_path) as db:
cursor = db.cursor()
cursor.execute("UPDATE comments SET approved = 1 WHERE rowid = ?", (comment_id,))
db.commit()
return Response(status=200)
return Response(status=403)

View File

@@ -6,8 +6,9 @@ from datetime import datetime
import frontmatter
from markdown import markdown
from bs4 import BeautifulSoup
from flask import render_template, Response, Blueprint
from flask import render_template, Response, Blueprint, request, redirect
from .storage import LocalStorage
from .comments import PostComments, Approval
class ContentArea(Blueprint):
def __init__(self, directory: LocalStorage, *args, **kwargs):
@@ -24,7 +25,7 @@ class ContentArea(Blueprint):
self.add_url_rule('/', view_func=self.projects)
self.add_url_rule('/category/<category_id>/', view_func=self.category)
self.add_url_rule('/<article_id>', view_func=self.article)
#self.add_url_rule('/image/<image_name>', view_func=self.image)
self.add_url_rule('/<article_id>/comment', view_func=self.comment, methods=['POST'])
def processor(self) -> dict:
''' Jninja processors '''
@@ -140,6 +141,16 @@ class ContentArea(Blueprint):
return Response(status=500)
the_article = articles[0]
comments = PostComments(the_article.metadata['id'], path.join(self.md_directory.uri, 'comments.db')).comments
return render_template('article.html', post=markdown(the_article.content),
metadata=the_article.metadata,
comments = comments,
page_title=f'{the_article.metadata["title"]} - ')
def comment(self, article_id: str):
PostComments(article_id, path.join(self.md_directory.uri, 'comments.db')).make_comment(
request.form['name'],
request.form['comment']
)
return redirect(f'/projects/{article_id}?comment=true#comments')

View File

@@ -22,4 +22,23 @@
{{post|safe}}
</section>
</main>
<section id="comments">
<h2>{{comments | length}} Comments</h2>
{% for comment in comments %}
<div class="comment">
<strong>{{ comment[0] }} - {{ comment[2] | human_date }}</strong>
<p>{{ comment[1] }}</p>
</div>
{% endfor %}
<h3>Leave a comment</h3>
{% if request.args.get('comment') is none %}
<form action="./{{metadata.id}}/comment" method="post">
<input type="text", name="name" placeholder="Name" required>
<textarea name="comment" placeholder="Comment" required></textarea>
<input type="submit" value="Submit">
</form>
{% else %}
<p>Thank you! Your comment will appear once it is approved</p>
{% endif %}
</section>
{% endblock %}

View File

@@ -329,3 +329,14 @@ pre{
height: 31vw;
object-fit: cover;
}
#comments {
border-top: 1px solid #e5e5e5;
}
.comment {
background-color: #4c4c4c;
margin: 10px;
padding: 5px;
border-radius: 10px;
}

20
src/templates/article.html Executable file
View File

@@ -0,0 +1,20 @@
{% include 'header.html' %}
<script src="/static/js/get_comments.js"></script>
<main>
<section id="article">
<h1>{{ metadata.title}} </h1>
<p>{{ metadata.date | human_date }}</p>
<hr />
{{post|safe}}
</section>
<section id="comments">
</section>
<form action="/comments/{{ metadata.id }}" method="post">
<label for="name">Name:</label>
<input type="text" name="name" />
<label for="comment">Comment:</label>
<textarea name="comment"></textarea>
<input type="submit" name="submit" value="Submit">
</form>
</main>
{% include 'footer.html' %}