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):