Files
vibesonic/internal/subsonic/sharing.go
T

199 lines
4.8 KiB
Go

package subsonic
import (
"database/sql"
"strconv"
"time"
"git.noroutes.net/NoRoutes/vibetunes/internal/database"
"git.noroutes.net/NoRoutes/vibetunes/internal/idgen"
)
func init() {
register("getShares", handleGetShares)
register("createShare", handleCreateShare)
register("updateShare", handleUpdateShare)
register("deleteShare", handleDeleteShare)
}
func shareURL(rc *Context, token string) string {
scheme := "http"
if rc.R.TLS != nil {
scheme = "https"
}
return scheme + "://" + rc.R.Host + "/share/" + token
}
func shareToModel(db *sql.DB, rc *Context, s *database.Share) (Share, error) {
owner, _ := database.GetUserByID(db, s.OwnerID)
ownerName := ""
if owner != nil {
ownerName = owner.Username
}
out := Share{
ID: s.ID,
URL: shareURL(rc, s.Token),
Description: s.Description,
Username: ownerName,
Created: isoTime(s.CreatedAt),
VisitCount: s.VisitCount,
}
if s.ExpiresAt.Valid {
if t, err := parseISOTime(s.ExpiresAt.String); err == nil {
out.Expires = isoTime(t)
}
}
if s.LastVisitedAt.Valid {
if t, err := parseISOTime(s.LastVisitedAt.String); err == nil {
out.LastVisited = isoTime(t)
}
}
songIDs, err := database.ShareSongIDs(db, s.ID)
if err != nil {
return out, err
}
for _, id := range songIDs {
song, serr := database.GetSong(db, id)
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), ""))
}
return out, nil
}
func handleGetShares(rc *Context) *apiError {
if !rc.User.Allows(rc.User.Permissions.Share) {
return errUnauthorized("sharing")
}
db := rc.Server.DB
var shares []*database.Share
var err error
if rc.User.IsAdmin {
shares, err = database.ListAllShares(db)
} else {
shares, err = database.ListSharesByOwner(db, rc.User.ID)
}
if err != nil {
return newError(ErrGeneric, err.Error())
}
out := &Shares{}
for _, s := range shares {
model, merr := shareToModel(db, rc, s)
if merr != nil {
continue
}
out.Share = append(out.Share, model)
}
rc.Response = newResponse()
rc.Response.Shares = out
return nil
}
func handleCreateShare(rc *Context) *apiError {
if !rc.User.Allows(rc.User.Permissions.Share) {
return errUnauthorized("sharing")
}
ids := rc.Params.GetAll("id")
if len(ids) == 0 {
return errMissingParameter("id")
}
db := rc.Server.DB
description := rc.Params.Get("description")
var expiresAt sql.NullString
if v := rc.Params.Get("expires"); v != "" {
millis, perr := strconv.ParseInt(v, 10, 64)
if perr == nil {
expiresAt = sql.NullString{String: isoTime(time.UnixMilli(millis)), Valid: true}
}
}
token := idgen.New("")
share, err := database.CreateShare(db, rc.User.ID, token, description, expiresAt)
if err != nil {
return newError(ErrGeneric, err.Error())
}
pos := 0
for _, id := range ids {
var songID, albumID sql.NullString
if _, err := database.GetSong(db, id); err == nil {
songID = sql.NullString{String: id, Valid: true}
} else if _, err := database.GetAlbum(db, id); err == nil {
albumID = sql.NullString{String: id, Valid: true}
} else {
continue
}
if err := database.AddShareItem(db, share.ID, songID, albumID, pos); err != nil {
return newError(ErrGeneric, err.Error())
}
pos++
}
model, merr := shareToModel(db, rc, share)
if merr != nil {
return newError(ErrGeneric, merr.Error())
}
rc.Response = newResponse()
rc.Response.Shares = &Shares{Share: []Share{model}}
return nil
}
func handleUpdateShare(rc *Context) *apiError {
id, aerr := rc.Params.Required("id")
if aerr != nil {
return aerr
}
db := rc.Server.DB
share, err := database.GetShare(db, id)
if err != nil {
return errNotFound("Share")
}
if share.OwnerID != rc.User.ID && !rc.User.IsAdmin {
return errUnauthorized("modifying this share")
}
description := share.Description
if rc.Params.Has("description") {
description = rc.Params.Get("description")
}
expiresAt := share.ExpiresAt
if v := rc.Params.Get("expires"); v != "" {
millis, perr := strconv.ParseInt(v, 10, 64)
if perr == nil {
expiresAt = sql.NullString{String: isoTime(time.UnixMilli(millis)), Valid: true}
}
}
if err := database.UpdateShare(db, id, description, expiresAt); err != nil {
return newError(ErrGeneric, err.Error())
}
rc.Response = newResponse()
return nil
}
func handleDeleteShare(rc *Context) *apiError {
id, aerr := rc.Params.Required("id")
if aerr != nil {
return aerr
}
db := rc.Server.DB
share, err := database.GetShare(db, id)
if err != nil {
return errNotFound("Share")
}
if share.OwnerID != rc.User.ID && !rc.User.IsAdmin {
return errUnauthorized("deleting this share")
}
if err := database.DeleteShare(db, id); err != nil {
return newError(ErrGeneric, err.Error())
}
rc.Response = newResponse()
return nil
}