mattermost-plugin-bridge-xmpp/server/bridge/mattermost/user.go
Felipe Martin 65038fb7a2
Some checks are pending
ci / plugin-ci (push) Waiting to run
feat: restore XMPP bridge to use direct client connection instead of bridge user
- Replace bridgeUser with bridgeClient (*xmppClient.Client) in XMPP bridge
- Update createXMPPClient to return XMPP client with TLS configuration
- Migrate connection, disconnection, and room operations to use bridgeClient
- Update Ping() and RoomExists() methods to use client methods directly
- Maintain bridge-agnostic user management system for additional users
- Fix formatting and import organization across bridge components

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-04 18:04:10 +02:00

300 lines
8.1 KiB
Go

package mattermost
import (
"context"
"fmt"
"sync"
"time"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/config"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/logger"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
mmModel "github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/plugin"
)
// MattermostUser represents a Mattermost user that implements the BridgeUser interface
type MattermostUser struct {
// User identity
id string
displayName string
username string
email string
// Mattermost API
api plugin.API
// State management
state model.UserState
stateMu sync.RWMutex
// Configuration
config *config.Configuration
// Goroutine lifecycle
ctx context.Context
cancel context.CancelFunc
// Logger
logger logger.Logger
}
// NewMattermostUser creates a new Mattermost user
func NewMattermostUser(id, displayName, username, email string, api plugin.API, cfg *config.Configuration, logger logger.Logger) *MattermostUser {
ctx, cancel := context.WithCancel(context.Background())
return &MattermostUser{
id: id,
displayName: displayName,
username: username,
email: email,
api: api,
state: model.UserStateOffline,
config: cfg,
ctx: ctx,
cancel: cancel,
logger: logger,
}
}
// Validation
func (u *MattermostUser) Validate() error {
if u.id == "" {
return fmt.Errorf("user ID cannot be empty")
}
if u.username == "" {
return fmt.Errorf("username cannot be empty")
}
if u.config == nil {
return fmt.Errorf("configuration cannot be nil")
}
if u.api == nil {
return fmt.Errorf("Mattermost API cannot be nil")
}
return nil
}
// Identity (bridge-agnostic)
func (u *MattermostUser) GetID() string {
return u.id
}
func (u *MattermostUser) GetDisplayName() string {
return u.displayName
}
// State management
func (u *MattermostUser) GetState() model.UserState {
u.stateMu.RLock()
defer u.stateMu.RUnlock()
return u.state
}
func (u *MattermostUser) SetState(state model.UserState) error {
u.stateMu.Lock()
defer u.stateMu.Unlock()
u.logger.LogDebug("Changing Mattermost user state", "user_id", u.id, "old_state", u.state, "new_state", state)
u.state = state
// TODO: Update user status in Mattermost if needed
// This could involve setting custom status or presence indicators
return nil
}
// Channel operations (abstracted from rooms/channels/groups)
func (u *MattermostUser) JoinChannel(channelID string) error {
u.logger.LogDebug("Mattermost user joining channel", "user_id", u.id, "channel_id", channelID)
// Add user to channel
_, appErr := u.api.AddUserToChannel(channelID, u.id, "")
if appErr != nil {
return fmt.Errorf("failed to add Mattermost user %s to channel %s: %w", u.id, channelID, appErr)
}
u.logger.LogInfo("Mattermost user joined channel", "user_id", u.id, "channel_id", channelID)
return nil
}
func (u *MattermostUser) LeaveChannel(channelID string) error {
u.logger.LogDebug("Mattermost user leaving channel", "user_id", u.id, "channel_id", channelID)
// Remove user from channel
appErr := u.api.DeleteChannelMember(channelID, u.id)
if appErr != nil {
return fmt.Errorf("failed to remove Mattermost user %s from channel %s: %w", u.id, channelID, appErr)
}
u.logger.LogInfo("Mattermost user left channel", "user_id", u.id, "channel_id", channelID)
return nil
}
func (u *MattermostUser) SendMessageToChannel(channelID, message string) error {
u.logger.LogDebug("Mattermost user sending message to channel", "user_id", u.id, "channel_id", channelID)
// Create post
post := &mmModel.Post{
UserId: u.id,
ChannelId: channelID,
Message: message,
}
// Send post
_, appErr := u.api.CreatePost(post)
if appErr != nil {
return fmt.Errorf("failed to send message to Mattermost channel %s: %w", channelID, appErr)
}
u.logger.LogDebug("Mattermost user sent message to channel", "user_id", u.id, "channel_id", channelID)
return nil
}
// Connection lifecycle
func (u *MattermostUser) Connect() error {
u.logger.LogDebug("Connecting Mattermost user", "user_id", u.id, "username", u.username)
// For Mattermost users, "connecting" means verifying the user exists and is accessible
user, appErr := u.api.GetUser(u.id)
if appErr != nil {
return fmt.Errorf("failed to verify Mattermost user %s: %w", u.id, appErr)
}
// Update user information if it has changed
if user.GetDisplayName("") != u.displayName {
u.displayName = user.GetDisplayName("")
u.logger.LogDebug("Updated Mattermost user display name", "user_id", u.id, "display_name", u.displayName)
}
u.logger.LogInfo("Mattermost user connected", "user_id", u.id, "username", u.username)
// Update state to online
_ = u.SetState(model.UserStateOnline)
return nil
}
func (u *MattermostUser) Disconnect() error {
u.logger.LogDebug("Disconnecting Mattermost user", "user_id", u.id, "username", u.username)
// For Mattermost users, "disconnecting" is mostly a state change
// The user still exists in Mattermost, but we're not actively managing them
_ = u.SetState(model.UserStateOffline)
u.logger.LogInfo("Mattermost user disconnected", "user_id", u.id, "username", u.username)
return nil
}
func (u *MattermostUser) IsConnected() bool {
return u.GetState() == model.UserStateOnline
}
func (u *MattermostUser) Ping() error {
if u.api == nil {
return fmt.Errorf("Mattermost API not initialized for user %s", u.id)
}
// Test API connectivity by getting server version
version := u.api.GetServerVersion()
if version == "" {
return fmt.Errorf("Mattermost API ping returned empty server version for user %s", u.id)
}
return nil
}
// CheckChannelExists checks if a Mattermost channel exists
func (u *MattermostUser) CheckChannelExists(channelID string) (bool, error) {
if u.api == nil {
return false, fmt.Errorf("Mattermost API not initialized for user %s", u.id)
}
// Try to get the channel by ID
_, appErr := u.api.GetChannel(channelID)
if appErr != nil {
// Check if it's a "not found" error
if appErr.StatusCode == 404 {
return false, nil // Channel doesn't exist
}
return false, fmt.Errorf("failed to check channel existence: %w", appErr)
}
return true, nil
}
// Goroutine lifecycle
func (u *MattermostUser) Start(ctx context.Context) error {
u.logger.LogDebug("Starting Mattermost user", "user_id", u.id, "username", u.username)
// Update context
u.ctx = ctx
// Connect to verify user exists
if err := u.Connect(); err != nil {
return fmt.Errorf("failed to start Mattermost user %s: %w", u.id, err)
}
// Start monitoring in a goroutine
go u.monitor()
u.logger.LogInfo("Mattermost user started", "user_id", u.id, "username", u.username)
return nil
}
func (u *MattermostUser) Stop() error {
u.logger.LogDebug("Stopping Mattermost user", "user_id", u.id, "username", u.username)
// Cancel context to stop goroutines
if u.cancel != nil {
u.cancel()
}
// Disconnect
if err := u.Disconnect(); err != nil {
u.logger.LogWarn("Error disconnecting Mattermost user during stop", "user_id", u.id, "error", err)
}
u.logger.LogInfo("Mattermost user stopped", "user_id", u.id, "username", u.username)
return nil
}
// monitor periodically checks the user's status and updates information
func (u *MattermostUser) monitor() {
u.logger.LogDebug("Starting monitor for Mattermost user", "user_id", u.id)
// Simple monitoring - check user exists periodically
for {
select {
case <-u.ctx.Done():
u.logger.LogDebug("Monitor stopped for Mattermost user", "user_id", u.id)
return
default:
// Wait before next check
timeoutCtx, cancel := context.WithTimeout(u.ctx, 60*time.Second)
select {
case <-u.ctx.Done():
cancel()
return
case <-timeoutCtx.Done():
cancel()
continue
}
}
}
}
// GetUsername returns the Mattermost username for this user (Mattermost-specific method)
func (u *MattermostUser) GetUsername() string {
return u.username
}
// GetEmail returns the Mattermost email for this user (Mattermost-specific method)
func (u *MattermostUser) GetEmail() string {
return u.email
}
// GetAPI returns the Mattermost API instance (for advanced operations)
func (u *MattermostUser) GetAPI() plugin.API {
return u.api
}