def about():
     return render_template("about.html")
 
+@app.get("/notifications")
+def notifications():
+    return render_template("notifications.html")
+
 def get_gif_data():
     # Convert all .gifs to base64 strings and embed them as dataset entries
     # in <div>s.  This is used by nav.js:customImage() - it replaces specific
 
 import bcrypt
 from flask import Blueprint, render_template, redirect, flash, g, request, current_app, session
 
-from . import comments, db
+from . import comments, db, users
 from .logutils import flash_and_log
 
 bp = Blueprint("auth", __name__)
         if not "userid" in session:
             return redirect("/login")
 
-        g.userid = session["userid"]
-        g.username = session["username"]
-
         return f(*args, **kwargs)
 
     return _wrapper
 
+@bp.before_app_request
+def load_user():
+    if "userid" in session:
+        g.userid = session["userid"]
+        g.username = session["username"]
+        g.user = users.by_id(session["userid"])
+
 
 
 from . import datadir
 
-DB_VERSION = 5
+DB_VERSION = 6
 
 def get():
     db = getattr(g, '_database', None)
 
+DROP TABLE IF EXISTS jam_events;
+CREATE TABLE jam_events(
+    eventid INTEGER PRIMARY KEY,
+    jamid INTEGER NOT NULL,
+    threadid INTEGER NOT NULL,
+    created TEXT NOT NULL,
+    title TEXT NOT NULL, -- Hidden until startdate
+    startdate TEXT,
+    enddate TEXT,
+    description TEXT, -- Hidden until startdate
+    FOREIGN KEY(jamid) REFERENCES jams(jamid),
+    FOREIGN KEY(threadid) REFERENCES comment_threads(threadid)
+);
+
 DROP TABLE IF EXISTS users;
 CREATE TABLE users (
     userid INTEGER PRIMARY KEY AUTOINCREMENT,
     title TEXT NOT NULL,
     description TEXT,
     threadid INTEGER,
-    FOREIGN KEY(userid) REFERENCES users(userid)
+    eventid INTEGER,
+    FOREIGN KEY(userid) REFERENCES users(userid),
+    FOREIGN KEY(eventid) REFERENCES jam_events(eventid)
 );
 CREATE INDEX idx_songs_by_user ON songs(userid);
+CREATE INDEX idx_songs_by_eventid ON songs(eventid);
 
 DROP TABLE IF EXISTS song_collaborators;
 CREATE TABLE song_collaborators (
     DELETE FROM notifications WHERE objectid = OLD.commentid AND objecttype = 0;
 END;
 
-PRAGMA user_version = 4;
+DROP TABLE IF EXISTS jams;
+CREATE TABLE jams (
+    jamid INTEGER PRIMARY KEY,
+    ownerid INTEGER NOT NULL,
+    created TEXT NOT NULL,
+    title TEXT NOT NULL,
+    description TEXT,
+    FOREIGN KEY(ownerid) REFERENCES users(userid)
+);
+
+PRAGMA user_version = 5;
 
 
-DROP INDEX idx_songs_by_eventid;
-ALTER TABLE songs DROP COLUMN eventid;
-DROP TABLE IF EXISTS jams;
-DROP TABLE IF EXISTS jam_events;
-
-PRAGMA user_version = 4;
+ALTER TABLE users DROP COLUMN ntfyuuid;
+PRAGMA user_version = 5;
 
 
---DROP TABLE IF EXISTS jams;
-CREATE TABLE jams (
-    jamid INTEGER PRIMARY KEY,
-    ownerid INTEGER NOT NULL,
-    created TEXT NOT NULL,
-    title TEXT NOT NULL,
-    description TEXT,
-    FOREIGN KEY(ownerid) REFERENCES users(userid)
-);
-
---DROP TABLE IF EXISTS jam_events;
-CREATE TABLE jam_events(
-    eventid INTEGER PRIMARY KEY,
-    jamid INTEGER NOT NULL,
-    threadid INTEGER NOT NULL,
-    created TEXT NOT NULL,
-    title TEXT NOT NULL, -- Hidden until startdate
-    startdate TEXT,
-    enddate TEXT,
-    description TEXT, -- Hidden until startdate
-    FOREIGN KEY(jamid) REFERENCES jams(jamid),
-    FOREIGN KEY(threadid) REFERENCES comment_threads(threadid)
-);
-
-ALTER TABLE songs ADD COLUMN eventid INTEGER REFERENCES jam_events(eventid);
-CREATE INDEX idx_songs_by_eventid ON songs(eventid);
-
-PRAGMA user_version = 5;
+ALTER TABLE users ADD COLUMN ntfyuuid TEXT;
+PRAGMA user_version = 6;
 
 
 
 {% block body %}
 
+<p><a href="/notifications">[push notifications available]</a></p>
+
 {% if comments %}
 <h1>activity</h1>
 
 
 </div>
 
 <h2>hot new tunes</h2>
+<p><a href="/notifications">[push notifications available]</a></p>
 {% include "song-list.html" %}
 
 {% endblock %}
 
 
 <h1>site news</h1>
 
+<p><a href="/notifications">[push notifications available]</a></p>
+
 <h2>2025-07-01 - Download Button</h2>
 <p>
 You can now download your own songs as mp3 files from the dedicated button on
 
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}Push Notifications{% endblock %}
+
+{% block body %}
+
+<h1>Push Notifications</h1>
+
+<p>
+littlesong.place can send you push notifications on your phone via
+<a href="https://ntfy.sh/">ntfy</a>.
+It takes a few minutes to set up (I know, it's a little clunky), but you only have to do it once.
+Here's how it works:
+</p>
+<ol>
+    <li>Install the <a href="https://ntfy.sh/">ntfy app</a> on your device.</li>
+    <li>Open the app and grant it permission to send you push notifications when asked.</li>
+    <li>In the ntfy app, open the <b>Settings</b> tab, and set <b>Default server</b> to <a href="https://ntfy.littlesong.place">https://ntfy.littlesong.place</a>.<br><img src="/static/ntfy_server.webp" width="256"></li>
+    <li>Subscribe to each topic you'd like to be notified about using the '+' button in the app (see available topics below).<br><img src="/static/ntfy_sub.webp" width="256"></li>
+    <li>When you're done, it should look something like this:<br><img src="/static/ntfy_done.webp" width="256"><br></li>
+</ol>
+Available topics include:
+<ul>
+    <li><b>songs</b> - get notified (at most once per day) when new songs are uploaded</li>
+    <li><b>news</b> - get notified about <a href="/site-news">site news</a></li>
+    <li><b>jams</b> - get notified when <a href="/site-news">jams</a> start or end</li>
+    {% if g.user %}
+    <li><b>{{ g.user.ntfy_uuid }}</b> (unique to you) - get notified about <a href="/activity">activity</a> (comments)</li>
+    {% endif %}
+</ul>
+
+{% endblock %}
 
+import uuid
 from dataclasses import dataclass
 
 from . import colors, datadir, db
     fgcolor: str
     bgcolor: str
     accolor: str
+    _ntfy_uuid: str
 
     @property
     def colors(self):
             "accolor": self.accolor,
         }
 
+    @property
+    def ntfy_uuid(self):
+        if not self._ntfy_uuid:
+            self._ntfy_uuid = str(uuid.uuid4())
+            db.query(
+                    "UPDATE users SET ntfyuuid = ? WHERE userid = ?",
+                    [self._ntfy_uuid, self.userid])
+            db.commit()
+        return self._ntfy_uuid
+
     @classmethod
     def from_row(cls, row):
         user_colors = get_user_colors(row)
         return User(
                 userid=row["userid"],
                 username=row["username"],
+                _ntfy_uuid=row["ntfyuuid"],
                 **user_colors)
 
 def by_id(userid):