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 }