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) }