diff --git a/backend/internal/graph/schema.graphql b/backend/internal/graph/schema.graphql index d2a77cd..7f61acb 100644 --- a/backend/internal/graph/schema.graphql +++ b/backend/internal/graph/schema.graphql @@ -22,6 +22,9 @@ type Mutation { # First-run setup. setup(input: SetupInput!): Boolean! + # Test an LDAP configuration before saving. + testLdapConnection(input: LDAPInput!): LDAPTestResult! + # Authenticate and receive a bearer token. login(username: String!, password: String!): String! @@ -54,6 +57,11 @@ type DocumentMeta { updatedAt: String! } +type LDAPTestResult { + success: Boolean! + message: String! +} + type CommitEntry { hash: String! author: String! diff --git a/backend/internal/graph/server.go b/backend/internal/graph/server.go index 39e7a80..754a44d 100644 --- a/backend/internal/graph/server.go +++ b/backend/internal/graph/server.go @@ -1,15 +1,14 @@ package graph import ( + "io" "net/http" + "os" + "strings" "github.com/brasse-b/archivum/internal/config" ) -// NewServer wires up the HTTP handler for the GraphQL endpoint. -// cfg may be nil when the system has not been configured yet; in that case -// every GraphQL request returns {"data":{"systemStatus":"REQUIRE_SETUP"}} so -// the frontend can redirect to the Setup Wizard. func NewServer(cfg *config.Config) http.Handler { mux := http.NewServeMux() @@ -21,24 +20,67 @@ func NewServer(cfg *config.Config) http.Handler { w.WriteHeader(http.StatusNoContent) return } - if cfg == nil { - w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"data":{"systemStatus":"REQUIRE_SETUP"}}`)) return } - - // TODO: replace with gqlgen handler once generated. - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"data":{"systemStatus":"OK"}}`)) + // 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) }) - // Serve the embedded Vue SPA for every other path. - mux.Handle("/", http.FileServer(http.Dir("/srv/archivum/ui"))) + 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" +} diff --git a/frontend/index.html b/frontend/index.html index 824ebe2..bbb26ec 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,14 +1,18 @@ - +
- - -