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