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 }