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:
parent
5d143808a3
commit
2e13d96dce
7 changed files with 480 additions and 43 deletions
257
server/bridge/mattermost/bridge.go
Normal file
257
server/bridge/mattermost/bridge.go
Normal 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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue