From 6af0ffff9d1e2d9dcd2c4826558e8dca6e0be146 Mon Sep 17 00:00:00 2001 From: GraceSolutions Date: Thu, 23 Apr 2026 12:27:59 -0400 Subject: [PATCH] feat(connections): wire full CRUD endpoints for AD connections - Add Create/Update/Delete handlers on ConnectionsHandler backed by the existing ConnectionRepository (soft-delete preserves audit history). - Register List/Get/Create/Update/Delete/Test routes on /ad-connections so the resource is no longer stubbed out. --- backend/internal/api/connections_handlers.go | 83 +++++++++++++++++++- backend/internal/server/server.go | 14 ++-- 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/backend/internal/api/connections_handlers.go b/backend/internal/api/connections_handlers.go index 3da22f2..a8879b7 100644 --- a/backend/internal/api/connections_handlers.go +++ b/backend/internal/api/connections_handlers.go @@ -5,11 +5,11 @@ import ( "net/http" "time" - "github.com/go-chi/chi/v5" "github.com/Grace-Solutions/OrchestrAD/internal/logging" "github.com/Grace-Solutions/OrchestrAD/internal/models" "github.com/Grace-Solutions/OrchestrAD/internal/repository" "github.com/Grace-Solutions/OrchestrAD/internal/services" + "github.com/go-chi/chi/v5" ) // ConnectionsHandler handles AD connection API endpoints @@ -107,6 +107,64 @@ func (h *ConnectionsHandler) Get(w http.ResponseWriter, r *http.Request) { WriteJSON(w, http.StatusOK, connectionToResponse(conn)) } +// Create handles POST /api/v1/ad-connections +func (h *ConnectionsHandler) Create(w http.ResponseWriter, r *http.Request) { + var req ConnectionRequest + if err := DecodeJSON(r, &req); err != nil { + WriteError(w, http.StatusBadRequest, ErrCodeBadRequest, "Invalid request body") + return + } + if req.Name == "" || req.Hosts == "" || req.RootDN == "" { + WriteError(w, http.StatusBadRequest, ErrCodeValidation, "name, hosts and rootDn are required") + return + } + conn := requestToConnection(&req, nil) + if err := h.repo.Create(conn); err != nil { + h.logger.Error("ConnectionsHandler", "Create failed: %v", err) + WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to create connection") + return + } + WriteJSON(w, http.StatusCreated, connectionToResponse(conn)) +} + +// Update handles PUT /api/v1/ad-connections/{id} +func (h *ConnectionsHandler) Update(w http.ResponseWriter, r *http.Request) { + id := chi.URLParam(r, "id") + existing, err := h.repo.GetByID(id) + if err != nil { + h.logger.Error("ConnectionsHandler", "GetByID failed: %v", err) + WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to load connection") + return + } + if existing == nil { + WriteError(w, http.StatusNotFound, ErrCodeNotFound, "Connection not found") + return + } + var req ConnectionRequest + if err := DecodeJSON(r, &req); err != nil { + WriteError(w, http.StatusBadRequest, ErrCodeBadRequest, "Invalid request body") + return + } + conn := requestToConnection(&req, existing) + if err := h.repo.Update(conn); err != nil { + h.logger.Error("ConnectionsHandler", "Update failed: %v", err) + WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to update connection") + return + } + WriteJSON(w, http.StatusOK, connectionToResponse(conn)) +} + +// Delete handles DELETE /api/v1/ad-connections/{id} +func (h *ConnectionsHandler) Delete(w http.ResponseWriter, r *http.Request) { + id := chi.URLParam(r, "id") + if err := h.repo.SoftDelete(id); err != nil { + h.logger.Error("ConnectionsHandler", "Delete failed: %v", err) + WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to delete connection") + return + } + WriteJSON(w, http.StatusOK, map[string]bool{"deleted": true}) +} + // Test handles POST /api/v1/ad-connections/{id}/test func (h *ConnectionsHandler) Test(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") @@ -133,6 +191,29 @@ func (h *ConnectionsHandler) Test(w http.ResponseWriter, r *http.Request) { WriteJSON(w, http.StatusOK, result) } +func requestToConnection(req *ConnectionRequest, existing *models.ADConnection) *models.ADConnection { + conn := &models.ADConnection{} + if existing != nil { + *conn = *existing + } + conn.Name = req.Name + conn.Description = req.Description + conn.IsEnabled = req.IsEnabled + conn.Hosts = req.Hosts + conn.Port = req.Port + conn.UseTLS = req.UseTLS + conn.UseStartTLS = req.UseStartTLS + conn.AllowInvalidCerts = req.AllowInvalidCerts + conn.RootDN = req.RootDN + conn.BindDN = req.BindDN + conn.CredentialID = req.CredentialID + conn.DefaultSearchScope = req.DefaultSearchScope + conn.TimeoutSeconds = req.TimeoutSeconds + conn.PagingEnabled = req.PagingEnabled + conn.PageSize = req.PageSize + return conn +} + func connectionToResponse(c *models.ADConnection) ConnectionResponse { resp := ConnectionResponse{ ID: c.ID, diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go index ffd5f34..73aa244 100644 --- a/backend/internal/server/server.go +++ b/backend/internal/server/server.go @@ -126,14 +126,14 @@ func (s *Server) setupRoutes() { }) // AD Connections + connectionsHandler := api.NewConnectionsHandler(s.deps.ConnRepo, s.deps.ConnService, s.logger) r.Route("/ad-connections", func(r chi.Router) { - r.Get("/", s.handleNotImplemented) - r.Post("/", s.handleNotImplemented) - r.Get("/{id}", s.handleNotImplemented) - r.Put("/{id}", s.handleNotImplemented) - r.Delete("/{id}", s.handleNotImplemented) - r.Post("/test", s.handleNotImplemented) - r.Post("/{id}/test", s.handleNotImplemented) + r.Get("/", connectionsHandler.List) + r.Post("/", connectionsHandler.Create) + r.Get("/{id}", connectionsHandler.Get) + r.Put("/{id}", connectionsHandler.Update) + r.Delete("/{id}", connectionsHandler.Delete) + r.Post("/{id}/test", connectionsHandler.Test) r.Post("/{id}/query-preview", s.handleNotImplemented) })