2020-01-20 01:34:54 +01:00
|
|
|
package app
|
|
|
|
|
|
|
|
import (
|
2020-01-20 02:40:18 +01:00
|
|
|
"context"
|
2020-01-20 01:34:54 +01:00
|
|
|
"net/http"
|
2020-01-20 02:40:18 +01:00
|
|
|
"net/url"
|
|
|
|
"strings"
|
2020-01-20 01:34:54 +01:00
|
|
|
|
2020-01-24 01:44:08 +01:00
|
|
|
"github.com/deluan/navidrome/assets"
|
2020-04-28 03:23:15 +02:00
|
|
|
"github.com/deluan/navidrome/conf"
|
2020-07-10 18:45:58 +02:00
|
|
|
"github.com/deluan/navidrome/core/auth"
|
2020-01-24 01:44:08 +01:00
|
|
|
"github.com/deluan/navidrome/model"
|
2020-01-20 02:40:18 +01:00
|
|
|
"github.com/deluan/rest"
|
2020-01-20 01:34:54 +01:00
|
|
|
"github.com/go-chi/chi"
|
2020-01-20 15:54:29 +01:00
|
|
|
"github.com/go-chi/jwtauth"
|
2020-01-20 01:34:54 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type Router struct {
|
2020-04-03 23:50:42 +02:00
|
|
|
ds model.DataStore
|
|
|
|
mux http.Handler
|
2020-01-20 01:34:54 +01:00
|
|
|
}
|
|
|
|
|
2020-04-03 23:50:42 +02:00
|
|
|
func New(ds model.DataStore) *Router {
|
|
|
|
return &Router{ds: ds}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (app *Router) Setup(path string) {
|
|
|
|
app.mux = app.routes(path)
|
2020-01-20 01:34:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (app *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
app.mux.ServeHTTP(w, r)
|
|
|
|
}
|
|
|
|
|
2020-04-03 23:50:42 +02:00
|
|
|
func (app *Router) routes(path string) http.Handler {
|
2020-01-20 01:34:54 +01:00
|
|
|
r := chi.NewRouter()
|
2020-01-20 02:40:18 +01:00
|
|
|
|
2020-01-20 15:54:29 +01:00
|
|
|
r.Post("/login", Login(app.ds))
|
2020-01-25 23:10:16 +01:00
|
|
|
r.Post("/createAdmin", CreateAdmin(app.ds))
|
2020-01-20 15:54:29 +01:00
|
|
|
|
2020-01-20 02:40:18 +01:00
|
|
|
r.Route("/api", func(r chi.Router) {
|
2020-04-06 22:03:20 +02:00
|
|
|
r.Use(mapAuthHeader())
|
2020-02-06 22:48:35 +01:00
|
|
|
r.Use(jwtauth.Verifier(auth.TokenAuth))
|
2020-04-06 22:03:20 +02:00
|
|
|
r.Use(authenticator(app.ds))
|
2020-04-28 03:23:15 +02:00
|
|
|
app.R(r, "/user", model.User{}, true)
|
|
|
|
app.R(r, "/song", model.MediaFile{}, true)
|
|
|
|
app.R(r, "/album", model.Album{}, true)
|
|
|
|
app.R(r, "/artist", model.Artist{}, true)
|
|
|
|
app.R(r, "/player", model.Player{}, true)
|
2020-05-04 02:05:03 +02:00
|
|
|
app.R(r, "/playlist", model.Playlist{}, true)
|
2020-04-28 03:23:15 +02:00
|
|
|
app.R(r, "/transcoding", model.Transcoding{}, conf.Server.EnableTranscodingConfig)
|
2020-05-09 05:48:06 +02:00
|
|
|
app.RX(r, "/translation", newTranslationRepository, false)
|
|
|
|
|
2020-05-16 02:47:15 +02:00
|
|
|
app.addPlaylistTrackRoute(r)
|
2020-02-05 19:16:11 +01:00
|
|
|
|
|
|
|
// Keepalive endpoint to be used to keep the session valid (ex: while playing songs)
|
2020-04-26 18:35:26 +02:00
|
|
|
r.Get("/keepalive/*", func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(`{"response":"ok"}`)) })
|
2020-01-20 02:40:18 +01:00
|
|
|
})
|
2020-01-23 00:35:44 +01:00
|
|
|
|
|
|
|
// Serve UI app assets
|
2020-04-06 23:01:32 +02:00
|
|
|
r.Handle("/", ServeIndex(app.ds, assets.AssetFile()))
|
2020-04-03 23:50:42 +02:00
|
|
|
r.Handle("/*", http.StripPrefix(path, http.FileServer(assets.AssetFile())))
|
2020-01-23 00:35:44 +01:00
|
|
|
|
2020-01-20 01:34:54 +01:00
|
|
|
return r
|
|
|
|
}
|
2020-01-20 02:40:18 +01:00
|
|
|
|
2020-04-28 03:23:15 +02:00
|
|
|
func (app *Router) R(r chi.Router, pathPrefix string, model interface{}, persistable bool) {
|
2020-01-22 18:32:31 +01:00
|
|
|
constructor := func(ctx context.Context) rest.Repository {
|
2020-01-27 15:41:33 +01:00
|
|
|
return app.ds.Resource(ctx, model)
|
2020-01-22 18:32:31 +01:00
|
|
|
}
|
2020-05-09 05:48:06 +02:00
|
|
|
app.RX(r, pathPrefix, constructor, persistable)
|
2020-05-02 00:29:50 +02:00
|
|
|
}
|
|
|
|
|
2020-05-09 05:48:06 +02:00
|
|
|
func (app *Router) RX(r chi.Router, pathPrefix string, constructor rest.RepositoryConstructor, persistable bool) {
|
2020-01-20 02:40:18 +01:00
|
|
|
r.Route(pathPrefix, func(r chi.Router) {
|
2020-01-22 18:32:31 +01:00
|
|
|
r.Get("/", rest.GetAll(constructor))
|
2020-04-28 03:23:15 +02:00
|
|
|
if persistable {
|
|
|
|
r.Post("/", rest.Post(constructor))
|
|
|
|
}
|
2020-05-02 00:29:50 +02:00
|
|
|
r.Route("/{id}", func(r chi.Router) {
|
2020-01-20 02:40:18 +01:00
|
|
|
r.Use(UrlParams)
|
2020-01-22 18:32:31 +01:00
|
|
|
r.Get("/", rest.Get(constructor))
|
2020-04-28 03:23:15 +02:00
|
|
|
if persistable {
|
|
|
|
r.Put("/", rest.Put(constructor))
|
|
|
|
r.Delete("/", rest.Delete(constructor))
|
|
|
|
}
|
2020-01-20 02:40:18 +01:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-05-09 05:48:06 +02:00
|
|
|
type restHandler = func(rest.RepositoryConstructor, ...rest.Logger) http.HandlerFunc
|
|
|
|
|
2020-05-16 02:47:15 +02:00
|
|
|
func (app *Router) addPlaylistTrackRoute(r chi.Router) {
|
2020-06-05 01:05:41 +02:00
|
|
|
// Add a middleware to capture the playlistId
|
2020-05-09 05:48:06 +02:00
|
|
|
wrapper := func(f restHandler) http.HandlerFunc {
|
|
|
|
return func(res http.ResponseWriter, req *http.Request) {
|
|
|
|
c := func(ctx context.Context) rest.Repository {
|
|
|
|
plsRepo := app.ds.Resource(ctx, model.Playlist{})
|
|
|
|
plsId := chi.URLParam(req, "playlistId")
|
|
|
|
return plsRepo.(model.PlaylistRepository).Tracks(plsId)
|
|
|
|
}
|
|
|
|
|
|
|
|
f(c).ServeHTTP(res, req)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
r.Route("/playlist/{playlistId}/tracks", func(r chi.Router) {
|
|
|
|
r.Get("/", wrapper(rest.GetAll))
|
|
|
|
r.Route("/{id}", func(r chi.Router) {
|
|
|
|
r.Use(UrlParams)
|
|
|
|
r.Get("/", wrapper(rest.Get))
|
2020-06-05 01:05:41 +02:00
|
|
|
r.Put("/", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
reorderItem(app.ds)(w, r)
|
|
|
|
})
|
2020-05-17 00:10:08 +02:00
|
|
|
r.Delete("/", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
deleteFromPlaylist(app.ds)(w, r)
|
|
|
|
})
|
2020-05-09 05:48:06 +02:00
|
|
|
})
|
2020-05-16 02:47:15 +02:00
|
|
|
r.With(UrlParams).Post("/", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
addToPlaylist(app.ds)(w, r)
|
|
|
|
})
|
2020-05-09 05:48:06 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-01-20 02:40:18 +01:00
|
|
|
// Middleware to convert Chi URL params (from Context) to query params, as expected by our REST package
|
|
|
|
func UrlParams(next http.Handler) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := chi.RouteContext(r.Context())
|
|
|
|
parts := make([]string, 0)
|
|
|
|
for i, key := range ctx.URLParams.Keys {
|
|
|
|
value := ctx.URLParams.Values[i]
|
|
|
|
if key == "*" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
parts = append(parts, url.QueryEscape(":"+key)+"="+url.QueryEscape(value))
|
|
|
|
}
|
|
|
|
q := strings.Join(parts, "&")
|
|
|
|
if r.URL.RawQuery == "" {
|
|
|
|
r.URL.RawQuery = q
|
|
|
|
} else {
|
|
|
|
r.URL.RawQuery += "&" + q
|
|
|
|
}
|
|
|
|
|
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
})
|
|
|
|
}
|