Adds OnChannelMappingDeleted method to BridgeManager for centralized cleanup of channel mappings across all bridge types. Updates slash commands to use centralized management and fixes method naming inconsistencies. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
312 lines
9.2 KiB
Go
312 lines
9.2 KiB
Go
package bridge
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/logger"
|
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
|
|
)
|
|
|
|
// Manager manages multiple bridge instances
|
|
type Manager struct {
|
|
bridges map[string]model.Bridge
|
|
mu sync.RWMutex
|
|
logger logger.Logger
|
|
}
|
|
|
|
// NewManager creates a new bridge manager
|
|
func NewManager(logger logger.Logger) model.BridgeManager {
|
|
if logger == nil {
|
|
panic("logger cannot be nil")
|
|
}
|
|
|
|
return &Manager{
|
|
bridges: make(map[string]model.Bridge),
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// RegisterBridge registers a bridge with the manager
|
|
func (m *Manager) RegisterBridge(name string, bridge model.Bridge) error {
|
|
if name == "" {
|
|
return fmt.Errorf("bridge name cannot be empty")
|
|
}
|
|
if bridge == nil {
|
|
return fmt.Errorf("bridge cannot be nil")
|
|
}
|
|
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if _, exists := m.bridges[name]; exists {
|
|
return fmt.Errorf("bridge '%s' is already registered", name)
|
|
}
|
|
|
|
m.bridges[name] = bridge
|
|
m.logger.LogInfo("Bridge registered", "name", name)
|
|
|
|
return nil
|
|
}
|
|
|
|
// StartBridge starts a specific bridge
|
|
func (m *Manager) StartBridge(name string) error {
|
|
m.mu.RLock()
|
|
bridge, exists := m.bridges[name]
|
|
m.mu.RUnlock()
|
|
|
|
if !exists {
|
|
return fmt.Errorf("bridge '%s' is not registered", name)
|
|
}
|
|
|
|
m.logger.LogInfo("Starting bridge", "name", name)
|
|
|
|
if err := bridge.Start(); err != nil {
|
|
m.logger.LogError("Failed to start bridge", "name", name, "error", err)
|
|
return fmt.Errorf("failed to start bridge '%s': %w", name, err)
|
|
}
|
|
|
|
m.logger.LogInfo("Bridge started successfully", "name", name)
|
|
return nil
|
|
}
|
|
|
|
// StopBridge stops a specific bridge
|
|
func (m *Manager) StopBridge(name string) error {
|
|
m.mu.RLock()
|
|
bridge, exists := m.bridges[name]
|
|
m.mu.RUnlock()
|
|
|
|
if !exists {
|
|
return fmt.Errorf("bridge '%s' is not registered", name)
|
|
}
|
|
|
|
m.logger.LogInfo("Stopping bridge", "name", name)
|
|
|
|
if err := bridge.Stop(); err != nil {
|
|
m.logger.LogError("Failed to stop bridge", "name", name, "error", err)
|
|
return fmt.Errorf("failed to stop bridge '%s': %w", name, err)
|
|
}
|
|
|
|
m.logger.LogInfo("Bridge stopped successfully", "name", name)
|
|
return nil
|
|
}
|
|
|
|
// UnregisterBridge removes a bridge from the manager
|
|
func (m *Manager) UnregisterBridge(name string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
bridge, exists := m.bridges[name]
|
|
if !exists {
|
|
return fmt.Errorf("bridge '%s' is not registered", name)
|
|
}
|
|
|
|
// Stop the bridge before unregistering
|
|
if bridge.IsConnected() {
|
|
if err := bridge.Stop(); err != nil {
|
|
m.logger.LogWarn("Failed to stop bridge during unregistration", "name", name, "error", err)
|
|
}
|
|
}
|
|
|
|
delete(m.bridges, name)
|
|
m.logger.LogInfo("Bridge unregistered", "name", name)
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetBridge retrieves a bridge by name
|
|
func (m *Manager) GetBridge(name string) (model.Bridge, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
bridge, exists := m.bridges[name]
|
|
if !exists {
|
|
return nil, fmt.Errorf("bridge '%s' is not registered", name)
|
|
}
|
|
|
|
return bridge, nil
|
|
}
|
|
|
|
// ListBridges returns a list of all registered bridge names
|
|
func (m *Manager) ListBridges() []string {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
bridges := make([]string, 0, len(m.bridges))
|
|
for name := range m.bridges {
|
|
bridges = append(bridges, name)
|
|
}
|
|
|
|
return bridges
|
|
}
|
|
|
|
// HasBridge checks if a bridge with the given name is registered
|
|
func (m *Manager) HasBridge(name string) bool {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
_, exists := m.bridges[name]
|
|
return exists
|
|
}
|
|
|
|
// HasBridges checks if any bridges are registered
|
|
func (m *Manager) HasBridges() bool {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
return len(m.bridges) > 0
|
|
}
|
|
|
|
// Shutdown stops and unregisters all bridges
|
|
func (m *Manager) Shutdown() error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
m.logger.LogInfo("Shutting down bridge manager", "bridge_count", len(m.bridges))
|
|
|
|
var errors []error
|
|
for name, bridge := range m.bridges {
|
|
if bridge.IsConnected() {
|
|
if err := bridge.Stop(); err != nil {
|
|
errors = append(errors, fmt.Errorf("failed to stop bridge '%s': %w", name, err))
|
|
m.logger.LogError("Failed to stop bridge during shutdown", "name", name, "error", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear all bridges
|
|
m.bridges = make(map[string]model.Bridge)
|
|
|
|
m.logger.LogInfo("Bridge manager shutdown complete")
|
|
|
|
if len(errors) > 0 {
|
|
return fmt.Errorf("shutdown completed with errors: %v", errors)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// OnPluginConfigurationChange propagates configuration changes to all registered bridges
|
|
func (m *Manager) OnPluginConfigurationChange(config any) error {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
if len(m.bridges) == 0 {
|
|
return nil
|
|
}
|
|
|
|
m.logger.LogInfo("Plugin configuration changed, propagating to bridges", "bridge_count", len(m.bridges))
|
|
|
|
var errors []error
|
|
for name, bridge := range m.bridges {
|
|
if err := bridge.UpdateConfiguration(config); err != nil {
|
|
errors = append(errors, fmt.Errorf("failed to update configuration for bridge '%s': %w", name, err))
|
|
m.logger.LogError("Failed to update bridge configuration", "name", name, "error", err)
|
|
} else {
|
|
m.logger.LogDebug("Successfully updated bridge configuration", "name", name)
|
|
}
|
|
}
|
|
|
|
if len(errors) > 0 {
|
|
return fmt.Errorf("configuration update completed with errors: %v", errors)
|
|
}
|
|
|
|
m.logger.LogInfo("Configuration changes propagated to all bridges")
|
|
return nil
|
|
}
|
|
|
|
// OnChannelMappingCreated handles the creation of a channel mapping by calling the appropriate bridge
|
|
func (m *Manager) OnChannelMappingCreated(channelID, bridgeName, bridgeRoomID string) error {
|
|
// Input validation
|
|
if channelID == "" {
|
|
return fmt.Errorf("channelID cannot be empty")
|
|
}
|
|
if bridgeName == "" {
|
|
return fmt.Errorf("bridgeName cannot be empty")
|
|
}
|
|
if bridgeRoomID == "" {
|
|
return fmt.Errorf("bridgeRoomID cannot be empty")
|
|
}
|
|
|
|
m.logger.LogDebug("Creating channel mapping", "channel_id", channelID, "bridge_name", bridgeName, "bridge_room_id", bridgeRoomID)
|
|
|
|
// Get the specific bridge
|
|
bridge, err := m.GetBridge(bridgeName)
|
|
if err != nil {
|
|
m.logger.LogError("Failed to get bridge", "bridge_name", bridgeName, "error", err)
|
|
return fmt.Errorf("failed to get bridge '%s': %w", bridgeName, err)
|
|
}
|
|
|
|
// Check if bridge is connected
|
|
if !bridge.IsConnected() {
|
|
return fmt.Errorf("bridge '%s' is not connected", bridgeName)
|
|
}
|
|
|
|
// Create the channel mapping on the receiving bridge
|
|
if err = bridge.CreateChannelMapping(channelID, bridgeRoomID); err != nil {
|
|
m.logger.LogError("Failed to create channel mapping", "channel_id", channelID, "bridge_name", bridgeName, "bridge_room_id", bridgeRoomID, "error", err)
|
|
return fmt.Errorf("failed to create channel mapping for bridge '%s': %w", bridgeName, err)
|
|
}
|
|
|
|
mattermostBridge, err := m.GetBridge("mattermost")
|
|
if err != nil {
|
|
m.logger.LogError("Failed to get Mattermost bridge", "error", err)
|
|
return fmt.Errorf("failed to get Mattermost bridge: %w", err)
|
|
}
|
|
|
|
// Create the channel mapping in the Mattermost bridge
|
|
if err = mattermostBridge.CreateChannelMapping(channelID, bridgeRoomID); err != nil {
|
|
m.logger.LogError("Failed to create channel mapping in Mattermost bridge", "channel_id", channelID, "bridge_name", bridgeName, "bridge_room_id", bridgeRoomID, "error", err)
|
|
return fmt.Errorf("failed to create channel mapping in Mattermost bridge: %w", err)
|
|
}
|
|
|
|
m.logger.LogInfo("Successfully created channel mapping", "channel_id", channelID, "bridge_name", bridgeName, "bridge_room_id", bridgeRoomID)
|
|
return nil
|
|
}
|
|
|
|
// OnChannelMappingDeleted handles the deletion of a channel mapping by calling the appropriate bridges
|
|
func (m *Manager) OnChannelMappingDeleted(channelID, bridgeName string) error {
|
|
// Input validation
|
|
if channelID == "" {
|
|
return fmt.Errorf("channelID cannot be empty")
|
|
}
|
|
if bridgeName == "" {
|
|
return fmt.Errorf("bridgeName cannot be empty")
|
|
}
|
|
|
|
m.logger.LogDebug("Deleting channel mapping", "channel_id", channelID, "bridge_name", bridgeName)
|
|
|
|
// Get the specific bridge
|
|
bridge, err := m.GetBridge(bridgeName)
|
|
if err != nil {
|
|
m.logger.LogError("Failed to get bridge", "bridge_name", bridgeName, "error", err)
|
|
return fmt.Errorf("failed to get bridge '%s': %w", bridgeName, err)
|
|
}
|
|
|
|
// Check if bridge is connected
|
|
if !bridge.IsConnected() {
|
|
return fmt.Errorf("bridge '%s' is not connected", bridgeName)
|
|
}
|
|
|
|
// Delete the channel mapping from the specific bridge
|
|
if err = bridge.DeleteChannelMapping(channelID); err != nil {
|
|
m.logger.LogError("Failed to delete channel mapping", "channel_id", channelID, "bridge_name", bridgeName, "error", err)
|
|
return fmt.Errorf("failed to delete channel mapping for bridge '%s': %w", bridgeName, err)
|
|
}
|
|
|
|
// Also delete from Mattermost bridge to clean up reverse mappings
|
|
mattermostBridge, err := m.GetBridge("mattermost")
|
|
if err != nil {
|
|
m.logger.LogError("Failed to get Mattermost bridge", "error", err)
|
|
return fmt.Errorf("failed to get Mattermost bridge: %w", err)
|
|
}
|
|
|
|
// Delete the channel mapping from the Mattermost bridge
|
|
if err = mattermostBridge.DeleteChannelMapping(channelID); err != nil {
|
|
m.logger.LogError("Failed to delete channel mapping from Mattermost bridge", "channel_id", channelID, "bridge_name", bridgeName, "error", err)
|
|
return fmt.Errorf("failed to delete channel mapping from Mattermost bridge: %w", err)
|
|
}
|
|
|
|
m.logger.LogInfo("Successfully deleted channel mapping", "channel_id", channelID, "bridge_name", bridgeName)
|
|
return nil
|
|
}
|