Refactor routing and documentation for SPA deployment

- Updated the application to serve the SPA from the root path instead of /map/, enhancing accessibility.
- Modified redirect logic to ensure backward compatibility with old /map/* URLs.
- Adjusted documentation across multiple files to reflect the new routing structure and API endpoints.
- Improved handling of OAuth redirects and session management in the backend.
- Updated frontend configuration to align with the new base URL settings.
This commit is contained in:
2026-02-25 00:32:59 +03:00
parent 2c7bf48719
commit fea17e6bac
11 changed files with 73 additions and 69 deletions

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"net/http"
"path/filepath"
"strings"
"sync"
"time"
@@ -139,41 +140,69 @@ type Page struct {
Title string `json:"title"`
}
// serveMapFrontend serves the map SPA: static files from frontend, fallback to index.html for client-side routes.
func (a *App) serveMapFrontend(rw http.ResponseWriter, req *http.Request) {
// serveSPARoot serves the map SPA from root: static files from frontend, fallback to index.html for client-side routes.
// Handles redirects from old /map/* URLs for backward compatibility.
func (a *App) serveSPARoot(rw http.ResponseWriter, req *http.Request) {
path := req.URL.Path
if len(path) <= len("/map/") {
path = ""
} else {
path = path[len("/map/"):]
// Redirect old /map/* URLs to flat routes
if path == "/map" || path == "/map/" {
http.Redirect(rw, req, "/", http.StatusFound)
return
}
root := a.frontendRoot
if path == "" {
path = "index.html"
if strings.HasPrefix(path, "/map/") {
rest := path[len("/map/"):]
switch {
case rest == "login":
http.Redirect(rw, req, "/login", http.StatusFound)
return
case rest == "profile":
http.Redirect(rw, req, "/profile", http.StatusFound)
return
case rest == "admin" || strings.HasPrefix(rest, "admin/"):
http.Redirect(rw, req, "/"+rest, http.StatusFound)
return
case rest == "setup":
http.Redirect(rw, req, "/setup", http.StatusFound)
return
case strings.HasPrefix(rest, "character/"):
http.Redirect(rw, req, "/"+rest, http.StatusFound)
return
case strings.HasPrefix(rest, "grid/"):
http.Redirect(rw, req, "/"+rest, http.StatusFound)
return
}
}
path = filepath.Clean(path)
if path == "." || path == ".." || (len(path) >= 2 && path[:2] == "..") {
// File serving: path relative to frontend root (with baseURL /, files are at root)
filePath := strings.TrimPrefix(path, "/")
if filePath == "" {
filePath = "index.html"
}
filePath = filepath.Clean(filePath)
if filePath == "." || filePath == ".." || strings.HasPrefix(filePath, "..") {
http.NotFound(rw, req)
return
}
tryPaths := []string{filepath.Join("map", path), path}
// Try both root and map/ for backward compatibility with old builds
tryPaths := []string{filePath, filepath.Join("map", filePath)}
var f http.File
for _, p := range tryPaths {
var err error
f, err = http.Dir(root).Open(p)
f, err = http.Dir(a.frontendRoot).Open(p)
if err == nil {
path = p
filePath = p
break
}
}
if f == nil {
http.ServeFile(rw, req, filepath.Join(root, "index.html"))
http.ServeFile(rw, req, filepath.Join(a.frontendRoot, "index.html"))
return
}
defer f.Close()
stat, err := f.Stat()
if err != nil || stat.IsDir() {
http.ServeFile(rw, req, filepath.Join(root, "index.html"))
http.ServeFile(rw, req, filepath.Join(a.frontendRoot, "index.html"))
return
}
http.ServeContent(rw, req, stat.Name(), stat.ModTime(), f)
@@ -195,15 +224,12 @@ func (a *App) CleanChars() {
// RegisterRoutes registers all HTTP handlers for the app.
func (a *App) RegisterRoutes() {
http.HandleFunc("/client/", a.client)
http.HandleFunc("/login", a.redirectLogin)
http.HandleFunc("/logout", a.redirectLogout)
http.HandleFunc("/admin", a.redirectAdmin)
http.HandleFunc("/admin/", a.redirectAdmin)
http.HandleFunc("/", a.redirectRoot)
http.HandleFunc("/map/api/", a.apiRouter)
http.HandleFunc("/map/updates", a.watchGridUpdates)
http.HandleFunc("/map/grids/", a.gridTile)
http.HandleFunc("/map/", a.serveMapFrontend)
// SPA catch-all: must be last
http.HandleFunc("/", a.serveSPARoot)
}

View File

@@ -69,7 +69,7 @@ func (a *App) client(rw http.ResponseWriter, req *http.Request) {
case "markerUpdate":
a.uploadMarkers(rw, req)
case "":
http.Redirect(rw, req, "/map/", 302)
http.Redirect(rw, req, "/", 302)
case "checkVersion":
if req.FormValue("version") == VERSION {
rw.WriteHeader(200)

View File

@@ -4,26 +4,6 @@ import (
"net/http"
)
func (a *App) redirectRoot(rw http.ResponseWriter, req *http.Request) {
if req.URL.Path != "/" {
http.NotFound(rw, req)
return
}
if a.setupRequired() {
http.Redirect(rw, req, "/map/setup", http.StatusFound)
return
}
http.Redirect(rw, req, "/map/profile", http.StatusFound)
}
func (a *App) redirectLogin(rw http.ResponseWriter, req *http.Request) {
if req.URL.Path != "/login" {
http.NotFound(rw, req)
return
}
http.Redirect(rw, req, "/map/login", http.StatusFound)
}
func (a *App) redirectLogout(rw http.ResponseWriter, req *http.Request) {
if req.URL.Path != "/logout" {
http.NotFound(rw, req)
@@ -33,9 +13,5 @@ func (a *App) redirectLogout(rw http.ResponseWriter, req *http.Request) {
if s != nil {
a.deleteSession(s)
}
http.Redirect(rw, req, "/map/login", http.StatusFound)
}
func (a *App) redirectAdmin(rw http.ResponseWriter, req *http.Request) {
http.Redirect(rw, req, "/map/admin", http.StatusFound)
http.Redirect(rw, req, "/login", http.StatusFound)
}

View File

@@ -186,7 +186,7 @@ func (a *App) oauthCallback(rw http.ResponseWriter, req *http.Request, provider
Secure: req.TLS != nil,
SameSite: http.SameSiteLaxMode,
})
redirectTo := "/map/profile"
redirectTo := "/profile"
if st.RedirectURI != "" {
if u, err := url.Parse(st.RedirectURI); err == nil && u.Path != "" && !strings.HasPrefix(u.Path, "//") {
redirectTo = u.Path