feat(gateway): 网关(REST /api/v1 + WS Hub /ws/chat + admin /api/v1/admin/* + JWT/RBAC + 审计,单端口 :8080)
This commit is contained in:
1 parent
ff34b62079
commit
05e23ea590
7 files changed
+1535
No files matched your search
@@ -0,0 +1,170 @@
|
||||
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"`
|
||||
}
|
||||
Reference in new issue
Block a user