refactor(podcast)!: make podcasts global not per user, to match spec
Release-As: 0.15.0
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,3 +15,4 @@ gonicscan
|
|||||||
gonicembed
|
gonicembed
|
||||||
.vscode
|
.vscode
|
||||||
*.swp
|
*.swp
|
||||||
|
.tags*
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
- browsing by tags (using [taglib](https://taglib.org/) - supports mp3, opus, flac, ape, m4a, wav, etc.)
|
- browsing by tags (using [taglib](https://taglib.org/) - supports mp3, opus, flac, ape, m4a, wav, etc.)
|
||||||
- on-the-fly audio transcoding and caching (requires [ffmpeg](https://ffmpeg.org/)) (thank you [spijet](https://github.com/spijet/))
|
- on-the-fly audio transcoding and caching (requires [ffmpeg](https://ffmpeg.org/)) (thank you [spijet](https://github.com/spijet/))
|
||||||
- jukebox mode (thank you [lxea](https://github.com/lxea/))
|
- jukebox mode (thank you [lxea](https://github.com/lxea/))
|
||||||
- support for per-user podcasts (thank you [lxea](https://github.com/lxea/))
|
- support for podcasts (thank you [lxea](https://github.com/lxea/))
|
||||||
- pretty fast scanning (with my library of ~27k tracks, initial scan takes about 10m, and about 5s after incrementally)
|
- pretty fast scanning (with my library of ~27k tracks, initial scan takes about 10m, and about 5s after incrementally)
|
||||||
- multiple users, each with their own transcoding preferences, playlists, top tracks, top artists, etc.
|
- multiple users, each with their own transcoding preferences, playlists, top tracks, top artists, etc.
|
||||||
- [last.fm](https://www.last.fm/) scrobbling
|
- [last.fm](https://www.last.fm/) scrobbling
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ func (db *DB) Migrate(ctx MigrationContext) error {
|
|||||||
construct(ctx, "202202092013", migrateArtistCover),
|
construct(ctx, "202202092013", migrateArtistCover),
|
||||||
construct(ctx, "202202121809", migrateAlbumRootDirAgain),
|
construct(ctx, "202202121809", migrateAlbumRootDirAgain),
|
||||||
construct(ctx, "202202241218", migratePublicPlaylist),
|
construct(ctx, "202202241218", migratePublicPlaylist),
|
||||||
|
construct(ctx, "202204270903", migratePodcastDropUserID),
|
||||||
}
|
}
|
||||||
|
|
||||||
return gormigrate.
|
return gormigrate.
|
||||||
@@ -332,3 +333,26 @@ func migrateAlbumRootDirAgain(tx *gorm.DB, ctx MigrationContext) error {
|
|||||||
func migratePublicPlaylist(tx *gorm.DB, ctx MigrationContext) error {
|
func migratePublicPlaylist(tx *gorm.DB, ctx MigrationContext) error {
|
||||||
return tx.AutoMigrate(Playlist{}).Error
|
return tx.AutoMigrate(Playlist{}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func migratePodcastDropUserID(tx *gorm.DB, _ MigrationContext) error {
|
||||||
|
step := tx.AutoMigrate(
|
||||||
|
Podcast{},
|
||||||
|
)
|
||||||
|
if err := step.Error; err != nil {
|
||||||
|
return fmt.Errorf("step auto migrate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tx.Dialect().HasColumn("podcasts", "user_id") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
step = tx.Exec(`
|
||||||
|
ALTER TABLE podcasts DROP COLUMN user_id;
|
||||||
|
`)
|
||||||
|
if err := step.Error; err != nil {
|
||||||
|
return fmt.Errorf("step migrate podcasts drop user_id: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,15 +7,14 @@ package db
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
// TODO: remove this dep
|
// TODO: remove this dep
|
||||||
|
|
||||||
"go.senan.xyz/gonic/server/ctrlsubsonic/specid"
|
|
||||||
"go.senan.xyz/gonic/mime"
|
"go.senan.xyz/gonic/mime"
|
||||||
|
"go.senan.xyz/gonic/server/ctrlsubsonic/specid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func splitInt(in, sep string) []int {
|
func splitInt(in, sep string) []int {
|
||||||
@@ -315,7 +314,6 @@ type Podcast struct {
|
|||||||
ID int `gorm:"primary_key"`
|
ID int `gorm:"primary_key"`
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
ModifiedAt time.Time
|
ModifiedAt time.Time
|
||||||
UserID int `sql:"default: null; type:int REFERENCES users(id) ON DELETE CASCADE"`
|
|
||||||
URL string
|
URL string
|
||||||
Title string
|
Title string
|
||||||
Description string
|
Description string
|
||||||
@@ -326,11 +324,6 @@ type Podcast struct {
|
|||||||
AutoDownload PodcastAutoDownload
|
AutoDownload PodcastAutoDownload
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Podcast) Fullpath(podcastPath string) string {
|
|
||||||
sanitizedTitle := strings.ReplaceAll(p.Title, "/", "_")
|
|
||||||
return filepath.Join(podcastPath, filepath.Clean(sanitizedTitle))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Podcast) SID() *specid.ID {
|
func (p *Podcast) SID() *specid.ID {
|
||||||
return &specid.ID{Type: specid.Podcast, Value: p.ID}
|
return &specid.ID{Type: specid.Podcast, Value: p.ID}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,13 +40,15 @@ func New(db *db.DB, base string, tagger tags.Reader) *Podcasts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Podcasts) GetPodcastOrAll(userID int, id int, includeEpisodes bool) ([]*db.Podcast, error) {
|
func (p *Podcasts) GetPodcastOrAll(id int, includeEpisodes bool) ([]*db.Podcast, error) {
|
||||||
|
var err error
|
||||||
podcasts := []*db.Podcast{}
|
podcasts := []*db.Podcast{}
|
||||||
q := p.db.Where("user_id=?", userID)
|
|
||||||
if id != 0 {
|
if id != 0 {
|
||||||
q = q.Where("id=?", id)
|
err = p.db.Where("id=?", id).Find(&podcasts).Error
|
||||||
|
} else {
|
||||||
|
err = p.db.Find(&podcasts).Error
|
||||||
}
|
}
|
||||||
if err := q.Find(&podcasts).Error; err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("finding podcasts: %w", err)
|
return nil, fmt.Errorf("finding podcasts: %w", err)
|
||||||
}
|
}
|
||||||
if !includeEpisodes {
|
if !includeEpisodes {
|
||||||
@@ -88,16 +90,14 @@ func (p *Podcasts) GetNewestPodcastEpisodes(count int) ([]*db.PodcastEpisode, er
|
|||||||
return episodes, nil
|
return episodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Podcasts) AddNewPodcast(rssURL string, feed *gofeed.Feed,
|
func (p *Podcasts) AddNewPodcast(rssURL string, feed *gofeed.Feed) (*db.Podcast, error) {
|
||||||
userID int) (*db.Podcast, error) {
|
|
||||||
podcast := db.Podcast{
|
podcast := db.Podcast{
|
||||||
Description: feed.Description,
|
Description: feed.Description,
|
||||||
ImageURL: feed.Image.URL,
|
ImageURL: feed.Image.URL,
|
||||||
UserID: userID,
|
|
||||||
Title: feed.Title,
|
Title: feed.Title,
|
||||||
URL: rssURL,
|
URL: rssURL,
|
||||||
}
|
}
|
||||||
podPath := podcast.Fullpath(p.baseDir)
|
podPath := absPath(p.baseDir, &podcast)
|
||||||
err := os.Mkdir(podPath, 0755)
|
err := os.Mkdir(podPath, 0755)
|
||||||
if err != nil && !os.IsExist(err) {
|
if err != nil && !os.IsExist(err) {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -252,8 +252,7 @@ func itemToEpisode(podcastID, size, duration int, audio string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Podcasts) findEnclosureAudio(podcastID, duration int,
|
func (p *Podcasts) findEnclosureAudio(podcastID, duration int, item *gofeed.Item) (*db.PodcastEpisode, bool) {
|
||||||
item *gofeed.Item) (*db.PodcastEpisode, bool) {
|
|
||||||
for _, enc := range item.Enclosures {
|
for _, enc := range item.Enclosures {
|
||||||
if !isAudio(enc.Type, enc.URL) {
|
if !isAudio(enc.Type, enc.URL) {
|
||||||
continue
|
continue
|
||||||
@@ -264,8 +263,7 @@ func (p *Podcasts) findEnclosureAudio(podcastID, duration int,
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Podcasts) findMediaAudio(podcastID, duration int,
|
func (p *Podcasts) findMediaAudio(podcastID, duration int, item *gofeed.Item) (*db.PodcastEpisode, bool) {
|
||||||
item *gofeed.Item) (*db.PodcastEpisode, bool) {
|
|
||||||
extensions, ok := item.Extensions["media"]["content"]
|
extensions, ok := item.Extensions["media"]["content"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false
|
return nil, false
|
||||||
@@ -291,22 +289,6 @@ func (p *Podcasts) RefreshPodcasts() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Podcasts) RefreshPodcastsForUser(userID int) error {
|
|
||||||
podcasts := []*db.Podcast{}
|
|
||||||
err := p.db.
|
|
||||||
Where("user_id=?", userID).
|
|
||||||
Find(&podcasts).
|
|
||||||
Error
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("find podcasts: %w", err)
|
|
||||||
}
|
|
||||||
var errs *multierr.Err
|
|
||||||
if errors.As(p.refreshPodcasts(podcasts), &errs) && errs.Len() > 0 {
|
|
||||||
return fmt.Errorf("refresh podcasts: %w", errs)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Podcasts) refreshPodcasts(podcasts []*db.Podcast) error {
|
func (p *Podcasts) refreshPodcasts(podcasts []*db.Podcast) error {
|
||||||
errs := &multierr.Err{}
|
errs := &multierr.Err{}
|
||||||
for _, podcast := range podcasts {
|
for _, podcast := range podcasts {
|
||||||
@@ -387,13 +369,12 @@ func (p *Podcasts) DownloadEpisode(episodeID int) error {
|
|||||||
filename = path.Base(audioURL.Path)
|
filename = path.Base(audioURL.Path)
|
||||||
}
|
}
|
||||||
filename = p.findUniqueEpisodeName(&podcast, &podcastEpisode, filename)
|
filename = p.findUniqueEpisodeName(&podcast, &podcastEpisode, filename)
|
||||||
audioFile, err := os.Create(path.Join(podcast.Fullpath(p.baseDir), filename))
|
audioFile, err := os.Create(path.Join(absPath(p.baseDir, &podcast), filename))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create audio file: %w", err)
|
return fmt.Errorf("create audio file: %w", err)
|
||||||
}
|
}
|
||||||
podcastEpisode.Filename = filename
|
podcastEpisode.Filename = filename
|
||||||
sanTitle := strings.ReplaceAll(podcast.Title, "/", "_")
|
podcastEpisode.Path = path.Join(pathSafe(podcast.Title), filename)
|
||||||
podcastEpisode.Path = path.Join(sanTitle, filename)
|
|
||||||
p.db.Save(&podcastEpisode)
|
p.db.Save(&podcastEpisode)
|
||||||
go func() {
|
go func() {
|
||||||
if err := p.doPodcastDownload(&podcastEpisode, audioFile, resp.Body); err != nil {
|
if err := p.doPodcastDownload(&podcastEpisode, audioFile, resp.Body); err != nil {
|
||||||
@@ -403,22 +384,18 @@ func (p *Podcasts) DownloadEpisode(episodeID int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Podcasts) findUniqueEpisodeName(
|
func (p *Podcasts) findUniqueEpisodeName(podcast *db.Podcast, podcastEpisode *db.PodcastEpisode, filename string) string {
|
||||||
podcast *db.Podcast,
|
podcastPath := path.Join(absPath(p.baseDir, podcast), filename)
|
||||||
podcastEpisode *db.PodcastEpisode,
|
|
||||||
filename string) string {
|
|
||||||
podcastPath := path.Join(podcast.Fullpath(p.baseDir), filename)
|
|
||||||
if _, err := os.Stat(podcastPath); os.IsNotExist(err) {
|
if _, err := os.Stat(podcastPath); os.IsNotExist(err) {
|
||||||
return filename
|
return filename
|
||||||
}
|
}
|
||||||
sanitizedTitle := strings.ReplaceAll(podcastEpisode.Title, "/", "_")
|
titlePath := fmt.Sprintf("%s%s", pathSafe(podcastEpisode.Title), filepath.Ext(filename))
|
||||||
titlePath := fmt.Sprintf("%s%s", sanitizedTitle, filepath.Ext(filename))
|
podcastPath = path.Join(absPath(p.baseDir, podcast), titlePath)
|
||||||
podcastPath = path.Join(podcast.Fullpath(p.baseDir), titlePath)
|
|
||||||
if _, err := os.Stat(podcastPath); os.IsNotExist(err) {
|
if _, err := os.Stat(podcastPath); os.IsNotExist(err) {
|
||||||
return titlePath
|
return titlePath
|
||||||
}
|
}
|
||||||
// try to find a filename like FILENAME (1).mp3 incrementing
|
// try to find a filename like FILENAME (1).mp3 incrementing
|
||||||
return findEpisode(podcast.Fullpath(p.baseDir), filename, 1)
|
return findEpisode(absPath(p.baseDir, podcast), filename, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func findEpisode(base, filename string, count int) string {
|
func findEpisode(base, filename string, count int) string {
|
||||||
@@ -462,9 +439,7 @@ func (p *Podcasts) downloadPodcastCover(podPath string, podcast *db.Podcast) err
|
|||||||
if _, err := io.Copy(coverFile, resp.Body); err != nil {
|
if _, err := io.Copy(coverFile, resp.Body); err != nil {
|
||||||
return fmt.Errorf("writing podcast cover: %w", err)
|
return fmt.Errorf("writing podcast cover: %w", err)
|
||||||
}
|
}
|
||||||
podcastPath := filepath.Clean(strings.ReplaceAll(podcast.Title, "/", "_"))
|
podcast.ImagePath = path.Join(pathSafe(podcast.Title), fmt.Sprintf("cover%s", ext))
|
||||||
podcastFilename := fmt.Sprintf("cover%s", ext)
|
|
||||||
podcast.ImagePath = path.Join(podcastPath, podcastFilename)
|
|
||||||
if err := p.db.Save(podcast).Error; err != nil {
|
if err := p.db.Save(podcast).Error; err != nil {
|
||||||
return fmt.Errorf("save podcast: %w", err)
|
return fmt.Errorf("save podcast: %w", err)
|
||||||
}
|
}
|
||||||
@@ -492,28 +467,20 @@ func (p *Podcasts) doPodcastDownload(podcastEpisode *db.PodcastEpisode, file *os
|
|||||||
return p.db.Save(podcastEpisode).Error
|
return p.db.Save(podcastEpisode).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Podcasts) DeletePodcast(userID, podcastID int) error {
|
func (p *Podcasts) DeletePodcast(podcastID int) error {
|
||||||
podcast := db.Podcast{}
|
podcast := db.Podcast{}
|
||||||
err := p.db.
|
err := p.db.
|
||||||
Where("id=? AND user_id=?", podcastID, userID).
|
Where("id=?", podcastID).
|
||||||
First(&podcast).
|
First(&podcast).
|
||||||
Error
|
Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var userCount int
|
if err := os.RemoveAll(absPath(p.baseDir, &podcast)); err != nil {
|
||||||
p.db.
|
|
||||||
Model(&db.Podcast{}).
|
|
||||||
Where("title=?", podcast.Title).
|
|
||||||
Count(&userCount)
|
|
||||||
if userCount == 1 {
|
|
||||||
// only delete the folder if there are not multiple listeners
|
|
||||||
if err = os.RemoveAll(podcast.Fullpath(p.baseDir)); err != nil {
|
|
||||||
return fmt.Errorf("delete podcast directory: %w", err)
|
return fmt.Errorf("delete podcast directory: %w", err)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
err = p.db.
|
err = p.db.
|
||||||
Where("id=? AND user_id=?", podcastID, userID).
|
Where("id=?", podcastID).
|
||||||
Delete(db.Podcast{}).
|
Delete(db.Podcast{}).
|
||||||
Error
|
Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -535,3 +502,11 @@ func (p *Podcasts) DeletePodcastEpisode(podcastEpisodeID int) error {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pathSafe(in string) string {
|
||||||
|
return filepath.Clean(strings.ReplaceAll(in, string(filepath.Separator), "_"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func absPath(base string, p *db.Podcast) string {
|
||||||
|
return filepath.Join(base, pathSafe(p.Title))
|
||||||
|
}
|
||||||
|
|||||||
@@ -169,7 +169,8 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="padded box">
|
{{ if .User.IsAdmin }}
|
||||||
|
<div class="padded box">
|
||||||
<div class="box-title">
|
<div class="box-title">
|
||||||
<i class="mdi mdi-rss-box"></i> podcasts
|
<i class="mdi mdi-rss-box"></i> podcasts
|
||||||
</div>
|
</div>
|
||||||
@@ -205,7 +206,8 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{ end }}
|
||||||
<div class="padded box">
|
<div class="padded box">
|
||||||
<div class="box-title">
|
<div class="box-title">
|
||||||
<i class="mdi mdi-playlist-music"></i> playlists
|
<i class="mdi mdi-playlist-music"></i> playlists
|
||||||
|
|||||||
@@ -391,7 +391,6 @@ func (c *Controller) ServeDeleteTranscodePrefDo(r *http.Request) *Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) ServePodcastAddDo(r *http.Request) *Response {
|
func (c *Controller) ServePodcastAddDo(r *http.Request) *Response {
|
||||||
user := r.Context().Value(CtxUser).(*db.User)
|
|
||||||
rssURL := r.FormValue("feed")
|
rssURL := r.FormValue("feed")
|
||||||
fp := gofeed.NewParser()
|
fp := gofeed.NewParser()
|
||||||
feed, err := fp.ParseURL(rssURL)
|
feed, err := fp.ParseURL(rssURL)
|
||||||
@@ -401,7 +400,7 @@ func (c *Controller) ServePodcastAddDo(r *http.Request) *Response {
|
|||||||
flashW: []string{fmt.Sprintf("could not create feed: %v", err)},
|
flashW: []string{fmt.Sprintf("could not create feed: %v", err)},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, err = c.Podcasts.AddNewPodcast(rssURL, feed, user.ID); err != nil {
|
if _, err = c.Podcasts.AddNewPodcast(rssURL, feed); err != nil {
|
||||||
return &Response{
|
return &Response{
|
||||||
redirect: "/admin/home",
|
redirect: "/admin/home",
|
||||||
flashW: []string{fmt.Sprintf("could not create feed: %v", err)},
|
flashW: []string{fmt.Sprintf("could not create feed: %v", err)},
|
||||||
@@ -454,12 +453,11 @@ func (c *Controller) ServePodcastUpdateDo(r *http.Request) *Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) ServePodcastDeleteDo(r *http.Request) *Response {
|
func (c *Controller) ServePodcastDeleteDo(r *http.Request) *Response {
|
||||||
user := r.Context().Value(CtxUser).(*db.User)
|
|
||||||
id, err := strconv.Atoi(r.URL.Query().Get("id"))
|
id, err := strconv.Atoi(r.URL.Query().Get("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Response{code: 400, err: "please provide a valid podcast id"}
|
return &Response{code: 400, err: "please provide a valid podcast id"}
|
||||||
}
|
}
|
||||||
if err := c.Podcasts.DeletePodcast(user.ID, id); err != nil {
|
if err := c.Podcasts.DeletePodcast(id); err != nil {
|
||||||
return &Response{code: 400, err: "please provide a valid podcast id"}
|
return &Response{code: 400, err: "please provide a valid podcast id"}
|
||||||
}
|
}
|
||||||
return &Response{
|
return &Response{
|
||||||
|
|||||||
@@ -14,9 +14,8 @@ import (
|
|||||||
func (c *Controller) ServeGetPodcasts(r *http.Request) *spec.Response {
|
func (c *Controller) ServeGetPodcasts(r *http.Request) *spec.Response {
|
||||||
params := r.Context().Value(CtxParams).(params.Params)
|
params := r.Context().Value(CtxParams).(params.Params)
|
||||||
isIncludeEpisodes := params.GetOrBool("includeEpisodes", true)
|
isIncludeEpisodes := params.GetOrBool("includeEpisodes", true)
|
||||||
user := r.Context().Value(CtxUser).(*db.User)
|
|
||||||
id, _ := params.GetID("id")
|
id, _ := params.GetID("id")
|
||||||
podcasts, err := c.Podcasts.GetPodcastOrAll(user.ID, id.Value, isIncludeEpisodes)
|
podcasts, err := c.Podcasts.GetPodcastOrAll(id.Value, isIncludeEpisodes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return spec.NewError(10, "failed get podcast(s): %s", err)
|
return spec.NewError(10, "failed get podcast(s): %s", err)
|
||||||
}
|
}
|
||||||
@@ -45,6 +44,10 @@ func (c *Controller) ServeGetNewestPodcasts(r *http.Request) *spec.Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) ServeDownloadPodcastEpisode(r *http.Request) *spec.Response {
|
func (c *Controller) ServeDownloadPodcastEpisode(r *http.Request) *spec.Response {
|
||||||
|
user := r.Context().Value(CtxUser).(*db.User)
|
||||||
|
if (!user.IsAdmin) {
|
||||||
|
return spec.NewError(10, "user not admin")
|
||||||
|
}
|
||||||
params := r.Context().Value(CtxParams).(params.Params)
|
params := r.Context().Value(CtxParams).(params.Params)
|
||||||
id, err := params.GetID("id")
|
id, err := params.GetID("id")
|
||||||
if err != nil || id.Type != specid.PodcastEpisode {
|
if err != nil || id.Type != specid.PodcastEpisode {
|
||||||
@@ -58,6 +61,9 @@ func (c *Controller) ServeDownloadPodcastEpisode(r *http.Request) *spec.Response
|
|||||||
|
|
||||||
func (c *Controller) ServeCreatePodcastChannel(r *http.Request) *spec.Response {
|
func (c *Controller) ServeCreatePodcastChannel(r *http.Request) *spec.Response {
|
||||||
user := r.Context().Value(CtxUser).(*db.User)
|
user := r.Context().Value(CtxUser).(*db.User)
|
||||||
|
if (!user.IsAdmin) {
|
||||||
|
return spec.NewError(10, "user not admin")
|
||||||
|
}
|
||||||
params := r.Context().Value(CtxParams).(params.Params)
|
params := r.Context().Value(CtxParams).(params.Params)
|
||||||
rssURL, _ := params.Get("url")
|
rssURL, _ := params.Get("url")
|
||||||
fp := gofeed.NewParser()
|
fp := gofeed.NewParser()
|
||||||
@@ -65,7 +71,7 @@ func (c *Controller) ServeCreatePodcastChannel(r *http.Request) *spec.Response {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return spec.NewError(10, "failed to parse feed: %s", err)
|
return spec.NewError(10, "failed to parse feed: %s", err)
|
||||||
}
|
}
|
||||||
if _, err = c.Podcasts.AddNewPodcast(rssURL, feed, user.ID); err != nil {
|
if _, err = c.Podcasts.AddNewPodcast(rssURL, feed); err != nil {
|
||||||
return spec.NewError(10, "failed to add feed: %s", err)
|
return spec.NewError(10, "failed to add feed: %s", err)
|
||||||
}
|
}
|
||||||
return spec.NewResponse()
|
return spec.NewResponse()
|
||||||
@@ -73,7 +79,10 @@ func (c *Controller) ServeCreatePodcastChannel(r *http.Request) *spec.Response {
|
|||||||
|
|
||||||
func (c *Controller) ServeRefreshPodcasts(r *http.Request) *spec.Response {
|
func (c *Controller) ServeRefreshPodcasts(r *http.Request) *spec.Response {
|
||||||
user := r.Context().Value(CtxUser).(*db.User)
|
user := r.Context().Value(CtxUser).(*db.User)
|
||||||
if err := c.Podcasts.RefreshPodcastsForUser(user.ID); err != nil {
|
if (!user.IsAdmin) {
|
||||||
|
return spec.NewError(10, "user not admin")
|
||||||
|
}
|
||||||
|
if err := c.Podcasts.RefreshPodcasts(); err != nil {
|
||||||
return spec.NewError(10, "failed to refresh feeds: %s", err)
|
return spec.NewError(10, "failed to refresh feeds: %s", err)
|
||||||
}
|
}
|
||||||
return spec.NewResponse()
|
return spec.NewResponse()
|
||||||
@@ -81,18 +90,25 @@ func (c *Controller) ServeRefreshPodcasts(r *http.Request) *spec.Response {
|
|||||||
|
|
||||||
func (c *Controller) ServeDeletePodcastChannel(r *http.Request) *spec.Response {
|
func (c *Controller) ServeDeletePodcastChannel(r *http.Request) *spec.Response {
|
||||||
user := r.Context().Value(CtxUser).(*db.User)
|
user := r.Context().Value(CtxUser).(*db.User)
|
||||||
|
if (!user.IsAdmin) {
|
||||||
|
return spec.NewError(10, "user not admin")
|
||||||
|
}
|
||||||
params := r.Context().Value(CtxParams).(params.Params)
|
params := r.Context().Value(CtxParams).(params.Params)
|
||||||
id, err := params.GetID("id")
|
id, err := params.GetID("id")
|
||||||
if err != nil || id.Type != specid.Podcast {
|
if err != nil || id.Type != specid.Podcast {
|
||||||
return spec.NewError(10, "please provide a valid podcast id")
|
return spec.NewError(10, "please provide a valid podcast id")
|
||||||
}
|
}
|
||||||
if err := c.Podcasts.DeletePodcast(user.ID, id.Value); err != nil {
|
if err := c.Podcasts.DeletePodcast(id.Value); err != nil {
|
||||||
return spec.NewError(10, "failed to delete podcast: %s", err)
|
return spec.NewError(10, "failed to delete podcast: %s", err)
|
||||||
}
|
}
|
||||||
return spec.NewResponse()
|
return spec.NewResponse()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) ServeDeletePodcastEpisode(r *http.Request) *spec.Response {
|
func (c *Controller) ServeDeletePodcastEpisode(r *http.Request) *spec.Response {
|
||||||
|
user := r.Context().Value(CtxUser).(*db.User)
|
||||||
|
if (!user.IsAdmin) {
|
||||||
|
return spec.NewError(10, "user not admin")
|
||||||
|
}
|
||||||
params := r.Context().Value(CtxParams).(params.Params)
|
params := r.Context().Value(CtxParams).(params.Params)
|
||||||
id, err := params.GetID("id")
|
id, err := params.GetID("id")
|
||||||
if err != nil || id.Type != specid.PodcastEpisode {
|
if err != nil || id.Type != specid.PodcastEpisode {
|
||||||
|
|||||||
@@ -169,10 +169,6 @@ func setupAdmin(r *mux.Router, ctrl *ctrladmin.Controller) {
|
|||||||
routUser.Handle("/delete_playlist_do", ctrl.H(ctrl.ServeDeletePlaylistDo))
|
routUser.Handle("/delete_playlist_do", ctrl.H(ctrl.ServeDeletePlaylistDo))
|
||||||
routUser.Handle("/create_transcode_pref_do", ctrl.H(ctrl.ServeCreateTranscodePrefDo))
|
routUser.Handle("/create_transcode_pref_do", ctrl.H(ctrl.ServeCreateTranscodePrefDo))
|
||||||
routUser.Handle("/delete_transcode_pref_do", ctrl.H(ctrl.ServeDeleteTranscodePrefDo))
|
routUser.Handle("/delete_transcode_pref_do", ctrl.H(ctrl.ServeDeleteTranscodePrefDo))
|
||||||
routUser.Handle("/add_podcast_do", ctrl.H(ctrl.ServePodcastAddDo))
|
|
||||||
routUser.Handle("/delete_podcast_do", ctrl.H(ctrl.ServePodcastDeleteDo))
|
|
||||||
routUser.Handle("/download_podcast_do", ctrl.H(ctrl.ServePodcastDownloadDo))
|
|
||||||
routUser.Handle("/update_podcast_do", ctrl.H(ctrl.ServePodcastUpdateDo))
|
|
||||||
|
|
||||||
// admin routes (if session is valid, and is admin)
|
// admin routes (if session is valid, and is admin)
|
||||||
routAdmin := routUser.NewRoute().Subrouter()
|
routAdmin := routUser.NewRoute().Subrouter()
|
||||||
@@ -189,6 +185,10 @@ func setupAdmin(r *mux.Router, ctrl *ctrladmin.Controller) {
|
|||||||
routAdmin.Handle("/update_lastfm_api_key_do", ctrl.H(ctrl.ServeUpdateLastFMAPIKeyDo))
|
routAdmin.Handle("/update_lastfm_api_key_do", ctrl.H(ctrl.ServeUpdateLastFMAPIKeyDo))
|
||||||
routAdmin.Handle("/start_scan_inc_do", ctrl.H(ctrl.ServeStartScanIncDo))
|
routAdmin.Handle("/start_scan_inc_do", ctrl.H(ctrl.ServeStartScanIncDo))
|
||||||
routAdmin.Handle("/start_scan_full_do", ctrl.H(ctrl.ServeStartScanFullDo))
|
routAdmin.Handle("/start_scan_full_do", ctrl.H(ctrl.ServeStartScanFullDo))
|
||||||
|
routAdmin.Handle("/add_podcast_do", ctrl.H(ctrl.ServePodcastAddDo))
|
||||||
|
routAdmin.Handle("/delete_podcast_do", ctrl.H(ctrl.ServePodcastDeleteDo))
|
||||||
|
routAdmin.Handle("/download_podcast_do", ctrl.H(ctrl.ServePodcastDownloadDo))
|
||||||
|
routAdmin.Handle("/update_podcast_do", ctrl.H(ctrl.ServePodcastUpdateDo))
|
||||||
|
|
||||||
// middlewares should be run for not found handler
|
// middlewares should be run for not found handler
|
||||||
// https://github.com/gorilla/mux/issues/416
|
// https://github.com/gorilla/mux/issues/416
|
||||||
|
|||||||
Reference in New Issue
Block a user