github.com/vnforks/kid@v5.11.1+incompatible/app/post.go (about) 1 // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package app 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "net/http" 10 "strings" 11 "time" 12 13 "github.com/mattermost/mattermost-server/mlog" 14 "github.com/mattermost/mattermost-server/model" 15 "github.com/mattermost/mattermost-server/plugin" 16 "github.com/mattermost/mattermost-server/store" 17 "github.com/mattermost/mattermost-server/utils" 18 ) 19 20 const ( 21 PENDING_POST_IDS_CACHE_SIZE = 25000 22 PENDING_POST_IDS_CACHE_TTL = 30 * time.Second 23 ) 24 25 func (a *App) CreatePostAsUser(post *model.Post, currentSessionId string) (*model.Post, *model.AppError) { 26 // Check that channel has not been deleted 27 result := <-a.Srv.Store.Channel().Get(post.ChannelId, true) 28 if result.Err != nil { 29 err := model.NewAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.channel_id"}, result.Err.Error(), http.StatusBadRequest) 30 return nil, err 31 } 32 channel := result.Data.(*model.Channel) 33 34 if strings.HasPrefix(post.Type, model.POST_SYSTEM_MESSAGE_PREFIX) { 35 err := model.NewAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.type"}, "", http.StatusBadRequest) 36 return nil, err 37 } 38 39 if channel.DeleteAt != 0 { 40 err := model.NewAppError("createPost", "api.post.create_post.can_not_post_to_deleted.error", nil, "", http.StatusBadRequest) 41 return nil, err 42 } 43 44 rp, err := a.CreatePost(post, channel, true) 45 if err != nil { 46 if err.Id == "api.post.create_post.root_id.app_error" || 47 err.Id == "api.post.create_post.channel_root_id.app_error" || 48 err.Id == "api.post.create_post.parent_id.app_error" { 49 err.StatusCode = http.StatusBadRequest 50 } 51 52 if err.Id == "api.post.create_post.town_square_read_only" { 53 result := <-a.Srv.Store.User().Get(post.UserId) 54 if result.Err != nil { 55 return nil, result.Err 56 } 57 user := result.Data.(*model.User) 58 59 T := utils.GetUserTranslations(user.Locale) 60 a.SendEphemeralPost( 61 post.UserId, 62 &model.Post{ 63 ChannelId: channel.Id, 64 ParentId: post.ParentId, 65 RootId: post.RootId, 66 UserId: post.UserId, 67 Message: T("api.post.create_post.town_square_read_only"), 68 CreateAt: model.GetMillis() + 1, 69 }, 70 ) 71 } 72 return nil, err 73 } 74 75 // Update the LastViewAt only if the post does not have from_webhook prop set (eg. Zapier app) 76 if _, ok := post.Props["from_webhook"]; !ok { 77 if _, err := a.MarkChannelsAsViewed([]string{post.ChannelId}, post.UserId, currentSessionId); err != nil { 78 mlog.Error(fmt.Sprintf("Encountered error updating last viewed, channel_id=%s, user_id=%s, err=%v", post.ChannelId, post.UserId, err)) 79 } 80 } 81 82 return rp, nil 83 } 84 85 func (a *App) CreatePostMissingChannel(post *model.Post, triggerWebhooks bool) (*model.Post, *model.AppError) { 86 result := <-a.Srv.Store.Channel().Get(post.ChannelId, true) 87 if result.Err != nil { 88 return nil, result.Err 89 } 90 channel := result.Data.(*model.Channel) 91 92 return a.CreatePost(post, channel, triggerWebhooks) 93 } 94 95 // deduplicateCreatePost attempts to make posting idempotent within a caching window. 96 func (a *App) deduplicateCreatePost(post *model.Post) (foundPost *model.Post, err *model.AppError) { 97 // We rely on the client sending the pending post id across "duplicate" requests. If there 98 // isn't one, we can't deduplicate, so allow creation normally. 99 if post.PendingPostId == "" { 100 return nil, nil 101 } 102 103 const unknownPostId = "" 104 105 // Query the cache atomically for the given pending post id, saving a record if 106 // it hasn't previously been seen. 107 value, loaded := a.Srv.seenPendingPostIdsCache.GetOrAdd(post.PendingPostId, unknownPostId, PENDING_POST_IDS_CACHE_TTL) 108 109 // If we were the first thread to save this pending post id into the cache, 110 // proceed with create post normally. 111 if !loaded { 112 return nil, nil 113 } 114 115 postId := value.(string) 116 117 // If another thread saved the cache record, but hasn't yet updated it with the actual post 118 // id (because it's still saving), notify the client with an error. Ideally, we'd wait 119 // for the other thread, but coordinating that adds complexity to the happy path. 120 if postId == unknownPostId { 121 return nil, model.NewAppError("deduplicateCreatePost", "api.post.deduplicate_create_post.pending", nil, "", http.StatusInternalServerError) 122 } 123 124 // If the other thread finished creating the post, return the created post back to the 125 // client, making the API call feel idempotent. 126 actualPost, err := a.GetSinglePost(postId) 127 if err != nil { 128 return nil, model.NewAppError("deduplicateCreatePost", "api.post.deduplicate_create_post.failed_to_get", nil, err.Error(), http.StatusInternalServerError) 129 } 130 131 mlog.Debug("Deduplicated create post", mlog.String("post_id", actualPost.Id), mlog.String("pending_post_id", post.PendingPostId)) 132 133 return actualPost, nil 134 } 135 136 func (a *App) CreatePost(post *model.Post, channel *model.Channel, triggerWebhooks bool) (savedPost *model.Post, err *model.AppError) { 137 foundPost, err := a.deduplicateCreatePost(post) 138 if err != nil { 139 return nil, err 140 } 141 if foundPost != nil { 142 return foundPost, nil 143 } 144 145 // If we get this far, we've recorded the client-provided pending post id to the cache. 146 // Remove it if we fail below, allowing a proper retry by the client. 147 defer func() { 148 if post.PendingPostId == "" { 149 return 150 } 151 152 if err != nil { 153 a.Srv.seenPendingPostIdsCache.Remove(post.PendingPostId) 154 return 155 } 156 157 a.Srv.seenPendingPostIdsCache.AddWithExpiresInSecs(post.PendingPostId, savedPost.Id, int64(PENDING_POST_IDS_CACHE_TTL.Seconds())) 158 }() 159 160 post.SanitizeProps() 161 162 var pchan store.StoreChannel 163 if len(post.RootId) > 0 { 164 pchan = a.Srv.Store.Post().Get(post.RootId) 165 } 166 167 result := <-a.Srv.Store.User().Get(post.UserId) 168 if result.Err != nil { 169 return nil, result.Err 170 } 171 user := result.Data.(*model.User) 172 173 if a.License() != nil && *a.Config().TeamSettings.ExperimentalTownSquareIsReadOnly && 174 !post.IsSystemMessage() && 175 channel.Name == model.DEFAULT_CHANNEL && 176 !a.RolesGrantPermission(user.GetRoles(), model.PERMISSION_MANAGE_SYSTEM.Id) { 177 return nil, model.NewAppError("createPost", "api.post.create_post.town_square_read_only", nil, "", http.StatusForbidden) 178 } 179 180 // Verify the parent/child relationships are correct 181 var parentPostList *model.PostList 182 if pchan != nil { 183 result = <-pchan 184 if result.Err != nil { 185 return nil, model.NewAppError("createPost", "api.post.create_post.root_id.app_error", nil, "", http.StatusBadRequest) 186 } 187 parentPostList = result.Data.(*model.PostList) 188 if len(parentPostList.Posts) == 0 || !parentPostList.IsChannelId(post.ChannelId) { 189 return nil, model.NewAppError("createPost", "api.post.create_post.channel_root_id.app_error", nil, "", http.StatusInternalServerError) 190 } 191 192 rootPost := parentPostList.Posts[post.RootId] 193 if len(rootPost.RootId) > 0 { 194 return nil, model.NewAppError("createPost", "api.post.create_post.root_id.app_error", nil, "", http.StatusBadRequest) 195 } 196 197 if post.ParentId == "" { 198 post.ParentId = post.RootId 199 } 200 201 if post.RootId != post.ParentId { 202 parent := parentPostList.Posts[post.ParentId] 203 if parent == nil { 204 return nil, model.NewAppError("createPost", "api.post.create_post.parent_id.app_error", nil, "", http.StatusInternalServerError) 205 } 206 } 207 } 208 209 post.Hashtags, _ = model.ParseHashtags(post.Message) 210 211 if err := a.FillInPostProps(post, channel); err != nil { 212 return nil, err 213 } 214 215 // Temporary fix so old plugins don't clobber new fields in SlackAttachment struct, see MM-13088 216 if attachments, ok := post.Props["attachments"].([]*model.SlackAttachment); ok { 217 jsonAttachments, err := json.Marshal(attachments) 218 if err == nil { 219 attachmentsInterface := []interface{}{} 220 err = json.Unmarshal(jsonAttachments, &attachmentsInterface) 221 post.Props["attachments"] = attachmentsInterface 222 } 223 if err != nil { 224 mlog.Error("Could not convert post attachments to map interface, err=%s" + err.Error()) 225 } 226 } 227 228 if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { 229 var rejectionError *model.AppError 230 pluginContext := a.PluginContext() 231 pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 232 replacementPost, rejectionReason := hooks.MessageWillBePosted(pluginContext, post) 233 if rejectionReason != "" { 234 rejectionError = model.NewAppError("createPost", "Post rejected by plugin. "+rejectionReason, nil, "", http.StatusBadRequest) 235 return false 236 } 237 if replacementPost != nil { 238 post = replacementPost 239 } 240 241 return true 242 }, plugin.MessageWillBePostedId) 243 if rejectionError != nil { 244 return nil, rejectionError 245 } 246 } 247 248 result = <-a.Srv.Store.Post().Save(post) 249 if result.Err != nil { 250 return nil, result.Err 251 } 252 rpost := result.Data.(*model.Post) 253 254 // Update the mapping from pending post id to the actual post id, for any clients that 255 // might be duplicating requests. 256 a.Srv.seenPendingPostIdsCache.AddWithExpiresInSecs(post.PendingPostId, rpost.Id, int64(PENDING_POST_IDS_CACHE_TTL.Seconds())) 257 258 if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { 259 a.Srv.Go(func() { 260 pluginContext := a.PluginContext() 261 pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 262 hooks.MessageHasBeenPosted(pluginContext, rpost) 263 return true 264 }, plugin.MessageHasBeenPostedId) 265 }) 266 } 267 268 esInterface := a.Elasticsearch 269 if esInterface != nil && *a.Config().ElasticsearchSettings.EnableIndexing { 270 a.Srv.Go(func() { 271 if err := esInterface.IndexPost(rpost, channel.TeamId); err != nil { 272 mlog.Error("Encountered error indexing post", mlog.String("post_id", post.Id), mlog.Err(err)) 273 } 274 }) 275 } 276 277 if a.Metrics != nil { 278 a.Metrics.IncrementPostCreate() 279 } 280 281 if len(post.FileIds) > 0 { 282 if err := a.attachFilesToPost(post); err != nil { 283 mlog.Error("Encountered error attaching files to post", mlog.String("post_id", post.Id), mlog.Any("file_ids", post.FileIds), mlog.Err(result.Err)) 284 } 285 286 if a.Metrics != nil { 287 a.Metrics.IncrementPostFileAttachment(len(post.FileIds)) 288 } 289 } 290 291 // Normally, we would let the API layer call PreparePostForClient, but we do it here since it also needs 292 // to be done when we send the post over the websocket in handlePostEvents 293 rpost = a.PreparePostForClient(rpost, true) 294 295 if err := a.handlePostEvents(rpost, user, channel, triggerWebhooks, parentPostList); err != nil { 296 mlog.Error("Failed to handle post events", mlog.Err(err)) 297 } 298 299 return rpost, nil 300 } 301 302 func (a *App) attachFilesToPost(post *model.Post) *model.AppError { 303 var attachedIds []string 304 for _, fileId := range post.FileIds { 305 result := <-a.Srv.Store.FileInfo().AttachToPost(fileId, post.Id, post.UserId) 306 if result.Err != nil { 307 mlog.Warn("Failed to attach file to post", mlog.String("file_id", fileId), mlog.String("post_id", post.Id), mlog.Err(result.Err)) 308 continue 309 } 310 311 attachedIds = append(attachedIds, fileId) 312 } 313 314 if len(post.FileIds) != len(attachedIds) { 315 // We couldn't attach all files to the post, so ensure that post.FileIds reflects what was actually attached 316 post.FileIds = attachedIds 317 318 result := <-a.Srv.Store.Post().Overwrite(post) 319 if result.Err != nil { 320 return result.Err 321 } 322 } 323 324 return nil 325 } 326 327 // FillInPostProps should be invoked before saving posts to fill in properties such as 328 // channel_mentions. 329 // 330 // If channel is nil, FillInPostProps will look up the channel corresponding to the post. 331 func (a *App) FillInPostProps(post *model.Post, channel *model.Channel) *model.AppError { 332 channelMentions := post.ChannelMentions() 333 channelMentionsProp := make(map[string]interface{}) 334 335 if len(channelMentions) > 0 { 336 if channel == nil { 337 result := <-a.Srv.Store.Channel().GetForPost(post.Id) 338 if result.Err != nil { 339 return model.NewAppError("FillInPostProps", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.channel_id"}, result.Err.Error(), http.StatusBadRequest) 340 } 341 channel = result.Data.(*model.Channel) 342 } 343 344 mentionedChannels, err := a.GetChannelsByNames(channelMentions, channel.TeamId) 345 if err != nil { 346 return err 347 } 348 349 for _, mentioned := range mentionedChannels { 350 if mentioned.Type == model.CHANNEL_OPEN { 351 channelMentionsProp[mentioned.Name] = map[string]interface{}{ 352 "display_name": mentioned.DisplayName, 353 } 354 } 355 } 356 } 357 358 if len(channelMentionsProp) > 0 { 359 post.AddProp("channel_mentions", channelMentionsProp) 360 } else if post.Props != nil { 361 delete(post.Props, "channel_mentions") 362 } 363 364 return nil 365 } 366 367 func (a *App) handlePostEvents(post *model.Post, user *model.User, channel *model.Channel, triggerWebhooks bool, parentPostList *model.PostList) *model.AppError { 368 var team *model.Team 369 if len(channel.TeamId) > 0 { 370 result := <-a.Srv.Store.Team().Get(channel.TeamId) 371 if result.Err != nil { 372 return result.Err 373 } 374 team = result.Data.(*model.Team) 375 } else { 376 // Blank team for DMs 377 team = &model.Team{} 378 } 379 380 a.InvalidateCacheForChannel(channel) 381 a.InvalidateCacheForChannelPosts(channel.Id) 382 383 if _, err := a.SendNotifications(post, team, channel, user, parentPostList); err != nil { 384 return err 385 } 386 387 if triggerWebhooks { 388 a.Srv.Go(func() { 389 if err := a.handleWebhookEvents(post, team, channel, user); err != nil { 390 mlog.Error(err.Error()) 391 } 392 }) 393 } 394 395 return nil 396 } 397 398 func (a *App) SendEphemeralPost(userId string, post *model.Post) *model.Post { 399 post.Type = model.POST_EPHEMERAL 400 401 // fill in fields which haven't been specified which have sensible defaults 402 if post.Id == "" { 403 post.Id = model.NewId() 404 } 405 if post.CreateAt == 0 { 406 post.CreateAt = model.GetMillis() 407 } 408 if post.Props == nil { 409 post.Props = model.StringInterface{} 410 } 411 412 post.GenerateActionIds() 413 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, "", post.ChannelId, userId, nil) 414 post = a.PreparePostForClient(post, true) 415 post = model.AddPostActionCookies(post, a.PostActionCookieSecret()) 416 message.Add("post", post.ToJson()) 417 a.Publish(message) 418 419 return post 420 } 421 422 func (a *App) UpdateEphemeralPost(userId string, post *model.Post) *model.Post { 423 post.Type = model.POST_EPHEMERAL 424 425 post.UpdateAt = model.GetMillis() 426 if post.Props == nil { 427 post.Props = model.StringInterface{} 428 } 429 430 post.GenerateActionIds() 431 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", post.ChannelId, userId, nil) 432 post = a.PreparePostForClient(post, true) 433 post = model.AddPostActionCookies(post, a.PostActionCookieSecret()) 434 message.Add("post", post.ToJson()) 435 a.Publish(message) 436 437 return post 438 } 439 440 func (a *App) DeleteEphemeralPost(userId string, post *model.Post) *model.Post { 441 post.Type = model.POST_EPHEMERAL 442 post.DeleteAt = model.GetMillis() 443 post.UpdateAt = post.DeleteAt 444 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", post.ChannelId, userId, nil) 445 446 message.Add("post", post.ToJson()) 447 a.Publish(message) 448 449 return post 450 } 451 452 func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model.AppError) { 453 post.SanitizeProps() 454 455 result := <-a.Srv.Store.Post().Get(post.Id) 456 if result.Err != nil { 457 return nil, result.Err 458 } 459 oldPost := result.Data.(*model.PostList).Posts[post.Id] 460 461 if oldPost == nil { 462 err := model.NewAppError("UpdatePost", "api.post.update_post.find.app_error", nil, "id="+post.Id, http.StatusBadRequest) 463 return nil, err 464 } 465 466 if oldPost.DeleteAt != 0 { 467 err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_details.app_error", map[string]interface{}{"PostId": post.Id}, "", http.StatusBadRequest) 468 return nil, err 469 } 470 471 if oldPost.IsSystemMessage() { 472 err := model.NewAppError("UpdatePost", "api.post.update_post.system_message.app_error", nil, "id="+post.Id, http.StatusBadRequest) 473 return nil, err 474 } 475 476 if a.License() != nil { 477 if *a.Config().ServiceSettings.PostEditTimeLimit != -1 && model.GetMillis() > oldPost.CreateAt+int64(*a.Config().ServiceSettings.PostEditTimeLimit*1000) && post.Message != oldPost.Message { 478 err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_time_limit.app_error", map[string]interface{}{"timeLimit": *a.Config().ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest) 479 return nil, err 480 } 481 } 482 483 channel, err := a.GetChannel(oldPost.ChannelId) 484 if err != nil { 485 return nil, err 486 } 487 488 if channel.DeleteAt != 0 { 489 err := model.NewAppError("UpdatePost", "api.post.update_post.can_not_update_post_in_deleted.error", nil, "", http.StatusBadRequest) 490 return nil, err 491 } 492 493 newPost := &model.Post{} 494 *newPost = *oldPost 495 496 if newPost.Message != post.Message { 497 newPost.Message = post.Message 498 newPost.EditAt = model.GetMillis() 499 newPost.Hashtags, _ = model.ParseHashtags(post.Message) 500 } 501 502 if !safeUpdate { 503 newPost.IsPinned = post.IsPinned 504 newPost.HasReactions = post.HasReactions 505 newPost.FileIds = post.FileIds 506 newPost.Props = post.Props 507 } 508 509 // Avoid deep-equal checks if EditAt was already modified through message change 510 if newPost.EditAt == oldPost.EditAt && (!oldPost.FileIds.Equals(newPost.FileIds) || !oldPost.AttachmentsEqual(newPost)) { 511 newPost.EditAt = model.GetMillis() 512 } 513 514 if err := a.FillInPostProps(post, nil); err != nil { 515 return nil, err 516 } 517 518 if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { 519 var rejectionReason string 520 pluginContext := a.PluginContext() 521 pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 522 newPost, rejectionReason = hooks.MessageWillBeUpdated(pluginContext, newPost, oldPost) 523 return post != nil 524 }, plugin.MessageWillBeUpdatedId) 525 if newPost == nil { 526 return nil, model.NewAppError("UpdatePost", "Post rejected by plugin. "+rejectionReason, nil, "", http.StatusBadRequest) 527 } 528 } 529 530 result = <-a.Srv.Store.Post().Update(newPost, oldPost) 531 if result.Err != nil { 532 return nil, result.Err 533 } 534 rpost := result.Data.(*model.Post) 535 536 if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { 537 a.Srv.Go(func() { 538 pluginContext := a.PluginContext() 539 pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 540 hooks.MessageHasBeenUpdated(pluginContext, newPost, oldPost) 541 return true 542 }, plugin.MessageHasBeenUpdatedId) 543 }) 544 } 545 546 esInterface := a.Elasticsearch 547 if esInterface != nil && *a.Config().ElasticsearchSettings.EnableIndexing { 548 a.Srv.Go(func() { 549 rchannel := <-a.Srv.Store.Channel().GetForPost(rpost.Id) 550 if rchannel.Err != nil { 551 mlog.Error(fmt.Sprintf("Couldn't get channel %v for post %v for Elasticsearch indexing.", rpost.ChannelId, rpost.Id)) 552 return 553 } 554 if err := esInterface.IndexPost(rpost, rchannel.Data.(*model.Channel).TeamId); err != nil { 555 mlog.Error("Encountered error indexing post", mlog.String("post_id", post.Id), mlog.Err(err)) 556 } 557 }) 558 } 559 560 rpost = a.PreparePostForClient(rpost, false) 561 562 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", rpost.ChannelId, "", nil) 563 message.Add("post", rpost.ToJson()) 564 a.Publish(message) 565 566 a.InvalidateCacheForChannelPosts(rpost.ChannelId) 567 568 return rpost, nil 569 } 570 571 func (a *App) PatchPost(postId string, patch *model.PostPatch) (*model.Post, *model.AppError) { 572 post, err := a.GetSinglePost(postId) 573 if err != nil { 574 return nil, err 575 } 576 577 channel, err := a.GetChannel(post.ChannelId) 578 if err != nil { 579 return nil, err 580 } 581 582 if channel.DeleteAt != 0 { 583 err = model.NewAppError("PatchPost", "api.post.patch_post.can_not_update_post_in_deleted.error", nil, "", http.StatusBadRequest) 584 return nil, err 585 } 586 587 post.Patch(patch) 588 589 updatedPost, err := a.UpdatePost(post, false) 590 if err != nil { 591 return nil, err 592 } 593 594 return updatedPost, nil 595 } 596 597 func (a *App) GetPostsPage(channelId string, page int, perPage int) (*model.PostList, *model.AppError) { 598 result := <-a.Srv.Store.Post().GetPosts(channelId, page*perPage, perPage, true) 599 if result.Err != nil { 600 return nil, result.Err 601 } 602 return result.Data.(*model.PostList), nil 603 } 604 605 func (a *App) GetPosts(channelId string, offset int, limit int) (*model.PostList, *model.AppError) { 606 result := <-a.Srv.Store.Post().GetPosts(channelId, offset, limit, true) 607 if result.Err != nil { 608 return nil, result.Err 609 } 610 return result.Data.(*model.PostList), nil 611 } 612 613 func (a *App) GetPostsEtag(channelId string) string { 614 return (<-a.Srv.Store.Post().GetEtag(channelId, true)).Data.(string) 615 } 616 617 func (a *App) GetPostsSince(channelId string, time int64) (*model.PostList, *model.AppError) { 618 result := <-a.Srv.Store.Post().GetPostsSince(channelId, time, true) 619 if result.Err != nil { 620 return nil, result.Err 621 } 622 return result.Data.(*model.PostList), nil 623 } 624 625 func (a *App) GetSinglePost(postId string) (*model.Post, *model.AppError) { 626 result := <-a.Srv.Store.Post().GetSingle(postId) 627 if result.Err != nil { 628 return nil, result.Err 629 } 630 return result.Data.(*model.Post), nil 631 } 632 633 func (a *App) GetPostThread(postId string) (*model.PostList, *model.AppError) { 634 result := <-a.Srv.Store.Post().Get(postId) 635 if result.Err != nil { 636 return nil, result.Err 637 } 638 return result.Data.(*model.PostList), nil 639 } 640 641 func (a *App) GetFlaggedPosts(userId string, offset int, limit int) (*model.PostList, *model.AppError) { 642 result := <-a.Srv.Store.Post().GetFlaggedPosts(userId, offset, limit) 643 if result.Err != nil { 644 return nil, result.Err 645 } 646 return result.Data.(*model.PostList), nil 647 } 648 649 func (a *App) GetFlaggedPostsForTeam(userId, teamId string, offset int, limit int) (*model.PostList, *model.AppError) { 650 result := <-a.Srv.Store.Post().GetFlaggedPostsForTeam(userId, teamId, offset, limit) 651 if result.Err != nil { 652 return nil, result.Err 653 } 654 return result.Data.(*model.PostList), nil 655 } 656 657 func (a *App) GetFlaggedPostsForChannel(userId, channelId string, offset int, limit int) (*model.PostList, *model.AppError) { 658 result := <-a.Srv.Store.Post().GetFlaggedPostsForChannel(userId, channelId, offset, limit) 659 if result.Err != nil { 660 return nil, result.Err 661 } 662 return result.Data.(*model.PostList), nil 663 } 664 665 func (a *App) GetPermalinkPost(postId string, userId string) (*model.PostList, *model.AppError) { 666 result := <-a.Srv.Store.Post().Get(postId) 667 if result.Err != nil { 668 return nil, result.Err 669 } 670 list := result.Data.(*model.PostList) 671 672 if len(list.Order) != 1 { 673 return nil, model.NewAppError("getPermalinkTmp", "api.post_get_post_by_id.get.app_error", nil, "", http.StatusNotFound) 674 } 675 post := list.Posts[list.Order[0]] 676 677 channel, err := a.GetChannel(post.ChannelId) 678 if err != nil { 679 return nil, err 680 } 681 682 if err = a.JoinChannel(channel, userId); err != nil { 683 return nil, err 684 } 685 686 return list, nil 687 } 688 689 func (a *App) GetPostsBeforePost(channelId, postId string, page, perPage int) (*model.PostList, *model.AppError) { 690 result := <-a.Srv.Store.Post().GetPostsBefore(channelId, postId, perPage, page*perPage) 691 if result.Err != nil { 692 return nil, result.Err 693 } 694 return result.Data.(*model.PostList), nil 695 } 696 697 func (a *App) GetPostsAfterPost(channelId, postId string, page, perPage int) (*model.PostList, *model.AppError) { 698 result := <-a.Srv.Store.Post().GetPostsAfter(channelId, postId, perPage, page*perPage) 699 if result.Err != nil { 700 return nil, result.Err 701 } 702 return result.Data.(*model.PostList), nil 703 } 704 705 func (a *App) GetPostsAroundPost(postId, channelId string, offset, limit int, before bool) (*model.PostList, *model.AppError) { 706 var pchan store.StoreChannel 707 if before { 708 pchan = a.Srv.Store.Post().GetPostsBefore(channelId, postId, limit, offset) 709 } else { 710 pchan = a.Srv.Store.Post().GetPostsAfter(channelId, postId, limit, offset) 711 } 712 713 result := <-pchan 714 if result.Err != nil { 715 return nil, result.Err 716 } 717 return result.Data.(*model.PostList), nil 718 } 719 720 func (a *App) DeletePost(postId, deleteByID string) (*model.Post, *model.AppError) { 721 result := <-a.Srv.Store.Post().GetSingle(postId) 722 if result.Err != nil { 723 result.Err.StatusCode = http.StatusBadRequest 724 return nil, result.Err 725 } 726 post := result.Data.(*model.Post) 727 728 channel, err := a.GetChannel(post.ChannelId) 729 if err != nil { 730 return nil, err 731 } 732 733 if channel.DeleteAt != 0 { 734 err := model.NewAppError("DeletePost", "api.post.delete_post.can_not_delete_post_in_deleted.error", nil, "", http.StatusBadRequest) 735 return nil, err 736 } 737 738 if result := <-a.Srv.Store.Post().Delete(postId, model.GetMillis(), deleteByID); result.Err != nil { 739 return nil, result.Err 740 } 741 742 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", post.ChannelId, "", nil) 743 message.Add("post", a.PreparePostForClient(post, false).ToJson()) 744 a.Publish(message) 745 746 a.Srv.Go(func() { 747 a.DeletePostFiles(post) 748 }) 749 a.Srv.Go(func() { 750 a.DeleteFlaggedPosts(post.Id) 751 }) 752 753 esInterface := a.Elasticsearch 754 if esInterface != nil && *a.Config().ElasticsearchSettings.EnableIndexing { 755 a.Srv.Go(func() { 756 if err := esInterface.DeletePost(post); err != nil { 757 mlog.Error("Encountered error deleting post", mlog.String("post_id", post.Id), mlog.Err(err)) 758 } 759 }) 760 } 761 762 a.InvalidateCacheForChannelPosts(post.ChannelId) 763 764 return post, nil 765 } 766 767 func (a *App) DeleteFlaggedPosts(postId string) { 768 if result := <-a.Srv.Store.Preference().DeleteCategoryAndName(model.PREFERENCE_CATEGORY_FLAGGED_POST, postId); result.Err != nil { 769 mlog.Warn(fmt.Sprintf("Unable to delete flagged post preference when deleting post, err=%v", result.Err)) 770 return 771 } 772 } 773 774 func (a *App) DeletePostFiles(post *model.Post) { 775 if len(post.FileIds) == 0 { 776 return 777 } 778 779 if result := <-a.Srv.Store.FileInfo().DeleteForPost(post.Id); result.Err != nil { 780 mlog.Warn(fmt.Sprintf("Encountered error when deleting files for post, post_id=%v, err=%v", post.Id, result.Err), mlog.String("post_id", post.Id)) 781 } 782 } 783 784 func (a *App) parseAndFetchChannelIdByNameFromInFilter(channelName, userId, teamId string, includeDeleted bool) (*model.Channel, error) { 785 if strings.HasPrefix(channelName, "@") && strings.Contains(channelName, ",") { 786 var userIds []string 787 users, err := a.GetUsersByUsernames(strings.Split(channelName[1:], ","), false) 788 if err != nil { 789 return nil, err 790 } 791 for _, user := range users { 792 userIds = append(userIds, user.Id) 793 } 794 795 channel, err := a.GetGroupChannel(userIds) 796 if err != nil { 797 return nil, err 798 } 799 return channel, nil 800 } 801 802 if strings.HasPrefix(channelName, "@") && !strings.Contains(channelName, ",") { 803 user, err := a.GetUserByUsername(channelName[1:]) 804 if err != nil { 805 return nil, err 806 } 807 channel, err := a.GetOrCreateDirectChannel(userId, user.Id) 808 if err != nil { 809 return nil, err 810 } 811 return channel, nil 812 } 813 814 channel, err := a.GetChannelByName(channelName, teamId, includeDeleted) 815 if err != nil { 816 return nil, err 817 } 818 return channel, nil 819 } 820 821 func (a *App) searchPostsInTeam(teamId string, userId string, paramsList []*model.SearchParams, modifierFun func(*model.SearchParams)) (*model.PostList, *model.AppError) { 822 channels := []store.StoreChannel{} 823 824 for _, params := range paramsList { 825 // Don't allow users to search for everything. 826 if params.Terms == "*" { 827 continue 828 } 829 modifierFun(params) 830 channels = append(channels, a.Srv.Store.Post().Search(teamId, userId, params)) 831 } 832 833 posts := model.NewPostList() 834 for _, channel := range channels { 835 result := <-channel 836 if result.Err != nil { 837 return nil, result.Err 838 } 839 data := result.Data.(*model.PostList) 840 posts.Extend(data) 841 } 842 843 posts.SortByCreateAt() 844 return posts, nil 845 } 846 847 func (a *App) SearchPostsInTeam(teamId string, paramsList []*model.SearchParams) (*model.PostList, *model.AppError) { 848 if !*a.Config().ServiceSettings.EnablePostSearch { 849 return nil, model.NewAppError("SearchPostsInTeam", "store.sql_post.search.disabled", nil, fmt.Sprintf("teamId=%v", teamId), http.StatusNotImplemented) 850 } 851 return a.searchPostsInTeam(teamId, "", paramsList, func(params *model.SearchParams) { 852 params.SearchWithoutUserId = true 853 }) 854 } 855 856 func (a *App) SearchPostsInTeamForUser(terms string, userId string, teamId string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int) (*model.PostSearchResults, *model.AppError) { 857 paramsList := model.ParseSearchParams(terms, timeZoneOffset) 858 includeDeleted := includeDeletedChannels && *a.Config().TeamSettings.ExperimentalViewArchivedChannels 859 860 esInterface := a.Elasticsearch 861 license := a.License() 862 if esInterface != nil && *a.Config().ElasticsearchSettings.EnableSearching && license != nil && *license.Features.Elasticsearch { 863 finalParamsList := []*model.SearchParams{} 864 865 for _, params := range paramsList { 866 params.OrTerms = isOrSearch 867 // Don't allow users to search for "*" 868 if params.Terms != "*" { 869 // Convert channel names to channel IDs 870 for idx, channelName := range params.InChannels { 871 channel, err := a.parseAndFetchChannelIdByNameFromInFilter(channelName, userId, teamId, includeDeletedChannels) 872 if err != nil { 873 mlog.Error(fmt.Sprint(err)) 874 continue 875 } 876 params.InChannels[idx] = channel.Id 877 } 878 879 // Convert usernames to user IDs 880 for idx, username := range params.FromUsers { 881 if user, err := a.GetUserByUsername(username); err != nil { 882 mlog.Error(fmt.Sprint(err)) 883 } else { 884 params.FromUsers[idx] = user.Id 885 } 886 } 887 888 finalParamsList = append(finalParamsList, params) 889 } 890 } 891 892 // If the processed search params are empty, return empty search results. 893 if len(finalParamsList) == 0 { 894 return model.MakePostSearchResults(model.NewPostList(), nil), nil 895 } 896 897 // We only allow the user to search in channels they are a member of. 898 userChannels, err := a.GetChannelsForUser(teamId, userId, includeDeleted) 899 if err != nil { 900 mlog.Error(fmt.Sprint(err)) 901 return nil, err 902 } 903 904 postIds, matches, err := a.Elasticsearch.SearchPosts(userChannels, finalParamsList, page, perPage) 905 if err != nil { 906 return nil, err 907 } 908 909 // Get the posts 910 postList := model.NewPostList() 911 if len(postIds) > 0 { 912 presult := <-a.Srv.Store.Post().GetPostsByIds(postIds) 913 if presult.Err != nil { 914 return nil, presult.Err 915 } 916 for _, p := range presult.Data.([]*model.Post) { 917 if p.DeleteAt == 0 { 918 postList.AddPost(p) 919 postList.AddOrder(p.Id) 920 } 921 } 922 } 923 924 return model.MakePostSearchResults(postList, matches), nil 925 } 926 927 if !*a.Config().ServiceSettings.EnablePostSearch { 928 return nil, model.NewAppError("SearchPostsInTeamForUser", "store.sql_post.search.disabled", nil, fmt.Sprintf("teamId=%v userId=%v", teamId, userId), http.StatusNotImplemented) 929 } 930 931 // Since we don't support paging we just return nothing for later pages 932 if page > 0 { 933 return model.MakePostSearchResults(model.NewPostList(), nil), nil 934 } 935 936 posts, err := a.searchPostsInTeam(teamId, userId, paramsList, func(params *model.SearchParams) { 937 params.IncludeDeletedChannels = includeDeleted 938 params.OrTerms = isOrSearch 939 for idx, channelName := range params.InChannels { 940 if strings.HasPrefix(channelName, "@") { 941 channel, err := a.parseAndFetchChannelIdByNameFromInFilter(channelName, userId, teamId, includeDeletedChannels) 942 if err != nil { 943 mlog.Error(fmt.Sprint(err)) 944 continue 945 } 946 params.InChannels[idx] = channel.Name 947 } 948 } 949 }) 950 if err != nil { 951 return nil, err 952 } 953 return model.MakePostSearchResults(posts, nil), nil 954 } 955 956 func (a *App) GetFileInfosForPostWithMigration(postId string) ([]*model.FileInfo, *model.AppError) { 957 pchan := a.Srv.Store.Post().GetSingle(postId) 958 959 infos, err := a.GetFileInfosForPost(postId) 960 if err != nil { 961 return nil, err 962 } 963 964 if len(infos) == 0 { 965 // No FileInfos were returned so check if they need to be created for this post 966 result := <-pchan 967 if result.Err != nil { 968 return nil, result.Err 969 } 970 post := result.Data.(*model.Post) 971 972 if len(post.Filenames) > 0 { 973 a.Srv.Store.FileInfo().InvalidateFileInfosForPostCache(postId) 974 // The post has Filenames that need to be replaced with FileInfos 975 infos = a.MigrateFilenamesToFileInfos(post) 976 } 977 } 978 979 return infos, nil 980 } 981 982 func (a *App) GetFileInfosForPost(postId string) ([]*model.FileInfo, *model.AppError) { 983 result := <-a.Srv.Store.FileInfo().GetForPost(postId, false, true) 984 if result.Err != nil { 985 return nil, result.Err 986 } 987 988 return result.Data.([]*model.FileInfo), nil 989 } 990 991 func (a *App) PostWithProxyAddedToImageURLs(post *model.Post) *model.Post { 992 if f := a.ImageProxyAdder(); f != nil { 993 return post.WithRewrittenImageURLs(f) 994 } 995 return post 996 } 997 998 func (a *App) PostWithProxyRemovedFromImageURLs(post *model.Post) *model.Post { 999 if f := a.ImageProxyRemover(); f != nil { 1000 return post.WithRewrittenImageURLs(f) 1001 } 1002 return post 1003 } 1004 1005 func (a *App) PostPatchWithProxyRemovedFromImageURLs(patch *model.PostPatch) *model.PostPatch { 1006 if f := a.ImageProxyRemover(); f != nil { 1007 return patch.WithRewrittenImageURLs(f) 1008 } 1009 return patch 1010 } 1011 1012 func (a *App) ImageProxyAdder() func(string) string { 1013 if !*a.Config().ImageProxySettings.Enable { 1014 return nil 1015 } 1016 1017 return func(url string) string { 1018 return a.Srv.ImageProxy.GetProxiedImageURL(url) 1019 } 1020 } 1021 1022 func (a *App) ImageProxyRemover() (f func(string) string) { 1023 if !*a.Config().ImageProxySettings.Enable { 1024 return nil 1025 } 1026 1027 return func(url string) string { 1028 return a.Srv.ImageProxy.GetUnproxiedImageURL(url) 1029 } 1030 } 1031 1032 func (a *App) MaxPostSize() int { 1033 result := <-a.Srv.Store.Post().GetMaxPostSize() 1034 if result.Err != nil { 1035 mlog.Error(fmt.Sprint(result.Err)) 1036 return model.POST_MESSAGE_MAX_RUNES_V1 1037 } 1038 return result.Data.(int) 1039 }