from . import datadir
-DB_VERSION = 8
+DB_VERSION = 9
def get():
db = getattr(g, '_database', None)
collaborators: list[str]
user_has_pfp: bool
hidden: bool
+ private: bool
eventid: Optional[int]
jamid: Optional[int]
queueid: Optional[int]
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
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"],
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]
# 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])
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]
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
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);
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 (
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;
-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;
-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
LEFT JOIN jam_events USING (eventid)
LEFT JOIN import_queue USING (queueid);
-PRAGMA user_version = 8;
+PRAGMA user_version = 9;
<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>
{% 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 — for any
<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 %}
<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() }}">