mattermost-plugin-bridge-xmpp/server/bridge/manager.go
Felipe Martin 2e13d96dce
feat: implement centralized channel mapping management
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>
2025-08-01 18:18:10 +02:00

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
}