Subsonic: internet radio, chat, bookmarks, play queue, scanner status endpoints

This commit is contained in:
2026-08-23 00:21:13 -05:00
parent c6e0131984
commit d92a25b141
13 changed files with 682 additions and 2 deletions
+7 -2
View File
@@ -144,6 +144,8 @@ type Bookmark struct {
SongID string
PositionMs int
Comment string
CreatedAt time.Time
UpdatedAt time.Time
}
func SetBookmark(db *sql.DB, userID, songID string, positionMs int, comment string) error {
@@ -159,7 +161,7 @@ func DeleteBookmark(db *sql.DB, userID, songID string) error {
}
func ListBookmarks(db *sql.DB, userID string) ([]*Bookmark, error) {
rows, err := db.Query(`SELECT id, song_id, position_ms, comment FROM bookmarks WHERE user_id = ? ORDER BY updated_at DESC`, userID)
rows, err := db.Query(`SELECT id, song_id, position_ms, comment, created_at, updated_at FROM bookmarks WHERE user_id = ? ORDER BY updated_at DESC`, userID)
if err != nil {
return nil, err
}
@@ -167,9 +169,12 @@ func ListBookmarks(db *sql.DB, userID string) ([]*Bookmark, error) {
var out []*Bookmark
for rows.Next() {
var b Bookmark
if err := rows.Scan(&b.ID, &b.SongID, &b.PositionMs, &b.Comment); err != nil {
var createdAt, updatedAt string
if err := rows.Scan(&b.ID, &b.SongID, &b.PositionMs, &b.Comment, &createdAt, &updatedAt); err != nil {
return nil, err
}
b.CreatedAt, _ = parseTime(createdAt)
b.UpdatedAt, _ = parseTime(updatedAt)
out = append(out, &b)
}
return out, rows.Err()
+43
View File
@@ -0,0 +1,43 @@
package database
import (
"database/sql"
"time"
"git.noroutes.net/NoRoutes/vibetunes/internal/idgen"
)
type ChatMessage struct {
Username string
Message string
CreatedAt time.Time
}
func AddChatMessage(db *sql.DB, userID, message string) error {
_, err := db.Exec(`INSERT INTO chat_messages (id, user_id, message) VALUES (?, ?, ?)`, idgen.New("cm"), userID, message)
return err
}
// ListChatMessagesSince returns chat messages newer than since (pass the
// zero time for all messages), oldest first, capped at 500 — a chat
// room does not need unbounded history in one response.
func ListChatMessagesSince(db *sql.DB, since time.Time) ([]ChatMessage, error) {
rows, err := db.Query(`SELECT u.username, c.message, c.created_at FROM chat_messages c
JOIN users u ON u.id = c.user_id
WHERE c.created_at > ? ORDER BY c.created_at ASC LIMIT 500`, since.UTC().Format(timeLayout))
if err != nil {
return nil, err
}
defer rows.Close()
var out []ChatMessage
for rows.Next() {
var m ChatMessage
var createdAt string
if err := rows.Scan(&m.Username, &m.Message, &createdAt); err != nil {
return nil, err
}
m.CreatedAt, _ = parseTime(createdAt)
out = append(out, m)
}
return out, rows.Err()
}
+66
View File
@@ -0,0 +1,66 @@
package database
import (
"database/sql"
"errors"
"git.noroutes.net/NoRoutes/vibetunes/internal/idgen"
)
type RadioStation struct {
ID string
Name string
StreamURL string
HomepageURL string
}
func CreateRadioStation(db *sql.DB, name, streamURL, homepageURL string) (*RadioStation, error) {
id := idgen.New("rs")
_, err := db.Exec(`INSERT INTO internet_radio_stations (id, name, stream_url, homepage_url) VALUES (?, ?, ?, ?)`,
id, name, streamURL, homepageURL)
if err != nil {
return nil, err
}
return GetRadioStation(db, id)
}
func GetRadioStation(db *sql.DB, id string) (*RadioStation, error) {
var r RadioStation
err := db.QueryRow(`SELECT id, name, stream_url, homepage_url FROM internet_radio_stations WHERE id = ?`, id).
Scan(&r.ID, &r.Name, &r.StreamURL, &r.HomepageURL)
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound
}
if err != nil {
return nil, err
}
return &r, nil
}
func ListRadioStations(db *sql.DB) ([]*RadioStation, error) {
rows, err := db.Query(`SELECT id, name, stream_url, homepage_url FROM internet_radio_stations ORDER BY name COLLATE NOCASE`)
if err != nil {
return nil, err
}
defer rows.Close()
var out []*RadioStation
for rows.Next() {
var r RadioStation
if err := rows.Scan(&r.ID, &r.Name, &r.StreamURL, &r.HomepageURL); err != nil {
return nil, err
}
out = append(out, &r)
}
return out, rows.Err()
}
func UpdateRadioStation(db *sql.DB, id, name, streamURL, homepageURL string) error {
_, err := db.Exec(`UPDATE internet_radio_stations SET name = ?, stream_url = ?, homepage_url = ? WHERE id = ?`,
name, streamURL, homepageURL, id)
return err
}
func DeleteRadioStation(db *sql.DB, id string) error {
_, err := db.Exec(`DELETE FROM internet_radio_stations WHERE id = ?`, id)
return err
}
+16
View File
@@ -259,3 +259,19 @@ func FindSongByTitleArtist(db *sql.DB, title, artist string) (*Song, error) {
}
return GetSong(db, id)
}
// TotalMediaFileCount returns how many (non-missing) media files are
// indexed across all folders, for getScanStatus's idle "count".
func TotalMediaFileCount(db *sql.DB) (int64, error) {
var n int64
err := db.QueryRow(`SELECT COUNT(*) FROM media_files WHERE missing = 0`).Scan(&n)
return n, err
}
// TotalFilesSeenAcrossScans sums files_seen for folders currently mid-
// scan, for getScanStatus's in-progress "count".
func TotalFilesSeenAcrossScans(db *sql.DB) (int64, error) {
var n int64
err := db.QueryRow(`SELECT COALESCE(SUM(files_seen), 0) FROM scan_state WHERE status = 'scanning'`).Scan(&n)
return n, err
}