feat: implement bridge-agnostic channel mapping keys

- Replace channel mapping keys with bridge-agnostic pattern: channel_map_<bridge>_<identifier>
- XMPP mappings now use: channel_map_mattermost_<channelID> → roomJID, channel_map_xmpp_<roomJID> → channelID
- Update KV store constants with BuildChannelMapKey() and ExtractIdentifierFromChannelMapKey()
- Make KV store completely bridge-agnostic for future Matrix/Discord/Slack bridge support
- Fix getAllChannelMappings() to correctly read XMPP keys for room joining on startup
- Scalable design supports N bridges with consistent naming pattern

🤖 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 16:20:21 +02:00
parent 8a8c9af611
commit 43f0fb1892
No known key found for this signature in database
GPG key ID: 52E5D65FCF99808A
2 changed files with 33 additions and 32 deletions

View file

@ -272,28 +272,30 @@ func (b *xmppBridge) getAllChannelMappings() (map[string]string, error) {
mappings := make(map[string]string) mappings := make(map[string]string)
// Get all keys with the channel mapping prefix // Get all keys with the XMPP room mapping prefix to find all mapped rooms
keys, err := b.kvstore.ListKeysWithPrefix(0, 1000, kvstore.KeyPrefixChannelMapping) xmppPrefix := kvstore.KeyPrefixChannelMap + "xmpp_"
keys, err := b.kvstore.ListKeysWithPrefix(0, 1000, xmppPrefix)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to list channel mapping keys: %w", err) return nil, fmt.Errorf("failed to list XMPP room mapping keys: %w", err)
} }
// Load each mapping // Load each mapping
for _, key := range keys { for _, key := range keys {
roomJIDBytes, err := b.kvstore.Get(key) channelIDBytes, err := b.kvstore.Get(key)
if err != nil { if err != nil {
b.logger.LogWarn("Failed to load mapping for key", "key", key, "error", err) b.logger.LogWarn("Failed to load mapping for key", "key", key, "error", err)
continue continue
} }
// Extract channel ID from the key // Extract room JID from the key
channelID := kvstore.ExtractChannelIDFromKey(key) roomJID := kvstore.ExtractIdentifierFromChannelMapKey(key, "xmpp")
if channelID == "" { if roomJID == "" {
b.logger.LogWarn("Failed to extract channel ID from key", "key", key) b.logger.LogWarn("Failed to extract room JID from key", "key", key)
continue continue
} }
mappings[channelID] = string(roomJIDBytes) channelID := string(channelIDBytes)
mappings[channelID] = roomJID
} }
return mappings, nil return mappings, nil
@ -382,13 +384,13 @@ func (b *xmppBridge) CreateChannelRoomMapping(channelID, roomJID string) error {
return fmt.Errorf("KV store not initialized") return fmt.Errorf("KV store not initialized")
} }
// Store forward and reverse mappings // Store forward and reverse mappings using bridge-agnostic keys
err := b.kvstore.Set(kvstore.BuildChannelMappingKey(channelID), []byte(roomJID)) err := b.kvstore.Set(kvstore.BuildChannelMapKey("mattermost", channelID), []byte(roomJID))
if err != nil { if err != nil {
return fmt.Errorf("failed to store channel room mapping: %w", err) return fmt.Errorf("failed to store channel room mapping: %w", err)
} }
err = b.kvstore.Set(kvstore.BuildRoomMappingKey(roomJID), []byte(channelID)) err = b.kvstore.Set(kvstore.BuildChannelMapKey("xmpp", roomJID), []byte(channelID))
if err != nil { if err != nil {
return fmt.Errorf("failed to store reverse room mapping: %w", err) return fmt.Errorf("failed to store reverse room mapping: %w", err)
} }
@ -424,8 +426,8 @@ func (b *xmppBridge) GetChannelRoomMapping(channelID string) (string, error) {
return "", fmt.Errorf("KV store not initialized") return "", fmt.Errorf("KV store not initialized")
} }
// Load from KV store // Check if we have a mapping in the KV store for this channel ID
roomJIDBytes, err := b.kvstore.Get(kvstore.BuildChannelMappingKey(channelID)) roomJIDBytes, err := b.kvstore.Get(kvstore.BuildChannelMapKey("mattermost", channelID))
if err != nil { if err != nil {
return "", nil // Unmapped channels are expected return "", nil // Unmapped channels are expected
} }

View file

@ -1,5 +1,7 @@
package kvstore package kvstore
import "strings"
// KV Store key prefixes and constants // KV Store key prefixes and constants
// This file centralizes all KV store key patterns used throughout the plugin // This file centralizes all KV store key patterns used throughout the plugin
// to ensure consistency and avoid key conflicts. // to ensure consistency and avoid key conflicts.
@ -12,10 +14,8 @@ const (
// KeyPrefixMattermostUser is the prefix for Mattermost user ID -> XMPP user ID mappings // KeyPrefixMattermostUser is the prefix for Mattermost user ID -> XMPP user ID mappings
KeyPrefixMattermostUser = "mattermost_user_" KeyPrefixMattermostUser = "mattermost_user_"
// KeyPrefixChannelMapping is the prefix for Mattermost channel ID -> XMPP room mappings // KeyPrefixChannelMap is the prefix for bridge-agnostic channel mappings
KeyPrefixChannelMapping = "channel_mapping_" KeyPrefixChannelMap = "channel_map_"
// KeyPrefixRoomMapping is the prefix for XMPP room identifier -> Mattermost channel ID mappings
KeyPrefixRoomMapping = "xmpp_room_mapping_"
// KeyPrefixGhostUser is the prefix for Mattermost user ID -> XMPP ghost user ID cache // KeyPrefixGhostUser is the prefix for Mattermost user ID -> XMPP ghost user ID cache
KeyPrefixGhostUser = "ghost_user_" KeyPrefixGhostUser = "ghost_user_"
@ -30,9 +30,9 @@ const (
// KeyStoreVersion is the key for tracking the current KV store schema version // KeyStoreVersion is the key for tracking the current KV store schema version
KeyStoreVersion = "kv_store_version" KeyStoreVersion = "kv_store_version"
// KeyPrefixLegacyDMMapping was the old prefix for DM mappings (migrated to channel_mapping_) // KeyPrefixLegacyDMMapping was the old prefix for DM mappings
KeyPrefixLegacyDMMapping = "dm_mapping_" KeyPrefixLegacyDMMapping = "dm_mapping_"
// KeyPrefixLegacyXMPPDMMapping was the old prefix for XMPP DM mappings (migrated to room_mapping_) // KeyPrefixLegacyXMPPDMMapping was the old prefix for XMPP DM mappings
KeyPrefixLegacyXMPPDMMapping = "xmpp_dm_mapping_" KeyPrefixLegacyXMPPDMMapping = "xmpp_dm_mapping_"
) )
@ -48,14 +48,9 @@ func BuildMattermostUserKey(mattermostUserID string) string {
return KeyPrefixMattermostUser + mattermostUserID return KeyPrefixMattermostUser + mattermostUserID
} }
// BuildChannelMappingKey creates a key for channel -> room mapping // BuildChannelMapKey creates a bridge-agnostic key for channel mappings
func BuildChannelMappingKey(channelID string) string { func BuildChannelMapKey(bridgeName, identifier string) string {
return KeyPrefixChannelMapping + channelID return KeyPrefixChannelMap + bridgeName + "_" + identifier
}
// BuildRoomMappingKey creates a key for room -> channel mapping
func BuildRoomMappingKey(roomIdentifier string) string {
return KeyPrefixRoomMapping + roomIdentifier
} }
// BuildGhostUserKey creates a key for ghost user cache // BuildGhostUserKey creates a key for ghost user cache
@ -78,10 +73,14 @@ func BuildXMPPReactionKey(reactionEventID string) string {
return KeyPrefixXMPPReaction + reactionEventID return KeyPrefixXMPPReaction + reactionEventID
} }
// ExtractChannelIDFromKey extracts the channel ID from a channel mapping key // ExtractIdentifierFromChannelMapKey extracts the identifier from a bridge-agnostic channel map key
func ExtractChannelIDFromKey(key string) string { func ExtractIdentifierFromChannelMapKey(key, bridgeName string) string {
if len(key) <= len(KeyPrefixChannelMapping) { expectedPrefix := KeyPrefixChannelMap + bridgeName + "_"
if len(key) <= len(expectedPrefix) {
return "" return ""
} }
return key[len(KeyPrefixChannelMapping):] if !strings.HasPrefix(key, expectedPrefix) {
return ""
}
return key[len(expectedPrefix):]
} }