From 75cd21da1fb5fe0bb457195eccec1e25073d6200 Mon Sep 17 00:00:00 2001 From: Deluan Date: Fri, 3 Apr 2020 17:50:42 -0400 Subject: [PATCH] Add BaseURL configuration (fixes #103) --- README.md | 1 + conf/configuration.go | 1 + consts/consts.go | 3 +++ docker-compose.yml | 1 + main.go | 4 ++-- server/app/app.go | 19 ++++++++++--------- server/app/serve_index.go | 3 +++ server/server.go | 18 ++++++++++++++---- server/subsonic/api.go | 2 ++ ui/package.json | 2 +- ui/src/App.js | 1 + ui/src/authProvider.js | 5 +++-- ui/src/dataProvider.js | 8 +++++--- ui/src/subsonic/index.js | 4 +++- ui/src/utils/baseUrl.js | 8 ++++++++ wire_gen.go | 4 ++-- wire_injectors.go | 2 +- 17 files changed, 61 insertions(+), 25 deletions(-) create mode 100644 ui/src/utils/baseUrl.js diff --git a/README.md b/README.md index 0392f114..c6d0f652 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ services: ND_PORT: 4533 ND_TRANSCODINGCACHESIZE: 100MB ND_SESSIONTIMEOUT: 30m + ND_BASEURL: "" volumes: - "./data:/data" - "/path/to/your/music/folder:/music:ro" diff --git a/conf/configuration.go b/conf/configuration.go index a51e70d7..30256c4b 100644 --- a/conf/configuration.go +++ b/conf/configuration.go @@ -20,6 +20,7 @@ type nd struct { DbPath string `` LogLevel string `default:"info"` SessionTimeout string `default:"30m"` + BaseURL string `default:""` IgnoredArticles string `default:"The El La Los Las Le Les Os As O A"` IndexGroups string `default:"A B C D E F G H I J K L M N O P Q R S T U V W X-Z(XYZ) [Unknown]([)"` diff --git a/consts/consts.go b/consts/consts.go index 48c02175..270de6e8 100644 --- a/consts/consts.go +++ b/consts/consts.go @@ -27,6 +27,9 @@ const ( DevInitialUserName = "admin" DevInitialName = "Dev Admin" + + URLPathUI = "/app" + URLPathSubsonicAPI = "/rest" ) var ( diff --git a/docker-compose.yml b/docker-compose.yml index 2dec11bb..1235e7e4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,7 @@ services: ND_PORT: 4533 ND_TRANSCODINGCACHESIZE: 100MB ND_SESSIONTIMEOUT: 30m + ND_BASEURL: "" volumes: - "./data:/data" - "./music:/music" diff --git a/main.go b/main.go index a33fca41..6521da10 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,7 @@ func main() { panic(fmt.Sprintf("Could not create the Subsonic API router. Aborting! err=%v", err)) } a := CreateServer(conf.Server.MusicFolder) - a.MountRouter("/rest", subsonic) - a.MountRouter("/app", CreateAppRouter("/app")) + a.MountRouter(consts.URLPathSubsonicAPI, subsonic) + a.MountRouter(consts.URLPathUI, CreateAppRouter()) a.Run(":" + conf.Server.Port) } diff --git a/server/app/app.go b/server/app/app.go index aa57e341..c1879fba 100644 --- a/server/app/app.go +++ b/server/app/app.go @@ -15,22 +15,23 @@ import ( ) type Router struct { - ds model.DataStore - mux http.Handler - path string + ds model.DataStore + mux http.Handler } -func New(ds model.DataStore, path string) *Router { - r := &Router{ds: ds, path: path} - r.mux = r.routes() - return r +func New(ds model.DataStore) *Router { + return &Router{ds: ds} +} + +func (app *Router) Setup(path string) { + app.mux = app.routes(path) } func (app *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { app.mux.ServeHTTP(w, r) } -func (app *Router) routes() http.Handler { +func (app *Router) routes(path string) http.Handler { r := chi.NewRouter() r.Post("/login", Login(app.ds)) @@ -52,7 +53,7 @@ func (app *Router) routes() http.Handler { // Serve UI app assets r.Handle("/", ServeIndex(app.ds)) - r.Handle("/*", http.StripPrefix(app.path, http.FileServer(assets.AssetFile()))) + r.Handle("/*", http.StripPrefix(path, http.FileServer(assets.AssetFile()))) return r } diff --git a/server/app/serve_index.go b/server/app/serve_index.go index 79cbcb90..0752a78a 100644 --- a/server/app/serve_index.go +++ b/server/app/serve_index.go @@ -5,8 +5,10 @@ import ( "html/template" "io/ioutil" "net/http" + "strings" "github.com/deluan/navidrome/assets" + "github.com/deluan/navidrome/conf" "github.com/deluan/navidrome/consts" "github.com/deluan/navidrome/log" "github.com/deluan/navidrome/model" @@ -31,6 +33,7 @@ func ServeIndex(ds model.DataStore) http.HandlerFunc { t, _ = t.Parse(string(indexStr)) appConfig := map[string]interface{}{ "firstTime": firstTime, + "baseURL": strings.TrimSuffix(conf.Server.BaseURL, "/"), } j, _ := json.Marshal(appConfig) data := map[string]interface{}{ diff --git a/server/server.go b/server/server.go index 5b5e6458..6e3db91b 100644 --- a/server/server.go +++ b/server/server.go @@ -3,10 +3,12 @@ package server import ( "net/http" "os" + "path" "path/filepath" "time" "github.com/deluan/navidrome/conf" + "github.com/deluan/navidrome/consts" "github.com/deluan/navidrome/log" "github.com/deluan/navidrome/model" "github.com/deluan/navidrome/scanner" @@ -15,6 +17,11 @@ import ( "github.com/go-chi/cors" ) +type Handler interface { + http.Handler + Setup(path string) +} + type Server struct { Scanner *scanner.Scanner router *chi.Mux @@ -29,11 +36,13 @@ func New(scanner *scanner.Scanner, ds model.DataStore) *Server { return a } -func (a *Server) MountRouter(path string, subRouter http.Handler) { - log.Info("Mounting routes", "path", path) +func (a *Server) MountRouter(urlPath string, subRouter Handler) { + urlPath = path.Join(conf.Server.BaseURL, urlPath) + log.Info("Mounting routes", "path", urlPath) + subRouter.Setup(urlPath) a.router.Group(func(r chi.Router) { r.Use(RequestLogger) - r.Mount(path, subRouter) + r.Mount(urlPath, subRouter) }) } @@ -53,8 +62,9 @@ func (a *Server) initRoutes() { r.Use(middleware.Heartbeat("/ping")) r.Use(InjectLogger) + indexHtml := path.Join(conf.Server.BaseURL, consts.URLPathUI, "index.html") r.Get("/", func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, "/app", 302) + http.Redirect(w, r, indexHtml, 302) }) workDir, _ := os.Getwd() diff --git a/server/subsonic/api.go b/server/subsonic/api.go index 37910a39..2fb6e077 100644 --- a/server/subsonic/api.go +++ b/server/subsonic/api.go @@ -42,6 +42,8 @@ func New(browser engine.Browser, cover engine.Cover, listGenerator engine.ListGe return r } +func (api *Router) Setup(path string) {} + func (api *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { api.mux.ServeHTTP(w, r) } diff --git a/ui/package.json b/ui/package.json index 48ef008c..a6515d61 100644 --- a/ui/package.json +++ b/ui/package.json @@ -25,7 +25,7 @@ "test": "react-scripts test", "eject": "react-scripts eject" }, - "homepage": "https://localhost/app/", + "homepage": ".", "proxy": "http://localhost:4633/", "eslintConfig": { "extends": "react-app" diff --git a/ui/src/App.js b/ui/src/App.js index 0f93eb46..d6d0249a 100644 --- a/ui/src/App.js +++ b/ui/src/App.js @@ -34,6 +34,7 @@ const App = () => { if (appConfig.firstTime) { localStorage.setItem('initialAccountCreation', 'true') } + localStorage.setItem('baseURL', appConfig.baseURL) } catch (e) {} return ( diff --git a/ui/src/authProvider.js b/ui/src/authProvider.js index 675faaa3..cc6bc0fd 100644 --- a/ui/src/authProvider.js +++ b/ui/src/authProvider.js @@ -1,11 +1,12 @@ import jwtDecode from 'jwt-decode' import md5 from 'md5-hex' +import baseUrl from './utils/baseUrl' const authProvider = { login: ({ username, password }) => { - let url = '/app/login' + let url = baseUrl('/app/login') if (localStorage.getItem('initialAccountCreation')) { - url = '/app/createAdmin' + url = baseUrl('/app/createAdmin') } const request = new Request(url, { method: 'POST', diff --git a/ui/src/dataProvider.js b/ui/src/dataProvider.js index a37dee89..49745fbf 100644 --- a/ui/src/dataProvider.js +++ b/ui/src/dataProvider.js @@ -1,10 +1,12 @@ import { fetchUtils } from 'react-admin' import jsonServerProvider from 'ra-data-json-server' +import baseUrl from './utils/baseUrl' -const baseUrl = '/app/api' +const restUrl = '/app/api' const httpClient = (url, options = {}) => { - url = url.replace(baseUrl + '/albumSong', baseUrl + '/song') + url = baseUrl(url) + url = url.replace(restUrl + '/albumSong', restUrl + '/song') if (!options.headers) { options.headers = new Headers({ Accept: 'application/json' }) } @@ -22,6 +24,6 @@ const httpClient = (url, options = {}) => { }) } -const dataProvider = jsonServerProvider(baseUrl, httpClient) +const dataProvider = jsonServerProvider(restUrl, httpClient) export default dataProvider diff --git a/ui/src/subsonic/index.js b/ui/src/subsonic/index.js index d739d8c2..a08f06f5 100644 --- a/ui/src/subsonic/index.js +++ b/ui/src/subsonic/index.js @@ -1,4 +1,5 @@ import { fetchUtils } from 'react-admin' +import baseUrl from "../utils/baseUrl" const url = (command, id, options) => { const params = new URLSearchParams() @@ -18,7 +19,8 @@ const url = (command, id, options) => { params.append(k, options[k]) }) } - return `rest/${command}?${params.toString()}` + const url = `/rest/${command}?${params.toString()}` + return baseUrl(url) } const scrobble = (id, submit) => { diff --git a/ui/src/utils/baseUrl.js b/ui/src/utils/baseUrl.js new file mode 100644 index 00000000..d19482cc --- /dev/null +++ b/ui/src/utils/baseUrl.js @@ -0,0 +1,8 @@ +const baseUrl = (path) => { + const base = localStorage.getItem('baseURL') || '' + const parts = [base] + parts.push(path.replace(/^\//, '')) + return parts.join('/') +} + +export default baseUrl diff --git a/wire_gen.go b/wire_gen.go index 29bef559..36f7296a 100644 --- a/wire_gen.go +++ b/wire_gen.go @@ -25,9 +25,9 @@ func CreateServer(musicFolder string) *server.Server { return serverServer } -func CreateAppRouter(path string) *app.Router { +func CreateAppRouter() *app.Router { dataStore := persistence.New() - router := app.New(dataStore, path) + router := app.New(dataStore) return router } diff --git a/wire_injectors.go b/wire_injectors.go index c477f76e..f104a15b 100644 --- a/wire_injectors.go +++ b/wire_injectors.go @@ -27,7 +27,7 @@ func CreateServer(musicFolder string) *server.Server { )) } -func CreateAppRouter(path string) *app.Router { +func CreateAppRouter() *app.Router { panic(wire.Build(allProviders)) }