]> littlesong.place Git - littlesongplace.git/commitdiff
Add private songs
authorChris Fulljames <christianfulljames@gmail.com>
Sat, 22 Aug 2026 12:36:38 +0000 (08:36 -0400)
committerChris Fulljames <christianfulljames@gmail.com>
Sat, 22 Aug 2026 12:36:38 +0000 (08:36 -0400)
src/littlesongplace/db.py
src/littlesongplace/songs.py
src/littlesongplace/sql/schema.sql
src/littlesongplace/sql/schema_revert.sql
src/littlesongplace/sql/schema_update.sql
src/littlesongplace/templates/edit-song.html
src/littlesongplace/templates/news.html
src/littlesongplace/templates/song-macros.html
src/littlesongplace/templates/song.html

index 3aae1bcfcae1a97efb3401e68372c075f1addfa7..cdcac92d4e4763b52d6940587941acde213ffdd3 100644 (file)
@@ -6,7 +6,7 @@ from flask import abort, g, current_app
 
 from . import datadir
 
-DB_VERSION = 8
+DB_VERSION = 9
 
 def get():
     db = getattr(g, '_database', None)
index 62b17b22bfc29bb6cbcdc2f3f4ebe47e6ff4725c..a590482938044fdb119ad42b8f76ef58bf8d72af 100644 (file)
@@ -34,6 +34,7 @@ class Song:
     collaborators: list[str]
     user_has_pfp: bool
     hidden: bool
+    private: bool
     eventid: Optional[int]
     jamid: Optional[int]
     queueid: Optional[int]
@@ -157,10 +158,13 @@ def _from_db(query, args=()):
         songid = sd["songid"]
         song_tags = sd["tags"].split(",") if sd["tags"] else []
         song_collabs = sd["collaborators"].split(",") if sd["collaborators"] else []
+        private = bool(sd["private"])
 
-        # Song is hidden if it is still queued for import or was submitted to
-        # an event that hasn't ended yet
-        hidden = sd["queueid"] != None
+        # Song is hidden if it is:
+        # - private
+        # - still queued for import
+        # - submitted to an event that hasn't ended yet
+        hidden = private or (sd["queueid"] is not None)
         if sd["event_enddate"]:
             enddate = datetime.fromisoformat(sd["event_enddate"])
             hidden |= datetime.now(timezone.utc) < enddate
@@ -182,6 +186,7 @@ def _from_db(query, args=()):
             collaborators=song_collabs,
             user_has_pfp=users.user_has_pfp(sd["userid"]),
             hidden=hidden,
+            private=private,
             eventid=sd["eventid"],
             jamid=sd["jamid"],
             queueid=sd["queueid"],
@@ -328,6 +333,7 @@ def update_song():
     description = request.form["description"]
     upload_type = request.form["upload-type"]
     song_duration = request.form["song-duration"]
+    private = "private" in request.form # Will not be present if unchecked
     fade_out = "fade-out" in request.form # Will not be present if unchecked
     tags = [t.strip() for t in request.form["tags"].split(",") if t]
     collaborators = [c.strip() for c in request.form["collabs"].split(",") if c]
@@ -362,10 +368,10 @@ def update_song():
         # Update songs table
         db.query(
             """
-            UPDATE songs SET title = ?, description = ?
+            UPDATE songs SET title = ?, description = ?, private = ?
             WHERE songid = ?
             """,
-            [title, description, songid])
+            [title, description, private, songid])
 
         # Update song_tags table
         db.query("delete from song_tags where songid = ?", [songid])
@@ -403,6 +409,7 @@ def create_song():
     description = request.form["description"]
     upload_type = request.form["upload-type"]
     song_duration = request.form["song-duration"]
+    private = "private" in request.form # Will not be present if unchecked
     fade_out = "fade-out" in request.form # Will not be present if unchecked
     tags = [t.strip() for t in request.form["tags"].split(",") if t]
     collaborators = [c.strip() for c in request.form["collabs"].split(",") if c]
@@ -425,11 +432,11 @@ def create_song():
             timestamp = datetime.now(timezone.utc).isoformat()
             song_data = db.query(
                 """
-                INSERT INTO songs (userid, title, description, created, threadid, eventid)
-                VALUES (?, ?, ?, ?, ?, ?)
+                INSERT INTO songs (userid, title, description, created, threadid, eventid, private)
+                VALUES (?, ?, ?, ?, ?, ?, ?)
                 RETURNING (songid)
                 """,
-                [session["userid"], title, description, timestamp, threadid, eventid],
+                [session["userid"], title, description, timestamp, threadid, eventid, private],
                 one=True)
 
             # Move file to permanent location
index d40ff514b540a362c3115d296eee46b8d58a87fd..47c5b73c573b22e209dfc7dc03d15ddb53eb24bf 100644 (file)
@@ -22,8 +22,10 @@ CREATE TABLE songs (
     description TEXT,
     threadid INTEGER,
     eventid INTEGER,
+    queueid INTEGER,
     FOREIGN KEY(userid) REFERENCES users(userid),
-    FOREIGN KEY(eventid) REFERENCES jam_events(eventid)
+    FOREIGN KEY(eventid) REFERENCES jam_events(eventid),
+    FOREIGN KEY(queueid) REFERENCES import_queue(queueid) ON DELETE SET NULL
 );
 CREATE INDEX idx_songs_by_user ON songs(userid);
 CREATE INDEX idx_songs_by_eventid ON songs(eventid);
@@ -180,12 +182,14 @@ CREATE VIEW songs_view AS
         jam_events.jamid AS jamid,
         jam_events.enddate AS event_enddate,
         tags_agg.tags,
-        collaborators_agg.collaborators
+        collaborators_agg.collaborators,
+        import_queue.status AS queue_status
     FROM songs
-    INNER JOIN users ON songs.userid = users.userid
-    LEFT JOIN tags_agg ON tags_agg.songid = songs.songid
-    LEFT JOIN collaborators_agg ON collaborators_agg.songid = songs.songid
-    LEFT JOIN jam_events ON jam_events.eventid = songs.eventid;
+    INNER JOIN users USING (userid)
+    LEFT JOIN tags_agg USING (songid)
+    LEFT JOIN collaborators_agg USING (songid)
+    LEFT JOIN jam_events USING (eventid)
+    LEFT JOIN import_queue USING (queueid);
 
 DROP TABLE IF EXISTS users_push_subscriptions;
 CREATE TABLE users_push_subscriptions (
@@ -196,5 +200,16 @@ CREATE TABLE users_push_subscriptions (
     FOREIGN KEY(userid) REFERENCES users(userid) ON DELETE CASCADE
 );
 
-PRAGMA user_version = 7;
+DROP TABLE IF EXISTS import_queue;
+CREATE TABLE import_queue (
+    queueid INTEGER PRIMARY KEY AUTOINCREMENT,
+    created TEXT NOT NULL,
+    indreamsurl TEXT NOT NULL,
+    songid INTEGER NOT NULL REFERENCES songs(songid) ON DELETE CASCADE,
+    status INTEGER NOT NULL,
+    duration INTEGER NOT NULL,
+    fadeout INTEGER NOT NULL
+);
+
+PRAGMA user_version = 8;
 
index 19e44da062605fedfab03a6717841b5daf41e7ec..018e8e3e45f53a742c3b79ebf3d1c177bbb7d213 100644 (file)
@@ -1,6 +1,5 @@
-DROP VIEW IF EXISTS songs_view;
-ALTER TABLE songs DROP COLUMN queueid;
-DROP TABLE IF EXISTS import_queue;
 
-PRAGMA user_version = 7;
+ALTER TABLE songs DROP COLUMN private;
+
+PRAGMA user_version = 8;
 
index ac26d913335845708060f80b28a2c2e86b55d348..247b13187398ee09e7713e9a8390fd07bed3d5e6 100644 (file)
@@ -1,15 +1,5 @@
-ALTER TABLE songs ADD COLUMN queueid INTEGER REFERENCES import_queue(queueid) ON DELETE SET NULL;
 
--- DROP TABLE IF EXISTS import_queue;
-CREATE TABLE import_queue (
-    queueid INTEGER PRIMARY KEY AUTOINCREMENT,
-    created TEXT NOT NULL,
-    indreamsurl TEXT NOT NULL,
-    songid INTEGER NOT NULL REFERENCES songs(songid) ON DELETE CASCADE,
-    status INTEGER NOT NULL,
-    duration INTEGER NOT NULL,
-    fadeout INTEGER NOT NULL
-);
+ALTER TABLE songs ADD COLUMN private INTEGER;
 
 DROP VIEW IF EXISTS songs_view;
 CREATE VIEW songs_view AS
@@ -43,5 +33,5 @@ CREATE VIEW songs_view AS
     LEFT JOIN jam_events USING (eventid)
     LEFT JOIN import_queue USING (queueid);
 
-PRAGMA user_version = 8;
+PRAGMA user_version = 9;
 
index 5dc7c1b556c57284da77e5d4c6485d607de4e2d0..8a550c11282e5f08ef6a2a5f266b636e6d8f29a2 100644 (file)
         <label>Fade Out (for Loops)<br>
             <input type="checkbox" name="fade-out" style="margin: 10px"/></label>
     </div>
+    <div class="upload-form">
+        <label>Private<br>
+            <input type="checkbox" name="private" style="margin: 10px" {% if song and song.private %}checked {% endif %}/></label>
+    </div>
     <div class="upload-form">
         <label for="title">Title</label><br>
         <input type="text" name="title" id="song-title" value="{{ song.title }}" maxlength="80" required>
index a580a9149280bd6873720676eb0cbd29de9534c0..a1b9f1aff8d546a6d0304590337d9c9f09c46caa 100644 (file)
@@ -5,6 +5,13 @@
 {% block body %}
 
 <h1>site news</h1>
+
+<h2>2026-08-22 - Private Songs</h2>
+<p>
+You can now mark songs as private, so they will only be visible to you.  This
+works basically the same way as it does for playlists.
+</p>
+
 <h2>2026-03-10 - New Email</h2>
 <p>
 The site finally has its own email address!  No more gmail &mdash; for any
index 7073dd625f1ea887c4b6e1f656adf2d39662c5b1..fc8d9466936821cf437c2daaa8b3290717043032 100644 (file)
@@ -19,7 +19,8 @@
 <div class="song-info">
     <!-- Song Title -->
     <div class="song-title">
-        {%- if song.hidden %}<span class="visibility-indicator" title="This song is not visible to others">[Hidden]</span>{% endif %}
+        {%- if song.private %}<span class="visibility-indicator" title="This song is private">[Private]</span>
+        {%- elif song.hidden %}<span class="visibility-indicator" title="This song is not visible to others">[Hidden]</span>{% endif %}
         {%- if song.queue_status == 0 %}<span class="visibility-indicator" title="Position in importer queue">[Queue Pos: {{ song.get_queue_position() }}]</span>{% endif %}
         {%- if song.queue_status == 1 %}<span class="visibility-indicator">[Importing Now]</span>{% endif %}
         {%- if song.queue_status == 2 %}<span class="visibility-indicator">[Import Failed]</span>{% endif %}
index 86b1ef2e209d9b0c57bf06f58b4085ab77327c96..55ce97fc409602d2b0ba6c80f75fb93483536c21 100644 (file)
@@ -14,6 +14,8 @@
 
 <p>Song by {{ song_artist(song) }}</p>
 
+{% if song.private %}<p><span class="visibility-indicator">[Private]</span></p>{% endif %}
+
 <p class="song-actions">
 <!-- Play Button -->
 <span class="song" data-song="{{ song.json() }}">