chore: establish local platform baseline
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"data-terminal-cloud/internal/concurrency"
|
||||
"data-terminal-cloud/internal/identity"
|
||||
"data-terminal-cloud/internal/opsconfig"
|
||||
)
|
||||
|
||||
func CreateOpsResourceReferenceHandler(service *opsconfig.Service) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
principal, ok := webPrincipal(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
tenantID, key, ok := opsCreateHeaders(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var body struct {
|
||||
ResourceType string `json:"resource_type"`
|
||||
ReferenceName string `json:"reference_name"`
|
||||
DisplayName string `json:"display_name"`
|
||||
VerifiedAt time.Time `json:"verified_at"`
|
||||
ExpiresAt *time.Time `json:"expires_at"`
|
||||
}
|
||||
if !decodeWebJSON(w, r, &body, "运营资源引用参数不正确") {
|
||||
return
|
||||
}
|
||||
item, created, err := service.CreateResourceReference(r.Context(), principal, tenantID, key, RequestID(r.Context()), body.ResourceType, body.ReferenceName, body.DisplayName, body.VerifiedAt, body.ExpiresAt)
|
||||
if err != nil {
|
||||
writeOpsConfigError(w, r, err)
|
||||
return
|
||||
}
|
||||
status := http.StatusCreated
|
||||
if !created {
|
||||
status = http.StatusOK
|
||||
w.Header().Set("Idempotency-Replayed", "true")
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
WriteJSON(w, r, status, item)
|
||||
})
|
||||
}
|
||||
|
||||
func ListOpsResourceReferencesHandler(service *opsconfig.Service) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
principal, ok := webPrincipal(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
tenantID, ok := opsTenantHeader(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
items, err := service.ListResourceReferences(r.Context(), principal, tenantID)
|
||||
if err != nil {
|
||||
writeOpsConfigError(w, r, err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
WriteJSON(w, r, http.StatusOK, map[string]any{"items": items})
|
||||
})
|
||||
}
|
||||
|
||||
func RevokeOpsResourceReferenceHandler(service *opsconfig.Service) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
principal, ok := resourcePrincipal(w, r, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
tenantID, ok := opsTenantHeader(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
expected, err := concurrency.ParseIfMatch(r)
|
||||
if err != nil {
|
||||
writeETagError(w, r, err)
|
||||
return
|
||||
}
|
||||
var body struct {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
if !decodeWebJSON(w, r, &body, "资源引用撤销参数不正确") {
|
||||
return
|
||||
}
|
||||
item, err := service.RevokeResourceReference(r.Context(), principal, tenantID, r.PathValue("id"), RequestID(r.Context()), body.Reason, expected)
|
||||
if err != nil {
|
||||
writeOpsConfigError(w, r, err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("ETag", concurrency.FormatETag(item.RowVersion))
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
WriteJSON(w, r, http.StatusOK, item)
|
||||
})
|
||||
}
|
||||
|
||||
func CreateOpsConfigEvaluationHandler(service *opsconfig.Service) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
principal, ok := resourcePrincipal(w, r, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
tenantID, key, ok := opsCreateHeaders(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var body struct {
|
||||
EvidenceType string `json:"evidence_type"`
|
||||
SuiteVersion string `json:"suite_version"`
|
||||
Result string `json:"result"`
|
||||
Metrics map[string]any `json:"metrics"`
|
||||
ReportRef string `json:"report_ref"`
|
||||
}
|
||||
if !decodeWebJSON(w, r, &body, "离线评测参数不正确") {
|
||||
return
|
||||
}
|
||||
item, created, err := service.CreateEvaluation(r.Context(), principal, tenantID, r.PathValue("id"), key, RequestID(r.Context()), body.EvidenceType, body.SuiteVersion, body.Result, body.ReportRef, body.Metrics)
|
||||
if err != nil {
|
||||
writeOpsConfigError(w, r, err)
|
||||
return
|
||||
}
|
||||
status := http.StatusCreated
|
||||
if !created {
|
||||
status = http.StatusOK
|
||||
w.Header().Set("Idempotency-Replayed", "true")
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
WriteJSON(w, r, status, item)
|
||||
})
|
||||
}
|
||||
|
||||
func ListOpsConfigEvaluationsHandler(service *opsconfig.Service) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
principal, ok := resourcePrincipal(w, r, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
tenantID, ok := opsTenantHeader(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
items, err := service.ListEvaluations(r.Context(), principal, tenantID, r.PathValue("id"))
|
||||
if err != nil {
|
||||
writeOpsConfigError(w, r, err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
WriteJSON(w, r, http.StatusOK, map[string]any{"items": items})
|
||||
})
|
||||
}
|
||||
|
||||
func CreateOpsDeviceGroupHandler(service *opsconfig.Service) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
principal, ok := webPrincipal(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
tenantID, key, ok := opsCreateHeaders(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var body struct {
|
||||
Name string `json:"name"`
|
||||
Selector map[string]any `json:"selector"`
|
||||
DeviceIDs []string `json:"device_ids"`
|
||||
}
|
||||
if !decodeWebJSON(w, r, &body, "设备分组参数不正确") {
|
||||
return
|
||||
}
|
||||
item, created, err := service.CreateDeviceGroup(r.Context(), principal, tenantID, key, RequestID(r.Context()), body.Name, body.Selector, body.DeviceIDs)
|
||||
if err != nil {
|
||||
writeOpsConfigError(w, r, err)
|
||||
return
|
||||
}
|
||||
status := http.StatusCreated
|
||||
if !created {
|
||||
status = http.StatusOK
|
||||
w.Header().Set("Idempotency-Replayed", "true")
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
WriteJSON(w, r, status, item)
|
||||
})
|
||||
}
|
||||
|
||||
func ListOpsDeviceGroupsHandler(service *opsconfig.Service) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
principal, ok := webPrincipal(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
tenantID, ok := opsTenantHeader(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
items, err := service.ListDeviceGroups(r.Context(), principal, tenantID)
|
||||
if err != nil {
|
||||
writeOpsConfigError(w, r, err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
WriteJSON(w, r, http.StatusOK, map[string]any{"items": items})
|
||||
})
|
||||
}
|
||||
|
||||
func ListOpsConfigRolloutsHandler(service *opsconfig.Service) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
principal, ok := webPrincipal(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
tenantID, ok := opsTenantHeader(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
items, err := service.ListRollouts(r.Context(), principal, tenantID, r.URL.Query().Get("config_key"))
|
||||
if err != nil {
|
||||
writeOpsConfigError(w, r, err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
WriteJSON(w, r, http.StatusOK, map[string]any{"items": items})
|
||||
})
|
||||
}
|
||||
|
||||
func PauseOpsConfigRolloutHandler(service *opsconfig.Service) http.Handler {
|
||||
return opsConfigRolloutTransitionHandler(func(r *http.Request, principal identity.Principal, tenantID, reason string, expected int64) (opsconfig.Rollout, error) {
|
||||
return service.PauseRollout(r.Context(), principal, tenantID, r.PathValue("id"), RequestID(r.Context()), reason, expected)
|
||||
})
|
||||
}
|
||||
|
||||
func ResumeOpsConfigRolloutHandler(service *opsconfig.Service) http.Handler {
|
||||
return opsConfigRolloutTransitionHandler(func(r *http.Request, principal identity.Principal, tenantID, reason string, expected int64) (opsconfig.Rollout, error) {
|
||||
return service.ResumeRollout(r.Context(), principal, tenantID, r.PathValue("id"), RequestID(r.Context()), reason, expected)
|
||||
})
|
||||
}
|
||||
|
||||
func opsConfigRolloutTransitionHandler(transition func(*http.Request, identity.Principal, string, string, int64) (opsconfig.Rollout, error)) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
principal, ok := resourcePrincipal(w, r, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
tenantID, ok := opsTenantHeader(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
expected, err := concurrency.ParseIfMatch(r)
|
||||
if err != nil {
|
||||
writeETagError(w, r, err)
|
||||
return
|
||||
}
|
||||
var body struct {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
if !decodeWebJSON(w, r, &body, "配置投放状态变更参数不正确") {
|
||||
return
|
||||
}
|
||||
item, err := transition(r, principal, tenantID, body.Reason, expected)
|
||||
if err != nil {
|
||||
writeOpsConfigError(w, r, err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("ETag", concurrency.FormatETag(item.RowVersion))
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
WriteJSON(w, r, http.StatusOK, item)
|
||||
})
|
||||
}
|
||||
|
||||
func opsTenantHeader(w http.ResponseWriter, r *http.Request) (string, bool) {
|
||||
tenantID := strings.TrimSpace(r.Header.Get("X-Tenant-Id"))
|
||||
if !validUUID(tenantID) {
|
||||
writeValidation(w, r, "运营范围参数不正确", FieldError{Field: "X-Tenant-Id", Message: "必须是有效UUID"})
|
||||
return "", false
|
||||
}
|
||||
return tenantID, true
|
||||
}
|
||||
func opsCreateHeaders(w http.ResponseWriter, r *http.Request) (string, string, bool) {
|
||||
tenantID, ok := opsTenantHeader(w, r)
|
||||
if !ok {
|
||||
return "", "", false
|
||||
}
|
||||
key := strings.TrimSpace(r.Header.Get("Idempotency-Key"))
|
||||
if len(key) < 16 || len(key) > 128 {
|
||||
writeValidation(w, r, "运营写入参数不正确", FieldError{Field: "Idempotency-Key", Message: "长度必须为16至128字符"})
|
||||
return "", "", false
|
||||
}
|
||||
return tenantID, key, true
|
||||
}
|
||||
Reference in New Issue
Block a user