github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/services/sharedchannel/sync_recv.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package sharedchannel
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/masterhung0112/hk_server/v5/app/request"
    15  	"github.com/masterhung0112/hk_server/v5/model"
    16  	"github.com/masterhung0112/hk_server/v5/services/remotecluster"
    17  	"github.com/masterhung0112/hk_server/v5/shared/mlog"
    18  )
    19  
    20  func (scs *Service) onReceiveSyncMessage(msg model.RemoteClusterMsg, rc *model.RemoteCluster, response *remotecluster.Response) error {
    21  	if msg.Topic != TopicSync {
    22  		return fmt.Errorf("wrong topic, expected `%s`, got `%s`", TopicSync, msg.Topic)
    23  	}
    24  
    25  	if len(msg.Payload) == 0 {
    26  		return errors.New("empty sync message")
    27  	}
    28  
    29  	if scs.server.GetLogger().IsLevelEnabled(mlog.LvlSharedChannelServiceMessagesInbound) {
    30  		scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceMessagesInbound, "inbound message",
    31  			mlog.String("remote", rc.DisplayName),
    32  			mlog.String("msg", string(msg.Payload)),
    33  		)
    34  	}
    35  
    36  	var sm syncMsg
    37  
    38  	if err := json.Unmarshal(msg.Payload, &sm); err != nil {
    39  		return fmt.Errorf("invalid sync message: %w", err)
    40  	}
    41  	return scs.processSyncMessage(&sm, rc, response)
    42  }
    43  
    44  func (scs *Service) processSyncMessage(syncMsg *syncMsg, rc *model.RemoteCluster, response *remotecluster.Response) error {
    45  	var channel *model.Channel
    46  	var team *model.Team
    47  
    48  	var err error
    49  	syncResp := SyncResponse{
    50  		UserErrors:     make([]string, 0),
    51  		UsersSyncd:     make([]string, 0),
    52  		PostErrors:     make([]string, 0),
    53  		ReactionErrors: make([]string, 0),
    54  	}
    55  
    56  	scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Sync msg received",
    57  		mlog.String("remote", rc.Name),
    58  		mlog.String("channel_id", syncMsg.ChannelId),
    59  		mlog.Int("user_count", len(syncMsg.Users)),
    60  		mlog.Int("post_count", len(syncMsg.Posts)),
    61  		mlog.Int("reaction_count", len(syncMsg.Reactions)),
    62  	)
    63  
    64  	if channel, err = scs.server.GetStore().Channel().Get(syncMsg.ChannelId, true); err != nil {
    65  		// if the channel doesn't exist then none of these sync items are going to work.
    66  		return fmt.Errorf("channel not found processing sync message: %w", err)
    67  	}
    68  
    69  	// add/update users before posts
    70  	for _, user := range syncMsg.Users {
    71  		if userSaved, err := scs.upsertSyncUser(user, channel, rc); err != nil {
    72  			scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error upserting sync user",
    73  				mlog.String("remote", rc.Name),
    74  				mlog.String("channel_id", syncMsg.ChannelId),
    75  				mlog.String("user_id", user.Id),
    76  				mlog.Err(err))
    77  		} else {
    78  			syncResp.UsersSyncd = append(syncResp.UsersSyncd, userSaved.Id)
    79  			if syncResp.UsersLastUpdateAt < user.UpdateAt {
    80  				syncResp.UsersLastUpdateAt = user.UpdateAt
    81  			}
    82  			scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "User upserted via sync",
    83  				mlog.String("remote", rc.Name),
    84  				mlog.String("channel_id", syncMsg.ChannelId),
    85  				mlog.String("user_id", user.Id),
    86  			)
    87  		}
    88  	}
    89  
    90  	for _, post := range syncMsg.Posts {
    91  		if syncMsg.ChannelId != post.ChannelId {
    92  			scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "ChannelId mismatch",
    93  				mlog.String("remote", rc.Name),
    94  				mlog.String("sm.ChannelId", syncMsg.ChannelId),
    95  				mlog.String("sm.Post.ChannelId", post.ChannelId),
    96  				mlog.String("PostId", post.Id),
    97  			)
    98  			syncResp.PostErrors = append(syncResp.PostErrors, post.Id)
    99  			continue
   100  		}
   101  
   102  		if channel.Type != model.CHANNEL_DIRECT && team == nil {
   103  			var err2 error
   104  			team, err2 = scs.server.GetStore().Channel().GetTeamForChannel(syncMsg.ChannelId)
   105  			if err2 != nil {
   106  				scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error getting Team for Channel",
   107  					mlog.String("ChannelId", post.ChannelId),
   108  					mlog.String("PostId", post.Id),
   109  					mlog.String("remote", rc.Name),
   110  					mlog.Err(err2),
   111  				)
   112  				syncResp.PostErrors = append(syncResp.PostErrors, post.Id)
   113  				continue
   114  			}
   115  		}
   116  
   117  		// process perma-links for remote
   118  		if team != nil {
   119  			post.Message = scs.processPermalinkFromRemote(post, team)
   120  		}
   121  
   122  		// add/update post
   123  		rpost, err := scs.upsertSyncPost(post, channel, rc)
   124  		if err != nil {
   125  			syncResp.PostErrors = append(syncResp.PostErrors, post.Id)
   126  			scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error upserting sync post",
   127  				mlog.String("post_id", post.Id),
   128  				mlog.String("channel_id", post.ChannelId),
   129  				mlog.String("remote", rc.Name),
   130  				mlog.Err(err),
   131  			)
   132  		} else if syncResp.PostsLastUpdateAt < rpost.UpdateAt {
   133  			syncResp.PostsLastUpdateAt = rpost.UpdateAt
   134  		}
   135  	}
   136  
   137  	// add/remove reactions
   138  	for _, reaction := range syncMsg.Reactions {
   139  		if _, err := scs.upsertSyncReaction(reaction, rc); err != nil {
   140  			scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceError, "Error upserting sync reaction",
   141  				mlog.String("remote", rc.Name),
   142  				mlog.String("user_id", reaction.UserId),
   143  				mlog.String("post_id", reaction.PostId),
   144  				mlog.String("emoji", reaction.EmojiName),
   145  				mlog.Int64("delete_at", reaction.DeleteAt),
   146  				mlog.Err(err),
   147  			)
   148  		} else {
   149  			scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Reaction upserted via sync",
   150  				mlog.String("remote", rc.Name),
   151  				mlog.String("user_id", reaction.UserId),
   152  				mlog.String("post_id", reaction.PostId),
   153  				mlog.String("emoji", reaction.EmojiName),
   154  				mlog.Int64("delete_at", reaction.DeleteAt),
   155  			)
   156  
   157  			if syncResp.ReactionsLastUpdateAt < reaction.UpdateAt {
   158  				syncResp.ReactionsLastUpdateAt = reaction.UpdateAt
   159  			}
   160  		}
   161  	}
   162  
   163  	response.SetPayload(syncResp)
   164  
   165  	return nil
   166  }
   167  
   168  func (scs *Service) upsertSyncUser(user *model.User, channel *model.Channel, rc *model.RemoteCluster) (*model.User, error) {
   169  	var err error
   170  	if user.RemoteId == nil || *user.RemoteId == "" {
   171  		user.RemoteId = model.NewString(rc.RemoteId)
   172  	}
   173  
   174  	// Check if user already exists
   175  	euser, err := scs.server.GetStore().User().Get(context.Background(), user.Id)
   176  	if err != nil {
   177  		if _, ok := err.(errNotFound); !ok {
   178  			return nil, fmt.Errorf("error checking sync user: %w", err)
   179  		}
   180  	}
   181  
   182  	var userSaved *model.User
   183  	if euser == nil {
   184  		if userSaved, err = scs.insertSyncUser(user, channel, rc); err != nil {
   185  			return nil, err
   186  		}
   187  	} else {
   188  		patch := &model.UserPatch{
   189  			Username:  &user.Username,
   190  			Nickname:  &user.Nickname,
   191  			FirstName: &user.FirstName,
   192  			LastName:  &user.LastName,
   193  			Email:     &user.Email,
   194  			Props:     user.Props,
   195  			Position:  &user.Position,
   196  			Locale:    &user.Locale,
   197  			Timezone:  user.Timezone,
   198  			RemoteId:  user.RemoteId,
   199  		}
   200  		if userSaved, err = scs.updateSyncUser(patch, euser, channel, rc); err != nil {
   201  			return nil, err
   202  		}
   203  	}
   204  
   205  	// Add user to team. We do this here regardless of whether the user was
   206  	// just created or patched since there are three steps to adding a user
   207  	// (insert rec, add to team, add to channel) and any one could fail.
   208  	// Instead of undoing what succeeded on any failure we simply do all steps each
   209  	// time. AddUserToChannel & AddUserToTeamByTeamId do not error if user was already
   210  	// added and exit quickly.
   211  	if err := scs.app.AddUserToTeamByTeamId(request.EmptyContext(), channel.TeamId, userSaved); err != nil {
   212  		return nil, fmt.Errorf("error adding sync user to Team: %w", err)
   213  	}
   214  
   215  	// add user to channel
   216  	if _, err := scs.app.AddUserToChannel(userSaved, channel, false); err != nil {
   217  		return nil, fmt.Errorf("error adding sync user to ChannelMembers: %w", err)
   218  	}
   219  	return userSaved, nil
   220  }
   221  
   222  func (scs *Service) insertSyncUser(user *model.User, channel *model.Channel, rc *model.RemoteCluster) (*model.User, error) {
   223  	var err error
   224  	var userSaved *model.User
   225  	var suffix string
   226  
   227  	// ensure the new user is created with system_user role and random password.
   228  	user = sanitizeUserForSync(user)
   229  
   230  	// save the original username and email in props (if not already done by another remote)
   231  	if _, ok := user.GetProp(KeyRemoteUsername); !ok {
   232  		user.SetProp(KeyRemoteUsername, user.Username)
   233  	}
   234  	if _, ok := user.GetProp(KeyRemoteEmail); !ok {
   235  		user.SetProp(KeyRemoteEmail, user.Email)
   236  	}
   237  
   238  	// Apply a suffix to the username until it is unique. Collisions will be quite
   239  	// rare since we are joining a username that is unique at a remote site with a unique
   240  	// name for that site. However we need to truncate the combined name to 64 chars and
   241  	// that might introduce a collision.
   242  	for i := 1; i <= MaxUpsertRetries; i++ {
   243  		if i > 1 {
   244  			suffix = strconv.FormatInt(int64(i), 10)
   245  		}
   246  
   247  		user.Username = mungUsername(user.Username, rc.Name, suffix, model.USER_NAME_MAX_LENGTH)
   248  		user.Email = mungEmail(rc.Name, model.USER_EMAIL_MAX_LENGTH)
   249  
   250  		if userSaved, err = scs.server.GetStore().User().Save(user); err != nil {
   251  			e, ok := err.(errInvalidInput)
   252  			if !ok {
   253  				break
   254  			}
   255  			_, field, value := e.InvalidInputInfo()
   256  			if field == "email" || field == "username" {
   257  				// username or email collision; try again with different suffix
   258  				scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceWarn, "Collision inserting sync user",
   259  					mlog.String("field", field),
   260  					mlog.Any("value", value),
   261  					mlog.Int("attempt", i),
   262  					mlog.Err(err),
   263  				)
   264  			}
   265  		} else {
   266  			scs.app.NotifySharedChannelUserUpdate(userSaved)
   267  			return userSaved, nil
   268  		}
   269  	}
   270  	return nil, fmt.Errorf("error inserting sync user %s: %w", user.Id, err)
   271  }
   272  
   273  func (scs *Service) updateSyncUser(patch *model.UserPatch, user *model.User, channel *model.Channel, rc *model.RemoteCluster) (*model.User, error) {
   274  	var err error
   275  	var update *model.UserUpdate
   276  	var suffix string
   277  
   278  	// preserve existing real username/email since Patch will over-write them;
   279  	// the real username/email in props can be updated if they don't contain colons,
   280  	// meaning the update is coming from the user's origin server (not munged).
   281  	realUsername, _ := user.GetProp(KeyRemoteUsername)
   282  	realEmail, _ := user.GetProp(KeyRemoteEmail)
   283  
   284  	if patch.Username != nil && !strings.Contains(*patch.Username, ":") {
   285  		realUsername = *patch.Username
   286  	}
   287  	if patch.Email != nil && !strings.Contains(*patch.Email, ":") {
   288  		realEmail = *patch.Email
   289  	}
   290  
   291  	user.Patch(patch)
   292  	user = sanitizeUserForSync(user)
   293  	user.SetProp(KeyRemoteUsername, realUsername)
   294  	user.SetProp(KeyRemoteEmail, realEmail)
   295  
   296  	// Apply a suffix to the username until it is unique.
   297  	for i := 1; i <= MaxUpsertRetries; i++ {
   298  		if i > 1 {
   299  			suffix = strconv.FormatInt(int64(i), 10)
   300  		}
   301  		user.Username = mungUsername(user.Username, rc.Name, suffix, model.USER_NAME_MAX_LENGTH)
   302  		user.Email = mungEmail(rc.Name, model.USER_EMAIL_MAX_LENGTH)
   303  
   304  		if update, err = scs.server.GetStore().User().Update(user, false); err != nil {
   305  			e, ok := err.(errInvalidInput)
   306  			if !ok {
   307  				break
   308  			}
   309  			_, field, value := e.InvalidInputInfo()
   310  			if field == "email" || field == "username" {
   311  				// username or email collision; try again with different suffix
   312  				scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceWarn, "Collision updating sync user",
   313  					mlog.String("field", field),
   314  					mlog.Any("value", value),
   315  					mlog.Int("attempt", i),
   316  					mlog.Err(err),
   317  				)
   318  			}
   319  		} else {
   320  			scs.app.InvalidateCacheForUser(update.New.Id)
   321  			scs.app.NotifySharedChannelUserUpdate(update.New)
   322  			return update.New, nil
   323  		}
   324  	}
   325  	return nil, fmt.Errorf("error updating sync user %s: %w", user.Id, err)
   326  }
   327  
   328  func (scs *Service) upsertSyncPost(post *model.Post, channel *model.Channel, rc *model.RemoteCluster) (*model.Post, error) {
   329  	var appErr *model.AppError
   330  
   331  	post.RemoteId = model.NewString(rc.RemoteId)
   332  
   333  	rpost, err := scs.server.GetStore().Post().GetSingle(post.Id, true)
   334  	if err != nil {
   335  		if _, ok := err.(errNotFound); !ok {
   336  			return nil, fmt.Errorf("error checking sync post: %w", err)
   337  		}
   338  	}
   339  
   340  	if rpost == nil {
   341  		// post doesn't exist; create new one
   342  		rpost, appErr = scs.app.CreatePost(request.EmptyContext(), post, channel, true, true)
   343  		if appErr == nil {
   344  			scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Created sync post",
   345  				mlog.String("post_id", post.Id),
   346  				mlog.String("channel_id", post.ChannelId),
   347  			)
   348  		}
   349  	} else if post.DeleteAt > 0 {
   350  		// delete post
   351  		rpost, appErr = scs.app.DeletePost(post.Id, post.UserId)
   352  		if appErr == nil {
   353  			scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Deleted sync post",
   354  				mlog.String("post_id", post.Id),
   355  				mlog.String("channel_id", post.ChannelId),
   356  			)
   357  		}
   358  	} else if post.EditAt > rpost.EditAt || post.Message != rpost.Message {
   359  		// update post
   360  		rpost, appErr = scs.app.UpdatePost(request.EmptyContext(), post, false)
   361  		if appErr == nil {
   362  			scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Updated sync post",
   363  				mlog.String("post_id", post.Id),
   364  				mlog.String("channel_id", post.ChannelId),
   365  			)
   366  		}
   367  	} else {
   368  		// nothing to update
   369  		scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceDebug, "Update to sync post ignored",
   370  			mlog.String("post_id", post.Id),
   371  			mlog.String("channel_id", post.ChannelId),
   372  		)
   373  	}
   374  
   375  	var rerr error
   376  	if appErr != nil {
   377  		rerr = errors.New(appErr.Error())
   378  	}
   379  	return rpost, rerr
   380  }
   381  
   382  func (scs *Service) upsertSyncReaction(reaction *model.Reaction, rc *model.RemoteCluster) (*model.Reaction, error) {
   383  	savedReaction := reaction
   384  	var appErr *model.AppError
   385  
   386  	reaction.RemoteId = model.NewString(rc.RemoteId)
   387  
   388  	if reaction.DeleteAt == 0 {
   389  		savedReaction, appErr = scs.app.SaveReactionForPost(request.EmptyContext(), reaction)
   390  	} else {
   391  		appErr = scs.app.DeleteReactionForPost(request.EmptyContext(), reaction)
   392  	}
   393  
   394  	var err error
   395  	if appErr != nil {
   396  		err = errors.New(appErr.Error())
   397  	}
   398  	return savedReaction, err
   399  }