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.
This commit is contained in:
GraceSolutions
2026-04-23 12:27:59 -04:00
parent c117d6d546
commit 6af0ffff9d
2 changed files with 89 additions and 8 deletions
+82 -1
View File
@@ -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,
+7 -7
View File
@@ -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)
})