Fixes: - Go server now serves index.html for all non-asset paths (SPA fallback) - Null guard on data.documents prevents undefined.length TypeError - App.vue waits for checkStatus() before rendering AppLayout (race condition) - PWA manifest no longer references missing icon files - index.html applies theme class before paint to prevent flash Features: - Theme store: dark/light toggle + 6 accent color presets + custom hex picker - ThemeToggle component in header dropdown - CSS custom properties for accent color (--accent-400/500/600/700) - Tailwind accent-* color utilities driven by CSS vars - SetupWizard: 3-step flow, password confirmation, LDAP test button, animated toggle, review step, proper error/success states - Sidebar: loading skeleton, active page highlight, null-safe document list - Global .input, .btn-primary, .btn-secondary, .card component classes - Stub GraphQL responses for setup/login/testLdapConnection Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
87 lines
2.4 KiB
Go
87 lines
2.4 KiB
Go
package graph
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/brasse-b/archivum/internal/config"
|
|
)
|
|
|
|
func NewServer(cfg *config.Config) http.Handler {
|
|
mux := http.NewServeMux()
|
|
|
|
mux.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
|
if r.Method == http.MethodOptions {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
if cfg == nil {
|
|
_, _ = w.Write([]byte(`{"data":{"systemStatus":"REQUIRE_SETUP"}}`))
|
|
return
|
|
}
|
|
// Stub: inspect operation name to return sensible responses.
|
|
body, _ := io.ReadAll(r.Body)
|
|
bs := string(body)
|
|
switch {
|
|
case strings.Contains(bs, "testLdapConnection"):
|
|
_, _ = w.Write([]byte(`{"data":{"testLdapConnection":{"success":false,"message":"LDAP resolver not yet implemented"}}}`))
|
|
case strings.Contains(bs, "setup"):
|
|
_, _ = w.Write([]byte(`{"data":{"setup":true}}`))
|
|
case strings.Contains(bs, "login"):
|
|
_, _ = w.Write([]byte(`{"data":{"login":"stub-token"}}`))
|
|
default:
|
|
_, _ = w.Write([]byte(`{"data":{"systemStatus":"OK","documents":[]}}`))
|
|
}
|
|
})
|
|
|
|
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
|
|
uiDir := os.Getenv("UI_DIR")
|
|
if uiDir == "" {
|
|
uiDir = "/srv/archivum/ui"
|
|
}
|
|
mux.Handle("/", spaHandler(uiDir))
|
|
|
|
return mux
|
|
}
|
|
|
|
// spaHandler serves static files and falls back to index.html for all paths
|
|
// that don't resolve to a real file — required for client-side routing.
|
|
func spaHandler(dir string) http.Handler {
|
|
fs := http.Dir(dir)
|
|
fileServer := http.FileServer(fs)
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Assets (JS, CSS, images) must be served as-is.
|
|
if isAssetPath(r.URL.Path) {
|
|
fileServer.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
// Try to open the path; if it exists serve it, otherwise serve SPA root.
|
|
f, err := fs.Open(r.URL.Path)
|
|
if err == nil {
|
|
f.Close()
|
|
fileServer.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
http.ServeFile(w, r, dir+"/index.html")
|
|
})
|
|
}
|
|
|
|
func isAssetPath(path string) bool {
|
|
return strings.HasPrefix(path, "/assets/") ||
|
|
strings.HasPrefix(path, "/icons/") ||
|
|
path == "/favicon.ico" ||
|
|
path == "/favicon.svg" ||
|
|
path == "/manifest.webmanifest" ||
|
|
path == "/registerSW.js" ||
|
|
path == "/sw.js"
|
|
}
|