package web import ( "net/http" "strings" "git.noroutes.net/NoRoutes/vibetunes/internal/database" assets "git.noroutes.net/NoRoutes/vibetunes/web" ) // NewRouter builds VibeSonic's full HTTP handler: static assets, health // checks, first-run setup, authentication, and the authenticated // application (library browsing, playback, playlists, favorites, // settings, admin). func (h *Web) NewRouter() http.Handler { mux := http.NewServeMux() mux.Handle("GET /static/", http.FileServer(http.FS(assets.FS))) mux.HandleFunc("GET /healthz", h.handleHealthz) mux.HandleFunc("GET /readyz", h.handleReadyz) mux.HandleFunc("GET /setup", h.handleSetupPage) mux.HandleFunc("POST /setup", h.handleSetup) mux.HandleFunc("GET /login", h.handleLoginPage) mux.HandleFunc("POST /login", h.handleLogin) mux.HandleFunc("POST /logout", h.handleLogout) mux.HandleFunc("GET /share/{token}", h.handleSharePage) mux.HandleFunc("GET /share/{token}/stream/{songID}", h.handleShareStream) mux.HandleFunc("GET /{$}", h.requireUser(h.handleHome)) mux.HandleFunc("GET /search", h.requireUser(h.handleSearch)) mux.HandleFunc("GET /artists", h.requireUser(h.handleArtists)) mux.HandleFunc("GET /artists/{id}", h.requireUser(h.handleArtist)) mux.HandleFunc("GET /albums", h.requireUser(h.handleAlbums)) mux.HandleFunc("GET /albums/{id}", h.requireUser(h.handleAlbum)) mux.HandleFunc("GET /songs", h.requireUser(h.handleSongs)) mux.HandleFunc("GET /genres", h.requireUser(h.handleGenres)) mux.HandleFunc("GET /playlists", h.requireUser(h.handlePlaylists)) mux.HandleFunc("POST /playlists", h.requireUser(h.handlePlaylistCreate)) mux.HandleFunc("GET /playlists/import", h.requireUser(h.handlePlaylistImportForm)) mux.HandleFunc("POST /playlists/import", h.requireUser(h.handlePlaylistImport)) mux.HandleFunc("GET /playlists/{id}", h.requireUser(h.handlePlaylist)) mux.HandleFunc("POST /playlists/{id}/delete", h.requireUser(h.handlePlaylistDelete)) mux.HandleFunc("GET /playlists/{id}/export", h.requireUser(h.handlePlaylistExport)) mux.HandleFunc("GET /favorites", h.requireUser(h.handleFavorites)) mux.HandleFunc("POST /favorites/star", h.requireUser(h.handleFavoriteStar)) mux.HandleFunc("POST /favorites/unstar", h.requireUser(h.handleFavoriteUnstar)) mux.HandleFunc("GET /recent", h.requireUser(h.handleRecent)) mux.HandleFunc("GET /podcasts", h.requireUser(h.handlePodcasts)) mux.HandleFunc("POST /podcasts/{id}/download", h.requireUser(h.handlePodcastDownload)) mux.HandleFunc("POST /podcasts/{id}/download-all", h.requireUser(h.handlePodcastDownloadAll)) mux.HandleFunc("GET /radio", h.requireUser(h.handleRadio)) mux.HandleFunc("POST /radio", h.requireAdmin(h.handleRadioCreate)) mux.HandleFunc("POST /radio/{id}/delete", h.requireAdmin(h.handleRadioDelete)) mux.HandleFunc("GET /shares", h.requireUser(h.handleShares)) mux.HandleFunc("POST /albums/{id}/share", h.requireUser(h.handleShareCreateForAlbum)) mux.HandleFunc("POST /shares/{id}/delete", h.requireUser(h.handleShareDelete)) mux.HandleFunc("GET /bookmarks", h.requireUser(h.handleBookmarks)) mux.HandleFunc("POST /bookmarks/{songID}/delete", h.requireUser(h.handleBookmarkDelete)) mux.HandleFunc("GET /settings", h.requireUser(h.handleSettings)) mux.HandleFunc("POST /settings/password", h.requireUser(h.handlePasswordChange)) mux.HandleFunc("POST /settings/api-keys", h.requireUser(h.handleAPIKeyCreate)) mux.HandleFunc("POST /settings/api-keys/{id}/revoke", h.requireUser(h.handleAPIKeyRevoke)) mux.HandleFunc("POST /settings/sessions/{id}/revoke", h.requireUser(h.handleSessionRevoke)) mux.HandleFunc("GET /stream/{id}", h.requireUser(h.handleStream)) mux.HandleFunc("GET /player/lyrics/{id}", h.requireUser(h.handleLyrics)) mux.HandleFunc("GET /cover-art/{id}", h.requireUser(h.handleCoverArt)) mux.HandleFunc("GET /admin", h.requireAdmin(h.handleAdminRoot)) mux.HandleFunc("GET /admin/users", h.requireAdmin(h.handleAdminUsers)) mux.HandleFunc("POST /admin/users", h.requireAdmin(h.handleAdminUserCreate)) mux.HandleFunc("POST /admin/users/{id}/toggle-disabled", h.requireAdmin(h.handleAdminUserToggleDisabled)) mux.HandleFunc("POST /admin/users/{id}/delete", h.requireAdmin(h.handleAdminUserDelete)) mux.HandleFunc("GET /admin/users/{id}/permissions", h.requireAdmin(h.handleAdminUserPermissions)) mux.HandleFunc("POST /admin/users/{id}/permissions", h.requireAdmin(h.handleAdminUserPermissionsUpdate)) mux.HandleFunc("GET /admin/folders", h.requireAdmin(h.handleAdminFolders)) mux.HandleFunc("POST /admin/folders", h.requireAdmin(h.handleAdminFolderCreate)) mux.HandleFunc("POST /admin/folders/{id}/toggle", h.requireAdmin(h.handleAdminFolderToggle)) mux.HandleFunc("POST /admin/folders/{id}/delete", h.requireAdmin(h.handleAdminFolderDelete)) mux.HandleFunc("POST /admin/folders/{id}/scan", h.requireAdmin(h.handleAdminFolderScan)) mux.HandleFunc("GET /admin/scanner", h.requireAdmin(h.handleAdminScanner)) mux.HandleFunc("GET /admin/settings", h.requireAdmin(h.handleAdminSettings)) mux.HandleFunc("POST /admin/settings", h.requireAdmin(h.handleAdminSettingsUpdate)) mux.HandleFunc("POST /admin/settings/artist-photos/refresh", h.requireAdmin(h.handleAdminArtistPhotosRefresh)) mux.HandleFunc("GET /admin/podcasts", h.requireAdmin(h.handleAdminPodcasts)) mux.HandleFunc("POST /admin/podcasts", h.requireAdmin(h.handleAdminPodcastCreate)) mux.HandleFunc("POST /admin/podcasts/{id}/refresh", h.requireAdmin(h.handleAdminPodcastRefresh)) mux.HandleFunc("POST /admin/podcasts/{id}/delete", h.requireAdmin(h.handleAdminPodcastDelete)) mux.HandleFunc("POST /admin/podcasts/episodes/{id}/download", h.requireAdmin(h.handleAdminPodcastEpisodeDownload)) mux.HandleFunc("POST /admin/podcasts/episodes/{id}/delete", h.requireAdmin(h.handleAdminPodcastEpisodeDelete)) mux.HandleFunc("GET /admin/artists/{id}/edit", h.requireAdmin(h.handleAdminArtistEdit)) mux.HandleFunc("POST /admin/artists/{id}/edit", h.requireAdmin(h.handleAdminArtistUpdate)) mux.HandleFunc("GET /admin/albums/{id}/edit", h.requireAdmin(h.handleAdminAlbumEdit)) mux.HandleFunc("POST /admin/albums/{id}/edit", h.requireAdmin(h.handleAdminAlbumUpdate)) mux.HandleFunc("GET /admin/songs/{id}/edit", h.requireAdmin(h.handleAdminSongEdit)) mux.HandleFunc("POST /admin/songs/{id}/edit", h.requireAdmin(h.handleAdminSongUpdate)) mux.HandleFunc("GET /admin/api-keys", h.requireAdmin(h.handleAdminAPIKeys)) mux.HandleFunc("POST /admin/api-keys/{id}/revoke", h.requireAdmin(h.handleAdminAPIKeyRevoke)) mux.HandleFunc("GET /admin/jukebox", h.requireAdmin(h.handleAdminJukebox)) mux.HandleFunc("POST /admin/jukebox/start", h.requireAdmin(h.handleAdminJukeboxStart)) mux.HandleFunc("POST /admin/jukebox/stop", h.requireAdmin(h.handleAdminJukeboxStop)) mux.HandleFunc("POST /admin/jukebox/clear", h.requireAdmin(h.handleAdminJukeboxClear)) var handler http.Handler = mux handler = h.CSRF.Protect(handler) handler = h.setupGate(handler) handler = h.Middleware.Authenticate(handler) return handler } func (h *Web) requireUser(fn http.HandlerFunc) http.HandlerFunc { wrapped := h.Middleware.RequireUser(fn) return wrapped.ServeHTTP } func (h *Web) requireAdmin(fn http.HandlerFunc) http.HandlerFunc { wrapped := h.Middleware.RequireAdmin(fn) return wrapped.ServeHTTP } // setupGate sends every request to /setup until an administrator // account exists, so a fresh install can never be used half-configured. func (h *Web) setupGate(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/setup" || r.URL.Path == "/healthz" || r.URL.Path == "/readyz" || strings.HasPrefix(r.URL.Path, "/static/") || strings.HasPrefix(r.URL.Path, "/share/") { next.ServeHTTP(w, r) return } n, err := database.CountUsers(h.DB) if err == nil && n == 0 { http.Redirect(w, r, "/setup", http.StatusFound) return } next.ServeHTTP(w, r) }) }