package httpapi import ( "errors" "log/slog" "net/http" "data-terminal-cloud/internal/alerts" "data-terminal-cloud/internal/concurrency" "data-terminal-cloud/internal/idempotency" ) func ListOpsAlertRoutingPoliciesHandler(service *alerts.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := webPrincipal(w, r) if !ok { return } tenant, ok := opsTenantHeader(w, r) if !ok { return } items, e := service.ListRoutingPolicies(r.Context(), p, tenant) if e != nil { writeAlertError(w, r, e) return } w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, http.StatusOK, map[string]any{"items": items}) }) } func CreateOpsAlertRoutingPolicyHandler(service *alerts.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := webPrincipal(w, r) if !ok { return } tenant, key, ok := opsCreateHeaders(w, r) if !ok { return } var body alerts.RoutingInput if !decodeWebJSON(w, r, &body, "告警路由参数不正确") { return } item, created, e := service.CreateRoutingPolicy(r.Context(), p, tenant, key, RequestID(r.Context()), body) if e != nil { writeAlertError(w, r, e) return } status := http.StatusCreated if !created { status = http.StatusOK w.Header().Set("Idempotency-Replayed", "true") } w.Header().Set("ETag", concurrency.FormatETag(item.RowVersion)) WriteJSON(w, r, status, item) }) } func ListOpsAlertDeliveryTestsHandler(service *alerts.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := webPrincipal(w, r) if !ok { return } tenant, ok := opsTenantHeader(w, r) if !ok { return } items, e := service.ListDeliveryTests(r.Context(), p, tenant) if e != nil { writeAlertError(w, r, e) return } w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, http.StatusOK, map[string]any{"items": items}) }) } func CreateOpsAlertDeliveryTestHandler(service *alerts.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } tenant, key, ok := opsCreateHeaders(w, r) if !ok { return } var body alerts.DeliveryTestInput if !decodeWebJSON(w, r, &body, "告警接收器联通测试参数不正确") { return } item, created, e := service.CreateDeliveryTest(r.Context(), p, tenant, r.PathValue("id"), key, RequestID(r.Context()), body) if e != nil { writeAlertError(w, r, e) return } status := http.StatusCreated if !created { status = http.StatusOK w.Header().Set("Idempotency-Replayed", "true") } WriteJSON(w, r, status, item) }) } func ListOpsAlertsHandler(service *alerts.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := webPrincipal(w, r) if !ok { return } tenant, ok := opsTenantHeader(w, r) if !ok { return } items, e := service.ListIncidents(r.Context(), p, tenant, r.URL.Query().Get("status")) if e != nil { writeAlertError(w, r, e) return } w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, http.StatusOK, map[string]any{"items": items}) }) } func GetOpsAlertHandler(service *alerts.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } tenant, ok := opsTenantHeader(w, r) if !ok { return } item, e := service.GetIncident(r.Context(), p, tenant, r.PathValue("id")) if e != nil { writeAlertError(w, r, e) return } w.Header().Set("Cache-Control", "no-store") w.Header().Set("ETag", concurrency.FormatETag(item.RowVersion)) WriteJSON(w, r, http.StatusOK, item) }) } func TransitionOpsAlertHandler(service *alerts.Service, action string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } tenant, ok := opsTenantHeader(w, r) if !ok { return } version, e := concurrency.ParseIfMatch(r) if e != nil { writeETagError(w, r, e) return } var body struct { Note string `json:"note"` } if !decodeWebJSON(w, r, &body, "告警处置参数不正确") { return } item, e := service.TransitionIncident(r.Context(), p, tenant, r.PathValue("id"), RequestID(r.Context()), action, body.Note, version) if e != nil { writeAlertError(w, r, e) return } w.Header().Set("ETag", concurrency.FormatETag(item.RowVersion)) WriteJSON(w, r, http.StatusOK, item) }) } func ListOpsAlertSilencesHandler(service *alerts.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := webPrincipal(w, r) if !ok { return } tenant, ok := opsTenantHeader(w, r) if !ok { return } items, e := service.ListSilences(r.Context(), p, tenant) if e != nil { writeAlertError(w, r, e) return } WriteJSON(w, r, http.StatusOK, map[string]any{"items": items}) }) } func CreateOpsAlertSilenceHandler(service *alerts.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := webPrincipal(w, r) if !ok { return } tenant, key, ok := opsCreateHeaders(w, r) if !ok { return } var body alerts.SilenceInput if !decodeWebJSON(w, r, &body, "告警静默参数不正确") { return } item, created, e := service.CreateSilence(r.Context(), p, tenant, key, RequestID(r.Context()), body) if e != nil { writeAlertError(w, r, e) return } status := http.StatusCreated if !created { status = http.StatusOK w.Header().Set("Idempotency-Replayed", "true") } w.Header().Set("ETag", concurrency.FormatETag(item.RowVersion)) WriteJSON(w, r, status, item) }) } func RevokeOpsAlertSilenceHandler(service *alerts.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, ok := resourcePrincipal(w, r, "id") if !ok { return } tenant, ok := opsTenantHeader(w, r) if !ok { return } version, e := concurrency.ParseIfMatch(r) if e != nil { writeETagError(w, r, e) return } var body struct { Reason string `json:"reason"` } if !decodeWebJSON(w, r, &body, "静默撤销参数不正确") { return } item, e := service.RevokeSilence(r.Context(), p, tenant, r.PathValue("id"), RequestID(r.Context()), body.Reason, version) if e != nil { writeAlertError(w, r, e) return } w.Header().Set("ETag", concurrency.FormatETag(item.RowVersion)) WriteJSON(w, r, http.StatusOK, item) }) } func writeAlertError(w http.ResponseWriter, r *http.Request, e error) { switch { case errors.Is(e, alerts.ErrInvalid): writeValidation(w, r, "告警参数不正确", FieldError{Field: "alert", Message: "字段、状态、时间范围或引用不符合约束"}) case errors.Is(e, alerts.ErrAccessDenied): WriteError(w, r, http.StatusForbidden, "OPS_ACCESS_DENIED", "当前账号没有所需的内部运营权限") case errors.Is(e, alerts.ErrNotFound): WriteError(w, r, http.StatusNotFound, "ALERT_NOT_FOUND", "告警、路由或静默记录不存在") case errors.Is(e, alerts.ErrVersionConflict): WriteError(w, r, http.StatusPreconditionFailed, "VERSION_CONFLICT", "资源版本已变化,请重新读取后再操作") case errors.Is(e, alerts.ErrStateConflict): WriteError(w, r, http.StatusConflict, "ALERT_STATE_CONFLICT", "当前告警或静默状态不允许该操作") case errors.Is(e, idempotency.ErrKeyConflict): WriteError(w, r, http.StatusConflict, "IDEMPOTENCY_KEY_CONFLICT", "重复请求识别码已被不同请求使用") default: slog.ErrorContext(r.Context(), "alert operation failed", "request_id", RequestID(r.Context()), "error", e) WriteError(w, r, http.StatusInternalServerError, "INTERNAL_ERROR", "告警处理失败") } }