Files
vibesonic/internal/database/playlists.go
T

126 lines
3.6 KiB
Go

package database
import (
"database/sql"
"errors"
"time"
"git.noroutes.net/NoRoutes/vibetunes/internal/idgen"
)
type Playlist struct {
ID string
OwnerID string
Name string
Description string
IsPublic bool
CreatedAt time.Time
UpdatedAt time.Time
}
func CreatePlaylist(db *sql.DB, ownerID, name, description string, isPublic bool) (*Playlist, error) {
id := idgen.New("pl")
_, err := db.Exec(`INSERT INTO playlists (id, owner_id, name, description, is_public) VALUES (?, ?, ?, ?, ?)`,
id, ownerID, name, description, boolInt(isPublic))
if err != nil {
return nil, err
}
return GetPlaylist(db, id)
}
const playlistSelect = `SELECT id, owner_id, name, description, is_public, created_at, updated_at FROM playlists`
func GetPlaylist(db *sql.DB, id string) (*Playlist, error) {
return scanPlaylist(db.QueryRow(playlistSelect+` WHERE id = ?`, id))
}
func scanPlaylist(row *sql.Row) (*Playlist, error) {
var p Playlist
var isPublic int
var createdAt, updatedAt string
err := row.Scan(&p.ID, &p.OwnerID, &p.Name, &p.Description, &isPublic, &createdAt, &updatedAt)
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound
}
if err != nil {
return nil, err
}
p.IsPublic = isPublic != 0
p.CreatedAt, _ = parseTime(createdAt)
p.UpdatedAt, _ = parseTime(updatedAt)
return &p, nil
}
// ListPlaylistsVisibleToUser returns playlists owned by the user plus any
// public playlists owned by others.
func ListPlaylistsVisibleToUser(db *sql.DB, userID string) ([]*Playlist, error) {
rows, err := db.Query(playlistSelect+` WHERE owner_id = ? OR is_public = 1 ORDER BY name COLLATE NOCASE`, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var out []*Playlist
for rows.Next() {
var p Playlist
var isPublic int
var createdAt, updatedAt string
if err := rows.Scan(&p.ID, &p.OwnerID, &p.Name, &p.Description, &isPublic, &createdAt, &updatedAt); err != nil {
return nil, err
}
p.IsPublic = isPublic != 0
p.CreatedAt, _ = parseTime(createdAt)
p.UpdatedAt, _ = parseTime(updatedAt)
out = append(out, &p)
}
return out, rows.Err()
}
func DeletePlaylist(db *sql.DB, id string) error {
_, err := db.Exec(`DELETE FROM playlists WHERE id = ?`, id)
return err
}
func RenamePlaylist(db *sql.DB, id, name, description string, isPublic bool) error {
_, err := db.Exec(`UPDATE playlists SET name=?, description=?, is_public=?, updated_at=strftime('%Y-%m-%dT%H:%M:%fZ','now') WHERE id=?`,
name, description, boolInt(isPublic), id)
return err
}
type PlaylistEntry struct {
ID string
SongID string
Position int
}
func ListPlaylistEntries(db *sql.DB, playlistID string) ([]*PlaylistEntry, error) {
rows, err := db.Query(`SELECT id, song_id, position FROM playlist_entries WHERE playlist_id = ? ORDER BY position`, playlistID)
if err != nil {
return nil, err
}
defer rows.Close()
var out []*PlaylistEntry
for rows.Next() {
var e PlaylistEntry
if err := rows.Scan(&e.ID, &e.SongID, &e.Position); err != nil {
return nil, err
}
out = append(out, &e)
}
return out, rows.Err()
}
// AppendPlaylistEntry adds a song at the end of the playlist.
func AppendPlaylistEntry(db *sql.DB, playlistID, songID string) error {
var next int
if err := db.QueryRow(`SELECT COALESCE(MAX(position), -1) + 1 FROM playlist_entries WHERE playlist_id = ?`, playlistID).Scan(&next); err != nil {
return err
}
_, err := db.Exec(`INSERT INTO playlist_entries (id, playlist_id, song_id, position) VALUES (?, ?, ?, ?)`, idgen.New("pe"), playlistID, songID, next)
return err
}
func RemovePlaylistEntry(db *sql.DB, entryID string) error {
_, err := db.Exec(`DELETE FROM playlist_entries WHERE id = ?`, entryID)
return err
}