Subsonic: playlist endpoints (getPlaylists, getPlaylist, createPlaylist, updatePlaylist, deletePlaylist)
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
package subsonic
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"strconv"
|
||||
|
||||
"git.noroutes.net/NoRoutes/vibetunes/internal/database"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("getPlaylists", handleGetPlaylists)
|
||||
register("getPlaylist", handleGetPlaylist)
|
||||
register("createPlaylist", handleCreatePlaylist)
|
||||
register("updatePlaylist", handleUpdatePlaylist)
|
||||
register("deletePlaylist", handleDeletePlaylist)
|
||||
}
|
||||
|
||||
func playlistToModel(db *sql.DB, p *database.Playlist) Playlist {
|
||||
entries, _ := database.ListPlaylistEntries(db, p.ID)
|
||||
duration := 0
|
||||
for _, e := range entries {
|
||||
if mf, ok := mediaFileForSongID(db, e.SongID); ok {
|
||||
duration += int(mf.DurationSeconds + 0.5)
|
||||
}
|
||||
}
|
||||
owner, _ := database.GetUserByID(db, p.OwnerID)
|
||||
ownerName := ""
|
||||
if owner != nil {
|
||||
ownerName = owner.Username
|
||||
}
|
||||
return Playlist{
|
||||
ID: p.ID,
|
||||
Name: p.Name,
|
||||
Comment: p.Description,
|
||||
Owner: ownerName,
|
||||
Public: p.IsPublic,
|
||||
SongCount: len(entries),
|
||||
Duration: duration,
|
||||
Created: isoTime(p.CreatedAt),
|
||||
Changed: isoTime(p.UpdatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
func mediaFileForSongID(db *sql.DB, songID string) (*database.MediaFile, bool) {
|
||||
song, err := database.GetSong(db, songID)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
mf, err := database.GetMediaFile(db, song.MediaFileID)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return mf, true
|
||||
}
|
||||
|
||||
// canSeePlaylist reports whether user may view playlist p: they own it,
|
||||
// it's public, or they're an admin.
|
||||
func canSeePlaylist(user *database.User, p *database.Playlist) bool {
|
||||
return user.IsAdmin || user.ID == p.OwnerID || p.IsPublic
|
||||
}
|
||||
|
||||
func handleGetPlaylists(rc *Context) *apiError {
|
||||
db := rc.Server.DB
|
||||
var playlists []*database.Playlist
|
||||
var err error
|
||||
|
||||
if username := rc.Params.Get("username"); username != "" {
|
||||
if !rc.User.IsAdmin && username != rc.User.Username {
|
||||
return errUnauthorized("viewing another user's playlists")
|
||||
}
|
||||
target, terr := database.GetUserByUsername(db, username)
|
||||
if terr != nil {
|
||||
return errNotFound("User")
|
||||
}
|
||||
playlists, err = database.ListPlaylistsByOwner(db, target.ID)
|
||||
} else {
|
||||
playlists, err = database.ListPlaylistsVisibleToUser(db, rc.User.ID)
|
||||
}
|
||||
if err != nil {
|
||||
return newError(ErrGeneric, err.Error())
|
||||
}
|
||||
|
||||
out := &Playlists{}
|
||||
for _, p := range playlists {
|
||||
out.Playlist = append(out.Playlist, playlistToModel(db, p))
|
||||
}
|
||||
rc.Response = newResponse()
|
||||
rc.Response.Playlists = out
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleGetPlaylist(rc *Context) *apiError {
|
||||
id, aerr := rc.Params.Required("id")
|
||||
if aerr != nil {
|
||||
return aerr
|
||||
}
|
||||
db := rc.Server.DB
|
||||
p, err := database.GetPlaylist(db, id)
|
||||
if err != nil {
|
||||
return errNotFound("Playlist")
|
||||
}
|
||||
if !canSeePlaylist(rc.User, p) {
|
||||
return errUnauthorized("viewing this playlist")
|
||||
}
|
||||
|
||||
entries, err := database.ListPlaylistEntries(db, id)
|
||||
if err != nil {
|
||||
return newError(ErrGeneric, err.Error())
|
||||
}
|
||||
out := &PlaylistWithSongs{Playlist: playlistToModel(db, p)}
|
||||
for _, e := range entries {
|
||||
song, serr := database.GetSong(db, e.SongID)
|
||||
if serr != nil {
|
||||
continue
|
||||
}
|
||||
mf, merr := database.GetMediaFile(db, song.MediaFileID)
|
||||
if merr != nil {
|
||||
continue
|
||||
}
|
||||
out.Entry = append(out.Entry, childFromSong(db, song, mf, albumOfSong(db, song), rc.User.ID))
|
||||
}
|
||||
rc.Response = newResponse()
|
||||
rc.Response.Playlist = out
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleCreatePlaylist(rc *Context) *apiError {
|
||||
if !rc.User.Allows(rc.User.Permissions.Playlist) {
|
||||
return errUnauthorized("creating playlists")
|
||||
}
|
||||
db := rc.Server.DB
|
||||
songIDs := rc.Params.GetAll("songId")
|
||||
|
||||
if playlistID := rc.Params.Get("playlistId"); playlistID != "" {
|
||||
p, err := database.GetPlaylist(db, playlistID)
|
||||
if err != nil {
|
||||
return errNotFound("Playlist")
|
||||
}
|
||||
if p.OwnerID != rc.User.ID && !rc.User.IsAdmin {
|
||||
return errUnauthorized("modifying this playlist")
|
||||
}
|
||||
if err := database.ReplacePlaylistSongs(db, playlistID, songIDs); err != nil {
|
||||
return newError(ErrGeneric, err.Error())
|
||||
}
|
||||
return respondWithPlaylist(rc, playlistID)
|
||||
}
|
||||
|
||||
name, aerr := rc.Params.Required("name")
|
||||
if aerr != nil {
|
||||
return aerr
|
||||
}
|
||||
p, err := database.CreatePlaylist(db, rc.User.ID, name, "", false)
|
||||
if err != nil {
|
||||
return newError(ErrGeneric, err.Error())
|
||||
}
|
||||
if len(songIDs) > 0 {
|
||||
if err := database.ReplacePlaylistSongs(db, p.ID, songIDs); err != nil {
|
||||
return newError(ErrGeneric, err.Error())
|
||||
}
|
||||
}
|
||||
return respondWithPlaylist(rc, p.ID)
|
||||
}
|
||||
|
||||
func respondWithPlaylist(rc *Context, playlistID string) *apiError {
|
||||
db := rc.Server.DB
|
||||
p, err := database.GetPlaylist(db, playlistID)
|
||||
if err != nil {
|
||||
return newError(ErrGeneric, err.Error())
|
||||
}
|
||||
entries, err := database.ListPlaylistEntries(db, playlistID)
|
||||
if err != nil {
|
||||
return newError(ErrGeneric, err.Error())
|
||||
}
|
||||
out := &PlaylistWithSongs{Playlist: playlistToModel(db, p)}
|
||||
for _, e := range entries {
|
||||
song, serr := database.GetSong(db, e.SongID)
|
||||
if serr != nil {
|
||||
continue
|
||||
}
|
||||
mf, merr := database.GetMediaFile(db, song.MediaFileID)
|
||||
if merr != nil {
|
||||
continue
|
||||
}
|
||||
out.Entry = append(out.Entry, childFromSong(db, song, mf, albumOfSong(db, song), rc.User.ID))
|
||||
}
|
||||
rc.Response = newResponse()
|
||||
rc.Response.Playlist = out
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleUpdatePlaylist(rc *Context) *apiError {
|
||||
id, aerr := rc.Params.Required("playlistId")
|
||||
if aerr != nil {
|
||||
return aerr
|
||||
}
|
||||
db := rc.Server.DB
|
||||
p, err := database.GetPlaylist(db, id)
|
||||
if err != nil {
|
||||
return errNotFound("Playlist")
|
||||
}
|
||||
if p.OwnerID != rc.User.ID && !rc.User.IsAdmin {
|
||||
return errUnauthorized("modifying this playlist")
|
||||
}
|
||||
|
||||
name := p.Name
|
||||
if v := rc.Params.Get("name"); v != "" {
|
||||
name = v
|
||||
}
|
||||
comment := p.Description
|
||||
if rc.Params.Has("comment") {
|
||||
comment = rc.Params.Get("comment")
|
||||
}
|
||||
public := p.IsPublic
|
||||
if rc.Params.Has("public") {
|
||||
public = rc.Params.Bool("public", p.IsPublic)
|
||||
}
|
||||
if err := database.RenamePlaylist(db, id, name, comment, public); err != nil {
|
||||
return newError(ErrGeneric, err.Error())
|
||||
}
|
||||
|
||||
toRemove := rc.Params.GetAll("songIndexToRemove")
|
||||
toAdd := rc.Params.GetAll("songIdToAdd")
|
||||
if len(toRemove) > 0 || len(toAdd) > 0 {
|
||||
entries, err := database.ListPlaylistEntries(db, id)
|
||||
if err != nil {
|
||||
return newError(ErrGeneric, err.Error())
|
||||
}
|
||||
removeSet := map[int]bool{}
|
||||
for _, s := range toRemove {
|
||||
if n, err := strconv.Atoi(s); err == nil {
|
||||
removeSet[n] = true
|
||||
}
|
||||
}
|
||||
var songIDs []string
|
||||
for i, e := range entries {
|
||||
if !removeSet[i] {
|
||||
songIDs = append(songIDs, e.SongID)
|
||||
}
|
||||
}
|
||||
songIDs = append(songIDs, toAdd...)
|
||||
if err := database.ReplacePlaylistSongs(db, id, songIDs); err != nil {
|
||||
return newError(ErrGeneric, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
rc.Response = newResponse()
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleDeletePlaylist(rc *Context) *apiError {
|
||||
id, aerr := rc.Params.Required("id")
|
||||
if aerr != nil {
|
||||
return aerr
|
||||
}
|
||||
db := rc.Server.DB
|
||||
p, err := database.GetPlaylist(db, id)
|
||||
if err != nil {
|
||||
return errNotFound("Playlist")
|
||||
}
|
||||
if p.OwnerID != rc.User.ID && !rc.User.IsAdmin {
|
||||
return errUnauthorized("deleting this playlist")
|
||||
}
|
||||
if err := database.DeletePlaylist(db, id); err != nil {
|
||||
return newError(ErrGeneric, err.Error())
|
||||
}
|
||||
rc.Response = newResponse()
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user