]> littlesong.place Git - littlesongplace.git/commitdiff
Add initial comment implementation
authorChris Fulljames <christianfulljames@gmail.com>
Sun, 26 Jan 2025 21:50:49 +0000 (16:50 -0500)
committerChris Fulljames <christianfulljames@gmail.com>
Sun, 26 Jan 2025 23:05:19 +0000 (18:05 -0500)
main.py
schema.sql
templates/comment.html [new file with mode: 0644]
templates/song-list.html
todo.txt

diff --git a/main.py b/main.py
index d5664121a3df3f06c140d67ed9f145fe638de0f5..e390285775dedb3d5bbc738a821eee9e9f541324 100644 (file)
--- a/main.py
+++ b/main.py
@@ -11,6 +11,7 @@ from dataclasses import dataclass
 from datetime import datetime, timezone
 from logging.handlers import RotatingFileHandler
 from pathlib import Path, PosixPath
+from typing import Optional
 
 import bcrypt
 import bleach
@@ -474,6 +475,63 @@ def songs():
             tag=tag,
             song_list=render_template("song-list.html", songs=songs))
 
+@app.get("/comment")
+def comment_get():
+    if not "songid" in request.args:
+        abort(400) # Must have songid
+
+    try:
+        song = Song.by_id(request.args["songid"])
+    except ValueError:
+        abort(404) # Invald songid
+
+    if not "userid" in session:
+        abort(401) # Must be logged in
+
+    comment = None
+    if "commentid" in request.args:
+        commentid = request.args["commentid"]
+        comment = query_db("select * from song_comments inner join users on song_comments.userid == users.userid where commentid = ?", [commentid], one=True)
+
+    session["previous_page"] = request.referrer
+    return render_template("comment.html", song=song, comment=comment)
+
+@app.post("/comment")
+def comment_post():
+    if not "songid" in request.args:
+        abort(400) # Must have songid
+
+    try:
+        song = Song.by_id(request.args["songid"])
+    except ValueError:
+        abort(404) # Invald songid
+
+    comment = None
+    if "commentid" in request.args:
+        commentid = request.args["commentid"]
+        comment = query_db("select * from song_comments inner join users on song_comments.userid == users.userid where commentid = ?", [commentid], one=True)
+        if not comment:
+            abort(404) # Invalid comment
+
+    if not "userid" in session:
+        abort(401) # Must be logged in
+
+    # Add new comment
+    timestamp = datetime.now(timezone.utc).isoformat()
+    content = request.form["content"]
+    userid = session["userid"]
+    songid = request.args["songid"]
+    replytoid = request.args.get("commentid", None)
+    query_db(
+            "insert into song_comments (songid, userid, replytoid, created, content) values (?, ?, ?, ?, ?)",
+            args=[songid, userid, replytoid, timestamp, content])
+    get_db().commit()
+
+    if "previous_page" in session:
+        return redirect(session["previous_page"])
+    else:
+        return redirect("/")
+
 @app.get("/site-news")
 def site_news():
     return render_template("news.html")
@@ -545,6 +603,16 @@ class Song:
     def json(self):
         return json.dumps(vars(self))
 
+    def get_comments(self):
+        comments = query_db("select * from song_comments inner join users on song_comments.userid == users.userid where songid = ?", [self.songid])
+        # Top-level comments
+        song_comments = sorted([dict(c) for c in comments if c["replytoid"] is None], key=lambda c: c["created"])
+        # Replies (can only reply to top-level)
+        for comment in song_comments:
+            comment["replies"] = sorted([c for c in comments if c["replytoid"] == comment["commentid"]], key=lambda c: c["created"])
+
+        return song_comments
+
     @classmethod
     def by_id(cls, songid):
         songs = cls._from_db("select * from songs inner join users on songs.userid = users.userid where songid = ?", [songid])
@@ -576,7 +644,7 @@ class Song:
     @classmethod
     def _from_db(cls, query, args=()):
         songs_data = query_db(query, args)
-        tags, collabs = cls._get_tags_and_collabs_for_songs(songs_data)
+        tags, collabs = cls._get_info_for_songs(songs_data)
         songs = []
         for sd in songs_data:
             song_tags = [t["tag"] for t in tags[sd["songid"]]]
@@ -586,7 +654,7 @@ class Song:
         return songs
 
     @classmethod
-    def _get_tags_and_collabs_for_songs(cls, songs):
+    def _get_info_for_songs(cls, songs):
         tags = {}
         collabs = {}
         for song in songs:
index 87470faccecf705b5a62a7ec4b3252b19b06cfe6..be54410511996f9b2e71087f9fb954664a62f630 100644 (file)
@@ -35,3 +35,16 @@ CREATE TABLE song_tags (
 );
 CREATE INDEX idx_song_tags_tag ON song_tags(tag);
 
+DROP TABLE IF EXISTS song_comments;
+CREATE TABLE song_comments (
+    commentid INTEGER PRIMARY KEY,
+    songid INTEGER NOT NULL,
+    userid INTEGER NOT NULL,
+    replytoid INTEGER,
+    created TEXT NOT NULL,
+    content TEXT NOT NULL,
+    FOREIGN KEY(songid) REFERENCES songs(songid),
+    FOREIGN KEY(userid) REFERENCES users(userid)
+);
+CREATE INDEX idx_comments_by_song ON song_comments(songid);
+
diff --git a/templates/comment.html b/templates/comment.html
new file mode 100644 (file)
index 0000000..2ed0100
--- /dev/null
@@ -0,0 +1,27 @@
+{% extends "base.html" %}
+
+{% block title %}Write a Comment{% endblock %}
+
+{% block body %}
+
+<h1>Write a Comment</h1>
+
+<p>
+Commenting on {{ song.title }}
+</p>
+
+{% if comment %}
+<p>
+In reply to:
+<a href="/users/{{ comment['username'] }}" class="profile-link">{{ comment['username'] }}</a>
+<br>
+{{ comment['content'] }}
+</p>
+{% endif %}
+
+<form method="post">
+    <textarea name="content"></textarea>
+    <input type="submit" value="Post Comment">
+</form>
+
+{% endblock %}
index 730efcf9465c155448c980534d8669bca12883e9..4809f9f95950b8b51f1ae83ac36eba3a7dcd258d 100644 (file)
@@ -10,7 +10,7 @@
                 <div class="song-info-sep">
                     -
                 </div>
-                
+
                 <!-- Song Artist -->
                 <div class="song-artist">
                     <a href="/users/{{ song.username }}" class="profile-link">{{ song.username }}</a>
                 <a href="/songs?user={{ song.username }}&tag={{ tag }}">{{ tag }}</a>
                 {% endfor %}
             </div>
+
+            <!-- Song Comments -->
+            <div class="song-comments">
+                Comments:
+                <a href="/comment?songid={{ song.songid }}">Add a Comment</a>
+
+                {% for comment in song.get_comments() %}
+                <div class="top-level-comment">
+                    <a href="/users/{{ comment['username'] }}" class="profile-link">{{ comment['username'] }}</a>
+                    <p>{{ comment['content'] }}</p>
+
+                    {% for reply in comment['replies'] %}
+                    <div class="reply-comment">
+                        <a href="/users/{{ reply['username'] }}" class="profile-link">{{ reply['username'] }}</a>
+                        <p>{{ reply['content'] }}</p>
+                    </div>
+                    {% endfor %}
+
+                    <a href="/comment?songid={{ song.songid }}&commentid={{ comment['commentid'] }}">Reply</a>
+                </div>
+                {% endfor %}
+            </div>
+
         </div>
     </div>
 {% endfor %}
index 473c749a91edcdbf80ed27054fff6d6b91c1a3f4..48f88e592ae4e3b615608b44c00b1cfed7cf4b88 100644 (file)
--- a/todo.txt
+++ b/todo.txt
@@ -1,10 +1,16 @@
-- Dark mode?
+- Comments:
+    - Leave comment
+    - Delete comment
+    - Notifications
+- YouTube importer
 - Autoplay toggle
-- Comments, notifications
-- Tips and Tricks
-- Multiline descriptions
+- Volume control
 - Shuffle all
-- Homepage activity log
+- Song sorter for song lists
+- Additional song info in player (collabs, description, tags)
+- Tips and Tricks (or html helper for bio/descriptions?)
+- Multiline/html descriptions
+- Dark mode/site color customization
 - Admin accounts
 - Account deletion
 - Change password (require email?)
@@ -24,9 +30,5 @@
     - Delete
     - Pin to profile
     - Albums?
-- Comments:
-    - Leave comment
-    - Delete comment
-    - Notifications
 - Song/User Search