package server import ( "bytes" "fmt" "net/http" "path/filepath" "time" "github.com/gorilla/mux" "senan.xyz/g/gonic/assets" "senan.xyz/g/gonic/db" "senan.xyz/g/gonic/server/ctrladmin" "senan.xyz/g/gonic/server/ctrlbase" "senan.xyz/g/gonic/server/ctrlsubsonic" ) type Server struct { *http.Server router *mux.Router ctrlBase *ctrlbase.Controller } func New(db *db.DB, musicPath string, listenAddr string) *Server { ctrlBase := &ctrlbase.Controller{ DB: db, MusicPath: musicPath, } router := mux.NewRouter() router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { // make the admin page the default http.Redirect(w, r, "/admin/home", http.StatusMovedPermanently) }) router.HandleFunc("/musicFolderSettings.view", func(w http.ResponseWriter, r *http.Request) { // jamstash seems to call "musicFolderSettings.view" to start a scan. notice // that there is no "/rest/" prefix, so i doesn't fit in with the nice router, // instead lets redirect to down there and use the scan endpoint // custom handler, middleware. etc setup that we've got in `SetupSubsonic()`. redirectTo := fmt.Sprintf("/rest/startScan.view?%s", r.URL.Query().Encode()) http.Redirect(w, r, redirectTo, http.StatusMovedPermanently) }) // common middleware for admin and subsonic routes router.Use(ctrlBase.WithLogging) router.Use(ctrlBase.WithCORS) server := &http.Server{ Addr: listenAddr, Handler: router, ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 15 * time.Second, } return &Server{ Server: server, router: router, ctrlBase: ctrlBase, } } func (s *Server) SetupAdmin() error { ctrl := ctrladmin.New(s.ctrlBase) // // begin public routes (creates session) routPublic := s.router.PathPrefix("/admin").Subrouter() routPublic.Use(ctrl.WithSession) routPublic.NotFoundHandler = ctrl.H(ctrl.ServeNotFound) routPublic.Handle("/login", ctrl.H(ctrl.ServeLogin)) routPublic.Handle("/login_do", ctrl.H(ctrl.ServeLoginDo)) assets.PrefixDo("static", func(path string, asset *assets.EmbeddedAsset) { _, name := filepath.Split(path) route := filepath.Join("/static", name) reader := bytes.NewReader(asset.Bytes) routPublic.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) { http.ServeContent(w, r, name, asset.ModTime, reader) }) }) // // begin user routes (if session is valid) routUser := routPublic.NewRoute().Subrouter() routUser.Use(ctrl.WithUserSession) routUser.Handle("/logout", ctrl.H(ctrl.ServeLogout)) routUser.Handle("/home", ctrl.H(ctrl.ServeHome)) routUser.Handle("/change_own_password", ctrl.H(ctrl.ServeChangeOwnPassword)) routUser.Handle("/change_own_password_do", ctrl.H(ctrl.ServeChangeOwnPasswordDo)) routUser.Handle("/link_lastfm_do", ctrl.H(ctrl.ServeLinkLastFMDo)) routUser.Handle("/unlink_lastfm_do", ctrl.H(ctrl.ServeUnlinkLastFMDo)) // // begin admin routes (if session is valid, and is admin) routAdmin := routUser.NewRoute().Subrouter() routAdmin.Use(ctrl.WithAdminSession) routAdmin.Handle("/change_password", ctrl.H(ctrl.ServeChangePassword)) routAdmin.Handle("/change_password_do", ctrl.H(ctrl.ServeChangePasswordDo)) routAdmin.Handle("/delete_user", ctrl.H(ctrl.ServeDeleteUser)) routAdmin.Handle("/delete_user_do", ctrl.H(ctrl.ServeDeleteUserDo)) routAdmin.Handle("/create_user", ctrl.H(ctrl.ServeCreateUser)) routAdmin.Handle("/create_user_do", ctrl.H(ctrl.ServeCreateUserDo)) routAdmin.Handle("/update_lastfm_api_key", ctrl.H(ctrl.ServeUpdateLastFMAPIKey)) routAdmin.Handle("/update_lastfm_api_key_do", ctrl.H(ctrl.ServeUpdateLastFMAPIKeyDo)) routAdmin.Handle("/start_scan_do", ctrl.H(ctrl.ServeStartScanDo)) return nil } func (s *Server) SetupSubsonic() error { ctrl := ctrlsubsonic.New(s.ctrlBase) rout := s.router.PathPrefix("/rest").Subrouter() rout.Use(ctrl.WithValidSubsonicArgs) rout.NotFoundHandler = ctrl.H(ctrl.ServeNotFound) // // begin common rout.Handle("/getLicense{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetLicence)) rout.Handle("/getMusicFolders{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetMusicFolders)) rout.Handle("/getScanStatus{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetScanStatus)) rout.Handle("/ping{_:(?:\\.view)?}", ctrl.H(ctrl.ServePing)) rout.Handle("/scrobble{_:(?:\\.view)?}", ctrl.H(ctrl.ServeScrobble)) rout.Handle("/startScan{_:(?:\\.view)?}", ctrl.H(ctrl.ServeStartScan)) rout.Handle("/getUser{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetUser)) // // begin raw rout.Handle("/download{_:(?:\\.view)?}", ctrl.HR(ctrl.ServeStream)) rout.Handle("/getCoverArt{_:(?:\\.view)?}", ctrl.HR(ctrl.ServeGetCoverArt)) rout.Handle("/stream{_:(?:\\.view)?}", ctrl.HR(ctrl.ServeStream)) // // begin browse by tag rout.Handle("/getAlbum{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetAlbum)) rout.Handle("/getAlbumList2{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetAlbumListTwo)) rout.Handle("/getArtist{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetArtist)) rout.Handle("/getArtists{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetArtists)) rout.Handle("/search3{_:(?:\\.view)?}", ctrl.H(ctrl.ServeSearchThree)) // // begin browse by folder rout.Handle("/getIndexes{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetIndexes)) rout.Handle("/getMusicDirectory{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetMusicDirectory)) rout.Handle("/getAlbumList{_:(?:\\.view)?}", ctrl.H(ctrl.ServeGetAlbumList)) rout.Handle("/search2{_:(?:\\.view)?}", ctrl.H(ctrl.ServeSearchTwo)) return nil }