feat: add retention policy and stability verification
This commit is contained in:
106
internal/transport/httpserver/retention.go
Normal file
106
internal/transport/httpserver/retention.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package httpserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"git.yuntuo.hk/k2safe/orbit/internal/fault"
|
||||
"git.yuntuo.hk/k2safe/orbit/internal/retention"
|
||||
"git.yuntuo.hk/k2safe/orbit/platform/auth"
|
||||
)
|
||||
|
||||
// RegisterConsoleRetention 注册保留策略配置、候选预览和只读归档导出。
|
||||
// 所有接口只接受控制台 HttpOnly 会话,不接受业务 Bearer token。
|
||||
func RegisterConsoleRetention(server *http.Server, consoleAuth RetentionConsoleAuth, store retention.Store, archive retention.ArchiveStore) {
|
||||
if server == nil || consoleAuth == nil || store == nil {
|
||||
return
|
||||
}
|
||||
mux, ok := server.Handler.(*http.ServeMux)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
check := func(w http.ResponseWriter, r *http.Request) (auth.Token, bool) {
|
||||
t, ok := consoleCookieToken(r)
|
||||
if !ok || consoleAuth.ValidateSession(r.Context(), t) != nil {
|
||||
writeError(w, "", fault.Unauthorized)
|
||||
return auth.Token{}, false
|
||||
}
|
||||
return t, true
|
||||
}
|
||||
mux.HandleFunc("GET /console/retention", func(w http.ResponseWriter, r *http.Request) {
|
||||
t, ok := check(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
p, err := store.GetRetentionPolicy(r.Context(), t.NamespaceID)
|
||||
if err != nil {
|
||||
writeError(w, "", err)
|
||||
return
|
||||
}
|
||||
writeConsoleJSON(w, 200, map[string]any{"data": p})
|
||||
})
|
||||
mux.HandleFunc("PUT /console/retention", func(w http.ResponseWriter, r *http.Request) {
|
||||
t, ok := check(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var in retention.UpdateInput
|
||||
if err := decodeBody(r, &in); err != nil {
|
||||
writeError(w, "", err)
|
||||
return
|
||||
}
|
||||
keys := r.Header.Values("Idempotency-Key")
|
||||
if len(keys) != 1 {
|
||||
writeError(w, "", fault.Invalid("exactly one Idempotency-Key is required"))
|
||||
return
|
||||
}
|
||||
p, err := store.UpdateRetentionPolicy(r.Context(), t, keys[0], in)
|
||||
if err != nil {
|
||||
writeError(w, "", err)
|
||||
return
|
||||
}
|
||||
writeConsoleJSON(w, 200, map[string]any{"data": p})
|
||||
})
|
||||
if archive == nil {
|
||||
return
|
||||
}
|
||||
mux.HandleFunc("GET /console/retention/preview", func(w http.ResponseWriter, r *http.Request) {
|
||||
t, ok := check(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
p, err := archive.PreviewRetention(r.Context(), t)
|
||||
if err != nil {
|
||||
writeError(w, "", err)
|
||||
return
|
||||
}
|
||||
writeConsoleJSON(w, 200, map[string]any{"data": p})
|
||||
})
|
||||
mux.HandleFunc("POST /console/retention/export", func(w http.ResponseWriter, r *http.Request) {
|
||||
t, ok := check(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var in retention.ArchiveInput
|
||||
if err := decodeBody(r, &in); err != nil {
|
||||
writeError(w, "", err)
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(in.Kind) == "" {
|
||||
writeError(w, "", fault.Invalid("archive kind is required"))
|
||||
return
|
||||
}
|
||||
snapshot, err := archive.ExportRetention(r.Context(), t, in)
|
||||
if err != nil {
|
||||
writeError(w, "", err)
|
||||
return
|
||||
}
|
||||
writeConsoleJSON(w, 200, map[string]any{"data": snapshot})
|
||||
})
|
||||
}
|
||||
|
||||
// RetentionConsoleAuth keeps route registration independent from the concrete DB auth type.
|
||||
type RetentionConsoleAuth interface {
|
||||
ValidateSession(context.Context, auth.Token) error
|
||||
}
|
||||
Reference in New Issue
Block a user