feat(archive): RET-06-API Aufbewahrungsfristen-Konfigurations-Backend
Board-Entscheidung: Backend-API zuerst, echtes Next.js-Frontend als separates Folgeticket - vermeidet Pseudo-Frontend-Protokoll. internal/retentionapi: 4 Endpunkte (anlegen/aendern, deaktivieren, liste, vorschau), Vorschau nutzt dieselbe ListExpiringObjects-Funktion wie RET-02s periodischer Job (keine Doppel-Implementierung). RequireRole ist AUSDRUECKLICH kein RBAC-02-Ersatz, sondern ein dokumentiertes Provisorium (Header-Check) - RBAC-02 ist reiner Core-interner Go-Code ohne HTTP-Schnittstelle fuer andere Module, derselbe Befund wie FDN-03/FDN-09. Provisorium real getestet inkl. Negativfall (403 ohne/mit falscher Rolle). retention_class_rules um active-Flag erweitert (deaktivieren ohne Historienverlust). Real auf 131 deployed und per curl end-to-end verifiziert.
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
package retentionapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/retentionengine"
|
||||
)
|
||||
|
||||
const adminRole = "archive_admin"
|
||||
|
||||
// Mount registriert alle RET-06-API-Endpunkte auf mux, jeweils hinter
|
||||
// dem provisorischen Rollen-Check (siehe authz.go) — Akzeptanzkriterium
|
||||
// 3: Änderungen an Fristen sind nur berechtigten Rollen zugänglich.
|
||||
func Mount(mux *http.ServeMux, pool *pgxpool.Pool) {
|
||||
mux.HandleFunc("POST /retention-classes", RequireRole(adminRole, configureHandler(pool)))
|
||||
mux.HandleFunc("POST /retention-classes/{class}/deactivate", RequireRole(adminRole, deactivateHandler(pool)))
|
||||
mux.HandleFunc("GET /retention-classes", RequireRole(adminRole, listHandler(pool)))
|
||||
mux.HandleFunc("GET /retention-classes/preview", RequireRole(adminRole, previewHandler(pool)))
|
||||
}
|
||||
|
||||
type configureRequest struct {
|
||||
RetentionClass string `json:"retention_class"`
|
||||
Duration string `json:"duration"`
|
||||
}
|
||||
|
||||
// configureHandler: Aufbewahrungsklasse anlegen ODER ändern
|
||||
// (Akzeptanzkriterium 1) — `retentionengine.ConfigureClassRule` ist ein
|
||||
// UPSERT, eine Änderung wirkt erst ab jetzt auf künftige
|
||||
// Stichtagsberechnungen (Pflichtprüfung: nicht rückwirkend).
|
||||
func configureHandler(pool *pgxpool.Pool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req configureRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "ungültiger request-body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if req.RetentionClass == "" || req.Duration == "" {
|
||||
http.Error(w, "retention_class und duration sind pflichtfelder", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := retentionengine.ConfigureClassRule(r.Context(), pool, req.RetentionClass, req.Duration); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
// deactivateHandler: Aufbewahrungsklasse deaktivieren (Akzeptanzkriterium 1).
|
||||
func deactivateHandler(pool *pgxpool.Pool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
class := r.PathValue("class")
|
||||
if err := retentionengine.DeactivateClassRule(r.Context(), pool, class); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
// listHandler liefert alle konfigurierten Aufbewahrungsklassen (aktiv
|
||||
// und deaktiviert) — Grundlage der künftigen Konfigurationsoberfläche.
|
||||
func listHandler(pool *pgxpool.Pool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
rules, err := retentionengine.ListClassRules(r.Context(), pool)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(rules)
|
||||
}
|
||||
}
|
||||
|
||||
// previewHandler liefert die Vorschauliste bald ablaufender Objekte
|
||||
// (Akzeptanzkriterium 2: Standard 30 Tage, per `days`-Query-Parameter
|
||||
// überschreibbar). Nutzt DIESELBE `ListExpiringObjects`-Funktion wie
|
||||
// der periodische Job (RET-02) — Pflichtprüfung: Vorschauliste stimmt
|
||||
// mit dem Ergebnis des periodischen Jobs überein (keine zweite,
|
||||
// abweichende Implementierung).
|
||||
func previewHandler(pool *pgxpool.Pool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
days := 30
|
||||
asOf := time.Now().UTC().AddDate(0, 0, days)
|
||||
|
||||
objects, err := retentionengine.ListExpiringObjects(r.Context(), pool, asOf)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(objects)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user