rwadurian/backend/mpc-system/services/message-router/domain/party_registry.go

218 lines
5.3 KiB
Go

package domain
import (
"sync"
"time"
)
// NotificationChannel represents notification channels for offline parties
type NotificationChannel struct {
Email string
Phone string
PushToken string
}
// HasAnyChannel returns true if any notification channel is configured
func (nc *NotificationChannel) HasAnyChannel() bool {
return nc != nil && (nc.Email != "" || nc.Phone != "" || nc.PushToken != "")
}
// RegisteredParty represents a party registered with the router
type RegisteredParty struct {
PartyID string
Role string // persistent, delegate, temporary
Version string
RegisteredAt time.Time
LastSeen time.Time
Online bool // Whether the party is currently connected
Notification *NotificationChannel // Optional notification channels for offline mode
}
// IsOfflineMode returns true if the party operates in offline mode (has notification channels)
func (p *RegisteredParty) IsOfflineMode() bool {
return p.Notification != nil && p.Notification.HasAnyChannel()
}
// PartyRegistry manages registered parties
type PartyRegistry struct {
parties map[string]*RegisteredParty
mu sync.RWMutex
}
// NewPartyRegistry creates a new party registry
func NewPartyRegistry() *PartyRegistry {
return &PartyRegistry{
parties: make(map[string]*RegisteredParty),
}
}
// Register registers a party
func (r *PartyRegistry) Register(partyID, role, version string) *RegisteredParty {
return r.RegisterWithNotification(partyID, role, version, nil)
}
// RegisterWithNotification registers a party with optional notification channels
func (r *PartyRegistry) RegisterWithNotification(partyID, role, version string, notification *NotificationChannel) *RegisteredParty {
r.mu.Lock()
defer r.mu.Unlock()
now := time.Now()
party := &RegisteredParty{
PartyID: partyID,
Role: role,
Version: version,
RegisteredAt: now,
LastSeen: now,
Online: true,
Notification: notification,
}
r.parties[partyID] = party
return party
}
// Get retrieves a registered party
func (r *PartyRegistry) Get(partyID string) (*RegisteredParty, bool) {
r.mu.RLock()
defer r.mu.RUnlock()
party, exists := r.parties[partyID]
return party, exists
}
// GetAll returns all registered parties
func (r *PartyRegistry) GetAll() []*RegisteredParty {
r.mu.RLock()
defer r.mu.RUnlock()
parties := make([]*RegisteredParty, 0, len(r.parties))
for _, party := range r.parties {
parties = append(parties, party)
}
return parties
}
// GetByRole returns registered parties filtered by role
func (r *PartyRegistry) GetByRole(role string) []*RegisteredParty {
r.mu.RLock()
defer r.mu.RUnlock()
parties := make([]*RegisteredParty, 0)
for _, party := range r.parties {
if party.Role == role {
parties = append(parties, party)
}
}
return parties
}
// UpdateLastSeen updates the last seen timestamp
func (r *PartyRegistry) UpdateLastSeen(partyID string) {
r.mu.Lock()
defer r.mu.Unlock()
if party, exists := r.parties[partyID]; exists {
party.LastSeen = time.Now()
}
}
// Unregister removes a party from the registry
func (r *PartyRegistry) Unregister(partyID string) {
r.mu.Lock()
defer r.mu.Unlock()
delete(r.parties, partyID)
}
// Count returns the number of registered parties
func (r *PartyRegistry) Count() int {
r.mu.RLock()
defer r.mu.RUnlock()
return len(r.parties)
}
// SetOnline sets the online status of a party
func (r *PartyRegistry) SetOnline(partyID string, online bool) {
r.mu.Lock()
defer r.mu.Unlock()
if party, exists := r.parties[partyID]; exists {
party.Online = online
if online {
party.LastSeen = time.Now()
}
}
}
// IsOnline checks if a party is currently online
func (r *PartyRegistry) IsOnline(partyID string) bool {
r.mu.RLock()
defer r.mu.RUnlock()
if party, exists := r.parties[partyID]; exists {
return party.Online
}
return false
}
// GetOnlineParties returns all online parties
func (r *PartyRegistry) GetOnlineParties() []*RegisteredParty {
r.mu.RLock()
defer r.mu.RUnlock()
parties := make([]*RegisteredParty, 0)
for _, party := range r.parties {
if party.Online {
parties = append(parties, party)
}
}
return parties
}
// GetOfflineParties returns all parties that are offline (have notification channels but not connected)
func (r *PartyRegistry) GetOfflineParties() []*RegisteredParty {
r.mu.RLock()
defer r.mu.RUnlock()
parties := make([]*RegisteredParty, 0)
for _, party := range r.parties {
if !party.Online && party.IsOfflineMode() {
parties = append(parties, party)
}
}
return parties
}
// MarkStalePartiesOffline marks parties as offline if they haven't sent a heartbeat within the timeout
// Returns the list of parties that were marked offline
func (r *PartyRegistry) MarkStalePartiesOffline(timeout time.Duration) []*RegisteredParty {
r.mu.Lock()
defer r.mu.Unlock()
now := time.Now()
staleParties := make([]*RegisteredParty, 0)
for _, party := range r.parties {
if party.Online && now.Sub(party.LastSeen) > timeout {
party.Online = false
staleParties = append(staleParties, party)
}
}
return staleParties
}
// Heartbeat updates the last seen timestamp and marks the party as online
func (r *PartyRegistry) Heartbeat(partyID string) bool {
r.mu.Lock()
defer r.mu.Unlock()
if party, exists := r.parties[partyID]; exists {
party.LastSeen = time.Now()
party.Online = true
return true
}
return false
}