From 43f0fb1892b9ec71857e8297d34e7111c445b8a5 Mon Sep 17 00:00:00 2001 From: Felipe Martin Date: Fri, 1 Aug 2025 16:20:21 +0200 Subject: [PATCH] feat: implement bridge-agnostic channel mapping keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace channel mapping keys with bridge-agnostic pattern: channel_map__ - XMPP mappings now use: channel_map_mattermost_ → roomJID, channel_map_xmpp_ → 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 --- server/bridge/xmpp/bridge.go | 30 +++++++++++++------------- server/store/kvstore/constants.go | 35 +++++++++++++++---------------- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/server/bridge/xmpp/bridge.go b/server/bridge/xmpp/bridge.go index c5517ae..4c6392a 100644 --- a/server/bridge/xmpp/bridge.go +++ b/server/bridge/xmpp/bridge.go @@ -272,28 +272,30 @@ func (b *xmppBridge) getAllChannelMappings() (map[string]string, error) { mappings := make(map[string]string) - // Get all keys with the channel mapping prefix - keys, err := b.kvstore.ListKeysWithPrefix(0, 1000, kvstore.KeyPrefixChannelMapping) + // Get all keys with the XMPP room mapping prefix to find all mapped rooms + xmppPrefix := kvstore.KeyPrefixChannelMap + "xmpp_" + keys, err := b.kvstore.ListKeysWithPrefix(0, 1000, xmppPrefix) 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 for _, key := range keys { - roomJIDBytes, err := b.kvstore.Get(key) + channelIDBytes, err := b.kvstore.Get(key) if err != nil { b.logger.LogWarn("Failed to load mapping for key", "key", key, "error", err) continue } - // Extract channel ID from the key - channelID := kvstore.ExtractChannelIDFromKey(key) - if channelID == "" { - b.logger.LogWarn("Failed to extract channel ID from key", "key", key) + // Extract room JID from the key + roomJID := kvstore.ExtractIdentifierFromChannelMapKey(key, "xmpp") + if roomJID == "" { + b.logger.LogWarn("Failed to extract room JID from key", "key", key) continue } - mappings[channelID] = string(roomJIDBytes) + channelID := string(channelIDBytes) + mappings[channelID] = roomJID } return mappings, nil @@ -382,13 +384,13 @@ func (b *xmppBridge) CreateChannelRoomMapping(channelID, roomJID string) error { return fmt.Errorf("KV store not initialized") } - // Store forward and reverse mappings - err := b.kvstore.Set(kvstore.BuildChannelMappingKey(channelID), []byte(roomJID)) + // Store forward and reverse mappings using bridge-agnostic keys + err := b.kvstore.Set(kvstore.BuildChannelMapKey("mattermost", channelID), []byte(roomJID)) if err != nil { 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 { 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") } - // Load from KV store - roomJIDBytes, err := b.kvstore.Get(kvstore.BuildChannelMappingKey(channelID)) + // Check if we have a mapping in the KV store for this channel ID + roomJIDBytes, err := b.kvstore.Get(kvstore.BuildChannelMapKey("mattermost", channelID)) if err != nil { return "", nil // Unmapped channels are expected } diff --git a/server/store/kvstore/constants.go b/server/store/kvstore/constants.go index 4150410..579bd5c 100644 --- a/server/store/kvstore/constants.go +++ b/server/store/kvstore/constants.go @@ -1,5 +1,7 @@ package kvstore +import "strings" + // KV Store key prefixes and constants // This file centralizes all KV store key patterns used throughout the plugin // 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 = "mattermost_user_" - // KeyPrefixChannelMapping is the prefix for Mattermost channel ID -> XMPP room mappings - KeyPrefixChannelMapping = "channel_mapping_" - // KeyPrefixRoomMapping is the prefix for XMPP room identifier -> Mattermost channel ID mappings - KeyPrefixRoomMapping = "xmpp_room_mapping_" + // KeyPrefixChannelMap is the prefix for bridge-agnostic channel mappings + KeyPrefixChannelMap = "channel_map_" // KeyPrefixGhostUser is the prefix for Mattermost user ID -> XMPP ghost user ID cache KeyPrefixGhostUser = "ghost_user_" @@ -30,9 +30,9 @@ const ( // KeyStoreVersion is the key for tracking the current KV store schema 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_" - // 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_" ) @@ -48,14 +48,9 @@ func BuildMattermostUserKey(mattermostUserID string) string { return KeyPrefixMattermostUser + mattermostUserID } -// BuildChannelMappingKey creates a key for channel -> room mapping -func BuildChannelMappingKey(channelID string) string { - return KeyPrefixChannelMapping + channelID -} - -// BuildRoomMappingKey creates a key for room -> channel mapping -func BuildRoomMappingKey(roomIdentifier string) string { - return KeyPrefixRoomMapping + roomIdentifier +// BuildChannelMapKey creates a bridge-agnostic key for channel mappings +func BuildChannelMapKey(bridgeName, identifier string) string { + return KeyPrefixChannelMap + bridgeName + "_" + identifier } // BuildGhostUserKey creates a key for ghost user cache @@ -78,10 +73,14 @@ func BuildXMPPReactionKey(reactionEventID string) string { return KeyPrefixXMPPReaction + reactionEventID } -// ExtractChannelIDFromKey extracts the channel ID from a channel mapping key -func ExtractChannelIDFromKey(key string) string { - if len(key) <= len(KeyPrefixChannelMapping) { +// ExtractIdentifierFromChannelMapKey extracts the identifier from a bridge-agnostic channel map key +func ExtractIdentifierFromChannelMapKey(key, bridgeName string) string { + expectedPrefix := KeyPrefixChannelMap + bridgeName + "_" + if len(key) <= len(expectedPrefix) { return "" } - return key[len(KeyPrefixChannelMapping):] + if !strings.HasPrefix(key, expectedPrefix) { + return "" + } + return key[len(expectedPrefix):] }