package nodes import ( "context" "github.com/redis/go-redis/v9" "github.com/wangjia/pangolin/server/internal/mtls" agentv1 "github.com/wangjia/pangolin/server/internal/pb/agentv1" ) // Service bundles all nodes-domain components and exposes assembly helpers. // Construct via NewService after initialising each dependency independently. type Service struct { handler *Handler hub *Hub store NodeStore load *LoadCache } // NewService wires up the full nodes service from its dependencies. // // - ca / tokens / crl: from the mtls package (task 5b) // - rdb: Redis client (for hub + load cache) // - store: NodeStore implementation (SQLNodeStore in production; mock in tests) func NewService( ca *mtls.CA, tokens *mtls.BootstrapTokenManager, rdb *redis.Client, store NodeStore, ) *Service { hub := NewHub(rdb) load := NewLoadCache(rdb) handler := NewHandler(ca, tokens, hub, store, load) return &Service{ handler: handler, hub: hub, store: store, load: load, } } // Handler returns the AgentServiceServer implementation for gRPC registration. func (s *Service) Handler() agentv1.AgentServiceServer { return s.handler } // Hub exposes the command routing hub for callers (e.g. task 5d/5e) that need to // Push or Broadcast commands. func (s *Service) Hub() *Hub { return s.hub } // Store exposes the NodeStore for callers that need direct DB access. func (s *Service) Store() NodeStore { return s.store } // RevokeDevice recalls a per-device data-plane credential: it pushes a Revoke // command to every node currently holding the dp_uuid (so sing-box drops that // user) and removes the connect_credentials rows. Satisfies devices.CredentialRevoker. // Best-effort: a queued Push is replayed when an offline node reconnects. func (s *Service) RevokeDevice(ctx context.Context, dpUUID string) error { if dpUUID == "" { return nil } locs, err := s.store.NodesHoldingCredential(ctx, dpUUID) if err != nil { return err } for _, loc := range locs { _ = s.hub.Push(ctx, loc.NodeUUID, &agentv1.Command{ Type: agentv1.CommandTypeRevoke, Revoke: &agentv1.RevokePayload{DpUUID: dpUUID}, }) _ = s.store.DeleteCredential(ctx, loc.NodeID, dpUUID) } return nil } // Load exposes the LoadCache for callers that display per-node load metrics. func (s *Service) Load() *LoadCache { return s.load }