Cybersecurity-Projects/PROJECTS/advanced/monitor-the-situation-dashb.../backend/internal/alerts/handler.go

197 lines
4.9 KiB
Go

// ©AngelaMos | 2026
// handler.go
package alerts
import (
"encoding/json"
"errors"
"net/http"
"strconv"
"github.com/go-chi/chi/v5"
"github.com/go-playground/validator/v10"
"github.com/carterperez-dev/monitor-the-situation/backend/internal/core"
"github.com/carterperez-dev/monitor-the-situation/backend/internal/middleware"
)
type Handler struct {
repo Repository
validator *validator.Validate
}
func NewHandler(repo Repository) *Handler {
return &Handler{
repo: repo,
validator: validator.New(validator.WithRequiredStructEnabled()),
}
}
func (h *Handler) RegisterRoutes(
r chi.Router,
authenticator func(http.Handler) http.Handler,
) {
r.Route("/me/alerts", func(r chi.Router) {
r.Use(authenticator)
r.Get("/", h.list)
r.Post("/", h.create)
r.Patch("/{id}", h.update)
r.Delete("/{id}", h.delete)
r.Get("/history", h.history)
})
}
type ruleDTO struct {
ID string `json:"id"`
Name string `json:"name"`
Topic string `json:"topic"`
Predicate string `json:"predicate"`
CooldownSec int `json:"cooldown_sec"`
Enabled bool `json:"enabled"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
func toRuleDTO(r Rule) ruleDTO {
return ruleDTO{
ID: r.ID,
Name: r.Name,
Topic: r.Topic,
Predicate: r.Predicate,
CooldownSec: r.CooldownSec,
Enabled: r.Enabled,
CreatedAt: r.CreatedAt.UTC().Format("2006-01-02T15:04:05Z"),
UpdatedAt: r.UpdatedAt.UTC().Format("2006-01-02T15:04:05Z"),
}
}
func (h *Handler) list(w http.ResponseWriter, r *http.Request) {
userID := middleware.GetUserID(r.Context())
rules, err := h.repo.ListByUser(r.Context(), userID)
if err != nil {
core.InternalServerError(w, err)
return
}
out := make([]ruleDTO, 0, len(rules))
for _, ru := range rules {
out = append(out, toRuleDTO(ru))
}
core.OK(w, out)
}
type createRuleRequest struct {
Name string `json:"name" validate:"required,min=1,max=200"`
Topic string `json:"topic" validate:"required,min=1,max=64"`
Predicate string `json:"predicate" validate:"omitempty,max=2048"`
CooldownSec int `json:"cooldown_sec" validate:"gte=0,lte=86400"`
Enabled bool `json:"enabled"`
}
func (h *Handler) create(w http.ResponseWriter, r *http.Request) {
userID := middleware.GetUserID(r.Context())
var req createRuleRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
core.BadRequest(w, "invalid request body")
return
}
if err := h.validator.Struct(req); err != nil {
core.BadRequest(w, core.FormatValidationError(err))
return
}
rule := &Rule{
UserID: userID,
Name: req.Name,
Topic: req.Topic,
Predicate: req.Predicate,
CooldownSec: req.CooldownSec,
Enabled: req.Enabled,
}
if err := h.repo.Create(r.Context(), rule); err != nil {
core.InternalServerError(w, err)
return
}
core.Created(w, toRuleDTO(*rule))
}
type updateRuleRequest struct {
Name *string `json:"name,omitempty" validate:"omitempty,min=1,max=200"`
Predicate *string `json:"predicate,omitempty" validate:"omitempty,max=2048"`
CooldownSec *int `json:"cooldown_sec,omitempty" validate:"omitempty,gte=0,lte=86400"`
Enabled *bool `json:"enabled,omitempty"`
}
func (h *Handler) update(w http.ResponseWriter, r *http.Request) {
userID := middleware.GetUserID(r.Context())
id := chi.URLParam(r, "id")
var req updateRuleRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
core.BadRequest(w, "invalid request body")
return
}
if err := h.validator.Struct(req); err != nil {
core.BadRequest(w, core.FormatValidationError(err))
return
}
rule, err := h.repo.Get(r.Context(), id, userID)
if err != nil {
if errors.Is(err, core.ErrNotFound) {
core.NotFound(w, "rule")
return
}
core.InternalServerError(w, err)
return
}
if req.Name != nil {
rule.Name = *req.Name
}
if req.Predicate != nil {
rule.Predicate = *req.Predicate
}
if req.CooldownSec != nil {
rule.CooldownSec = *req.CooldownSec
}
if req.Enabled != nil {
rule.Enabled = *req.Enabled
}
if err := h.repo.Update(r.Context(), rule); err != nil {
core.InternalServerError(w, err)
return
}
core.OK(w, toRuleDTO(*rule))
}
func (h *Handler) delete(w http.ResponseWriter, r *http.Request) {
userID := middleware.GetUserID(r.Context())
id := chi.URLParam(r, "id")
if err := h.repo.Delete(r.Context(), id, userID); err != nil {
if errors.Is(err, core.ErrNotFound) {
core.NotFound(w, "rule")
return
}
core.InternalServerError(w, err)
return
}
core.NoContent(w)
}
func (h *Handler) history(w http.ResponseWriter, r *http.Request) {
userID := middleware.GetUserID(r.Context())
limit := 50
if v := r.URL.Query().Get("limit"); v != "" {
if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= 500 {
limit = n
}
}
rows, err := h.repo.RecentHistory(r.Context(), userID, limit)
if err != nil {
core.InternalServerError(w, err)
return
}
core.OK(w, rows)
}