password = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
timestamp = datetime.now(timezone.utc).isoformat()
- query_db("insert into users (username, password, created) values (?, ?, ?)", [username, password, timestamp])
+
+ user_data = query_db("insert into users (username, password, created, threadid) values (?, ?, ?, ?) returning userid", [username, password, timestamp, threadid], one=True)
+
+ # Create profile comment thread
+ threadid = create_comment_thread(ThreadType.PROFILE, user_data["userid"])
+ query_db("update users set threadid = ? where userid = ?", [threadid, user_data["userid"]])
get_db().commit()
flash("User created. Please sign in to continue.", "success")
# Get songs for current profile
songs = Song.get_all_for_userid(profile_userid)
+ # Get comments for current profile
+ comments = get_comments(profile_data["threadid"])
+
# Sanitize bio
profile_bio = ""
if profile_data["bio"] is not None:
**get_user_colors(profile_data),
playlists=plist_data,
songs=songs,
- user_has_pfp=user_has_pfp(profile_userid),
- is_profile_song_list=True)
+ comments=comments,
+ threadid=profile_data["threadid"],
+ user_has_pfp=user_has_pfp(profile_userid))
@app.post("/edit-profile")
def edit_profile():
if not passed:
return True
else:
+ # Create comment thread
+ threadid = create_comment_thread(ThreadType.SONG, session["userid"])
# Create song
timestamp = datetime.now(timezone.utc).isoformat()
song_data = query_db(
- "insert into songs (userid, title, description, created) values (?, ?, ?, ?) returning (songid)",
- [session["userid"], title, description, timestamp], one=True)
+ "insert into songs (userid, title, description, created, threadid) values (?, ?, ?, ?, ?) returning (songid)",
+ [session["userid"], title, description, timestamp, threadid], one=True)
songid = song_data["songid"]
filepath = get_user_songs_path(session["userid"]) / (str(song_data["songid"]) + ".mp3")
if not "threadid" in request.args:
abort(400) # Must have threadid
- thread = query_db("select * from comment_threads where threadid = ?", [threadid], one=True)
+ thread = query_db("select * from comment_threads where threadid = ?", [request.args["threadid"]], one=True)
if not thread:
abort(400) # Invalid threadid
# Add new comment
timestamp = datetime.now(timezone.utc).isoformat()
userid = session["userid"]
- songid = request.args["songid"]
replytoid = request.args.get("replytoid", None)
+ threadid = request.args["threadid"]
comment = query_db(
"insert into comments (threadid, userid, replytoid, created, content) values (?, ?, ?, ?, ?) returning (commentid)",
args=[threadid, userid, replytoid, timestamp, content], one=True)
user_data = query_db("select activitytime from users where userid = ?", [session["userid"]], one=True)
comment_data = query_db(
"""\
- select sc.created from comment_notifications as cn
+ select c.created from comment_notifications as cn
inner join comments as c on cn.commentid = c.commentid
where cn.targetuserid = ?
order by c.created desc
private = request.form["type"] == "private"
+ threadid = create_comment_thread(ThreadType.PLAYLIST, session["userid"])
+
query_db(
- "insert into playlists (created, updated, userid, name, private) values (?, ?, ?, ?, ?)",
+ "insert into playlists (created, updated, userid, name, private, threadid) values (?, ?, ?, ?, ?, ?)",
args=[
timestamp,
timestamp,
session["userid"],
name,
private,
+ threadid
]
)
get_db().commit()
# Get songs
songs = Song.get_for_playlist(playlistid)
+ # Get comments
+ comments = get_comments(plist_data["threadid"])
+
# Show page
return render_template(
"playlist.html",
private=plist_data["private"],
userid=plist_data["userid"],
username=plist_data["username"],
+ threadid=plist_data["threadid"],
**get_user_colors(plist_data),
- songs=songs)
+ songs=songs,
+ comments=comments)
def flash_and_log(msg, category=None):
flash(msg, category)
attributes=allowed_attributes,
css_sanitizer=css_sanitizer)
+def create_comment_thread(threadtype, userid):
+ thread = query_db("insert into comment_threads (threadtype, userid) values (?, ?) returning threadid", [threadtype, userid], one=True)
+ get_db().commit()
+ return thread["threadid"]
+
+def get_comments(threadid):
+ comments = query_db("select * from comments inner join users on comments.userid == users.userid where comments.threadid = ?", [threadid])
+ comments = [dict(c) for c in comments]
+ for c in comments:
+ c["content"] = sanitize_user_text(c["content"])
+
+ # Top-level comments
+ song_comments = sorted([dict(c) for c in comments if c["replytoid"] is None], key=lambda c: c["created"])
+ song_comments = list(reversed(song_comments))
+ # 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
+
def get_gif_data():
gifs = []
static_path = Path(__file__).parent / "static"
def assign_thread_ids(db, table, id_col, threadtype):
cur = db.execute(f"select * from {table}")
for row in cur:
- thread_cur = db.execute("insert into comment_threads (threadtype) values (?) returning threadid", [threadtype])
+ thread_cur = db.execute("insert into comment_threads (threadtype, userid) values (?, ?) returning threadid", [threadtype, row["userid"]])
threadid = thread_cur.fetchone()[0]
thread_cur.close()
return json.dumps(vars(self))
def get_comments(self):
- comments = query_db("select * from comments inner join users on comments.userid == users.userid where threadid = ?", [self.threadid])
- comments = [dict(c) for c in comments]
- for c in comments:
- c["content"] = sanitize_user_text(c["content"])
-
- # Top-level comments
- song_comments = sorted([dict(c) for c in comments if c["replytoid"] is None], key=lambda c: c["created"])
- song_comments = list(reversed(song_comments))
- # 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
+ return get_comments(self.threadid)
@classmethod
def by_id(cls, songid):
song_collabs = [c["name"] for c in collabs[sd["songid"]] if c["name"]]
created = datetime.fromisoformat(sd["created"]).astimezone().strftime("%Y-%m-%d")
has_pfp = user_has_pfp(sd["userid"])
- songs.append(cls(sd["songid"], sd["userid"], sd["username"], sd["title"], sanitize_user_text(sd["description"]), created, song_tags, song_collabs, has_pfp))
+ songs.append(cls(sd["songid"], sd["userid"], sd["threadid"], sd["username"], sd["title"], sanitize_user_text(sd["description"]), created, song_tags, song_collabs, has_pfp))
return songs
@classmethod
--- /dev/null
+{% macro comment_thread(threadid, current_userid, thread_userid, comments) %}
+ <div class="comment-thread">
+ {% if current_userid %}
+ <a href="/comment?threadid={{ threadid }}" class="song-list-button" title="Add a Comment"><img class="lsp_btn_add02" /></a>
+ {% endif %}
+
+ {% for comment in comments %}
+ <div class="top-level-comment">
+
+ <a href="/users/{{ comment['username'] }}" class="profile-link">{{ comment['username'] }}</a>:
+ {{ (comment['content'].replace("\n", "<br>"))|safe }}
+
+ {% if current_userid == comment['userid'] or current_userid == thread_userid %}
+ <div class="comment-button-container">
+ <!-- Only commenter can edit comment -->
+ {% if current_userid == comment['userid'] %}
+ <a href="/comment?commentid={{ comment['commentid'] }}&threadid={{ threadid }}" class="song-list-button" title="Edit">
+ <img class="lsp_btn_edit02" />
+ </a>
+ {% endif %}
+
+ <!-- Commenter and content owner can delete comment -->
+ <a href="/delete-comment/{{ comment['commentid'] }}" onclick="return confirm("Are you sure you want to delete this comment?")" class="song-list-button" title="Delete">
+ <img class="lsp_btn_delete02" />
+ </a>
+ </div>
+ {% endif %}
+
+ {% for reply in comment['replies'] %}
+ <div class="reply-comment">
+
+ <a href="/users/{{ reply['username'] }}" class="profile-link">{{ reply['username'] }}</a>:
+ {{ reply['content'] }}
+
+ {% if current_userid == reply['userid'] or current_userid == thread_userid %}
+ <div class="comment-button-container">
+ <!-- Only commenter can edit comment -->
+ {% if current_userid == reply['userid'] %}
+ <a href="/comment?commentid={{ reply['commentid'] }}&threadid={{ threadid }}&replytoid={{ comment['commentid'] }}" class="song-list-button" title="Edit">
+ <img class="lsp_btn_edit02" />
+ </a>
+ {% endif %}
+
+ <!-- Commenter and content owner can delete comment -->
+ <a href="/delete-comment/{{ reply['commentid'] }}" onclick="return confirm("Are you sure you want to delete this comment?")" class="song-list-button" title="delete">
+ <img class="lsp_btn_delete02" />
+ </a>
+ </div>
+ {% endif %}
+ </div>
+ {% endfor %}
+
+ <div class="comment-button-container">
+ <a href="/comment?threadid={{ threadid }}&replytoid={{ comment['commentid'] }}">Reply</a>
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+{% endmacro %}
+{% from "comment-thread.html" import comment_thread %}
+
{% macro song_artist(song) %}
<span class="song-artist">
<a href="/users/{{ song.username }}" class="profile-link">{{ song.username }}</a>
</div>
<!-- Song Comments -->
- <div class="song-comments">
- Comments:<br>
- {% if session['userid'] %}
- <a href="/comment?songid={{ song.songid }}" class="song-list-button" title="Add a Comment"><img class="lsp_btn_add02" /></a>
- {% endif %}
-
- {% for comment in song.get_comments() %}
- <div class="top-level-comment">
-
- <a href="/users/{{ comment['username'] }}" class="profile-link">{{ comment['username'] }}</a>:
- {{ (comment['content'].replace("\n", "<br>"))|safe }}
-
- {% if session['userid'] == comment['userid'] or session['userid'] == song.userid %}
- <div class="comment-button-container">
- {% endif %}
-
- <!-- Only commenter can edit comment -->
- {% if session['userid'] == comment['userid'] %}
- <a href="/comment?commentid={{ comment['commentid'] }}&songid={{ song.songid }}" class="song-list-button" title="Edit">
- <img class="lsp_btn_edit02" />
- </a>
- {% endif %}
-
- <!-- Commenter and song owner can delete comment -->
- {% if session['userid'] == comment['userid'] or session['userid'] == song.userid %}
- <a href="/delete-comment/{{ comment['commentid'] }}" onclick="return confirm("Are you sure you want to delete this comment?")" class="song-list-button" title="Delete">
- <img class="lsp_btn_delete02" />
- </a>
- {% endif %}
-
- {% if session['userid'] == comment['userid'] or session['userid'] == song.userid %}
- </div>
- {% endif %}
-
- {% for reply in comment['replies'] %}
- <div class="reply-comment">
-
- <a href="/users/{{ reply['username'] }}" class="profile-link">{{ reply['username'] }}</a>:
- {{ reply['content'] }}
-
- {% if session['userid'] == reply['userid'] or session['userid'] == song.userid %}
- <div class="comment-button-container">
- {% endif %}
-
- <!-- Only commenter can edit comment -->
- {% if session['userid'] == reply['userid'] %}
- <a href="/comment?commentid={{ reply['commentid'] }}&songid={{ song.songid }}&replytoid={{ comment['commentid'] }}" class="song-list-button" title="Edit">
- <img class="lsp_btn_edit02" />
- </a>
- {% endif %}
-
- <!-- Commenter and song owner can delete comment -->
- {% if session['userid'] == reply['userid'] or session['userid'] == song.userid %}
- <a href="/delete-comment/{{ reply['commentid'] }}" onclick="return confirm("Are you sure you want to delete this comment?")" class="song-list-button" title="delete">
- <img class="lsp_btn_delete02" />
- </a>
- {% endif %}
-
- {% if session['userid'] == reply['userid'] or session['userid'] == song.userid %}
- </div>
- {% endif %}
- </div>
- {% endfor %}
-
- <div class="comment-button-container">
- <a href="/comment?songid={{ song.songid }}&replytoid={{ comment['commentid'] }}">Reply</a>
- </div>
- </div>
- {% endfor %}
- </div>
+ <strong>Comments</strong>
+ {{ comment_thread(song.threadid, session["userid"], song.userid, song.get_comments()) }}
</div>
{% endmacro %}