271 lines
7.8 KiB
Go
271 lines
7.8 KiB
Go
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", "告警处理失败")
|
|
}
|
|
}
|