Files
EvoBGP/internal/httpapi/routes_revisions_prune.go
DenozordecandCursor 4d83b8d673
CI / changes (push) Successful in 6s
CI / commitlint (push) Skipped
CI / openapi (push) Successful in 27s
CI / web (push) Successful in 51s
CI / go (push) Successful in 2m19s
CI / bird2 (push) Successful in 13s
CI / release (push) Successful in 4m24s
feat(auth): integrate portal JWT for enhanced authentication and authorization
Added support for portal JWT authentication, enabling single sign-on (SSO) capabilities. Updated the application to handle JWT claims for user permissions and roles, enhancing security and access control. Refactored relevant components and API routes to accommodate the new authentication flow, ensuring a seamless user experience. Updated documentation to reflect the new authentication requirements and configurations.

Co-authored-by: Cursor <[email protected]>
2026-07-18 23:23:52 +07:00

102 lines
3.2 KiB
Go

package httpapi
import (
"encoding/json"
"net/http"
"strconv"
"time"
"evobgp/internal/pipeline"
)
func (s *Server) resolveRevisionRetentionMinutesQuery(r *http.Request, tenantID string) (minutes int, ok bool) {
if raw := r.URL.Query().Get("retention_minutes"); raw != "" {
n, err := strconv.Atoi(raw)
if err != nil {
return 0, false
}
if n < pipeline.RevisionMinTTLMin || n > pipeline.RevisionMaxTTLMin {
return 0, false
}
return n, true
}
return s.defaultRevisionRetentionMinutes(tenantID)
}
func (s *Server) defaultRevisionRetentionMinutes(tenantID string) (int, bool) {
settings, err := s.store.ListGlobalSettings(tenantID)
if err != nil {
return pipeline.ClampRevisionRetentionMinutes(0), true
}
return pipeline.ClampRevisionRetentionMinutes(pipeline.RevisionRetentionMinutesFromSettings(settings)), true
}
func (s *Server) resolveRevisionRetentionMinutesBody(r *http.Request, tenantID string) (minutes int, ok bool) {
var body struct {
RetentionMinutes *int `json:"retention_minutes"`
}
if r.Body != nil && r.ContentLength != 0 {
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
return 0, false
}
if body.RetentionMinutes != nil {
m := *body.RetentionMinutes
if m < pipeline.RevisionMinTTLMin || m > pipeline.RevisionMaxTTLMin {
return 0, false
}
return m, true
}
}
return s.defaultRevisionRetentionMinutes(tenantID)
}
func (s *Server) handleRevisionPruneEstimate(w http.ResponseWriter, r *http.Request) {
a, ok := authFromContext(r.Context())
if !ok || !s.requirePerm(w, a, "bgp:operations:read") {
return
}
minutes, valid := s.resolveRevisionRetentionMinutesQuery(r, a.TenantID)
if !valid {
writeProblem(w, http.StatusUnprocessableEntity, "Unprocessable Entity", "retention_minutes must be an integer in range 15..43200")
return
}
cutoff := pipeline.RevisionCutoffFromMinutes(minutes)
est, err := s.store.EstimateRevisionPrune(a.TenantID, cutoff, minutes)
if err != nil {
writeInternalError(w, "revision prune estimate", err)
return
}
writeJSON(w, http.StatusOK, map[string]any{
"retention_minutes": est.RetentionMinutes,
"cutoff_at": est.CutoffAt.UTC().Format(time.RFC3339Nano),
"revision_count": est.RevisionCount,
"prefix_row_count": est.PrefixRowCount,
"orphan_snapshot_count": est.OrphanSnapshotCount,
"bytes_estimate": est.BytesEstimate,
})
}
func (s *Server) handleRevisionPrune(w http.ResponseWriter, r *http.Request) {
a, ok := authFromContext(r.Context())
if !ok || !s.requirePerm(w, a, "bgp:operations:admin") {
return
}
minutes, valid := s.resolveRevisionRetentionMinutesBody(r, a.TenantID)
if !valid {
writeProblem(w, http.StatusUnprocessableEntity, "Unprocessable Entity", "retention_minutes must be an integer in range 15..43200")
return
}
cutoff := pipeline.RevisionCutoffFromMinutes(minutes)
res, err := s.store.PruneRevisionsWithStats(a.TenantID, cutoff)
if err != nil {
writeInternalError(w, "revision prune", err)
return
}
writeJSON(w, http.StatusOK, map[string]any{
"deleted_revisions": res.DeletedRevisions,
"deleted_prefix_snapshots": res.DeletedPrefixSnapshots,
"deleted_prefix_rows": res.DeletedPrefixRows,
"bytes_estimate": res.BytesEstimate,
})
}