This commit implements a complete multi-user bridge management system that allows bridges to control multiple users with async goroutine management and convenience methods for channel operations. Key features: - Bridge-agnostic BridgeUser interface with validation, identity, state management, channel operations, connection lifecycle, and goroutine lifecycle methods - BridgeUserManager interface for user lifecycle management with bridge type identification - XMPPUser implementation for XMPP bridge with XMPP client integration, connection monitoring, and room operations - MattermostUser implementation for Mattermost bridge with API integration and channel management - Updated Bridge interface to include GetUserManager() method - Base UserManager implementation with generic user management logic - Added Ping() and CheckChannelExists() methods to BridgeUser interface for health checking and room validation - Updated bridge manager naming from Manager to BridgeManager for clarity The system enables bridges to manage multiple users (like "Mattermost Bridge" user in XMPP) with proper state management, connection monitoring, and channel operations abstracted across different bridge protocols. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
207 lines
6.2 KiB
Go
207 lines
6.2 KiB
Go
package model
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/config"
|
|
)
|
|
|
|
type BridgeID string
|
|
|
|
type UserState int
|
|
|
|
const (
|
|
UserStateOnline UserState = iota
|
|
UserStateAway
|
|
UserStateBusy
|
|
UserStateOffline
|
|
)
|
|
|
|
// CreateChannelMappingRequest contains information needed to create a channel mapping
|
|
type CreateChannelMappingRequest struct {
|
|
ChannelID string // Mattermost channel ID
|
|
BridgeName string // Name of the bridge (e.g., "xmpp")
|
|
BridgeRoomID string // Remote room/channel ID (e.g., JID for XMPP)
|
|
UserID string // ID of user who triggered the mapping creation
|
|
TeamID string // Team ID where the channel belongs
|
|
}
|
|
|
|
// Validate checks if all required fields are present and valid
|
|
func (r CreateChannelMappingRequest) Validate() error {
|
|
if r.ChannelID == "" {
|
|
return fmt.Errorf("channelID cannot be empty")
|
|
}
|
|
if r.BridgeName == "" {
|
|
return fmt.Errorf("bridgeName cannot be empty")
|
|
}
|
|
if r.BridgeRoomID == "" {
|
|
return fmt.Errorf("bridgeRoomID cannot be empty")
|
|
}
|
|
if r.UserID == "" {
|
|
return fmt.Errorf("userID cannot be empty")
|
|
}
|
|
if r.TeamID == "" {
|
|
return fmt.Errorf("teamID cannot be empty")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DeleteChannelMappingRequest contains information needed to delete a channel mapping
|
|
type DeleteChannelMappingRequest struct {
|
|
ChannelID string // Mattermost channel ID
|
|
BridgeName string // Name of the bridge (e.g., "xmpp")
|
|
UserID string // ID of user who triggered the mapping deletion
|
|
TeamID string // Team ID where the channel belongs
|
|
}
|
|
|
|
// Validate checks if all required fields are present and valid
|
|
func (r DeleteChannelMappingRequest) Validate() error {
|
|
if r.ChannelID == "" {
|
|
return fmt.Errorf("channelID cannot be empty")
|
|
}
|
|
if r.BridgeName == "" {
|
|
return fmt.Errorf("bridgeName cannot be empty")
|
|
}
|
|
if r.UserID == "" {
|
|
return fmt.Errorf("userID cannot be empty")
|
|
}
|
|
if r.TeamID == "" {
|
|
return fmt.Errorf("teamID cannot be empty")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type BridgeManager interface {
|
|
// RegisterBridge registers a bridge with the given name. Returns an error if the name is empty,
|
|
// the bridge is nil, or a bridge with the same name is already registered.
|
|
RegisterBridge(name string, bridge Bridge) error
|
|
|
|
// StartBridge starts the bridge with the given name. Returns an error if the bridge
|
|
// is not registered or fails to start.
|
|
StartBridge(name string) error
|
|
|
|
// StopBridge stops the bridge with the given name. Returns an error if the bridge
|
|
// is not registered or fails to stop.
|
|
StopBridge(name string) error
|
|
|
|
// UnregisterBridge removes the bridge with the given name from the manager.
|
|
// The bridge is stopped before removal if it's currently connected.
|
|
// Returns an error if the bridge is not registered.
|
|
UnregisterBridge(name string) error
|
|
|
|
// GetBridge retrieves the bridge instance with the given name.
|
|
// Returns an error if the bridge is not registered.
|
|
GetBridge(name string) (Bridge, error)
|
|
|
|
// ListBridges returns a list of all registered bridge names.
|
|
ListBridges() []string
|
|
|
|
// HasBridge checks if a bridge with the given name is registered.
|
|
HasBridge(name string) bool
|
|
|
|
// HasBridges checks if any bridges are currently registered.
|
|
HasBridges() bool
|
|
|
|
// Shutdown stops and unregisters all bridges. Returns an error if any bridge
|
|
// fails to stop, but continues to attempt stopping all bridges.
|
|
Shutdown() error
|
|
|
|
// OnPluginConfigurationChange propagates configuration changes to all registered bridges.
|
|
// Returns an error if any bridge fails to update its configuration, but continues to
|
|
// attempt updating all bridges.
|
|
OnPluginConfigurationChange(config any) error
|
|
|
|
// CreateChannelMapping is called when a channel mapping is created.
|
|
CreateChannelMapping(req CreateChannelMappingRequest) error
|
|
|
|
// DeleteChannepMapping is called when a channel mapping is deleted.
|
|
DeleteChannepMapping(req DeleteChannelMappingRequest) error
|
|
}
|
|
|
|
type Bridge interface {
|
|
// UpdateConfiguration updates the bridge configuration
|
|
UpdateConfiguration(config any) error
|
|
|
|
// Start starts the bridge
|
|
Start() error
|
|
|
|
// Stop stops the bridge
|
|
Stop() error
|
|
|
|
// CreateChannelMapping creates a mapping between a Mattermost channel ID and an bridge room ID.
|
|
CreateChannelMapping(channelID, roomJID string) error
|
|
|
|
// GetChannelMapping retrieves the bridge room ID for a given Mattermost channel ID.
|
|
GetChannelMapping(channelID string) (string, error)
|
|
|
|
// DeleteChannelMapping removes a mapping between a Mattermost channel ID and a bridge room ID.
|
|
DeleteChannelMapping(channelID string) error
|
|
|
|
// RoomExists checks if a room/channel exists on the remote service.
|
|
RoomExists(roomID string) (bool, error)
|
|
|
|
// GetRoomMapping retrieves the Mattermost channel ID for a given room ID (reverse lookup).
|
|
GetRoomMapping(roomID string) (string, error)
|
|
|
|
// IsConnected checks if the bridge is connected to the remote service.
|
|
IsConnected() bool
|
|
|
|
// Ping actively tests the bridge connection health by sending a lightweight request.
|
|
Ping() error
|
|
|
|
// GetUserManager returns the user manager for this bridge.
|
|
GetUserManager() BridgeUserManager
|
|
}
|
|
|
|
// BridgeUser represents a user connected to any bridge service
|
|
type BridgeUser interface {
|
|
// Validation
|
|
Validate() error
|
|
|
|
// Identity (bridge-agnostic)
|
|
GetID() string
|
|
GetDisplayName() string
|
|
|
|
// State management
|
|
GetState() UserState
|
|
SetState(state UserState) error
|
|
|
|
// Channel operations (abstracted from rooms/channels/groups)
|
|
JoinChannel(channelID string) error
|
|
LeaveChannel(channelID string) error
|
|
SendMessageToChannel(channelID, message string) error
|
|
|
|
// Connection lifecycle
|
|
Connect() error
|
|
Disconnect() error
|
|
IsConnected() bool
|
|
Ping() error
|
|
|
|
// Channel existence check
|
|
CheckChannelExists(channelID string) (bool, error)
|
|
|
|
// Goroutine lifecycle
|
|
Start(ctx context.Context) error
|
|
Stop() error
|
|
}
|
|
|
|
// BridgeUserManager manages users for a specific bridge
|
|
type BridgeUserManager interface {
|
|
// User lifecycle
|
|
CreateUser(user BridgeUser) error
|
|
GetUser(userID string) (BridgeUser, error)
|
|
DeleteUser(userID string) error
|
|
ListUsers() []BridgeUser
|
|
HasUser(userID string) bool
|
|
|
|
// Manager lifecycle
|
|
Start(ctx context.Context) error
|
|
Stop() error
|
|
|
|
// Configuration updates
|
|
UpdateConfiguration(config *config.Configuration) error
|
|
|
|
// Bridge type identification
|
|
GetBridgeType() string
|
|
}
|