package gateway import ( "context" "encoding/json" "errors" "net/http" capreg "git.yeij.top/AskaEth/Cyrene_v2/internal/capability/registry" apperr "git.yeij.top/AskaEth/Cyrene_v2/internal/errors" "git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk" ) // PluginSummary is the wire shape of a capability/plugin in the admin list. type PluginSummary struct { Name string `json:"name"` Kind string `json:"kind"` Description string `json:"description"` AllowInGroup bool `json:"allowInGroup"` AllowInStranger bool `json:"allowInStranger"` } // ChannelSummary is the wire shape of a registered channel adapter. type ChannelSummary struct { Name string `json:"name"` Connected bool `json:"connected"` } // policyRequest is the POST /api/v1/admin/plugins/policy body. type policyRequest struct { Name string `json:"name"` AllowInGroup bool `json:"allowInGroup"` AllowInStranger bool `json:"allowInStranger"` } // adminListPersonas lists persona summaries (admin read). func (s *Server) adminListPersonas(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, s.collectPersonas()) } // adminListSessions lists channel sessions (admin read). func (s *Server) adminListSessions(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, s.collectSessions(r.Context())) } // adminListMemories lists memory entries (admin read). func (s *Server) adminListMemories(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, s.collectMemories(r.Context())) } // adminListPlugins lists every registered capability (admin read). func (s *Server) adminListPlugins(w http.ResponseWriter, r *http.Request) { out := make([]PluginSummary, 0) if s.caps != nil { for _, cap := range s.caps.List("") { out = append(out, PluginSummary{ Name: cap.Name, Kind: string(cap.Kind), Description: cap.Description, AllowInGroup: cap.Policy.AllowInGroup, AllowInStranger: cap.Policy.AllowInStranger, }) } } writeJSON(w, http.StatusOK, out) } // adminListChannels lists the registered channel adapters (admin read). func (s *Server) adminListChannels(w http.ResponseWriter, r *http.Request) { out := make([]ChannelSummary, 0) if s.caps != nil { for _, cap := range s.caps.List(capreg.KindChannel) { ch, ok := cap.Value.(sdk.Channel) if !ok { continue } out = append(out, ChannelSummary{Name: ch.Name(), Connected: ch.IsConnected()}) } } writeJSON(w, http.StatusOK, out) } // adminListIoT lists IoT devices (admin read). func (s *Server) adminListIoT(w http.ResponseWriter, r *http.Request) { out := make([]iotDeviceSummary, 0) if s.iot != nil { devices, err := s.iot.List(r.Context()) if err != nil { s.log.Error("list iot", "err", err) writeError(w, apperr.Wrap(apperr.ErrInternal, "iot_failed", "failed to list devices")) return } for _, d := range devices { out = append(out, iotDeviceSummary{ID: d.ID, Name: d.Name, Type: d.Type, State: d.State, Online: d.Online}) } } writeJSON(w, http.StatusOK, out) } // adminSetPolicy upserts the reachability markers for a capability. It is the // audited admin write example (owner-only, enforced by the route registration). func (s *Server) adminSetPolicy(w http.ResponseWriter, r *http.Request) { var req policyRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, apperr.Wrap(apperr.ErrBadRequest, "bad_request", "invalid request body")) return } if req.Name == "" { writeError(w, apperr.Wrap(apperr.ErrBadRequest, "bad_request", "name is required")) return } if s.caps == nil { s.auditClaims(r, "policy.set", "error") writeError(w, apperr.Wrap(apperr.ErrInternal, "policy_unavailable", "capability service is not configured")) return } err := s.caps.SetPolicy(req.Name, capreg.ToolPolicy{AllowInGroup: req.AllowInGroup, AllowInStranger: req.AllowInStranger}) if err != nil { s.auditClaims(r, "policy.set", "error") if errors.Is(err, capreg.ErrNotFound) { writeError(w, apperr.Wrap(apperr.ErrNotFound, "not_found", "capability %q not found", req.Name)) return } writeError(w, apperr.Wrap(apperr.ErrInternal, "policy_failed", "failed to set policy")) return } s.auditClaims(r, "policy.set", "ok") writeJSON(w, http.StatusOK, map[string]any{"name": req.Name, "allowInGroup": req.AllowInGroup, "allowInStranger": req.AllowInStranger}) } // collectSessions returns a non-nil session slice from the configured store. func (s *Server) collectSessions(ctx context.Context) []SessionSummary { out := make([]SessionSummary, 0) if s.sessions == nil { return out } list, err := s.sessions.List(ctx) if err != nil { s.log.Error("list sessions", "err", err) return out } for _, sess := range list { out = append(out, summaryOfSession(sess)) } return out } // collectMemories returns a non-nil memory slice from the configured store. func (s *Server) collectMemories(ctx context.Context) []MemorySummary { out := make([]MemorySummary, 0) if s.memories == nil { return out } list, err := s.memories.List(ctx) if err != nil { s.log.Error("list memories", "err", err) return out } out = append(out, list...) return out } // iotDeviceSummary is the wire shape of an IoT device. type iotDeviceSummary struct { ID string `json:"id"` Name string `json:"name"` Type string `json:"type"` State map[string]any `json:"state"` Online bool `json:"online"` }