github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/app/post.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package app 5 6 import ( 7 "context" 8 "encoding/json" 9 "errors" 10 "fmt" 11 "net/http" 12 "strings" 13 "sync" 14 "time" 15 16 "github.com/mattermost/mattermost-server/v5/mlog" 17 "github.com/mattermost/mattermost-server/v5/model" 18 "github.com/mattermost/mattermost-server/v5/plugin" 19 "github.com/mattermost/mattermost-server/v5/services/cache" 20 "github.com/mattermost/mattermost-server/v5/store" 21 "github.com/mattermost/mattermost-server/v5/utils" 22 ) 23 24 const ( 25 PendingPostIDsCacheSize = 25000 26 PendingPostIDsCacheTTL = 30 * time.Second 27 PageDefault = 0 28 ) 29 30 func (a *App) CreatePostAsUser(post *model.Post, currentSessionId string, setOnline bool) (*model.Post, *model.AppError) { 31 // Check that channel has not been deleted 32 channel, errCh := a.Srv().Store.Channel().Get(post.ChannelId, true) 33 if errCh != nil { 34 err := model.NewAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.channel_id"}, errCh.Error(), http.StatusBadRequest) 35 return nil, err 36 } 37 38 if strings.HasPrefix(post.Type, model.POST_SYSTEM_MESSAGE_PREFIX) { 39 err := model.NewAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.type"}, "", http.StatusBadRequest) 40 return nil, err 41 } 42 43 if channel.DeleteAt != 0 { 44 err := model.NewAppError("createPost", "api.post.create_post.can_not_post_to_deleted.error", nil, "", http.StatusBadRequest) 45 return nil, err 46 } 47 48 rp, err := a.CreatePost(post, channel, true, setOnline) 49 if err != nil { 50 if err.Id == "api.post.create_post.root_id.app_error" || 51 err.Id == "api.post.create_post.channel_root_id.app_error" || 52 err.Id == "api.post.create_post.parent_id.app_error" { 53 err.StatusCode = http.StatusBadRequest 54 } 55 56 if err.Id == "api.post.create_post.town_square_read_only" { 57 user, nErr := a.Srv().Store.User().Get(context.Background(), post.UserId) 58 if nErr != nil { 59 var nfErr *store.ErrNotFound 60 switch { 61 case errors.As(nErr, &nfErr): 62 return nil, model.NewAppError("CreatePostAsUser", MissingAccountError, nil, nfErr.Error(), http.StatusNotFound) 63 default: 64 return nil, model.NewAppError("CreatePostAsUser", "app.user.get.app_error", nil, nErr.Error(), http.StatusInternalServerError) 65 } 66 } 67 68 T := utils.GetUserTranslations(user.Locale) 69 a.SendEphemeralPost( 70 post.UserId, 71 &model.Post{ 72 ChannelId: channel.Id, 73 ParentId: post.ParentId, 74 RootId: post.RootId, 75 UserId: post.UserId, 76 Message: T("api.post.create_post.town_square_read_only"), 77 CreateAt: model.GetMillis() + 1, 78 }, 79 ) 80 } 81 return nil, err 82 } 83 84 // Update the LastViewAt only if the post does not have from_webhook prop set (e.g. Zapier app), 85 // or if it does not have from_bot set (e.g. from discovering the user is a bot within CreatePost). 86 _, fromWebhook := post.GetProps()["from_webhook"] 87 _, fromBot := post.GetProps()["from_bot"] 88 if !fromWebhook && !fromBot { 89 if _, err := a.MarkChannelsAsViewed([]string{post.ChannelId}, post.UserId, currentSessionId); err != nil { 90 mlog.Warn( 91 "Encountered error updating last viewed", 92 mlog.String("channel_id", post.ChannelId), 93 mlog.String("user_id", post.UserId), 94 mlog.Err(err), 95 ) 96 } 97 } 98 99 return rp, nil 100 } 101 102 func (a *App) CreatePostMissingChannel(post *model.Post, triggerWebhooks bool) (*model.Post, *model.AppError) { 103 channel, err := a.Srv().Store.Channel().Get(post.ChannelId, true) 104 if err != nil { 105 var nfErr *store.ErrNotFound 106 switch { 107 case errors.As(err, &nfErr): 108 return nil, model.NewAppError("CreatePostMissingChannel", "app.channel.get.existing.app_error", nil, nfErr.Error(), http.StatusNotFound) 109 default: 110 return nil, model.NewAppError("CreatePostMissingChannel", "app.channel.get.find.app_error", nil, err.Error(), http.StatusInternalServerError) 111 } 112 } 113 114 return a.CreatePost(post, channel, triggerWebhooks, true) 115 } 116 117 // deduplicateCreatePost attempts to make posting idempotent within a caching window. 118 func (a *App) deduplicateCreatePost(post *model.Post) (foundPost *model.Post, err *model.AppError) { 119 // We rely on the client sending the pending post id across "duplicate" requests. If there 120 // isn't one, we can't deduplicate, so allow creation normally. 121 if post.PendingPostId == "" { 122 return nil, nil 123 } 124 125 const unknownPostId = "" 126 127 // Query the cache atomically for the given pending post id, saving a record if 128 // it hasn't previously been seen. 129 var postId string 130 nErr := a.Srv().seenPendingPostIdsCache.Get(post.PendingPostId, &postId) 131 if nErr == cache.ErrKeyNotFound { 132 a.Srv().seenPendingPostIdsCache.SetWithExpiry(post.PendingPostId, unknownPostId, PendingPostIDsCacheTTL) 133 return nil, nil 134 } 135 136 if nErr != nil { 137 return nil, model.NewAppError("errorGetPostId", "api.post.error_get_post_id.pending", nil, "", http.StatusInternalServerError) 138 } 139 140 // If another thread saved the cache record, but hasn't yet updated it with the actual post 141 // id (because it's still saving), notify the client with an error. Ideally, we'd wait 142 // for the other thread, but coordinating that adds complexity to the happy path. 143 if postId == unknownPostId { 144 return nil, model.NewAppError("deduplicateCreatePost", "api.post.deduplicate_create_post.pending", nil, "", http.StatusInternalServerError) 145 } 146 147 // If the other thread finished creating the post, return the created post back to the 148 // client, making the API call feel idempotent. 149 actualPost, err := a.GetSinglePost(postId) 150 if err != nil { 151 return nil, model.NewAppError("deduplicateCreatePost", "api.post.deduplicate_create_post.failed_to_get", nil, err.Error(), http.StatusInternalServerError) 152 } 153 154 mlog.Debug("Deduplicated create post", mlog.String("post_id", actualPost.Id), mlog.String("pending_post_id", post.PendingPostId)) 155 156 return actualPost, nil 157 } 158 159 func (a *App) CreatePost(post *model.Post, channel *model.Channel, triggerWebhooks, setOnline bool) (savedPost *model.Post, err *model.AppError) { 160 foundPost, err := a.deduplicateCreatePost(post) 161 if err != nil { 162 return nil, err 163 } 164 if foundPost != nil { 165 return foundPost, nil 166 } 167 168 // If we get this far, we've recorded the client-provided pending post id to the cache. 169 // Remove it if we fail below, allowing a proper retry by the client. 170 defer func() { 171 if post.PendingPostId == "" { 172 return 173 } 174 175 if err != nil { 176 a.Srv().seenPendingPostIdsCache.Remove(post.PendingPostId) 177 return 178 } 179 180 a.Srv().seenPendingPostIdsCache.SetWithExpiry(post.PendingPostId, savedPost.Id, PendingPostIDsCacheTTL) 181 }() 182 183 post.SanitizeProps() 184 185 var pchan chan store.StoreResult 186 if post.RootId != "" { 187 pchan = make(chan store.StoreResult, 1) 188 go func() { 189 r, pErr := a.Srv().Store.Post().Get(post.RootId, false, false, false) 190 pchan <- store.StoreResult{Data: r, NErr: pErr} 191 close(pchan) 192 }() 193 } 194 195 user, nErr := a.Srv().Store.User().Get(context.Background(), post.UserId) 196 if nErr != nil { 197 var nfErr *store.ErrNotFound 198 switch { 199 case errors.As(nErr, &nfErr): 200 return nil, model.NewAppError("CreatePost", MissingAccountError, nil, nfErr.Error(), http.StatusNotFound) 201 default: 202 return nil, model.NewAppError("CreatePost", "app.user.get.app_error", nil, nErr.Error(), http.StatusInternalServerError) 203 } 204 } 205 206 if user.IsBot { 207 post.AddProp("from_bot", "true") 208 } 209 210 if a.Srv().License() != nil && *a.Config().TeamSettings.ExperimentalTownSquareIsReadOnly && 211 !post.IsSystemMessage() && 212 channel.Name == model.DEFAULT_CHANNEL && 213 !a.RolesGrantPermission(user.GetRoles(), model.PERMISSION_MANAGE_SYSTEM.Id) { 214 return nil, model.NewAppError("createPost", "api.post.create_post.town_square_read_only", nil, "", http.StatusForbidden) 215 } 216 217 var ephemeralPost *model.Post 218 if post.Type == "" && !a.HasPermissionToChannel(user.Id, channel.Id, model.PERMISSION_USE_CHANNEL_MENTIONS) { 219 mention := post.DisableMentionHighlights() 220 if mention != "" { 221 T := utils.GetUserTranslations(user.Locale) 222 ephemeralPost = &model.Post{ 223 UserId: user.Id, 224 RootId: post.RootId, 225 ParentId: post.ParentId, 226 ChannelId: channel.Id, 227 Message: T("model.post.channel_notifications_disabled_in_channel.message", model.StringInterface{"ChannelName": channel.Name, "Mention": mention}), 228 Props: model.StringInterface{model.POST_PROPS_MENTION_HIGHLIGHT_DISABLED: true}, 229 } 230 } 231 } 232 233 // Verify the parent/child relationships are correct 234 var parentPostList *model.PostList 235 if pchan != nil { 236 result := <-pchan 237 if result.NErr != nil { 238 return nil, model.NewAppError("createPost", "api.post.create_post.root_id.app_error", nil, "", http.StatusBadRequest) 239 } 240 parentPostList = result.Data.(*model.PostList) 241 if len(parentPostList.Posts) == 0 || !parentPostList.IsChannelId(post.ChannelId) { 242 return nil, model.NewAppError("createPost", "api.post.create_post.channel_root_id.app_error", nil, "", http.StatusInternalServerError) 243 } 244 245 rootPost := parentPostList.Posts[post.RootId] 246 if rootPost.RootId != "" { 247 return nil, model.NewAppError("createPost", "api.post.create_post.root_id.app_error", nil, "", http.StatusBadRequest) 248 } 249 250 if post.ParentId == "" { 251 post.ParentId = post.RootId 252 } 253 254 if post.RootId != post.ParentId { 255 parent := parentPostList.Posts[post.ParentId] 256 if parent == nil { 257 return nil, model.NewAppError("createPost", "api.post.create_post.parent_id.app_error", nil, "", http.StatusInternalServerError) 258 } 259 } 260 } 261 262 post.Hashtags, _ = model.ParseHashtags(post.Message) 263 264 if err = a.FillInPostProps(post, channel); err != nil { 265 return nil, err 266 } 267 268 // Temporary fix so old plugins don't clobber new fields in SlackAttachment struct, see MM-13088 269 if attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment); ok { 270 jsonAttachments, err := json.Marshal(attachments) 271 if err == nil { 272 attachmentsInterface := []interface{}{} 273 err = json.Unmarshal(jsonAttachments, &attachmentsInterface) 274 post.AddProp("attachments", attachmentsInterface) 275 } 276 if err != nil { 277 mlog.Warn("Could not convert post attachments to map interface.", mlog.Err(err)) 278 } 279 } 280 281 if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { 282 var rejectionError *model.AppError 283 pluginContext := a.PluginContext() 284 pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 285 replacementPost, rejectionReason := hooks.MessageWillBePosted(pluginContext, post) 286 if rejectionReason != "" { 287 id := "Post rejected by plugin. " + rejectionReason 288 if rejectionReason == plugin.DismissPostError { 289 id = plugin.DismissPostError 290 } 291 rejectionError = model.NewAppError("createPost", id, nil, "", http.StatusBadRequest) 292 return false 293 } 294 if replacementPost != nil { 295 post = replacementPost 296 } 297 298 return true 299 }, plugin.MessageWillBePostedId) 300 301 if rejectionError != nil { 302 return nil, rejectionError 303 } 304 } 305 306 rpost, nErr := a.Srv().Store.Post().Save(post) 307 if nErr != nil { 308 var appErr *model.AppError 309 var invErr *store.ErrInvalidInput 310 switch { 311 case errors.As(nErr, &appErr): 312 return nil, appErr 313 case errors.As(nErr, &invErr): 314 return nil, model.NewAppError("CreatePost", "app.post.save.existing.app_error", nil, invErr.Error(), http.StatusBadRequest) 315 default: 316 return nil, model.NewAppError("CreatePost", "app.post.save.app_error", nil, nErr.Error(), http.StatusInternalServerError) 317 } 318 } 319 320 // Update the mapping from pending post id to the actual post id, for any clients that 321 // might be duplicating requests. 322 a.Srv().seenPendingPostIdsCache.SetWithExpiry(post.PendingPostId, rpost.Id, PendingPostIDsCacheTTL) 323 324 // We make a copy of the post for the plugin hook to avoid a race condition. 325 rPostCopy := rpost.Clone() 326 if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { 327 a.Srv().Go(func() { 328 pluginContext := a.PluginContext() 329 pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 330 hooks.MessageHasBeenPosted(pluginContext, rPostCopy) 331 return true 332 }, plugin.MessageHasBeenPostedId) 333 }) 334 } 335 336 if a.Metrics() != nil { 337 a.Metrics().IncrementPostCreate() 338 } 339 340 if len(post.FileIds) > 0 { 341 if err = a.attachFilesToPost(post); err != nil { 342 mlog.Warn("Encountered error attaching files to post", mlog.String("post_id", post.Id), mlog.Any("file_ids", post.FileIds), mlog.Err(err)) 343 } 344 345 if a.Metrics() != nil { 346 a.Metrics().IncrementPostFileAttachment(len(post.FileIds)) 347 } 348 } 349 350 // Normally, we would let the API layer call PreparePostForClient, but we do it here since it also needs 351 // to be done when we send the post over the websocket in handlePostEvents 352 rpost = a.PreparePostForClient(rpost, true, false) 353 354 if err := a.handlePostEvents(rpost, user, channel, triggerWebhooks, parentPostList, setOnline); err != nil { 355 mlog.Warn("Failed to handle post events", mlog.Err(err)) 356 } 357 358 // Send any ephemeral posts after the post is created to ensure it shows up after the latest post created 359 if ephemeralPost != nil { 360 a.SendEphemeralPost(post.UserId, ephemeralPost) 361 } 362 363 return rpost, nil 364 } 365 366 func (a *App) attachFilesToPost(post *model.Post) *model.AppError { 367 var attachedIds []string 368 for _, fileId := range post.FileIds { 369 err := a.Srv().Store.FileInfo().AttachToPost(fileId, post.Id, post.UserId) 370 if err != nil { 371 mlog.Warn("Failed to attach file to post", mlog.String("file_id", fileId), mlog.String("post_id", post.Id), mlog.Err(err)) 372 continue 373 } 374 375 attachedIds = append(attachedIds, fileId) 376 } 377 378 if len(post.FileIds) != len(attachedIds) { 379 // We couldn't attach all files to the post, so ensure that post.FileIds reflects what was actually attached 380 post.FileIds = attachedIds 381 382 if _, err := a.Srv().Store.Post().Overwrite(post); err != nil { 383 return model.NewAppError("attachFilesToPost", "app.post.overwrite.app_error", nil, err.Error(), http.StatusInternalServerError) 384 } 385 } 386 387 return nil 388 } 389 390 // FillInPostProps should be invoked before saving posts to fill in properties such as 391 // channel_mentions. 392 // 393 // If channel is nil, FillInPostProps will look up the channel corresponding to the post. 394 func (a *App) FillInPostProps(post *model.Post, channel *model.Channel) *model.AppError { 395 channelMentions := post.ChannelMentions() 396 channelMentionsProp := make(map[string]interface{}) 397 398 if len(channelMentions) > 0 { 399 if channel == nil { 400 postChannel, err := a.Srv().Store.Channel().GetForPost(post.Id) 401 if err != nil { 402 return model.NewAppError("FillInPostProps", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.channel_id"}, err.Error(), http.StatusBadRequest) 403 } 404 channel = postChannel 405 } 406 407 mentionedChannels, err := a.GetChannelsByNames(channelMentions, channel.TeamId) 408 if err != nil { 409 return err 410 } 411 412 for _, mentioned := range mentionedChannels { 413 if mentioned.Type == model.CHANNEL_OPEN { 414 team, err := a.Srv().Store.Team().Get(mentioned.TeamId) 415 if err != nil { 416 mlog.Warn("Failed to get team of the channel mention", mlog.String("team_id", channel.TeamId), mlog.String("channel_id", channel.Id), mlog.Err(err)) 417 continue 418 } 419 channelMentionsProp[mentioned.Name] = map[string]interface{}{ 420 "display_name": mentioned.DisplayName, 421 "team_name": team.Name, 422 } 423 } 424 } 425 } 426 427 if len(channelMentionsProp) > 0 { 428 post.AddProp("channel_mentions", channelMentionsProp) 429 } else if post.GetProps() != nil { 430 post.DelProp("channel_mentions") 431 } 432 433 matched := model.AT_MENTION_PATTEN.MatchString(post.Message) 434 if a.Srv().License() != nil && *a.Srv().License().Features.LDAPGroups && matched && !a.HasPermissionToChannel(post.UserId, post.ChannelId, model.PERMISSION_USE_GROUP_MENTIONS) { 435 post.AddProp(model.POST_PROPS_GROUP_HIGHLIGHT_DISABLED, true) 436 } 437 438 return nil 439 } 440 441 func (a *App) handlePostEvents(post *model.Post, user *model.User, channel *model.Channel, triggerWebhooks bool, parentPostList *model.PostList, setOnline bool) error { 442 var team *model.Team 443 if channel.TeamId != "" { 444 t, err := a.Srv().Store.Team().Get(channel.TeamId) 445 if err != nil { 446 return err 447 } 448 team = t 449 } else { 450 // Blank team for DMs 451 team = &model.Team{} 452 } 453 454 a.invalidateCacheForChannel(channel) 455 a.invalidateCacheForChannelPosts(channel.Id) 456 457 if _, err := a.SendNotifications(post, team, channel, user, parentPostList, setOnline); err != nil { 458 return err 459 } 460 461 if *a.Config().ServiceSettings.ThreadAutoFollow && post.RootId != "" { 462 if err := a.Srv().Store.Thread().CreateMembershipIfNeeded(post.UserId, post.RootId, true, false, true); err != nil { 463 return err 464 } 465 } 466 467 if post.Type != model.POST_AUTO_RESPONDER { // don't respond to an auto-responder 468 a.Srv().Go(func() { 469 _, err := a.SendAutoResponseIfNecessary(channel, user, post) 470 if err != nil { 471 mlog.Error("Failed to send auto response", mlog.String("user_id", user.Id), mlog.String("post_id", post.Id), mlog.Err(err)) 472 } 473 }) 474 } 475 476 if triggerWebhooks { 477 a.Srv().Go(func() { 478 if err := a.handleWebhookEvents(post, team, channel, user); err != nil { 479 mlog.Error(err.Error()) 480 } 481 }) 482 } 483 484 return nil 485 } 486 487 func (a *App) SendEphemeralPost(userID string, post *model.Post) *model.Post { 488 post.Type = model.POST_EPHEMERAL 489 490 // fill in fields which haven't been specified which have sensible defaults 491 if post.Id == "" { 492 post.Id = model.NewId() 493 } 494 if post.CreateAt == 0 { 495 post.CreateAt = model.GetMillis() 496 } 497 if post.GetProps() == nil { 498 post.SetProps(make(model.StringInterface)) 499 } 500 501 post.GenerateActionIds() 502 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, "", post.ChannelId, userID, nil) 503 post = a.PreparePostForClient(post, true, false) 504 post = model.AddPostActionCookies(post, a.PostActionCookieSecret()) 505 message.Add("post", post.ToJson()) 506 a.Publish(message) 507 508 return post 509 } 510 511 func (a *App) UpdateEphemeralPost(userID string, post *model.Post) *model.Post { 512 post.Type = model.POST_EPHEMERAL 513 514 post.UpdateAt = model.GetMillis() 515 if post.GetProps() == nil { 516 post.SetProps(make(model.StringInterface)) 517 } 518 519 post.GenerateActionIds() 520 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", post.ChannelId, userID, nil) 521 post = a.PreparePostForClient(post, true, false) 522 post = model.AddPostActionCookies(post, a.PostActionCookieSecret()) 523 message.Add("post", post.ToJson()) 524 a.Publish(message) 525 526 return post 527 } 528 529 func (a *App) DeleteEphemeralPost(userID, postId string) { 530 post := &model.Post{ 531 Id: postId, 532 UserId: userID, 533 Type: model.POST_EPHEMERAL, 534 DeleteAt: model.GetMillis(), 535 UpdateAt: model.GetMillis(), 536 } 537 538 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", "", userID, nil) 539 message.Add("post", post.ToJson()) 540 a.Publish(message) 541 } 542 543 func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model.AppError) { 544 post.SanitizeProps() 545 546 postLists, nErr := a.Srv().Store.Post().Get(post.Id, false, false, false) 547 if nErr != nil { 548 var nfErr *store.ErrNotFound 549 var invErr *store.ErrInvalidInput 550 switch { 551 case errors.As(nErr, &invErr): 552 return nil, model.NewAppError("UpdatePost", "app.post.get.app_error", nil, invErr.Error(), http.StatusBadRequest) 553 case errors.As(nErr, &nfErr): 554 return nil, model.NewAppError("UpdatePost", "app.post.get.app_error", nil, nfErr.Error(), http.StatusNotFound) 555 default: 556 return nil, model.NewAppError("UpdatePost", "app.post.get.app_error", nil, nErr.Error(), http.StatusInternalServerError) 557 } 558 } 559 oldPost := postLists.Posts[post.Id] 560 561 var err *model.AppError 562 if oldPost == nil { 563 err = model.NewAppError("UpdatePost", "api.post.update_post.find.app_error", nil, "id="+post.Id, http.StatusBadRequest) 564 return nil, err 565 } 566 567 if oldPost.DeleteAt != 0 { 568 err = model.NewAppError("UpdatePost", "api.post.update_post.permissions_details.app_error", map[string]interface{}{"PostId": post.Id}, "", http.StatusBadRequest) 569 return nil, err 570 } 571 572 if oldPost.IsSystemMessage() { 573 err = model.NewAppError("UpdatePost", "api.post.update_post.system_message.app_error", nil, "id="+post.Id, http.StatusBadRequest) 574 return nil, err 575 } 576 577 if a.Srv().License() != nil { 578 if *a.Config().ServiceSettings.PostEditTimeLimit != -1 && model.GetMillis() > oldPost.CreateAt+int64(*a.Config().ServiceSettings.PostEditTimeLimit*1000) && post.Message != oldPost.Message { 579 err = model.NewAppError("UpdatePost", "api.post.update_post.permissions_time_limit.app_error", map[string]interface{}{"timeLimit": *a.Config().ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest) 580 return nil, err 581 } 582 } 583 584 channel, err := a.GetChannel(oldPost.ChannelId) 585 if err != nil { 586 return nil, err 587 } 588 589 if channel.DeleteAt != 0 { 590 return nil, model.NewAppError("UpdatePost", "api.post.update_post.can_not_update_post_in_deleted.error", nil, "", http.StatusBadRequest) 591 } 592 593 newPost := &model.Post{} 594 newPost = oldPost.Clone() 595 596 if newPost.Message != post.Message { 597 newPost.Message = post.Message 598 newPost.EditAt = model.GetMillis() 599 newPost.Hashtags, _ = model.ParseHashtags(post.Message) 600 } 601 602 if !safeUpdate { 603 newPost.IsPinned = post.IsPinned 604 newPost.HasReactions = post.HasReactions 605 newPost.FileIds = post.FileIds 606 newPost.SetProps(post.GetProps()) 607 } 608 609 // Avoid deep-equal checks if EditAt was already modified through message change 610 if newPost.EditAt == oldPost.EditAt && (!oldPost.FileIds.Equals(newPost.FileIds) || !oldPost.AttachmentsEqual(newPost)) { 611 newPost.EditAt = model.GetMillis() 612 } 613 614 if err = a.FillInPostProps(post, nil); err != nil { 615 return nil, err 616 } 617 618 if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { 619 var rejectionReason string 620 pluginContext := a.PluginContext() 621 pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 622 newPost, rejectionReason = hooks.MessageWillBeUpdated(pluginContext, newPost, oldPost) 623 return post != nil 624 }, plugin.MessageWillBeUpdatedId) 625 if newPost == nil { 626 return nil, model.NewAppError("UpdatePost", "Post rejected by plugin. "+rejectionReason, nil, "", http.StatusBadRequest) 627 } 628 } 629 630 rpost, nErr := a.Srv().Store.Post().Update(newPost, oldPost) 631 if nErr != nil { 632 var appErr *model.AppError 633 switch { 634 case errors.As(nErr, &appErr): 635 return nil, appErr 636 default: 637 return nil, model.NewAppError("UpdatePost", "app.post.update.app_error", nil, nErr.Error(), http.StatusInternalServerError) 638 } 639 } 640 641 if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { 642 a.Srv().Go(func() { 643 pluginContext := a.PluginContext() 644 pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 645 hooks.MessageHasBeenUpdated(pluginContext, newPost, oldPost) 646 return true 647 }, plugin.MessageHasBeenUpdatedId) 648 }) 649 } 650 651 rpost = a.PreparePostForClient(rpost, false, true) 652 653 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", rpost.ChannelId, "", nil) 654 message.Add("post", rpost.ToJson()) 655 a.Publish(message) 656 657 a.invalidateCacheForChannelPosts(rpost.ChannelId) 658 659 return rpost, nil 660 } 661 662 func (a *App) PatchPost(postId string, patch *model.PostPatch) (*model.Post, *model.AppError) { 663 post, err := a.GetSinglePost(postId) 664 if err != nil { 665 return nil, err 666 } 667 668 channel, err := a.GetChannel(post.ChannelId) 669 if err != nil { 670 return nil, err 671 } 672 673 if channel.DeleteAt != 0 { 674 err = model.NewAppError("PatchPost", "api.post.patch_post.can_not_update_post_in_deleted.error", nil, "", http.StatusBadRequest) 675 return nil, err 676 } 677 678 if !a.HasPermissionToChannel(post.UserId, post.ChannelId, model.PERMISSION_USE_CHANNEL_MENTIONS) { 679 patch.DisableMentionHighlights() 680 } 681 682 post.Patch(patch) 683 684 updatedPost, err := a.UpdatePost(post, false) 685 if err != nil { 686 return nil, err 687 } 688 689 return updatedPost, nil 690 } 691 692 func (a *App) GetPostsPage(options model.GetPostsOptions) (*model.PostList, *model.AppError) { 693 postList, err := a.Srv().Store.Post().GetPosts(options, false) 694 if err != nil { 695 var invErr *store.ErrInvalidInput 696 switch { 697 case errors.As(err, &invErr): 698 return nil, model.NewAppError("GetPostsPage", "app.post.get_posts.app_error", nil, invErr.Error(), http.StatusBadRequest) 699 default: 700 return nil, model.NewAppError("GetPostsPage", "app.post.get_root_posts.app_error", nil, err.Error(), http.StatusInternalServerError) 701 } 702 } 703 704 return postList, nil 705 } 706 707 func (a *App) GetPosts(channelId string, offset int, limit int) (*model.PostList, *model.AppError) { 708 postList, err := a.Srv().Store.Post().GetPosts(model.GetPostsOptions{ChannelId: channelId, Page: offset, PerPage: limit}, true) 709 if err != nil { 710 var invErr *store.ErrInvalidInput 711 switch { 712 case errors.As(err, &invErr): 713 return nil, model.NewAppError("GetPosts", "app.post.get_posts.app_error", nil, invErr.Error(), http.StatusBadRequest) 714 default: 715 return nil, model.NewAppError("GetPosts", "app.post.get_root_posts.app_error", nil, err.Error(), http.StatusInternalServerError) 716 } 717 } 718 719 return postList, nil 720 } 721 722 func (a *App) GetPostsEtag(channelId string, collapsedThreads bool) string { 723 return a.Srv().Store.Post().GetEtag(channelId, true, collapsedThreads) 724 } 725 726 func (a *App) GetPostsSince(options model.GetPostsSinceOptions) (*model.PostList, *model.AppError) { 727 postList, err := a.Srv().Store.Post().GetPostsSince(options, true) 728 if err != nil { 729 return nil, model.NewAppError("GetPostsSince", "app.post.get_posts_since.app_error", nil, err.Error(), http.StatusInternalServerError) 730 } 731 732 return postList, nil 733 } 734 735 func (a *App) GetSinglePost(postId string) (*model.Post, *model.AppError) { 736 post, err := a.Srv().Store.Post().GetSingle(postId) 737 if err != nil { 738 var nfErr *store.ErrNotFound 739 switch { 740 case errors.As(err, &nfErr): 741 return nil, model.NewAppError("GetSinglePost", "app.post.get.app_error", nil, nfErr.Error(), http.StatusNotFound) 742 default: 743 return nil, model.NewAppError("GetSinglePost", "app.post.get.app_error", nil, err.Error(), http.StatusInternalServerError) 744 } 745 } 746 747 return post, nil 748 } 749 750 func (a *App) GetPostThread(postId string, skipFetchThreads, collapsedThreads, collapsedThreadsExtended bool) (*model.PostList, *model.AppError) { 751 posts, err := a.Srv().Store.Post().Get(postId, skipFetchThreads, collapsedThreads, collapsedThreadsExtended) 752 if err != nil { 753 var nfErr *store.ErrNotFound 754 var invErr *store.ErrInvalidInput 755 switch { 756 case errors.As(err, &invErr): 757 return nil, model.NewAppError("GetPostThread", "app.post.get.app_error", nil, invErr.Error(), http.StatusBadRequest) 758 case errors.As(err, &nfErr): 759 return nil, model.NewAppError("GetPostThread", "app.post.get.app_error", nil, nfErr.Error(), http.StatusNotFound) 760 default: 761 return nil, model.NewAppError("GetPostThread", "app.post.get.app_error", nil, err.Error(), http.StatusInternalServerError) 762 } 763 } 764 765 return posts, nil 766 } 767 768 func (a *App) GetFlaggedPosts(userID string, offset int, limit int) (*model.PostList, *model.AppError) { 769 postList, err := a.Srv().Store.Post().GetFlaggedPosts(userID, offset, limit) 770 if err != nil { 771 return nil, model.NewAppError("GetFlaggedPosts", "app.post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError) 772 } 773 774 return postList, nil 775 } 776 777 func (a *App) GetFlaggedPostsForTeam(userID, teamID string, offset int, limit int) (*model.PostList, *model.AppError) { 778 postList, err := a.Srv().Store.Post().GetFlaggedPostsForTeam(userID, teamID, offset, limit) 779 if err != nil { 780 return nil, model.NewAppError("GetFlaggedPostsForTeam", "app.post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError) 781 } 782 783 return postList, nil 784 } 785 786 func (a *App) GetFlaggedPostsForChannel(userID, channelId string, offset int, limit int) (*model.PostList, *model.AppError) { 787 postList, err := a.Srv().Store.Post().GetFlaggedPostsForChannel(userID, channelId, offset, limit) 788 if err != nil { 789 return nil, model.NewAppError("GetFlaggedPostsForChannel", "app.post.get_flagged_posts.app_error", nil, err.Error(), http.StatusInternalServerError) 790 } 791 792 return postList, nil 793 } 794 795 func (a *App) GetPermalinkPost(postId string, userID string) (*model.PostList, *model.AppError) { 796 list, nErr := a.Srv().Store.Post().Get(postId, false, false, false) 797 if nErr != nil { 798 var nfErr *store.ErrNotFound 799 var invErr *store.ErrInvalidInput 800 switch { 801 case errors.As(nErr, &invErr): 802 return nil, model.NewAppError("GetPermalinkPost", "app.post.get.app_error", nil, invErr.Error(), http.StatusBadRequest) 803 case errors.As(nErr, &nfErr): 804 return nil, model.NewAppError("GetPermalinkPost", "app.post.get.app_error", nil, nfErr.Error(), http.StatusNotFound) 805 default: 806 return nil, model.NewAppError("GetPermalinkPost", "app.post.get.app_error", nil, nErr.Error(), http.StatusInternalServerError) 807 } 808 } 809 810 if len(list.Order) != 1 { 811 return nil, model.NewAppError("getPermalinkTmp", "api.post_get_post_by_id.get.app_error", nil, "", http.StatusNotFound) 812 } 813 post := list.Posts[list.Order[0]] 814 815 channel, err := a.GetChannel(post.ChannelId) 816 if err != nil { 817 return nil, err 818 } 819 820 if err = a.JoinChannel(channel, userID); err != nil { 821 return nil, err 822 } 823 824 return list, nil 825 } 826 827 func (a *App) GetPostsBeforePost(options model.GetPostsOptions) (*model.PostList, *model.AppError) { 828 postList, err := a.Srv().Store.Post().GetPostsBefore(options) 829 if err != nil { 830 var invErr *store.ErrInvalidInput 831 switch { 832 case errors.As(err, &invErr): 833 return nil, model.NewAppError("GetPostsBeforePost", "app.post.get_posts_around.get.app_error", nil, invErr.Error(), http.StatusBadRequest) 834 default: 835 return nil, model.NewAppError("GetPostsBeforePost", "app.post.get_posts_around.get.app_error", nil, err.Error(), http.StatusInternalServerError) 836 } 837 } 838 839 return postList, nil 840 } 841 842 func (a *App) GetPostsAfterPost(options model.GetPostsOptions) (*model.PostList, *model.AppError) { 843 postList, err := a.Srv().Store.Post().GetPostsAfter(options) 844 if err != nil { 845 var invErr *store.ErrInvalidInput 846 switch { 847 case errors.As(err, &invErr): 848 return nil, model.NewAppError("GetPostsAfterPost", "app.post.get_posts_around.get.app_error", nil, invErr.Error(), http.StatusBadRequest) 849 default: 850 return nil, model.NewAppError("GetPostsAfterPost", "app.post.get_posts_around.get.app_error", nil, err.Error(), http.StatusInternalServerError) 851 } 852 } 853 854 return postList, nil 855 } 856 857 func (a *App) GetPostsAroundPost(before bool, options model.GetPostsOptions) (*model.PostList, *model.AppError) { 858 var postList *model.PostList 859 var err error 860 if before { 861 postList, err = a.Srv().Store.Post().GetPostsBefore(options) 862 } else { 863 postList, err = a.Srv().Store.Post().GetPostsAfter(options) 864 } 865 866 if err != nil { 867 var invErr *store.ErrInvalidInput 868 switch { 869 case errors.As(err, &invErr): 870 return nil, model.NewAppError("GetPostsAroundPost", "app.post.get_posts_around.get.app_error", nil, invErr.Error(), http.StatusBadRequest) 871 default: 872 return nil, model.NewAppError("GetPostsAroundPost", "app.post.get_posts_around.get.app_error", nil, err.Error(), http.StatusInternalServerError) 873 } 874 } 875 876 return postList, nil 877 } 878 879 func (a *App) GetPostAfterTime(channelId string, time int64) (*model.Post, *model.AppError) { 880 post, err := a.Srv().Store.Post().GetPostAfterTime(channelId, time) 881 if err != nil { 882 return nil, model.NewAppError("GetPostAfterTime", "app.post.get_post_after_time.app_error", nil, err.Error(), http.StatusInternalServerError) 883 } 884 885 return post, nil 886 } 887 888 func (a *App) GetPostIdAfterTime(channelId string, time int64) (string, *model.AppError) { 889 postId, err := a.Srv().Store.Post().GetPostIdAfterTime(channelId, time) 890 if err != nil { 891 return "", model.NewAppError("GetPostIdAfterTime", "app.post.get_post_id_around.app_error", nil, err.Error(), http.StatusInternalServerError) 892 } 893 894 return postId, nil 895 } 896 897 func (a *App) GetPostIdBeforeTime(channelId string, time int64) (string, *model.AppError) { 898 postId, err := a.Srv().Store.Post().GetPostIdBeforeTime(channelId, time) 899 if err != nil { 900 return "", model.NewAppError("GetPostIdBeforeTime", "app.post.get_post_id_around.app_error", nil, err.Error(), http.StatusInternalServerError) 901 } 902 903 return postId, nil 904 } 905 906 func (a *App) GetNextPostIdFromPostList(postList *model.PostList) string { 907 if len(postList.Order) > 0 { 908 firstPostId := postList.Order[0] 909 firstPost := postList.Posts[firstPostId] 910 nextPostId, err := a.GetPostIdAfterTime(firstPost.ChannelId, firstPost.CreateAt) 911 if err != nil { 912 mlog.Warn("GetNextPostIdFromPostList: failed in getting next post", mlog.Err(err)) 913 } 914 915 return nextPostId 916 } 917 918 return "" 919 } 920 921 func (a *App) GetPrevPostIdFromPostList(postList *model.PostList) string { 922 if len(postList.Order) > 0 { 923 lastPostId := postList.Order[len(postList.Order)-1] 924 lastPost := postList.Posts[lastPostId] 925 previousPostId, err := a.GetPostIdBeforeTime(lastPost.ChannelId, lastPost.CreateAt) 926 if err != nil { 927 mlog.Warn("GetPrevPostIdFromPostList: failed in getting previous post", mlog.Err(err)) 928 } 929 930 return previousPostId 931 } 932 933 return "" 934 } 935 936 // AddCursorIdsForPostList adds NextPostId and PrevPostId as cursor to the PostList. 937 // The conditional blocks ensure that it sets those cursor IDs immediately as afterPost, beforePost or empty, 938 // and only query to database whenever necessary. 939 func (a *App) AddCursorIdsForPostList(originalList *model.PostList, afterPost, beforePost string, since int64, page, perPage int) { 940 prevPostIdSet := false 941 prevPostId := "" 942 nextPostIdSet := false 943 nextPostId := "" 944 945 if since > 0 { // "since" query to return empty NextPostId and PrevPostId 946 nextPostIdSet = true 947 prevPostIdSet = true 948 } else if afterPost != "" { 949 if page == 0 { 950 prevPostId = afterPost 951 prevPostIdSet = true 952 } 953 954 if len(originalList.Order) < perPage { 955 nextPostIdSet = true 956 } 957 } else if beforePost != "" { 958 if page == 0 { 959 nextPostId = beforePost 960 nextPostIdSet = true 961 } 962 963 if len(originalList.Order) < perPage { 964 prevPostIdSet = true 965 } 966 } 967 968 if !nextPostIdSet { 969 nextPostId = a.GetNextPostIdFromPostList(originalList) 970 } 971 972 if !prevPostIdSet { 973 prevPostId = a.GetPrevPostIdFromPostList(originalList) 974 } 975 976 originalList.NextPostId = nextPostId 977 originalList.PrevPostId = prevPostId 978 } 979 func (a *App) GetPostsForChannelAroundLastUnread(channelId, userID string, limitBefore, limitAfter int, skipFetchThreads bool, collapsedThreads, collapsedThreadsExtended bool) (*model.PostList, *model.AppError) { 980 var member *model.ChannelMember 981 var err *model.AppError 982 if member, err = a.GetChannelMember(channelId, userID); err != nil { 983 return nil, err 984 } else if member.LastViewedAt == 0 { 985 return model.NewPostList(), nil 986 } 987 988 lastUnreadPostId, err := a.GetPostIdAfterTime(channelId, member.LastViewedAt) 989 if err != nil { 990 return nil, err 991 } else if lastUnreadPostId == "" { 992 return model.NewPostList(), nil 993 } 994 995 postList, err := a.GetPostThread(lastUnreadPostId, skipFetchThreads, collapsedThreads, collapsedThreadsExtended) 996 if err != nil { 997 return nil, err 998 } 999 // Reset order to only include the last unread post: if the thread appears in the centre 1000 // channel organically, those replies will be added below. 1001 postList.Order = []string{lastUnreadPostId} 1002 1003 if postListBefore, err := a.GetPostsBeforePost(model.GetPostsOptions{ChannelId: channelId, PostId: lastUnreadPostId, Page: PageDefault, PerPage: limitBefore, SkipFetchThreads: skipFetchThreads, CollapsedThreads: collapsedThreads, CollapsedThreadsExtended: collapsedThreadsExtended}); err != nil { 1004 return nil, err 1005 } else if postListBefore != nil { 1006 postList.Extend(postListBefore) 1007 } 1008 1009 if postListAfter, err := a.GetPostsAfterPost(model.GetPostsOptions{ChannelId: channelId, PostId: lastUnreadPostId, Page: PageDefault, PerPage: limitAfter - 1, SkipFetchThreads: skipFetchThreads, CollapsedThreads: collapsedThreads, CollapsedThreadsExtended: collapsedThreadsExtended}); err != nil { 1010 return nil, err 1011 } else if postListAfter != nil { 1012 postList.Extend(postListAfter) 1013 } 1014 1015 postList.SortByCreateAt() 1016 return postList, nil 1017 } 1018 1019 func (a *App) DeletePost(postId, deleteByID string) (*model.Post, *model.AppError) { 1020 post, nErr := a.Srv().Store.Post().GetSingle(postId) 1021 if nErr != nil { 1022 return nil, model.NewAppError("DeletePost", "app.post.get.app_error", nil, nErr.Error(), http.StatusBadRequest) 1023 } 1024 1025 channel, err := a.GetChannel(post.ChannelId) 1026 if err != nil { 1027 return nil, err 1028 } 1029 1030 if channel.DeleteAt != 0 { 1031 err := model.NewAppError("DeletePost", "api.post.delete_post.can_not_delete_post_in_deleted.error", nil, "", http.StatusBadRequest) 1032 return nil, err 1033 } 1034 1035 if err := a.Srv().Store.Post().Delete(postId, model.GetMillis(), deleteByID); err != nil { 1036 var nfErr *store.ErrNotFound 1037 switch { 1038 case errors.As(err, &nfErr): 1039 return nil, model.NewAppError("DeletePost", "app.post.delete.app_error", nil, nfErr.Error(), http.StatusNotFound) 1040 default: 1041 return nil, model.NewAppError("DeletePost", "app.post.delete.app_error", nil, err.Error(), http.StatusInternalServerError) 1042 } 1043 } 1044 1045 postData := a.PreparePostForClient(post, false, false).ToJson() 1046 1047 userMessage := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", post.ChannelId, "", nil) 1048 userMessage.Add("post", postData) 1049 userMessage.GetBroadcast().ContainsSanitizedData = true 1050 a.Publish(userMessage) 1051 1052 adminMessage := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", post.ChannelId, "", nil) 1053 adminMessage.Add("post", postData) 1054 adminMessage.Add("delete_by", deleteByID) 1055 adminMessage.GetBroadcast().ContainsSensitiveData = true 1056 a.Publish(adminMessage) 1057 1058 a.Srv().Go(func() { 1059 a.DeletePostFiles(post) 1060 }) 1061 a.Srv().Go(func() { 1062 a.DeleteFlaggedPosts(post.Id) 1063 }) 1064 1065 a.invalidateCacheForChannelPosts(post.ChannelId) 1066 1067 return post, nil 1068 } 1069 1070 func (a *App) DeleteFlaggedPosts(postId string) { 1071 if err := a.Srv().Store.Preference().DeleteCategoryAndName(model.PREFERENCE_CATEGORY_FLAGGED_POST, postId); err != nil { 1072 mlog.Warn("Unable to delete flagged post preference when deleting post.", mlog.Err(err)) 1073 return 1074 } 1075 } 1076 1077 func (a *App) DeletePostFiles(post *model.Post) { 1078 if len(post.FileIds) == 0 { 1079 return 1080 } 1081 1082 if _, err := a.Srv().Store.FileInfo().DeleteForPost(post.Id); err != nil { 1083 mlog.Warn("Encountered error when deleting files for post", mlog.String("post_id", post.Id), mlog.Err(err)) 1084 } 1085 } 1086 1087 func (a *App) parseAndFetchChannelIdByNameFromInFilter(channelName, userID, teamID string, includeDeleted bool) (*model.Channel, error) { 1088 if strings.HasPrefix(channelName, "@") && strings.Contains(channelName, ",") { 1089 var userIDs []string 1090 users, err := a.GetUsersByUsernames(strings.Split(channelName[1:], ","), false, nil) 1091 if err != nil { 1092 return nil, err 1093 } 1094 for _, user := range users { 1095 userIDs = append(userIDs, user.Id) 1096 } 1097 1098 channel, err := a.GetGroupChannel(userIDs) 1099 if err != nil { 1100 return nil, err 1101 } 1102 return channel, nil 1103 } 1104 1105 if strings.HasPrefix(channelName, "@") && !strings.Contains(channelName, ",") { 1106 user, err := a.GetUserByUsername(channelName[1:]) 1107 if err != nil { 1108 return nil, err 1109 } 1110 channel, err := a.GetOrCreateDirectChannel(userID, user.Id) 1111 if err != nil { 1112 return nil, err 1113 } 1114 return channel, nil 1115 } 1116 1117 channel, err := a.GetChannelByName(channelName, teamID, includeDeleted) 1118 if err != nil { 1119 return nil, err 1120 } 1121 return channel, nil 1122 } 1123 1124 func (a *App) searchPostsInTeam(teamID string, userID string, paramsList []*model.SearchParams, modifierFun func(*model.SearchParams)) (*model.PostList, *model.AppError) { 1125 var wg sync.WaitGroup 1126 1127 pchan := make(chan store.StoreResult, len(paramsList)) 1128 1129 for _, params := range paramsList { 1130 // Don't allow users to search for everything. 1131 if params.Terms == "*" { 1132 continue 1133 } 1134 modifierFun(params) 1135 wg.Add(1) 1136 1137 go func(params *model.SearchParams) { 1138 defer wg.Done() 1139 postList, err := a.Srv().Store.Post().Search(teamID, userID, params) 1140 pchan <- store.StoreResult{Data: postList, NErr: err} 1141 }(params) 1142 } 1143 1144 wg.Wait() 1145 close(pchan) 1146 1147 posts := model.NewPostList() 1148 1149 for result := range pchan { 1150 if result.NErr != nil { 1151 return nil, model.NewAppError("searchPostsInTeam", "app.post.search.app_error", nil, result.NErr.Error(), http.StatusInternalServerError) 1152 } 1153 data := result.Data.(*model.PostList) 1154 posts.Extend(data) 1155 } 1156 1157 posts.SortByCreateAt() 1158 return posts, nil 1159 } 1160 1161 func (a *App) convertChannelNamesToChannelIds(channels []string, userID string, teamID string, includeDeletedChannels bool) []string { 1162 for idx, channelName := range channels { 1163 channel, err := a.parseAndFetchChannelIdByNameFromInFilter(channelName, userID, teamID, includeDeletedChannels) 1164 if err != nil { 1165 mlog.Warn("error getting channel id by name from in filter", mlog.Err(err)) 1166 continue 1167 } 1168 channels[idx] = channel.Id 1169 } 1170 return channels 1171 } 1172 1173 func (a *App) convertUserNameToUserIds(usernames []string) []string { 1174 for idx, username := range usernames { 1175 user, err := a.GetUserByUsername(username) 1176 if err != nil { 1177 mlog.Warn("error getting user by username", mlog.String("user_name", username), mlog.Err(err)) 1178 continue 1179 } 1180 usernames[idx] = user.Id 1181 } 1182 return usernames 1183 } 1184 1185 func (a *App) SearchPostsInTeam(teamID string, paramsList []*model.SearchParams) (*model.PostList, *model.AppError) { 1186 if !*a.Config().ServiceSettings.EnablePostSearch { 1187 return nil, model.NewAppError("SearchPostsInTeam", "store.sql_post.search.disabled", nil, fmt.Sprintf("teamId=%v", teamID), http.StatusNotImplemented) 1188 } 1189 return a.searchPostsInTeam(teamID, "", paramsList, func(params *model.SearchParams) { 1190 params.SearchWithoutUserId = true 1191 }) 1192 } 1193 1194 func (a *App) SearchPostsInTeamForUser(terms string, userID string, teamID string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int) (*model.PostSearchResults, *model.AppError) { 1195 var postSearchResults *model.PostSearchResults 1196 paramsList := model.ParseSearchParams(strings.TrimSpace(terms), timeZoneOffset) 1197 includeDeleted := includeDeletedChannels && *a.Config().TeamSettings.ExperimentalViewArchivedChannels 1198 1199 if !*a.Config().ServiceSettings.EnablePostSearch { 1200 return nil, model.NewAppError("SearchPostsInTeamForUser", "store.sql_post.search.disabled", nil, fmt.Sprintf("teamId=%v userId=%v", teamID, userID), http.StatusNotImplemented) 1201 } 1202 1203 finalParamsList := []*model.SearchParams{} 1204 1205 for _, params := range paramsList { 1206 params.OrTerms = isOrSearch 1207 params.IncludeDeletedChannels = includeDeleted 1208 // Don't allow users to search for "*" 1209 if params.Terms != "*" { 1210 // Convert channel names to channel IDs 1211 params.InChannels = a.convertChannelNamesToChannelIds(params.InChannels, userID, teamID, includeDeletedChannels) 1212 params.ExcludedChannels = a.convertChannelNamesToChannelIds(params.ExcludedChannels, userID, teamID, includeDeletedChannels) 1213 1214 // Convert usernames to user IDs 1215 params.FromUsers = a.convertUserNameToUserIds(params.FromUsers) 1216 params.ExcludedUsers = a.convertUserNameToUserIds(params.ExcludedUsers) 1217 1218 finalParamsList = append(finalParamsList, params) 1219 } 1220 } 1221 1222 // If the processed search params are empty, return empty search results. 1223 if len(finalParamsList) == 0 { 1224 return model.MakePostSearchResults(model.NewPostList(), nil), nil 1225 } 1226 1227 postSearchResults, nErr := a.Srv().Store.Post().SearchPostsInTeamForUser(finalParamsList, userID, teamID, page, perPage) 1228 if nErr != nil { 1229 var appErr *model.AppError 1230 switch { 1231 case errors.As(nErr, &appErr): 1232 return nil, appErr 1233 default: 1234 return nil, model.NewAppError("SearchPostsInTeamForUser", "app.post.search.app_error", nil, nErr.Error(), http.StatusInternalServerError) 1235 } 1236 } 1237 1238 return postSearchResults, nil 1239 } 1240 1241 func (a *App) GetFileInfosForPostWithMigration(postId string) ([]*model.FileInfo, *model.AppError) { 1242 1243 pchan := make(chan store.StoreResult, 1) 1244 go func() { 1245 post, err := a.Srv().Store.Post().GetSingle(postId) 1246 pchan <- store.StoreResult{Data: post, NErr: err} 1247 close(pchan) 1248 }() 1249 1250 infos, err := a.GetFileInfosForPost(postId, false) 1251 if err != nil { 1252 return nil, err 1253 } 1254 1255 if len(infos) == 0 { 1256 // No FileInfos were returned so check if they need to be created for this post 1257 result := <-pchan 1258 if result.NErr != nil { 1259 var nfErr *store.ErrNotFound 1260 switch { 1261 case errors.As(result.NErr, &nfErr): 1262 return nil, model.NewAppError("GetFileInfosForPostWithMigration", "app.post.get.app_error", nil, nfErr.Error(), http.StatusNotFound) 1263 default: 1264 return nil, model.NewAppError("GetFileInfosForPostWithMigration", "app.post.get.app_error", nil, result.NErr.Error(), http.StatusInternalServerError) 1265 } 1266 } 1267 post := result.Data.(*model.Post) 1268 1269 if len(post.Filenames) > 0 { 1270 a.Srv().Store.FileInfo().InvalidateFileInfosForPostCache(postId, false) 1271 a.Srv().Store.FileInfo().InvalidateFileInfosForPostCache(postId, true) 1272 // The post has Filenames that need to be replaced with FileInfos 1273 infos = a.MigrateFilenamesToFileInfos(post) 1274 } 1275 } 1276 1277 return infos, nil 1278 } 1279 1280 func (a *App) GetFileInfosForPost(postId string, fromMaster bool) ([]*model.FileInfo, *model.AppError) { 1281 fileInfos, err := a.Srv().Store.FileInfo().GetForPost(postId, fromMaster, false, true) 1282 if err != nil { 1283 return nil, model.NewAppError("GetFileInfosForPost", "app.file_info.get_for_post.app_error", nil, err.Error(), http.StatusInternalServerError) 1284 } 1285 1286 a.generateMiniPreviewForInfos(fileInfos) 1287 1288 return fileInfos, nil 1289 } 1290 1291 func (a *App) PostWithProxyAddedToImageURLs(post *model.Post) *model.Post { 1292 if f := a.ImageProxyAdder(); f != nil { 1293 return post.WithRewrittenImageURLs(f) 1294 } 1295 return post 1296 } 1297 1298 func (a *App) PostWithProxyRemovedFromImageURLs(post *model.Post) *model.Post { 1299 if f := a.ImageProxyRemover(); f != nil { 1300 return post.WithRewrittenImageURLs(f) 1301 } 1302 return post 1303 } 1304 1305 func (a *App) PostPatchWithProxyRemovedFromImageURLs(patch *model.PostPatch) *model.PostPatch { 1306 if f := a.ImageProxyRemover(); f != nil { 1307 return patch.WithRewrittenImageURLs(f) 1308 } 1309 return patch 1310 } 1311 1312 func (a *App) ImageProxyAdder() func(string) string { 1313 if !*a.Config().ImageProxySettings.Enable { 1314 return nil 1315 } 1316 1317 return func(url string) string { 1318 return a.Srv().ImageProxy.GetProxiedImageURL(url) 1319 } 1320 } 1321 1322 func (a *App) ImageProxyRemover() (f func(string) string) { 1323 if !*a.Config().ImageProxySettings.Enable { 1324 return nil 1325 } 1326 1327 return func(url string) string { 1328 return a.Srv().ImageProxy.GetUnproxiedImageURL(url) 1329 } 1330 } 1331 1332 func (s *Server) MaxPostSize() int { 1333 maxPostSize := s.Store.Post().GetMaxPostSize() 1334 if maxPostSize == 0 { 1335 return model.POST_MESSAGE_MAX_RUNES_V1 1336 } 1337 1338 return maxPostSize 1339 } 1340 1341 func (a *App) MaxPostSize() int { 1342 return a.Srv().MaxPostSize() 1343 } 1344 1345 // countThreadMentions returns the number of times the user is mentioned in a specified thread after the timestamp. 1346 func (a *App) countThreadMentions(user *model.User, post *model.Post, teamID string, timestamp int64) (int64, *model.AppError) { 1347 channel, err := a.GetChannel(post.ChannelId) 1348 if err != nil { 1349 return 0, err 1350 } 1351 1352 keywords := addMentionKeywordsForUser( 1353 map[string][]string{}, 1354 user, 1355 map[string]string{}, 1356 &model.Status{Status: model.STATUS_ONLINE}, // Assume the user is online since they would've triggered this 1357 true, // Assume channel mentions are always allowed for simplicity 1358 ) 1359 1360 posts, nErr := a.Srv().Store.Thread().GetPosts(post.Id, timestamp) 1361 if nErr != nil { 1362 return 0, model.NewAppError("countMentionsFromPost", "app.channel.count_posts_since.app_error", nil, nErr.Error(), http.StatusInternalServerError) 1363 } 1364 1365 count := 0 1366 1367 if channel.Type == model.CHANNEL_DIRECT { 1368 // In a DM channel, every post made by the other user is a mention 1369 otherId := channel.GetOtherUserIdForDM(user.Id) 1370 for _, p := range posts { 1371 if p.UserId == otherId { 1372 count++ 1373 } 1374 } 1375 1376 return int64(count), nil 1377 } 1378 1379 var team *model.Team 1380 if teamID != "" { 1381 team, err = a.GetTeam(teamID) 1382 if err != nil { 1383 return 0, err 1384 } 1385 } 1386 1387 groups, nErr := a.getGroupsAllowedForReferenceInChannel(channel, team) 1388 if nErr != nil { 1389 return 0, model.NewAppError("countMentionsFromPost", "app.channel.count_posts_since.app_error", nil, nErr.Error(), http.StatusInternalServerError) 1390 } 1391 1392 mentions := getExplicitMentions(post, keywords, groups) 1393 if _, ok := mentions.Mentions[user.Id]; ok { 1394 count += 1 1395 } 1396 1397 for _, p := range posts { 1398 mentions = getExplicitMentions(p, keywords, groups) 1399 if _, ok := mentions.Mentions[user.Id]; ok { 1400 count += 1 1401 } 1402 } 1403 1404 return int64(count), nil 1405 } 1406 1407 // countMentionsFromPost returns the number of posts in the post's channel that mention the user after and including the 1408 // given post. 1409 func (a *App) countMentionsFromPost(user *model.User, post *model.Post) (int, *model.AppError) { 1410 channel, err := a.GetChannel(post.ChannelId) 1411 if err != nil { 1412 return 0, err 1413 } 1414 1415 if channel.Type == model.CHANNEL_DIRECT { 1416 // In a DM channel, every post made by the other user is a mention 1417 count, nErr := a.Srv().Store.Channel().CountPostsAfter(post.ChannelId, post.CreateAt-1, channel.GetOtherUserIdForDM(user.Id)) 1418 if nErr != nil { 1419 return 0, model.NewAppError("countMentionsFromPost", "app.channel.count_posts_since.app_error", nil, nErr.Error(), http.StatusInternalServerError) 1420 } 1421 1422 return count, nil 1423 } 1424 1425 channelMember, err := a.GetChannelMember(channel.Id, user.Id) 1426 if err != nil { 1427 return 0, err 1428 } 1429 1430 keywords := addMentionKeywordsForUser( 1431 map[string][]string{}, 1432 user, 1433 channelMember.NotifyProps, 1434 &model.Status{Status: model.STATUS_ONLINE}, // Assume the user is online since they would've triggered this 1435 true, // Assume channel mentions are always allowed for simplicity 1436 ) 1437 commentMentions := user.NotifyProps[model.COMMENTS_NOTIFY_PROP] 1438 checkForCommentMentions := commentMentions == model.COMMENTS_NOTIFY_ROOT || commentMentions == model.COMMENTS_NOTIFY_ANY 1439 1440 // A mapping of thread root IDs to whether or not a post in that thread mentions the user 1441 mentionedByThread := make(map[string]bool) 1442 1443 thread, err := a.GetPostThread(post.Id, false, false, false) 1444 if err != nil { 1445 return 0, err 1446 } 1447 1448 count := 0 1449 1450 if isPostMention(user, post, keywords, thread.Posts, mentionedByThread, checkForCommentMentions) { 1451 count += 1 1452 } 1453 1454 page := 0 1455 perPage := 200 1456 for { 1457 postList, err := a.GetPostsAfterPost(model.GetPostsOptions{ 1458 ChannelId: post.ChannelId, 1459 PostId: post.Id, 1460 Page: page, 1461 PerPage: perPage, 1462 }) 1463 if err != nil { 1464 return 0, err 1465 } 1466 1467 for _, postId := range postList.Order { 1468 if isPostMention(user, postList.Posts[postId], keywords, postList.Posts, mentionedByThread, checkForCommentMentions) { 1469 count += 1 1470 } 1471 } 1472 1473 if len(postList.Order) < perPage { 1474 break 1475 } 1476 1477 page += 1 1478 } 1479 1480 return count, nil 1481 } 1482 1483 func isCommentMention(user *model.User, post *model.Post, otherPosts map[string]*model.Post, mentionedByThread map[string]bool) bool { 1484 if post.RootId == "" { 1485 // Not a comment 1486 return false 1487 } 1488 1489 if mentioned, ok := mentionedByThread[post.RootId]; ok { 1490 // We've already figured out if the user was mentioned by this thread 1491 return mentioned 1492 } 1493 1494 // Whether or not the user was mentioned because they started the thread 1495 mentioned := otherPosts[post.RootId].UserId == user.Id 1496 1497 // Or because they commented on it before this post 1498 if !mentioned && user.NotifyProps[model.COMMENTS_NOTIFY_PROP] == model.COMMENTS_NOTIFY_ANY { 1499 for _, otherPost := range otherPosts { 1500 if otherPost.Id == post.Id { 1501 continue 1502 } 1503 1504 if otherPost.RootId != post.RootId { 1505 continue 1506 } 1507 1508 if otherPost.UserId == user.Id && otherPost.CreateAt < post.CreateAt { 1509 // Found a comment made by the user from before this post 1510 mentioned = true 1511 break 1512 } 1513 } 1514 } 1515 1516 mentionedByThread[post.RootId] = mentioned 1517 return mentioned 1518 } 1519 1520 func isPostMention(user *model.User, post *model.Post, keywords map[string][]string, otherPosts map[string]*model.Post, mentionedByThread map[string]bool, checkForCommentMentions bool) bool { 1521 // Prevent the user from mentioning themselves 1522 if post.UserId == user.Id && post.GetProp("from_webhook") != "true" { 1523 return false 1524 } 1525 1526 // Check for keyword mentions 1527 mentions := getExplicitMentions(post, keywords, make(map[string]*model.Group)) 1528 if _, ok := mentions.Mentions[user.Id]; ok { 1529 return true 1530 } 1531 1532 // Check for mentions caused by being added to the channel 1533 if post.Type == model.POST_ADD_TO_CHANNEL { 1534 if addedUserId, ok := post.GetProp(model.POST_PROPS_ADDED_USER_ID).(string); ok && addedUserId == user.Id { 1535 return true 1536 } 1537 } 1538 1539 // Check for comment mentions 1540 if checkForCommentMentions && isCommentMention(user, post, otherPosts, mentionedByThread) { 1541 return true 1542 } 1543 1544 return false 1545 } 1546 1547 func (a *App) GetThreadMembershipsForUser(userID, teamID string) ([]*model.ThreadMembership, error) { 1548 return a.Srv().Store.Thread().GetMembershipsForUser(userID, teamID) 1549 }