171 lines
5.4 KiB
Go
171 lines
5.4 KiB
Go
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"`
|
|
}
|