package httpserver import ( "encoding/json" "net/http" "strings" "time" "github.com/tim/cairnquire/apps/server/internal/docs" ) type adminUserInput struct { Email string `json:"email"` DisplayName string `json:"displayName"` Role string `json:"role"` } type adminSettingsInput struct { SignupsEnabled bool `json:"signupsEnabled"` } func (s *Server) handleAdminLogin(w http.ResponseWriter, r *http.Request) { var input adminUserInput if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": "invalid json"}) return } user, ok := s.normalizeAdminUserInput(input) if !ok { writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": "email is required"}) return } if user.Role == "viewer" { user.Role = "admin" } created, err := s.repository.UpsertUser(r.Context(), user) if err != nil { writeJSONWithStatus(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusOK, map[string]any{ "user": created, "session": map[string]any{ "mode": "foundation", "createdAt": time.Now().UTC(), }, }) } func (s *Server) handleAdminUsers(w http.ResponseWriter, r *http.Request) { users, err := s.repository.ListUsers(r.Context()) if err != nil { writeJSONWithStatus(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusOK, map[string]any{"users": users}) } func (s *Server) handleAdminCreateUser(w http.ResponseWriter, r *http.Request) { var input adminUserInput if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": "invalid json"}) return } user, ok := s.normalizeAdminUserInput(input) if !ok { writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": "email is required"}) return } created, err := s.repository.UpsertUser(r.Context(), user) if err != nil { writeJSONWithStatus(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } writeJSONWithStatus(w, http.StatusCreated, map[string]any{"user": created}) } func (s *Server) handleAdminWorkspace(w http.ResponseWriter, r *http.Request) { documents, err := s.documents.ListDocuments(r.Context()) if err != nil { writeJSONWithStatus(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } users, err := s.repository.ListUsers(r.Context()) if err != nil { writeJSONWithStatus(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusOK, map[string]any{ "workspace": map[string]any{ "sourceDir": s.config.Content.SourceDir, "storeDir": s.config.Content.StoreDir, "databasePath": s.config.Database.Path, "documentCount": len(documents), "userCount": len(users), }, "documents": documents, }) } func (s *Server) handleAdminWorkspaceSync(w http.ResponseWriter, r *http.Request) { changes, err := s.documents.SyncSourceDir(r.Context()) if err != nil { writeJSONWithStatus(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusOK, map[string]any{ "changes": changes, "syncedAt": time.Now().UTC(), }) } func (s *Server) handleAdminSettings(w http.ResponseWriter, r *http.Request) { settings, err := s.auth.GetInstanceSettings(r.Context()) if err != nil { writeJSONWithStatus(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusOK, map[string]any{"settings": settings}) } func (s *Server) handleAdminSettingsUpdate(w http.ResponseWriter, r *http.Request) { principal, _ := requirePrincipal(r) var input adminSettingsInput if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": "invalid json"}) return } settings, err := s.auth.UpdateSignupsEnabled(r.Context(), principal, input.SignupsEnabled) if err != nil { writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusOK, map[string]any{"settings": settings}) } func (s *Server) normalizeAdminUserInput(input adminUserInput) (docs.UserRecord, bool) { email := strings.TrimSpace(strings.ToLower(input.Email)) if email == "" { return docs.UserRecord{}, false } role := strings.TrimSpace(strings.ToLower(input.Role)) switch role { case "admin", "editor", "viewer": default: role = "viewer" } displayName := strings.TrimSpace(input.DisplayName) if displayName == "" { displayName = email } return docs.UserRecord{ Email: email, DisplayName: displayName, Role: role, }, true }