218 lines
5.3 KiB
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
|
|
}
|