79a3358810
web/handlers/window.go: GET /window (list), GET /window/new (form),
POST /window (open), GET /window/{id} (detail+lifecycle+audit), POST
/window/{id}/activate|revoke|expire (lifecycle transitions). Store
extensions: OpenWindow/ActivateWindow/RevokeWindow/ExpireWindow/
ListWindows/GetWindow/GetAuditLog — all call the REAL x/window/types
Window.Activate/Revoke/Expire methods (not reimplementation). Revoke on
Expired is a no-op (v0.2 terminal-state contract). AuditEntry appended on
each transition. 3 Window templates. window_test.go: lifecycle
correctness (asserts real methods invoked), idempotent revoke, revoke-on-
expired no-op, G-026 error lexicon checks. Coverage: store 99.2%,
handlers 87.7%.
---ci---
project: oy
phase: 3
milestone: v0.6
status: execute
---/ci---
120 lines
4.1 KiB
Go
120 lines
4.1 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
windowtypes "github.com/oy/openyield/x/window/types"
|
|
)
|
|
|
|
// registerWindow wires the Window authorization routes (REQ-042) into the mux.
|
|
func (s *Server) registerWindow(mux *http.ServeMux) {
|
|
mux.HandleFunc("GET /window", s.handleWindowList)
|
|
mux.HandleFunc("GET /window/new", s.handleWindowNew)
|
|
mux.HandleFunc("POST /window", s.handleWindowOpen)
|
|
mux.HandleFunc("GET /window/{id}", s.handleWindowDetail)
|
|
mux.HandleFunc("POST /window/{id}/activate", s.handleWindowActivate)
|
|
mux.HandleFunc("POST /window/{id}/revoke", s.handleWindowRevoke)
|
|
mux.HandleFunc("POST /window/{id}/expire", s.handleWindowExpire)
|
|
}
|
|
|
|
// handleWindowList renders all Windows for a grantor holder (defaults to
|
|
// holder-alia if no query param, so the list view has something to show).
|
|
func (s *Server) handleWindowList(w http.ResponseWriter, r *http.Request) {
|
|
grantor := r.URL.Query().Get("grantor")
|
|
if grantor == "" {
|
|
grantor = "holder-alia"
|
|
}
|
|
windows := s.Store.ListWindows(grantor)
|
|
s.render(w, "window_list.html", map[string]any{"Windows": windows, "Grantor": grantor})
|
|
}
|
|
|
|
// handleWindowNew renders the "Open a Window" form.
|
|
func (s *Server) handleWindowNew(w http.ResponseWriter, r *http.Request) {
|
|
s.render(w, "window_new.html", nil)
|
|
}
|
|
|
|
// handleWindowOpen handles the POST from the "Open a Window" form. Calls
|
|
// store.OpenWindow (creates a Window status=Open + an initial AuditEntry).
|
|
func (s *Server) handleWindowOpen(w http.ResponseWriter, r *http.Request) {
|
|
grantor := r.FormValue("grantor_holder")
|
|
grantee := r.FormValue("grantee")
|
|
scopeKind := windowtypes.ScopeKind(r.FormValue("scope_kind"))
|
|
resourceID := r.FormValue("resource_id")
|
|
startStr := r.FormValue("start_unix")
|
|
endStr := r.FormValue("end_unix")
|
|
maxActionsStr := r.FormValue("max_actions")
|
|
|
|
if grantor == "" {
|
|
http.Error(w, "grantor holder is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if grantee == "" {
|
|
http.Error(w, "grantee is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
start, _ := strconv.ParseInt(startStr, 10, 64)
|
|
end, _ := strconv.ParseInt(endStr, 10, 64)
|
|
if start == 0 {
|
|
start = time.Now().Unix()
|
|
}
|
|
if end == 0 {
|
|
end = start + 3600
|
|
}
|
|
maxActions, _ := strconv.ParseUint(maxActionsStr, 10, 32)
|
|
if maxActions == 0 {
|
|
maxActions = 10
|
|
}
|
|
scope := windowtypes.Scope{Kind: scopeKind, ResourceID: resourceID}
|
|
rateLimit := windowtypes.RateLimit{MaxActions: uint32(maxActions), PerDurationSeconds: 3600}
|
|
win, err := s.Store.OpenWindow(grantor, grantee, scope, start, end, rateLimit)
|
|
if err != nil {
|
|
http.Error(w, "could not open a Window: "+err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
http.Redirect(w, r, "/window/"+win.WindowID, http.StatusFound)
|
|
}
|
|
|
|
// handleWindowDetail renders one Window + its lifecycle state + audit log.
|
|
func (s *Server) handleWindowDetail(w http.ResponseWriter, r *http.Request) {
|
|
id := r.PathValue("id")
|
|
win, ok := s.Store.GetWindow(id)
|
|
if !ok {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
auditLog := s.Store.GetAuditLog(id)
|
|
s.render(w, "window_detail.html", map[string]any{"Window": win, "AuditLog": auditLog})
|
|
}
|
|
|
|
// handleWindowActivate transitions Open → Active (calls Window.Activate).
|
|
func (s *Server) handleWindowActivate(w http.ResponseWriter, r *http.Request) {
|
|
id := r.PathValue("id")
|
|
if err := s.Store.ActivateWindow(id); err != nil {
|
|
http.Error(w, "could not activate: "+err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
http.Redirect(w, r, "/window/"+id, http.StatusSeeOther)
|
|
}
|
|
|
|
// handleWindowRevoke transitions to Revoked (calls Window.Revoke; idempotent).
|
|
func (s *Server) handleWindowRevoke(w http.ResponseWriter, r *http.Request) {
|
|
id := r.PathValue("id")
|
|
if err := s.Store.RevokeWindow(id); err != nil {
|
|
http.Error(w, "could not revoke: "+err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
http.Redirect(w, r, "/window/"+id, http.StatusSeeOther)
|
|
}
|
|
|
|
// handleWindowExpire transitions to Expired (calls Window.Expire).
|
|
func (s *Server) handleWindowExpire(w http.ResponseWriter, r *http.Request) {
|
|
id := r.PathValue("id")
|
|
if err := s.Store.ExpireWindow(id); err != nil {
|
|
http.Error(w, "could not expire: "+err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
http.Redirect(w, r, "/window/"+id, http.StatusSeeOther)
|
|
}
|