feat: expanded health checks, deploy filtering, per-project notifications, error sanitization, and audit trail
- Expand health endpoint to check DB, Docker, and NPM connectivity (FUNC-M4) - Add project_id, stage_id, offset query params to deploys endpoint (FUNC-M5, FUNC-M6) - Add notification_url field to Stage model for per-project overrides (FUNC-M2) - Add NPM Ping method for health checking - Sanitize all internal error messages in API handlers (SEC-M4) - Add audit trail events for admin actions (FUNC-M3) - Add EventLog event type to event bus
This commit is contained in:
@@ -2,10 +2,12 @@ package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/alexei/docker-watcher/internal/events"
|
||||
"github.com/alexei/docker-watcher/internal/store"
|
||||
)
|
||||
|
||||
@@ -24,7 +26,8 @@ type projectRequest struct {
|
||||
func (s *Server) listProjects(w http.ResponseWriter, r *http.Request) {
|
||||
projects, err := s.store.GetAllProjects()
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "failed to list projects: "+err.Error())
|
||||
slog.Error("failed to list projects", "error", err)
|
||||
respondError(w, http.StatusInternalServerError, "internal server error")
|
||||
return
|
||||
}
|
||||
respondJSON(w, http.StatusOK, projects)
|
||||
@@ -62,9 +65,20 @@ func (s *Server) createProject(w http.ResponseWriter, r *http.Request) {
|
||||
Volumes: req.Volumes,
|
||||
})
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "failed to create project: "+err.Error())
|
||||
slog.Error("failed to create project", "error", err)
|
||||
respondError(w, http.StatusInternalServerError, "internal server error")
|
||||
return
|
||||
}
|
||||
|
||||
s.eventBus.Publish(events.Event{
|
||||
Type: events.EventLog,
|
||||
Payload: events.EventLogPayload{
|
||||
Source: "admin",
|
||||
Severity: "info",
|
||||
Message: "project created: " + project.Name,
|
||||
},
|
||||
})
|
||||
|
||||
respondJSON(w, http.StatusCreated, project)
|
||||
}
|
||||
|
||||
@@ -77,14 +91,16 @@ func (s *Server) getProject(w http.ResponseWriter, r *http.Request) {
|
||||
respondNotFound(w, "project")
|
||||
return
|
||||
}
|
||||
respondError(w, http.StatusInternalServerError, "failed to get project: "+err.Error())
|
||||
slog.Error("failed to get project", "error", err)
|
||||
respondError(w, http.StatusInternalServerError, "internal server error")
|
||||
return
|
||||
}
|
||||
|
||||
// Also fetch stages for this project.
|
||||
stages, err := s.store.GetStagesByProjectID(id)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "failed to get stages: "+err.Error())
|
||||
slog.Error("failed to get stages", "error", err)
|
||||
respondError(w, http.StatusInternalServerError, "internal server error")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -104,7 +120,8 @@ func (s *Server) updateProject(w http.ResponseWriter, r *http.Request) {
|
||||
respondNotFound(w, "project")
|
||||
return
|
||||
}
|
||||
respondError(w, http.StatusInternalServerError, "failed to get project: "+err.Error())
|
||||
slog.Error("failed to get project", "error", err)
|
||||
respondError(w, http.StatusInternalServerError, "internal server error")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -132,9 +149,20 @@ func (s *Server) updateProject(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err := s.store.UpdateProject(updated); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "failed to update project: "+err.Error())
|
||||
slog.Error("failed to update project", "error", err)
|
||||
respondError(w, http.StatusInternalServerError, "internal server error")
|
||||
return
|
||||
}
|
||||
|
||||
s.eventBus.Publish(events.Event{
|
||||
Type: events.EventLog,
|
||||
Payload: events.EventLogPayload{
|
||||
Source: "admin",
|
||||
Severity: "info",
|
||||
Message: "project updated: " + updated.Name,
|
||||
},
|
||||
})
|
||||
|
||||
respondJSON(w, http.StatusOK, updated)
|
||||
}
|
||||
|
||||
@@ -146,8 +174,18 @@ func (s *Server) deleteProject(w http.ResponseWriter, r *http.Request) {
|
||||
respondNotFound(w, "project")
|
||||
return
|
||||
}
|
||||
respondError(w, http.StatusInternalServerError, "failed to delete project: "+err.Error())
|
||||
slog.Error("failed to delete project", "error", err)
|
||||
respondError(w, http.StatusInternalServerError, "internal server error")
|
||||
return
|
||||
}
|
||||
s.eventBus.Publish(events.Event{
|
||||
Type: events.EventLog,
|
||||
Payload: events.EventLogPayload{
|
||||
Source: "admin",
|
||||
Severity: "info",
|
||||
Message: "project deleted: " + id,
|
||||
},
|
||||
})
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]string{"deleted": id})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user