feat(subsonic): add internet radio support
* Initial commit of internet radio support. * Added first test for internet radio. * Refactor to prepare for more test cases. * Added a few more tests. Realized that I was not calling as admin so added ability to mock admin. * Added more internet radio tests. Added proper JSON unmarshaling for ID. * More test cases. Fixed some accidental tabs in files. * Fixed some more tabs. * lint fixes * Changed placeholder for homepage URL to fit into box. * Finished out internet radio test cases. Found a few bad error codes in internet radio AND podcasts (mea culpa). * Realized that delete via website was not checking properly if id existed. Fixed. gofmt
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -16,3 +16,4 @@ gonicembed
|
|||||||
.vscode
|
.vscode
|
||||||
*.swp
|
*.swp
|
||||||
.tags*
|
.tags*
|
||||||
|
*.test
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ func (db *DB) Migrate(ctx MigrationContext) error {
|
|||||||
construct(ctx, "202202121809", migrateAlbumRootDirAgain),
|
construct(ctx, "202202121809", migrateAlbumRootDirAgain),
|
||||||
construct(ctx, "202202241218", migratePublicPlaylist),
|
construct(ctx, "202202241218", migratePublicPlaylist),
|
||||||
construct(ctx, "202204270903", migratePodcastDropUserID),
|
construct(ctx, "202204270903", migratePodcastDropUserID),
|
||||||
|
construct(ctx, "202206011628", migrateInternetRadioStations),
|
||||||
}
|
}
|
||||||
|
|
||||||
return gormigrate.
|
return gormigrate.
|
||||||
@@ -356,3 +357,9 @@ func migratePodcastDropUserID(tx *gorm.DB, _ MigrationContext) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func migrateInternetRadioStations(tx *gorm.DB, _ MigrationContext) error {
|
||||||
|
return tx.AutoMigrate(
|
||||||
|
InternetRadioStation{},
|
||||||
|
).
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|||||||
11
db/model.go
11
db/model.go
@@ -396,3 +396,14 @@ type Bookmark struct {
|
|||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InternetRadioStation struct {
|
||||||
|
ID int `gorm:"primary_key"`
|
||||||
|
StreamURL string
|
||||||
|
Name string
|
||||||
|
HomepageURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ir *InternetRadioStation) SID() *specid.ID {
|
||||||
|
return &specid.ID{Type: specid.InternetRadioStation, Value: ir.ID}
|
||||||
|
}
|
||||||
|
|||||||
@@ -208,6 +208,38 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ if .User.IsAdmin }}
|
||||||
|
<div class="padded box">
|
||||||
|
<div class="box-title">
|
||||||
|
<i class="mdi mdi-radio"></i> internet_radio_stations
|
||||||
|
</div>
|
||||||
|
<div class="box-description text-light">
|
||||||
|
<p>you can add and update internet radio stations here</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<table id="irs-preferences">
|
||||||
|
{{ range $pref := .InternetRadioStations }}
|
||||||
|
<tr>
|
||||||
|
<form id="irs-{{ $pref.ID }}-update" action="{{ printf "/admin/update_internet_radio_station_do?id=%d" $pref.ID | path }}" method="post"></form>
|
||||||
|
<form id="irs-{{ $pref.ID }}-delete" action="{{ printf "/admin/delete_internet_radio_station_do?id=%d" $pref.ID | path }}" method="post"></form>
|
||||||
|
<td><input form="irs-{{ $pref.ID }}-update" type="text" name="streamURL" value={{ $pref.StreamURL }}></td>
|
||||||
|
<td><input form="irs-{{ $pref.ID }}-update" type="text" name="name" value={{ $pref.Name }}></td>
|
||||||
|
<td><input form="irs-{{ $pref.ID }}-update" type="text" name="homepageURL" value={{ $pref.HomepageURL }}></td>
|
||||||
|
<td><input form="irs-{{ $pref.ID }}-update" type="submit" value="update"></td>
|
||||||
|
<td><input form="irs-{{ $pref.ID }}-delete" type="submit" value="delete"></td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
<tr>
|
||||||
|
<form id="irs-add" action="{{ path "/admin/add_internet_radio_station_do" }}" method="post"></form>
|
||||||
|
<td><input form="irs-add" type="text" name="streamURL" placeholder="stream URL"></td>
|
||||||
|
<td><input form="irs-add" type="text" name="name" placeholder="name"></td>
|
||||||
|
<td><input form="irs-add" type="text" name="homepageURL" placeholder="[homepage URL]"></td>
|
||||||
|
<td><input form="irs-add" type="submit" value="add"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</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
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ type templateData struct {
|
|||||||
SelectedUser *db.User
|
SelectedUser *db.User
|
||||||
|
|
||||||
Podcasts []*db.Podcast
|
Podcasts []*db.Podcast
|
||||||
|
InternetRadioStations []*db.InternetRadioStation
|
||||||
}
|
}
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -73,6 +74,9 @@ func (c *Controller) ServeHome(r *http.Request) *Response {
|
|||||||
// podcasts box
|
// podcasts box
|
||||||
c.DB.Find(&data.Podcasts)
|
c.DB.Find(&data.Podcasts)
|
||||||
|
|
||||||
|
// internet radio box
|
||||||
|
c.DB.Find(&data.InternetRadioStations)
|
||||||
|
|
||||||
return &Response{
|
return &Response{
|
||||||
template: "home.tmpl",
|
template: "home.tmpl",
|
||||||
data: data,
|
data: data,
|
||||||
@@ -400,7 +404,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); 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)},
|
||||||
@@ -464,3 +468,105 @@ func (c *Controller) ServePodcastDeleteDo(r *http.Request) *Response {
|
|||||||
redirect: "/admin/home",
|
redirect: "/admin/home",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ServeInternetRadioStationAddDo(r *http.Request) *Response {
|
||||||
|
streamURL := r.FormValue("streamURL")
|
||||||
|
name := r.FormValue("name")
|
||||||
|
homepageURL := r.FormValue("homepageURL")
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
return &Response{redirect: "/admin/home", flashW: []string{"no name provided"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := url.ParseRequestURI(streamURL); err != nil {
|
||||||
|
return &Response{redirect: "/admin/home", flashW: []string{fmt.Sprintf("bad stream URL provided: %v", err)}}
|
||||||
|
}
|
||||||
|
|
||||||
|
if homepageURL != "" {
|
||||||
|
if _, err := url.ParseRequestURI(homepageURL); err != nil {
|
||||||
|
return &Response{redirect: "/admin/home", flashW: []string{fmt.Sprintf("bad homepage URL provided: %v", err)}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var station db.InternetRadioStation
|
||||||
|
station.StreamURL = streamURL
|
||||||
|
station.Name = name
|
||||||
|
station.HomepageURL = homepageURL
|
||||||
|
if err := c.DB.Save(&station).Error; err != nil {
|
||||||
|
return &Response{code: 500, err: fmt.Sprintf("error saving station: %v", err)}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Response{
|
||||||
|
redirect: "/admin/home",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ServeInternetRadioStationUpdateDo(r *http.Request) *Response {
|
||||||
|
stationID, err := strconv.Atoi(r.URL.Query().Get("id"))
|
||||||
|
if err != nil {
|
||||||
|
return &Response{code: 400, err: "please provide a valid internet radio station id"}
|
||||||
|
}
|
||||||
|
|
||||||
|
streamURL := r.FormValue("streamURL")
|
||||||
|
name := r.FormValue("name")
|
||||||
|
homepageURL := r.FormValue("homepageURL")
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
return &Response{
|
||||||
|
redirect: "/admin/home",
|
||||||
|
flashW: []string{"no name provided"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := url.ParseRequestURI(streamURL); err != nil {
|
||||||
|
return &Response{
|
||||||
|
redirect: "/admin/home",
|
||||||
|
flashW: []string{fmt.Sprintf("bad stream URL provided: %v", err)},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if homepageURL != "" {
|
||||||
|
if _, err := url.ParseRequestURI(homepageURL); err != nil {
|
||||||
|
return &Response{
|
||||||
|
redirect: "/admin/home",
|
||||||
|
flashW: []string{fmt.Sprintf("bad homepage URL provided: %v", err)},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var station db.InternetRadioStation
|
||||||
|
if err := c.DB.Where("id=?", stationID).First(&station).Error; err != nil {
|
||||||
|
return &Response{code: 404, err: fmt.Sprintf("find station by id: %v", err)}
|
||||||
|
}
|
||||||
|
|
||||||
|
station.StreamURL = streamURL
|
||||||
|
station.Name = name
|
||||||
|
station.HomepageURL = homepageURL
|
||||||
|
if err := c.DB.Save(&station).Error; err != nil {
|
||||||
|
return &Response{code: 500, err: "please provide a valid internet radio station id"}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Response{
|
||||||
|
redirect: "/admin/home",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ServeInternetRadioStationDeleteDo(r *http.Request) *Response {
|
||||||
|
stationID, err := strconv.Atoi(r.URL.Query().Get("id"))
|
||||||
|
if err != nil {
|
||||||
|
return &Response{code: 400, err: "please provide a valid internet radio station id"}
|
||||||
|
}
|
||||||
|
|
||||||
|
var station db.InternetRadioStation
|
||||||
|
if err := c.DB.Where("id=?", stationID).First(&station).Error; err != nil {
|
||||||
|
return &Response{code: 404, err: fmt.Sprintf("find station by id: %v", err)}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.DB.Where("id=?", stationID).Delete(&db.InternetRadioStation{}).Error; err != nil {
|
||||||
|
return &Response{code: 500, err: fmt.Sprintf("deleting radio station: %v", err)}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Response{
|
||||||
|
redirect: "/admin/home",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -69,6 +69,15 @@ func makeHTTPMock(query url.Values) (*httptest.ResponseRecorder, *http.Request)
|
|||||||
return rr, req
|
return rr, req
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeHTTPMockWithAdmin(query url.Values) (*httptest.ResponseRecorder, *http.Request) {
|
||||||
|
rr, req := makeHTTPMock(query)
|
||||||
|
ctx := req.Context()
|
||||||
|
ctx = context.WithValue(ctx, CtxUser, &db.User{IsAdmin: true})
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
|
return rr, req
|
||||||
|
}
|
||||||
|
|
||||||
func serveRaw(t *testing.T, contr *Controller, h handlerSubsonicRaw, rr *httptest.ResponseRecorder, req *http.Request) {
|
func serveRaw(t *testing.T, contr *Controller, h handlerSubsonicRaw, rr *httptest.ResponseRecorder, req *http.Request) {
|
||||||
type middleware func(http.Handler) http.Handler
|
type middleware func(http.Handler) http.Handler
|
||||||
middlewares := []middleware{
|
middlewares := []middleware{
|
||||||
|
|||||||
131
server/ctrlsubsonic/handlers_internet_radio.go
Normal file
131
server/ctrlsubsonic/handlers_internet_radio.go
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package ctrlsubsonic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"go.senan.xyz/gonic/db"
|
||||||
|
"go.senan.xyz/gonic/server/ctrlsubsonic/params"
|
||||||
|
"go.senan.xyz/gonic/server/ctrlsubsonic/spec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Controller) ServeGetInternetRadioStations(r *http.Request) *spec.Response {
|
||||||
|
var stations []*db.InternetRadioStation
|
||||||
|
if err := c.DB.Find(&stations).Error; err != nil {
|
||||||
|
return spec.NewError(0, "find stations: %v", err)
|
||||||
|
}
|
||||||
|
sub := spec.NewResponse()
|
||||||
|
sub.InternetRadioStations = &spec.InternetRadioStations{
|
||||||
|
List: make([]*spec.InternetRadioStation, len(stations)),
|
||||||
|
}
|
||||||
|
for i, station := range stations {
|
||||||
|
sub.InternetRadioStations.List[i] = spec.NewInternetRadioStation(station)
|
||||||
|
}
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ServeCreateInternetRadioStation(r *http.Request) *spec.Response {
|
||||||
|
user := r.Context().Value(CtxUser).(*db.User)
|
||||||
|
if !user.IsAdmin {
|
||||||
|
return spec.NewError(50, "user not admin")
|
||||||
|
}
|
||||||
|
|
||||||
|
params := r.Context().Value(CtxParams).(params.Params)
|
||||||
|
|
||||||
|
streamURL, err := params.Get("streamUrl")
|
||||||
|
if err != nil {
|
||||||
|
return spec.NewError(10, "no stream URL provided: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := url.ParseRequestURI(streamURL); err != nil {
|
||||||
|
return spec.NewError(70, "bad stream URL provided: %v", err)
|
||||||
|
}
|
||||||
|
name, err := params.Get("name")
|
||||||
|
if err != nil {
|
||||||
|
return spec.NewError(10, "no name provided: %v", err)
|
||||||
|
}
|
||||||
|
homepageURL, err := params.Get("homepageUrl")
|
||||||
|
if err == nil {
|
||||||
|
if _, err := url.ParseRequestURI(homepageURL); err != nil {
|
||||||
|
return spec.NewError(70, "bad homepage URL provided: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var station db.InternetRadioStation
|
||||||
|
station.StreamURL = streamURL
|
||||||
|
station.Name = name
|
||||||
|
station.HomepageURL = homepageURL
|
||||||
|
|
||||||
|
if err := c.DB.Save(&station).Error; err != nil {
|
||||||
|
return spec.NewError(0, "save station: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec.NewResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ServeUpdateInternetRadioStation(r *http.Request) *spec.Response {
|
||||||
|
user := r.Context().Value(CtxUser).(*db.User)
|
||||||
|
if !user.IsAdmin {
|
||||||
|
return spec.NewError(50, "user not admin")
|
||||||
|
}
|
||||||
|
params := r.Context().Value(CtxParams).(params.Params)
|
||||||
|
|
||||||
|
stationID, err := params.GetID("id")
|
||||||
|
if err != nil {
|
||||||
|
return spec.NewError(10, "no id provided: %v", err)
|
||||||
|
}
|
||||||
|
streamURL, err := params.Get("streamUrl")
|
||||||
|
if err != nil {
|
||||||
|
return spec.NewError(10, "no stream URL provided: %v", err)
|
||||||
|
}
|
||||||
|
if _, err = url.ParseRequestURI(streamURL); err != nil {
|
||||||
|
return spec.NewError(70, "bad stream URL provided: %v", err)
|
||||||
|
}
|
||||||
|
name, err := params.Get("name")
|
||||||
|
if err != nil {
|
||||||
|
return spec.NewError(10, "no name provided: %v", err)
|
||||||
|
}
|
||||||
|
homepageURL, err := params.Get("homepageUrl")
|
||||||
|
if err == nil {
|
||||||
|
if _, err := url.ParseRequestURI(homepageURL); err != nil {
|
||||||
|
return spec.NewError(70, "bad homepage URL provided: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var station db.InternetRadioStation
|
||||||
|
if err := c.DB.Where("id=?", stationID.Value).First(&station).Error; err != nil {
|
||||||
|
return spec.NewError(70, "id not found: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
station.StreamURL = streamURL
|
||||||
|
station.Name = name
|
||||||
|
station.HomepageURL = homepageURL
|
||||||
|
|
||||||
|
if err := c.DB.Save(&station).Error; err != nil {
|
||||||
|
return spec.NewError(0, "save station: %v", err)
|
||||||
|
}
|
||||||
|
return spec.NewResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ServeDeleteInternetRadioStation(r *http.Request) *spec.Response {
|
||||||
|
user := r.Context().Value(CtxUser).(*db.User)
|
||||||
|
if !user.IsAdmin {
|
||||||
|
return spec.NewError(50, "user not admin")
|
||||||
|
}
|
||||||
|
params := r.Context().Value(CtxParams).(params.Params)
|
||||||
|
|
||||||
|
stationID, err := params.GetID("id")
|
||||||
|
if err != nil {
|
||||||
|
return spec.NewError(10, "no id provided: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var station db.InternetRadioStation
|
||||||
|
if err := c.DB.Where("id=?", stationID.Value).First(&station).Error; err != nil {
|
||||||
|
return spec.NewError(70, "id not found: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.DB.Delete(&station).Error; err != nil {
|
||||||
|
return spec.NewError(70, "id not found: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec.NewResponse()
|
||||||
|
}
|
||||||
380
server/ctrlsubsonic/handlers_internet_radio_test.go
Normal file
380
server/ctrlsubsonic/handlers_internet_radio_test.go
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
package ctrlsubsonic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.senan.xyz/gonic/server/ctrlsubsonic/spec"
|
||||||
|
"go.senan.xyz/gonic/server/ctrlsubsonic/specid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const station1ID = "ir-1"
|
||||||
|
|
||||||
|
var station1IDT = specid.ID{Type: specid.InternetRadioStation, Value: 1}
|
||||||
|
|
||||||
|
const (
|
||||||
|
station1StreamURL = "http://lyd.nrk.no/nrk_radio_p1_ostlandssendingen_mp3_m"
|
||||||
|
station1Name = "NRK P1"
|
||||||
|
station1HomepageURL = "http://www.nrk.no/p1"
|
||||||
|
)
|
||||||
|
|
||||||
|
const station2ID = "ir-2"
|
||||||
|
|
||||||
|
var station2IDT = specid.ID{Type: specid.InternetRadioStation, Value: 2}
|
||||||
|
|
||||||
|
const (
|
||||||
|
station2StreamURL = "http://lyd.nrk.no/nrk_radio_p2_mp3_m"
|
||||||
|
station2Name = "NRK P2"
|
||||||
|
station2HomepageURL = "http://p3.no"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
newstation1StreamURL = "http://media.kcrw.com/pls/kcrwmusic.pls"
|
||||||
|
newstation1Name = "KCRW Eclectic"
|
||||||
|
newstation1HomepageURL = "https://www.kcrw.com/music/shows/eclectic24"
|
||||||
|
)
|
||||||
|
|
||||||
|
const newstation2StreamURL = "http://media.kcrw.com/pls/kcrwsantabarbara.pls"
|
||||||
|
const newstation2Name = "KCRW Santa Barbara"
|
||||||
|
|
||||||
|
const station3ID = "ir-3"
|
||||||
|
|
||||||
|
const notAURL = "not_a_url"
|
||||||
|
|
||||||
|
func TestInternetRadio(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
contr := makeController(t)
|
||||||
|
t.Run("TestInternetRadioInitialEmpty", func(t *testing.T) { testInternetRadioInitialEmpty(t, contr) })
|
||||||
|
t.Run("TestInternetRadioBadCreates", func(t *testing.T) { testInternetRadioBadCreates(t, contr) })
|
||||||
|
t.Run("TestInternetRadioInitialAdds", func(t *testing.T) { testInternetRadioInitialAdds(t, contr) })
|
||||||
|
t.Run("TestInternetRadioUpdateHomepage", func(t *testing.T) { testInternetRadioUpdateHomepage(t, contr) })
|
||||||
|
t.Run("TestInternetRadioNotAdmin", func(t *testing.T) { testInternetRadioNotAdmin(t, contr) })
|
||||||
|
t.Run("TestInternetRadioUpdates", func(t *testing.T) { testInternetRadioUpdates(t, contr) })
|
||||||
|
t.Run("TestInternetRadioDeletes", func(t *testing.T) { testInternetRadioDeletes(t, contr) })
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTestCase(t *testing.T, contr *Controller, h handlerSubsonic, q url.Values, admin bool) *spec.SubsonicResponse {
|
||||||
|
var rr *httptest.ResponseRecorder
|
||||||
|
var req *http.Request
|
||||||
|
|
||||||
|
if admin {
|
||||||
|
rr, req = makeHTTPMockWithAdmin(q)
|
||||||
|
} else {
|
||||||
|
rr, req = makeHTTPMock(q)
|
||||||
|
}
|
||||||
|
contr.H(h).ServeHTTP(rr, req)
|
||||||
|
body := rr.Body.String()
|
||||||
|
if status := rr.Code; status != http.StatusOK {
|
||||||
|
t.Fatalf("didn't give a 200\n%s", body)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response spec.SubsonicResponse
|
||||||
|
if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil {
|
||||||
|
switch ty := err.(type) {
|
||||||
|
case *json.SyntaxError:
|
||||||
|
jsn := body[0:ty.Offset]
|
||||||
|
jsn += "<--(Invalid Character)"
|
||||||
|
t.Fatalf("invalid character at offset %v\n %s", ty.Offset, jsn)
|
||||||
|
case *json.UnmarshalTypeError:
|
||||||
|
jsn := body[0:ty.Offset]
|
||||||
|
jsn += "<--(Invalid Type)"
|
||||||
|
t.Fatalf("invalid type at offset %v\n %s", ty.Offset, jsn)
|
||||||
|
default:
|
||||||
|
t.Fatalf("json unmarshal failed: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkSuccess(t *testing.T, response *spec.SubsonicResponse) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if response.Response.Status != "ok" {
|
||||||
|
t.Fatal("didn't return ok status")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkError(t *testing.T, response *spec.SubsonicResponse, code int) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if response.Response.Status != "failed" {
|
||||||
|
t.Fatal("didn't return failed status")
|
||||||
|
}
|
||||||
|
if response.Response.Error.Code != code {
|
||||||
|
t.Fatal("returned wrong error code")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkMissingParameter(t *testing.T, response *spec.SubsonicResponse) {
|
||||||
|
t.Helper()
|
||||||
|
checkError(t, response, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkBadParameter(t *testing.T, response *spec.SubsonicResponse) {
|
||||||
|
t.Helper()
|
||||||
|
checkError(t, response, 70)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkNotAdmin(t *testing.T, response *spec.SubsonicResponse) {
|
||||||
|
t.Helper()
|
||||||
|
checkError(t, response, 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInternetRadioBadCreates(t *testing.T, contr *Controller) {
|
||||||
|
var response *spec.SubsonicResponse
|
||||||
|
|
||||||
|
// no parameters
|
||||||
|
response = runTestCase(t, contr, contr.ServeCreateInternetRadioStation, url.Values{}, true)
|
||||||
|
checkMissingParameter(t, response)
|
||||||
|
|
||||||
|
// just one required parameter
|
||||||
|
response = runTestCase(t, contr, contr.ServeCreateInternetRadioStation,
|
||||||
|
url.Values{"streamUrl": {station1StreamURL}}, true)
|
||||||
|
checkMissingParameter(t, response)
|
||||||
|
|
||||||
|
response = runTestCase(t, contr, contr.ServeCreateInternetRadioStation,
|
||||||
|
url.Values{"name": {station1Name}}, true)
|
||||||
|
checkMissingParameter(t, response)
|
||||||
|
|
||||||
|
// bad URLs
|
||||||
|
response = runTestCase(t, contr, contr.ServeCreateInternetRadioStation,
|
||||||
|
url.Values{"streamUrl": {station1StreamURL}, "name": {station1Name}, "homepageUrl": {notAURL}}, true)
|
||||||
|
checkBadParameter(t, response)
|
||||||
|
|
||||||
|
response = runTestCase(t, contr, contr.ServeCreateInternetRadioStation,
|
||||||
|
url.Values{"streamUrl": {notAURL}, "name": {station1Name}, "homepageUrl": {station1HomepageURL}}, true)
|
||||||
|
checkBadParameter(t, response)
|
||||||
|
|
||||||
|
// check for empty get after
|
||||||
|
response = runTestCase(t, contr, contr.ServeGetInternetRadioStations, url.Values{}, false) // no need to be admin
|
||||||
|
checkSuccess(t, response)
|
||||||
|
|
||||||
|
if (response.Response.InternetRadioStations == nil) || (len(response.Response.InternetRadioStations.List) != 0) {
|
||||||
|
t.Fatal("didn't return empty stations")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInternetRadioInitialEmpty(t *testing.T, contr *Controller) {
|
||||||
|
// check for empty get on new DB
|
||||||
|
response := runTestCase(t, contr, contr.ServeGetInternetRadioStations, url.Values{}, false) // no need to be admin
|
||||||
|
checkSuccess(t, response)
|
||||||
|
|
||||||
|
if (response.Response.InternetRadioStations == nil) || (len(response.Response.InternetRadioStations.List) != 0) {
|
||||||
|
t.Fatal("didn't return empty stations")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInternetRadioInitialAdds(t *testing.T, contr *Controller) {
|
||||||
|
// successful adds and read back
|
||||||
|
response := runTestCase(t, contr, contr.ServeCreateInternetRadioStation,
|
||||||
|
url.Values{"streamUrl": {station1StreamURL}, "name": {station1Name}, "homepageUrl": {station1HomepageURL}}, true)
|
||||||
|
checkSuccess(t, response)
|
||||||
|
|
||||||
|
response = runTestCase(t, contr, contr.ServeCreateInternetRadioStation,
|
||||||
|
url.Values{"streamUrl": {station2StreamURL}, "name": {station2Name}}, true) // NOTE: no homepage Url
|
||||||
|
checkSuccess(t, response)
|
||||||
|
|
||||||
|
response = runTestCase(t, contr, contr.ServeGetInternetRadioStations, url.Values{}, false) // no need to be admin
|
||||||
|
checkSuccess(t, response)
|
||||||
|
|
||||||
|
if response.Response.InternetRadioStations == nil {
|
||||||
|
t.Fatal("didn't return stations")
|
||||||
|
}
|
||||||
|
if len(response.Response.InternetRadioStations.List) != 2 {
|
||||||
|
t.Fatal("wrong number of stations")
|
||||||
|
}
|
||||||
|
if (*response.Response.InternetRadioStations.List[0].ID != station1IDT) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].StreamURL != station1StreamURL) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].Name != station1Name) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].HomepageURL != station1HomepageURL) ||
|
||||||
|
(*response.Response.InternetRadioStations.List[1].ID != station2IDT) ||
|
||||||
|
(response.Response.InternetRadioStations.List[1].StreamURL != station2StreamURL) ||
|
||||||
|
(response.Response.InternetRadioStations.List[1].Name != station2Name) ||
|
||||||
|
(response.Response.InternetRadioStations.List[1].HomepageURL != "") {
|
||||||
|
t.Fatal("bad data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInternetRadioUpdateHomepage(t *testing.T, contr *Controller) {
|
||||||
|
// update empty homepage URL without other parameters (fails)
|
||||||
|
response := runTestCase(t, contr, contr.ServeUpdateInternetRadioStation,
|
||||||
|
url.Values{"id": {station2ID}, "homepageUrl": {station2HomepageURL}}, true)
|
||||||
|
checkMissingParameter(t, response)
|
||||||
|
|
||||||
|
// update empty homepage URL properly and read back
|
||||||
|
response = runTestCase(t, contr, contr.ServeUpdateInternetRadioStation,
|
||||||
|
url.Values{"id": {station2ID}, "streamUrl": {station2StreamURL}, "name": {station2Name}, "homepageUrl": {station2HomepageURL}}, true)
|
||||||
|
checkSuccess(t, response)
|
||||||
|
|
||||||
|
response = runTestCase(t, contr, contr.ServeGetInternetRadioStations, url.Values{}, false) // no need to be admin
|
||||||
|
checkSuccess(t, response)
|
||||||
|
|
||||||
|
if response.Response.InternetRadioStations == nil {
|
||||||
|
t.Fatal("didn't return stations")
|
||||||
|
}
|
||||||
|
if len(response.Response.InternetRadioStations.List) != 2 {
|
||||||
|
t.Fatal("wrong number of stations")
|
||||||
|
}
|
||||||
|
if (*response.Response.InternetRadioStations.List[0].ID != station1IDT) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].StreamURL != station1StreamURL) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].Name != station1Name) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].HomepageURL != station1HomepageURL) ||
|
||||||
|
(*response.Response.InternetRadioStations.List[1].ID != station2IDT) ||
|
||||||
|
(response.Response.InternetRadioStations.List[1].StreamURL != station2StreamURL) ||
|
||||||
|
(response.Response.InternetRadioStations.List[1].Name != station2Name) ||
|
||||||
|
(response.Response.InternetRadioStations.List[1].HomepageURL != station2HomepageURL) {
|
||||||
|
t.Fatal("bad data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInternetRadioNotAdmin(t *testing.T, contr *Controller) {
|
||||||
|
// create, update, delete w/o admin privileges (fails and does not modify data)
|
||||||
|
response := runTestCase(t, contr, contr.ServeCreateInternetRadioStation,
|
||||||
|
url.Values{"streamUrl": {station1StreamURL}, "name": {station1Name}, "homepageUrl": {station1HomepageURL}}, false)
|
||||||
|
checkNotAdmin(t, response)
|
||||||
|
|
||||||
|
response = runTestCase(t, contr, contr.ServeUpdateInternetRadioStation,
|
||||||
|
url.Values{"id": {station1ID}, "streamUrl": {newstation1StreamURL}, "name": {newstation1Name}, "homepageUrl": {newstation1HomepageURL}}, false)
|
||||||
|
checkNotAdmin(t, response)
|
||||||
|
|
||||||
|
response = runTestCase(t, contr, contr.ServeDeleteInternetRadioStation,
|
||||||
|
url.Values{"id": {station1ID}}, false)
|
||||||
|
checkNotAdmin(t, response)
|
||||||
|
|
||||||
|
response = runTestCase(t, contr, contr.ServeGetInternetRadioStations, url.Values{}, false) // no need to be admin
|
||||||
|
checkSuccess(t, response)
|
||||||
|
|
||||||
|
if response.Response.InternetRadioStations == nil {
|
||||||
|
t.Fatal("didn't return stations")
|
||||||
|
}
|
||||||
|
if len(response.Response.InternetRadioStations.List) != 2 {
|
||||||
|
t.Fatal("wrong number of stations")
|
||||||
|
}
|
||||||
|
if (*response.Response.InternetRadioStations.List[0].ID != station1IDT) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].StreamURL != station1StreamURL) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].Name != station1Name) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].HomepageURL != station1HomepageURL) ||
|
||||||
|
(*response.Response.InternetRadioStations.List[1].ID != station2IDT) ||
|
||||||
|
(response.Response.InternetRadioStations.List[1].StreamURL != station2StreamURL) ||
|
||||||
|
(response.Response.InternetRadioStations.List[1].Name != station2Name) ||
|
||||||
|
(response.Response.InternetRadioStations.List[1].HomepageURL != station2HomepageURL) {
|
||||||
|
t.Fatal("bad data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInternetRadioUpdates(t *testing.T, contr *Controller) {
|
||||||
|
// replace station 1 and read back
|
||||||
|
response := runTestCase(t, contr, contr.ServeUpdateInternetRadioStation,
|
||||||
|
url.Values{"id": {station1ID}, "streamUrl": {newstation1StreamURL}, "name": {newstation1Name}, "homepageUrl": {newstation1HomepageURL}}, true)
|
||||||
|
|
||||||
|
checkSuccess(t, response)
|
||||||
|
response = runTestCase(t, contr, contr.ServeGetInternetRadioStations, url.Values{}, false) // no need to be admin
|
||||||
|
checkSuccess(t, response)
|
||||||
|
|
||||||
|
if response.Response.InternetRadioStations == nil {
|
||||||
|
t.Fatal("didn't return stations")
|
||||||
|
}
|
||||||
|
if len(response.Response.InternetRadioStations.List) != 2 {
|
||||||
|
t.Fatal("wrong number of stations")
|
||||||
|
}
|
||||||
|
if (*response.Response.InternetRadioStations.List[0].ID != station1IDT) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].StreamURL != newstation1StreamURL) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].Name != newstation1Name) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].HomepageURL != newstation1HomepageURL) ||
|
||||||
|
(*response.Response.InternetRadioStations.List[1].ID != station2IDT) ||
|
||||||
|
(response.Response.InternetRadioStations.List[1].StreamURL != station2StreamURL) ||
|
||||||
|
(response.Response.InternetRadioStations.List[1].Name != station2Name) ||
|
||||||
|
(response.Response.InternetRadioStations.List[1].HomepageURL != station2HomepageURL) {
|
||||||
|
t.Fatal("bad data")
|
||||||
|
}
|
||||||
|
|
||||||
|
// update station 2 but without homepage URL and read back
|
||||||
|
response = runTestCase(t, contr, contr.ServeUpdateInternetRadioStation,
|
||||||
|
url.Values{"id": {station2ID}, "streamUrl": {newstation2StreamURL}, "name": {newstation2Name}}, true)
|
||||||
|
checkSuccess(t, response)
|
||||||
|
|
||||||
|
response = runTestCase(t, contr, contr.ServeGetInternetRadioStations, url.Values{}, false) // no need to be admin
|
||||||
|
checkSuccess(t, response)
|
||||||
|
|
||||||
|
if response.Response.InternetRadioStations == nil {
|
||||||
|
t.Fatal("didn't return stations")
|
||||||
|
}
|
||||||
|
if len(response.Response.InternetRadioStations.List) != 2 {
|
||||||
|
t.Fatal("wrong number of stations")
|
||||||
|
}
|
||||||
|
if (*response.Response.InternetRadioStations.List[0].ID != station1IDT) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].StreamURL != newstation1StreamURL) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].Name != newstation1Name) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].HomepageURL != newstation1HomepageURL) ||
|
||||||
|
(*response.Response.InternetRadioStations.List[1].ID != station2IDT) ||
|
||||||
|
(response.Response.InternetRadioStations.List[1].StreamURL != newstation2StreamURL) ||
|
||||||
|
(response.Response.InternetRadioStations.List[1].Name != newstation2Name) ||
|
||||||
|
(response.Response.InternetRadioStations.List[1].HomepageURL != "") {
|
||||||
|
t.Fatal("bad data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInternetRadioDeletes(t *testing.T, contr *Controller) {
|
||||||
|
// delete non-existent station 3 (fails and does not modify data)
|
||||||
|
response := runTestCase(t, contr, contr.ServeDeleteInternetRadioStation,
|
||||||
|
url.Values{"id": {station3ID}}, true)
|
||||||
|
checkBadParameter(t, response)
|
||||||
|
|
||||||
|
response = runTestCase(t, contr, contr.ServeGetInternetRadioStations, url.Values{}, false) // no need to be admin
|
||||||
|
checkSuccess(t, response)
|
||||||
|
|
||||||
|
if response.Response.InternetRadioStations == nil {
|
||||||
|
t.Fatal("didn't return stations")
|
||||||
|
}
|
||||||
|
if len(response.Response.InternetRadioStations.List) != 2 {
|
||||||
|
t.Fatal("wrong number of stations")
|
||||||
|
}
|
||||||
|
if (*response.Response.InternetRadioStations.List[0].ID != station1IDT) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].StreamURL != newstation1StreamURL) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].Name != newstation1Name) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].HomepageURL != newstation1HomepageURL) ||
|
||||||
|
(*response.Response.InternetRadioStations.List[1].ID != station2IDT) ||
|
||||||
|
(response.Response.InternetRadioStations.List[1].StreamURL != newstation2StreamURL) ||
|
||||||
|
(response.Response.InternetRadioStations.List[1].Name != newstation2Name) ||
|
||||||
|
(response.Response.InternetRadioStations.List[1].HomepageURL != "") {
|
||||||
|
t.Fatal("bad data")
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete station 1 and recheck
|
||||||
|
response = runTestCase(t, contr, contr.ServeDeleteInternetRadioStation,
|
||||||
|
url.Values{"id": {station1ID}}, true)
|
||||||
|
checkSuccess(t, response)
|
||||||
|
|
||||||
|
response = runTestCase(t, contr, contr.ServeGetInternetRadioStations, url.Values{}, false) // no need to be admin
|
||||||
|
checkSuccess(t, response)
|
||||||
|
|
||||||
|
if response.Response.InternetRadioStations == nil {
|
||||||
|
t.Fatal("didn't return stations")
|
||||||
|
}
|
||||||
|
if len(response.Response.InternetRadioStations.List) != 1 {
|
||||||
|
t.Fatal("wrong number of stations")
|
||||||
|
}
|
||||||
|
if (*response.Response.InternetRadioStations.List[0].ID != station2IDT) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].StreamURL != newstation2StreamURL) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].Name != newstation2Name) ||
|
||||||
|
(response.Response.InternetRadioStations.List[0].HomepageURL != "") {
|
||||||
|
t.Fatal("bad data")
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete station 2 and check that they're all gone
|
||||||
|
response = runTestCase(t, contr, contr.ServeDeleteInternetRadioStation,
|
||||||
|
url.Values{"id": {station2ID}}, true)
|
||||||
|
checkSuccess(t, response)
|
||||||
|
|
||||||
|
response = runTestCase(t, contr, contr.ServeGetInternetRadioStations, url.Values{}, false) // no need to be admin
|
||||||
|
checkSuccess(t, response)
|
||||||
|
|
||||||
|
if (response.Response.InternetRadioStations == nil) || (len(response.Response.InternetRadioStations.List) != 0) {
|
||||||
|
t.Fatal("didn't return empty stations")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@ 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)
|
user := r.Context().Value(CtxUser).(*db.User)
|
||||||
if (!user.IsAdmin) {
|
if (!user.IsAdmin) {
|
||||||
return spec.NewError(10, "user not admin")
|
return spec.NewError(50, "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")
|
||||||
@@ -62,7 +62,7 @@ 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) {
|
if (!user.IsAdmin) {
|
||||||
return spec.NewError(10, "user not admin")
|
return spec.NewError(50, "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")
|
||||||
@@ -80,7 +80,7 @@ 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 (!user.IsAdmin) {
|
if (!user.IsAdmin) {
|
||||||
return spec.NewError(10, "user not admin")
|
return spec.NewError(50, "user not admin")
|
||||||
}
|
}
|
||||||
if err := c.Podcasts.RefreshPodcasts(); err != nil {
|
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)
|
||||||
@@ -91,7 +91,7 @@ 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) {
|
if (!user.IsAdmin) {
|
||||||
return spec.NewError(10, "user not admin")
|
return spec.NewError(50, "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")
|
||||||
@@ -107,7 +107,7 @@ func (c *Controller) ServeDeletePodcastChannel(r *http.Request) *spec.Response {
|
|||||||
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)
|
user := r.Context().Value(CtxUser).(*db.User)
|
||||||
if (!user.IsAdmin) {
|
if (!user.IsAdmin) {
|
||||||
return spec.NewError(10, "user not admin")
|
return spec.NewError(50, "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")
|
||||||
|
|||||||
12
server/ctrlsubsonic/spec/construct_internet_radio.go
Normal file
12
server/ctrlsubsonic/spec/construct_internet_radio.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package spec
|
||||||
|
|
||||||
|
import "go.senan.xyz/gonic/db"
|
||||||
|
|
||||||
|
func NewInternetRadioStation(irs *db.InternetRadioStation) *InternetRadioStation {
|
||||||
|
return &InternetRadioStation{
|
||||||
|
ID: irs.SID(),
|
||||||
|
Name: irs.Name,
|
||||||
|
StreamURL: irs.StreamURL,
|
||||||
|
HomepageURL: irs.HomepageURL,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,10 @@ const (
|
|||||||
xmlns = "http://subsonic.org/restapi"
|
xmlns = "http://subsonic.org/restapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SubsonicResponse struct {
|
||||||
|
Response Response `xml:"subsonic-response" json:"subsonic-response"`
|
||||||
|
}
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
Status string `xml:"status,attr" json:"status"`
|
Status string `xml:"status,attr" json:"status"`
|
||||||
Version string `xml:"version,attr" json:"version"`
|
Version string `xml:"version,attr" json:"version"`
|
||||||
@@ -51,6 +55,7 @@ type Response struct {
|
|||||||
TopSongs *TopSongs `xml:"topSongs" json:"topSongs,omitempty"`
|
TopSongs *TopSongs `xml:"topSongs" json:"topSongs,omitempty"`
|
||||||
SimilarSongs *SimilarSongs `xml:"similarSongs" json:"similarSongs,omitempty"`
|
SimilarSongs *SimilarSongs `xml:"similarSongs" json:"similarSongs,omitempty"`
|
||||||
SimilarSongsTwo *SimilarSongsTwo `xml:"similarSongs2" json:"similarSongs2,omitempty"`
|
SimilarSongsTwo *SimilarSongsTwo `xml:"similarSongs2" json:"similarSongs2,omitempty"`
|
||||||
|
InternetRadioStations *InternetRadioStations `xml:"internetRadioStations" json:"internetRadioStations,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResponse() *Response {
|
func NewResponse() *Response {
|
||||||
@@ -372,3 +377,15 @@ type SimilarSongs struct {
|
|||||||
type SimilarSongsTwo struct {
|
type SimilarSongsTwo struct {
|
||||||
Tracks []*TrackChild `xml:"song,omitempty" json:"song,omitempty"`
|
Tracks []*TrackChild `xml:"song,omitempty" json:"song,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InternetRadioStations struct {
|
||||||
|
List []*InternetRadioStation `xml:"internetRadioStation" json:"internetRadioStation,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InternetRadioStation struct {
|
||||||
|
ID *specid.ID `xml:"id,attr" json:"id"`
|
||||||
|
Name string `xml:"name,attr" json:"name"`
|
||||||
|
StreamURL string `xml:"streamUrl,attr" json:"streamUrl"`
|
||||||
|
HomepageURL string `xml:"homepageUrl,attr" json:"homepageUrl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ var (
|
|||||||
ErrBadSeparator = errors.New("bad separator")
|
ErrBadSeparator = errors.New("bad separator")
|
||||||
ErrNotAnInt = errors.New("not an int")
|
ErrNotAnInt = errors.New("not an int")
|
||||||
ErrBadPrefix = errors.New("bad prefix")
|
ErrBadPrefix = errors.New("bad prefix")
|
||||||
|
ErrBadJSON = errors.New("bad JSON")
|
||||||
)
|
)
|
||||||
|
|
||||||
type IDT string
|
type IDT string
|
||||||
@@ -25,6 +26,7 @@ const (
|
|||||||
Track IDT = "tr"
|
Track IDT = "tr"
|
||||||
Podcast IDT = "pd"
|
Podcast IDT = "pd"
|
||||||
PodcastEpisode IDT = "pe"
|
PodcastEpisode IDT = "pe"
|
||||||
|
InternetRadioStation IDT = "ir"
|
||||||
separator = "-"
|
separator = "-"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -55,6 +57,8 @@ func New(in string) (ID, error) {
|
|||||||
return ID{Type: Podcast, Value: val}, nil
|
return ID{Type: Podcast, Value: val}, nil
|
||||||
case PodcastEpisode:
|
case PodcastEpisode:
|
||||||
return ID{Type: PodcastEpisode, Value: val}, nil
|
return ID{Type: PodcastEpisode, Value: val}, nil
|
||||||
|
case InternetRadioStation:
|
||||||
|
return ID{Type: InternetRadioStation, Value: val}, nil
|
||||||
default:
|
default:
|
||||||
return ID{}, fmt.Errorf("%q: %w", partType, ErrBadPrefix)
|
return ID{}, fmt.Errorf("%q: %w", partType, ErrBadPrefix)
|
||||||
}
|
}
|
||||||
@@ -71,6 +75,17 @@ func (i ID) MarshalJSON() ([]byte, error) {
|
|||||||
return json.Marshal(i.String())
|
return json.Marshal(i.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *ID) UnmarshalJSON(data []byte) (error) {
|
||||||
|
if (len(data) <= 2) {
|
||||||
|
return fmt.Errorf("too short: %w", ErrBadJSON)
|
||||||
|
}
|
||||||
|
id, err := New(string(data[1:len(data)-1])) // Strip quotes
|
||||||
|
if (err == nil) {
|
||||||
|
*i = id;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
func (i ID) MarshalText() ([]byte, error) {
|
func (i ID) MarshalText() ([]byte, error) {
|
||||||
return []byte(i.String()), nil
|
return []byte(i.String()), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,6 +189,9 @@ func setupAdmin(r *mux.Router, ctrl *ctrladmin.Controller) {
|
|||||||
routAdmin.Handle("/delete_podcast_do", ctrl.H(ctrl.ServePodcastDeleteDo))
|
routAdmin.Handle("/delete_podcast_do", ctrl.H(ctrl.ServePodcastDeleteDo))
|
||||||
routAdmin.Handle("/download_podcast_do", ctrl.H(ctrl.ServePodcastDownloadDo))
|
routAdmin.Handle("/download_podcast_do", ctrl.H(ctrl.ServePodcastDownloadDo))
|
||||||
routAdmin.Handle("/update_podcast_do", ctrl.H(ctrl.ServePodcastUpdateDo))
|
routAdmin.Handle("/update_podcast_do", ctrl.H(ctrl.ServePodcastUpdateDo))
|
||||||
|
routAdmin.Handle("/add_internet_radio_station_do", ctrl.H(ctrl.ServeInternetRadioStationAddDo))
|
||||||
|
routAdmin.Handle("/delete_internet_radio_station_do", ctrl.H(ctrl.ServeInternetRadioStationDeleteDo))
|
||||||
|
routAdmin.Handle("/update_internet_radio_station_do", ctrl.H(ctrl.ServeInternetRadioStationUpdateDo))
|
||||||
|
|
||||||
// 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
|
||||||
@@ -260,6 +263,12 @@ func setupSubsonic(r *mux.Router, ctrl *ctrlsubsonic.Controller) {
|
|||||||
r.Handle("/deletePodcastChannel{_:(?:\\.view)?}", ctrl.H(ctrl.ServeDeletePodcastChannel))
|
r.Handle("/deletePodcastChannel{_:(?:\\.view)?}", ctrl.H(ctrl.ServeDeletePodcastChannel))
|
||||||
r.Handle("/deletePodcastEpisode{_:(?:\\.view)?}", ctrl.H(ctrl.ServeDeletePodcastEpisode))
|
r.Handle("/deletePodcastEpisode{_:(?:\\.view)?}", ctrl.H(ctrl.ServeDeletePodcastEpisode))
|
||||||
|
|
||||||
|
// internet radio
|
||||||
|
r.Handle("/getInternetRadioStations{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetInternetRadioStations))
|
||||||
|
r.Handle("/createInternetRadioStation{_:(?:\\.view)?}", ctrl.H(ctrl.ServeCreateInternetRadioStation))
|
||||||
|
r.Handle("/updateInternetRadioStation{_:(?:\\.view)?}", ctrl.H(ctrl.ServeUpdateInternetRadioStation))
|
||||||
|
r.Handle("/deleteInternetRadioStation{_:(?:\\.view)?}", ctrl.H(ctrl.ServeDeleteInternetRadioStation))
|
||||||
|
|
||||||
// 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
|
||||||
notFoundHandler := ctrl.H(ctrl.ServeNotFound)
|
notFoundHandler := ctrl.H(ctrl.ServeNotFound)
|
||||||
|
|||||||
Reference in New Issue
Block a user