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>
This commit is contained in:
Felipe M 2025-08-01 18:18:10 +02:00
parent 5d143808a3
commit 2e13d96dce
No known key found for this signature in database
GPG key ID: 52E5D65FCF99808A
7 changed files with 480 additions and 43 deletions

View file

@ -0,0 +1,257 @@
package mattermost
import (
"context"
"fmt"
"sync"
"sync/atomic"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/config"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/logger"
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/store/kvstore"
"github.com/mattermost/mattermost/server/public/plugin"
)
// mattermostBridge handles syncing messages between Mattermost instances
type mattermostBridge struct {
logger logger.Logger
api plugin.API
kvstore kvstore.KVStore
// Connection management
connected atomic.Bool
ctx context.Context
cancel context.CancelFunc
// Current configuration
config *config.Configuration
configMu sync.RWMutex
// Channel mappings cache
channelMappings map[string]string
mappingsMu sync.RWMutex
}
// NewBridge creates a new Mattermost bridge
func NewBridge(log logger.Logger, api plugin.API, kvstore kvstore.KVStore, cfg *config.Configuration) pluginModel.Bridge {
ctx, cancel := context.WithCancel(context.Background())
bridge := &mattermostBridge{
logger: log,
api: api,
kvstore: kvstore,
ctx: ctx,
cancel: cancel,
channelMappings: make(map[string]string),
config: cfg,
}
return bridge
}
// UpdateConfiguration updates the bridge configuration
func (b *mattermostBridge) UpdateConfiguration(newConfig any) error {
cfg, ok := newConfig.(*config.Configuration)
if !ok {
return fmt.Errorf("invalid configuration type")
}
b.configMu.Lock()
oldConfig := b.config
b.config = cfg
b.configMu.Unlock()
// Log the configuration change
b.logger.LogInfo("Mattermost bridge configuration updated", "old_config", oldConfig, "new_config", cfg)
return nil
}
// Start initializes the bridge
func (b *mattermostBridge) Start() error {
b.logger.LogDebug("Starting Mattermost bridge")
b.configMu.RLock()
config := b.config
b.configMu.RUnlock()
if config == nil {
return fmt.Errorf("bridge configuration not set")
}
// For Mattermost bridge, we're always "connected" since we're running within Mattermost
b.connected.Store(true)
// Load existing channel mappings
if err := b.loadChannelMappings(); err != nil {
b.logger.LogWarn("Failed to load some channel mappings", "error", err)
}
b.logger.LogInfo("Mattermost bridge started successfully")
return nil
}
// Stop shuts down the bridge
func (b *mattermostBridge) Stop() error {
b.logger.LogInfo("Stopping Mattermost bridge")
if b.cancel != nil {
b.cancel()
}
b.connected.Store(false)
b.logger.LogInfo("Mattermost bridge stopped")
return nil
}
// loadChannelMappings loads existing channel mappings from KV store
func (b *mattermostBridge) loadChannelMappings() error {
b.logger.LogDebug("Loading channel mappings for Mattermost bridge")
// Get all channel mappings from KV store for Mattermost bridge
mappings, err := b.getAllChannelMappings()
if err != nil {
return fmt.Errorf("failed to load channel mappings: %w", err)
}
if len(mappings) == 0 {
b.logger.LogInfo("No channel mappings found for Mattermost bridge")
return nil
}
b.logger.LogInfo("Found channel mappings for Mattermost bridge", "count", len(mappings))
// Update local cache
b.mappingsMu.Lock()
for channelID, roomID := range mappings {
b.channelMappings[channelID] = roomID
}
b.mappingsMu.Unlock()
return nil
}
// getAllChannelMappings retrieves all channel mappings from KV store for Mattermost bridge
func (b *mattermostBridge) getAllChannelMappings() (map[string]string, error) {
if b.kvstore == nil {
return nil, fmt.Errorf("KV store not initialized")
}
mappings := make(map[string]string)
// Get all keys with the Mattermost bridge mapping prefix
mattermostPrefix := kvstore.KeyPrefixChannelMap + "mattermost_"
keys, err := b.kvstore.ListKeysWithPrefix(0, 1000, mattermostPrefix)
if err != nil {
return nil, fmt.Errorf("failed to list Mattermost bridge mapping keys: %w", err)
}
// Load each mapping
for _, key := range keys {
channelIDBytes, err := b.kvstore.Get(key)
if err != nil {
b.logger.LogWarn("Failed to load mapping for key", "key", key, "error", err)
continue
}
// Extract room ID from the key
roomID := kvstore.ExtractIdentifierFromChannelMapKey(key, "mattermost")
if roomID == "" {
b.logger.LogWarn("Failed to extract room ID from key", "key", key)
continue
}
channelID := string(channelIDBytes)
mappings[channelID] = roomID
}
return mappings, nil
}
// IsConnected returns whether the bridge is connected
func (b *mattermostBridge) IsConnected() bool {
// Mattermost bridge is always "connected" since it runs within Mattermost
return b.connected.Load()
}
// CreateChannelMapping creates a mapping between a Mattermost channel and another Mattermost room/channel
func (b *mattermostBridge) CreateChannelMapping(channelID, roomID string) error {
if b.kvstore == nil {
return fmt.Errorf("KV store not initialized")
}
// Store forward and reverse mappings using bridge-agnostic keys
err := b.kvstore.Set(kvstore.BuildChannelMapKey("mattermost", channelID), []byte(roomID))
if err != nil {
return fmt.Errorf("failed to store channel room mapping: %w", err)
}
// Update local cache
b.mappingsMu.Lock()
b.channelMappings[channelID] = roomID
b.mappingsMu.Unlock()
b.logger.LogInfo("Created Mattermost channel room mapping", "channel_id", channelID, "room_id", roomID)
return nil
}
// GetChannelMapping gets the room ID for a Mattermost channel
func (b *mattermostBridge) GetChannelMapping(channelID string) (string, error) {
// Check cache first
b.mappingsMu.RLock()
roomID, exists := b.channelMappings[channelID]
b.mappingsMu.RUnlock()
if exists {
return roomID, nil
}
if b.kvstore == nil {
return "", fmt.Errorf("KV store not initialized")
}
// Check if we have a mapping in the KV store for this channel ID
roomIDBytes, err := b.kvstore.Get(kvstore.BuildChannelMapKey("mattermost", channelID))
if err != nil {
return "", nil // Unmapped channels are expected
}
roomID = string(roomIDBytes)
// Update cache
b.mappingsMu.Lock()
b.channelMappings[channelID] = roomID
b.mappingsMu.Unlock()
return roomID, nil
}
// DeleteChannelMapping removes a mapping between a Mattermost channel and room
func (b *mattermostBridge) DeleteChannelMapping(channelID string) error {
if b.kvstore == nil {
return fmt.Errorf("KV store not initialized")
}
// Get the room ID from the mapping before deleting
roomID, err := b.GetChannelMapping(channelID)
if err != nil {
return fmt.Errorf("failed to get channel mapping: %w", err)
}
if roomID == "" {
return fmt.Errorf("channel is not mapped to any room")
}
// Delete forward and reverse mappings from KV store
err = b.kvstore.Delete(kvstore.BuildChannelMapKey("mattermost", channelID))
if err != nil {
return fmt.Errorf("failed to delete channel room mapping: %w", err)
}
// Remove from local cache
b.mappingsMu.Lock()
delete(b.channelMappings, channelID)
b.mappingsMu.Unlock()
b.logger.LogInfo("Deleted Mattermost channel room mapping", "channel_id", channelID, "room_id", roomID)
return nil
}