github.com/vnforks/kid/v5@v5.22.1-0.20200408055009-b89d99c65676/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 "encoding/json" 8 "net/http" 9 "strings" 10 "time" 11 12 "github.com/vnforks/kid/v5/mlog" 13 "github.com/vnforks/kid/v5/model" 14 "github.com/vnforks/kid/v5/store" 15 "github.com/vnforks/kid/v5/utils" 16 ) 17 18 const ( 19 PENDING_POST_IDS_CACHE_SIZE = 25000 20 PENDING_POST_IDS_CACHE_TTL = 30 * time.Second 21 PAGE_DEFAULT = 0 22 ) 23 24 func (a *App) CreatePostAsUser(post *model.Post, currentSessionId string) (*model.Post, *model.AppError) { 25 // Check that class has not been deleted 26 class, errCh := a.Srv().Store.Class().Get(post.ClassId, true) 27 if errCh != nil { 28 err := model.NewAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.class_id"}, errCh.Error(), http.StatusBadRequest) 29 return nil, err 30 } 31 32 if strings.HasPrefix(post.Type, model.POST_SYSTEM_MESSAGE_PREFIX) { 33 err := model.NewAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.type"}, "", http.StatusBadRequest) 34 return nil, err 35 } 36 37 if class.DeleteAt != 0 { 38 err := model.NewAppError("createPost", "api.post.create_post.can_not_post_to_deleted.error", nil, "", http.StatusBadRequest) 39 return nil, err 40 } 41 42 rp, err := a.CreatePost(post, class, true) 43 if err != nil { 44 if err.Id == "api.post.create_post.root_id.app_error" || 45 err.Id == "api.post.create_post.class_root_id.app_error" || 46 err.Id == "api.post.create_post.parent_id.app_error" { 47 err.StatusCode = http.StatusBadRequest 48 } 49 50 if err.Id == "api.post.create_post.town_square_read_only" { 51 user, userErr := a.Srv().Store.User().Get(post.UserId) 52 if userErr != nil { 53 return nil, userErr 54 } 55 56 T := utils.GetUserTranslations(user.Locale) 57 a.SendEphemeralPost( 58 post.UserId, 59 &model.Post{ 60 ClassId: class.Id, 61 UserId: post.UserId, 62 Message: T("api.post.create_post.town_square_read_only"), 63 CreateAt: model.GetMillis() + 1, 64 }, 65 ) 66 } 67 return nil, err 68 } 69 70 // Update the LastViewAt only if the post does not have from_webhook prop set (eg. Zapier app) 71 // if _, ok := post.GetProps()["from_webhook"]; !ok { 72 // if _, err := a.MarkClassesAsViewed([]string{post.ClassId}, post.UserId, currentSessionId); err != nil { 73 // mlog.Error( 74 // "Encountered error updating last viewed", 75 // mlog.String("class_id", post.ClassId), 76 // mlog.String("user_id", post.UserId), 77 // mlog.Err(err), 78 // ) 79 // } 80 // } 81 82 return rp, nil 83 } 84 85 func (a *App) CreatePostMissingClass(post *model.Post, triggerWebhooks bool) (*model.Post, *model.AppError) { 86 class, err := a.Srv().Store.Class().Get(post.ClassId, true) 87 if err != nil { 88 return nil, err 89 } 90 91 return a.CreatePost(post, class, triggerWebhooks) 92 } 93 94 // deduplicateCreatePost attempts to make posting idempotent within a caching window. 95 func (a *App) deduplicateCreatePost(post *model.Post) (foundPost *model.Post, err *model.AppError) { 96 // We rely on the client sending the pending post id across "duplicate" requests. If there 97 // isn't one, we can't deduplicate, so allow creation normally. 98 if post.PendingPostId == "" { 99 return nil, nil 100 } 101 102 const unknownPostId = "" 103 104 // Query the cache atomically for the given pending post id, saving a record if 105 // it hasn't previously been seen. 106 value, loaded := a.Srv().seenPendingPostIdsCache.GetOrAdd(post.PendingPostId, unknownPostId, PENDING_POST_IDS_CACHE_TTL) 107 108 // If we were the first thread to save this pending post id into the cache, 109 // proceed with create post normally. 110 if !loaded { 111 return nil, nil 112 } 113 114 postId := value.(string) 115 116 // If another thread saved the cache record, but hasn't yet updated it with the actual post 117 // id (because it's still saving), notify the client with an error. Ideally, we'd wait 118 // for the other thread, but coordinating that adds complexity to the happy path. 119 if postId == unknownPostId { 120 return nil, model.NewAppError("deduplicateCreatePost", "api.post.deduplicate_create_post.pending", nil, "", http.StatusInternalServerError) 121 } 122 123 // If the other thread finished creating the post, return the created post back to the 124 // client, making the API call feel idempotent. 125 actualPost, err := a.GetSinglePost(postId) 126 if err != nil { 127 return nil, model.NewAppError("deduplicateCreatePost", "api.post.deduplicate_create_post.failed_to_get", nil, err.Error(), http.StatusInternalServerError) 128 } 129 130 mlog.Debug("Deduplicated create post", mlog.String("post_id", actualPost.Id), mlog.String("pending_post_id", post.PendingPostId)) 131 132 return actualPost, nil 133 } 134 135 func (a *App) CreatePost(post *model.Post, class *model.Class, triggerWebhooks bool) (savedPost *model.Post, err *model.AppError) { 136 foundPost, err := a.deduplicateCreatePost(post) 137 if err != nil { 138 return nil, err 139 } 140 if foundPost != nil { 141 return foundPost, nil 142 } 143 144 // If we get this far, we've recorded the client-provided pending post id to the cache. 145 // Remove it if we fail below, allowing a proper retry by the client. 146 defer func() { 147 if post.PendingPostId == "" { 148 return 149 } 150 151 if err != nil { 152 a.Srv().seenPendingPostIdsCache.Remove(post.PendingPostId) 153 return 154 } 155 156 a.Srv().seenPendingPostIdsCache.AddWithExpiresInSecs(post.PendingPostId, savedPost.Id, int64(PENDING_POST_IDS_CACHE_TTL.Seconds())) 157 }() 158 159 post.SanitizeProps() 160 161 // var pchan chan store.StoreResult 162 // if len(post.RootId) > 0 { 163 // pchan = make(chan store.StoreResult, 1) 164 // go func() { 165 // r, pErr := a.Srv().Store.Post().Get(post.RootId, false) 166 // pchan <- store.StoreResult{Data: r, Err: pErr} 167 // close(pchan) 168 // }() 169 // } 170 171 user, err := a.Srv().Store.User().Get(post.UserId) 172 if err != nil { 173 return nil, err 174 } 175 176 if user.IsBot { 177 post.AddProp("from_bot", "true") 178 } 179 180 if a.License() != nil && *a.Config().BranchSettings.ExperimentalTownSquareIsReadOnly && 181 !post.IsSystemMessage() && 182 !a.RolesGrantPermission(user.GetRoles(), model.PERMISSION_MANAGE_SYSTEM.Id) { 183 return nil, model.NewAppError("createPost", "api.post.create_post.town_square_read_only", nil, "", http.StatusForbidden) 184 } 185 186 var ephemeralPost *model.Post 187 if post.Type == "" && !a.HasPermissionToClass(user.Id, class.Id, model.PERMISSION_USE_CLASS_MENTIONS) { 188 mention := post.DisableMentionHighlights() 189 if mention != "" { 190 T := utils.GetUserTranslations(user.Locale) 191 ephemeralPost = &model.Post{ 192 UserId: user.Id, 193 ClassId: class.Id, 194 Message: T("model.post.class_notifications_disabled_in_class.message", model.StringInterface{"ClassName": class.Name, "Mention": mention}), 195 Props: model.StringInterface{model.POST_PROPS_MENTION_HIGHLIGHT_DISABLED: true}, 196 } 197 } 198 } 199 200 post.Hashtags, _ = model.ParseHashtags(post.Message) 201 202 // if err = a.FillInPostProps(post, class); err != nil { 203 // return nil, err 204 // } 205 206 // Temporary fix so old plugins don't clobber new fields in SlackAttachment struct, see MM-13088 207 if attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment); ok { 208 jsonAttachments, err := json.Marshal(attachments) 209 if err == nil { 210 attachmentsInterface := []interface{}{} 211 err = json.Unmarshal(jsonAttachments, &attachmentsInterface) 212 post.AddProp("attachments", attachmentsInterface) 213 } 214 if err != nil { 215 mlog.Error("Could not convert post attachments to map interface.", mlog.Err(err)) 216 } 217 } 218 219 rpost, err := a.Srv().Store.Post().Save(post) 220 if err != nil { 221 return nil, err 222 } 223 224 // Update the mapping from pending post id to the actual post id, for any clients that 225 // might be duplicating requests. 226 a.Srv().seenPendingPostIdsCache.AddWithExpiresInSecs(post.PendingPostId, rpost.Id, int64(PENDING_POST_IDS_CACHE_TTL.Seconds())) 227 228 if a.Metrics() != nil { 229 a.Metrics().IncrementPostCreate() 230 } 231 232 if len(post.FileIds) > 0 { 233 if err = a.attachFilesToPost(post); err != nil { 234 mlog.Error("Encountered error attaching files to post", mlog.String("post_id", post.Id), mlog.Any("file_ids", post.FileIds), mlog.Err(err)) 235 } 236 237 if a.Metrics() != nil { 238 a.Metrics().IncrementPostFileAttachment(len(post.FileIds)) 239 } 240 } 241 242 // Normally, we would let the API layer call PreparePostForClient, but we do it here since it also needs 243 // to be done when we send the post over the websocket in handlePostEvents 244 rpost = a.PreparePostForClient(rpost, true, false) 245 246 // if err := a.handlePostEvents(rpost, user, class, triggerWebhooks, parentPostList); err != nil { 247 // mlog.Error("Failed to handle post events", mlog.Err(err)) 248 // } 249 250 // Send any ephemeral posts after the post is created to ensure it shows up after the latest post created 251 if ephemeralPost != nil { 252 a.SendEphemeralPost(post.UserId, ephemeralPost) 253 } 254 255 return rpost, nil 256 } 257 258 func (a *App) attachFilesToPost(post *model.Post) *model.AppError { 259 var attachedIds []string 260 for _, fileId := range post.FileIds { 261 err := a.Srv().Store.FileInfo().AttachToPost(fileId, post.Id, post.UserId) 262 if err != nil { 263 mlog.Warn("Failed to attach file to post", mlog.String("file_id", fileId), mlog.String("post_id", post.Id), mlog.Err(err)) 264 continue 265 } 266 267 attachedIds = append(attachedIds, fileId) 268 } 269 270 if len(post.FileIds) != len(attachedIds) { 271 // We couldn't attach all files to the post, so ensure that post.FileIds reflects what was actually attached 272 post.FileIds = attachedIds 273 274 if _, err := a.Srv().Store.Post().Overwrite(post); err != nil { 275 return err 276 } 277 } 278 279 return nil 280 } 281 282 // FillInPostProps should be invoked before saving posts to fill in properties such as 283 // class_mentions. 284 // 285 // If class is nil, FillInPostProps will look up the class corresponding to the post. 286 /*func (a *App) FillInPostProps(post *model.Post, class *model.Class) *model.AppError { 287 classMentions := post.ClassMentions() 288 classMentionsProp := make(map[string]interface{}) 289 290 if len(classMentions) > 0 { 291 if class == nil { 292 postClass, err := a.Srv().Store.Class().GetForPost(post.Id) 293 if err != nil { 294 return model.NewAppError("FillInPostProps", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.class_id"}, err.Error(), http.StatusBadRequest) 295 } 296 class = postClass 297 } 298 299 mentionedClasses, err := a.GetClassesByNames(classMentions, class.BranchId) 300 if err != nil { 301 return err 302 } 303 304 for _, mentioned := range mentionedClasses { 305 if mentioned.Type == model.CLASS_OPEN { 306 branch, err := a.Srv().Store.Branch().Get(mentioned.BranchId) 307 if err != nil { 308 mlog.Error("Failed to get branch of the class mention", mlog.String("branch_id", class.BranchId), mlog.String("class_id", class.Id), mlog.Err(err)) 309 } 310 classMentionsProp[mentioned.Name] = map[string]interface{}{ 311 "display_name": mentioned.DisplayName, 312 "branch_name": branch.Name, 313 } 314 } 315 } 316 } 317 318 if len(classMentionsProp) > 0 { 319 post.AddProp("class_mentions", classMentionsProp) 320 } else if post.GetProps() != nil { 321 post.DelProp("class_mentions") 322 } 323 324 return nil 325 } 326 327 func (a *App) handlePostEvents(post *model.Post, user *model.User, class *model.Class, triggerWebhooks bool, parentPostList *model.PostList) error { 328 var branch *model.Branch 329 if len(class.BranchId) > 0 { 330 t, err := a.Srv().Store.Branch().Get(class.BranchId) 331 if err != nil { 332 return err 333 } 334 branch = t 335 } else { 336 // Blank branch for DMs 337 branch = &model.Branch{} 338 } 339 340 a.invalidateCacheForClass(class) 341 a.invalidateCacheForClassPosts(class.Id) 342 343 if _, err := a.SendNotifications(post, branch, class, user, parentPostList); err != nil { 344 return err 345 } 346 347 a.Srv().Go(func() { 348 _, err := a.SendAutoResponseIfNecessary(class, user) 349 if err != nil { 350 mlog.Error("Failed to send auto response", mlog.String("user_id", user.Id), mlog.String("post_id", post.Id), mlog.Err(err)) 351 } 352 }) 353 354 if triggerWebhooks { 355 a.Srv().Go(func() { 356 if err := a.handleWebhookEvents(post, branch, class, user); err != nil { 357 mlog.Error(err.Error()) 358 } 359 }) 360 } 361 362 return nil 363 } 364 */ 365 func (a *App) SendEphemeralPost(userId string, post *model.Post) *model.Post { 366 post.Type = model.POST_EPHEMERAL 367 368 // fill in fields which haven't been specified which have sensible defaults 369 if post.Id == "" { 370 post.Id = model.NewId() 371 } 372 if post.CreateAt == 0 { 373 post.CreateAt = model.GetMillis() 374 } 375 if post.GetProps() == nil { 376 post.SetProps(make(model.StringInterface)) 377 } 378 379 post.GenerateActionIds() 380 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, "", post.ClassId, userId, nil) 381 post = a.PreparePostForClient(post, true, false) 382 post = model.AddPostActionCookies(post, a.PostActionCookieSecret()) 383 message.Add("post", post.ToJson()) 384 a.Publish(message) 385 386 return post 387 } 388 389 func (a *App) UpdateEphemeralPost(userId string, post *model.Post) *model.Post { 390 post.Type = model.POST_EPHEMERAL 391 392 post.UpdateAt = model.GetMillis() 393 if post.GetProps() == nil { 394 post.SetProps(make(model.StringInterface)) 395 } 396 397 post.GenerateActionIds() 398 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", post.ClassId, userId, nil) 399 post = a.PreparePostForClient(post, true, false) 400 post = model.AddPostActionCookies(post, a.PostActionCookieSecret()) 401 message.Add("post", post.ToJson()) 402 a.Publish(message) 403 404 return post 405 } 406 407 func (a *App) DeleteEphemeralPost(userId, postId string) { 408 post := &model.Post{ 409 Id: postId, 410 UserId: userId, 411 Type: model.POST_EPHEMERAL, 412 DeleteAt: model.GetMillis(), 413 UpdateAt: model.GetMillis(), 414 } 415 416 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", "", userId, nil) 417 message.Add("post", post.ToJson()) 418 a.Publish(message) 419 } 420 421 func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model.AppError) { 422 post.SanitizeProps() 423 424 postLists, err := a.Srv().Store.Post().Get(post.Id, false) 425 if err != nil { 426 return nil, err 427 } 428 oldPost := postLists.Posts[post.Id] 429 430 if oldPost == nil { 431 err = model.NewAppError("UpdatePost", "api.post.update_post.find.app_error", nil, "id="+post.Id, http.StatusBadRequest) 432 return nil, err 433 } 434 435 if oldPost.DeleteAt != 0 { 436 err = model.NewAppError("UpdatePost", "api.post.update_post.permissions_details.app_error", map[string]interface{}{"PostId": post.Id}, "", http.StatusBadRequest) 437 return nil, err 438 } 439 440 if oldPost.IsSystemMessage() { 441 err = model.NewAppError("UpdatePost", "api.post.update_post.system_message.app_error", nil, "id="+post.Id, http.StatusBadRequest) 442 return nil, err 443 } 444 445 if a.License() != nil { 446 if *a.Config().ServiceSettings.PostEditTimeLimit != -1 && model.GetMillis() > oldPost.CreateAt+int64(*a.Config().ServiceSettings.PostEditTimeLimit*1000) && post.Message != oldPost.Message { 447 err = model.NewAppError("UpdatePost", "api.post.update_post.permissions_time_limit.app_error", map[string]interface{}{"timeLimit": *a.Config().ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest) 448 return nil, err 449 } 450 } 451 452 class, err := a.GetClass(oldPost.ClassId) 453 if err != nil { 454 return nil, err 455 } 456 457 if class.DeleteAt != 0 { 458 return nil, model.NewAppError("UpdatePost", "api.post.update_post.can_not_update_post_in_deleted.error", nil, "", http.StatusBadRequest) 459 } 460 461 newPost := &model.Post{} 462 newPost = oldPost.Clone() 463 464 if newPost.Message != post.Message { 465 newPost.Message = post.Message 466 newPost.EditAt = model.GetMillis() 467 newPost.Hashtags, _ = model.ParseHashtags(post.Message) 468 } 469 470 if !safeUpdate { 471 newPost.HasReactions = post.HasReactions 472 newPost.FileIds = post.FileIds 473 newPost.SetProps(post.GetProps()) 474 } 475 476 // Avoid deep-equal checks if EditAt was already modified through message change 477 if newPost.EditAt == oldPost.EditAt && (!oldPost.FileIds.Equals(newPost.FileIds) || !oldPost.AttachmentsEqual(newPost)) { 478 newPost.EditAt = model.GetMillis() 479 } 480 481 // if err = a.FillInPostProps(post, nil); err != nil { 482 // return nil, err 483 // } 484 485 rpost, err := a.Srv().Store.Post().Update(newPost, oldPost) 486 if err != nil { 487 return nil, err 488 } 489 490 rpost = a.PreparePostForClient(rpost, false, true) 491 492 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", rpost.ClassId, "", nil) 493 message.Add("post", rpost.ToJson()) 494 a.Publish(message) 495 496 // a.invalidateCacheForClassPosts(rpost.ClassId) 497 498 return rpost, nil 499 } 500 501 func (a *App) PatchPost(postId string, patch *model.PostPatch) (*model.Post, *model.AppError) { 502 post, err := a.GetSinglePost(postId) 503 if err != nil { 504 return nil, err 505 } 506 507 class, err := a.GetClass(post.ClassId) 508 if err != nil { 509 return nil, err 510 } 511 512 if class.DeleteAt != 0 { 513 err = model.NewAppError("PatchPost", "api.post.patch_post.can_not_update_post_in_deleted.error", nil, "", http.StatusBadRequest) 514 return nil, err 515 } 516 517 if !a.HasPermissionToClass(post.UserId, post.ClassId, model.PERMISSION_USE_CLASS_MENTIONS) { 518 patch.DisableMentionHighlights() 519 } 520 521 post.Patch(patch) 522 523 updatedPost, err := a.UpdatePost(post, false) 524 if err != nil { 525 return nil, err 526 } 527 528 return updatedPost, nil 529 } 530 531 func (a *App) GetPostsPage(options model.GetPostsOptions) (*model.PostList, *model.AppError) { 532 return a.Srv().Store.Post().GetPosts(options, false) 533 } 534 535 func (a *App) GetPosts(classId string, offset int, limit int) (*model.PostList, *model.AppError) { 536 return a.Srv().Store.Post().GetPosts(model.GetPostsOptions{ClassId: classId, Page: offset, PerPage: limit}, true) 537 } 538 539 func (a *App) GetPostsEtag(classId string) string { 540 return a.Srv().Store.Post().GetEtag(classId, true) 541 } 542 543 func (a *App) GetPostsSince(options model.GetPostsSinceOptions) (*model.PostList, *model.AppError) { 544 return a.Srv().Store.Post().GetPostsSince(options, true) 545 } 546 547 func (a *App) GetSinglePost(postId string) (*model.Post, *model.AppError) { 548 return a.Srv().Store.Post().GetSingle(postId) 549 } 550 551 func (a *App) GetFlaggedPosts(userId string, offset int, limit int) (*model.PostList, *model.AppError) { 552 return a.Srv().Store.Post().GetFlaggedPosts(userId, offset, limit) 553 } 554 555 func (a *App) GetFlaggedPostsForBranch(userId, branchId string, offset int, limit int) (*model.PostList, *model.AppError) { 556 return a.Srv().Store.Post().GetFlaggedPostsForBranch(userId, branchId, offset, limit) 557 } 558 559 func (a *App) GetFlaggedPostsForClass(userId, classId string, offset int, limit int) (*model.PostList, *model.AppError) { 560 return a.Srv().Store.Post().GetFlaggedPostsForClass(userId, classId, offset, limit) 561 } 562 563 func (a *App) GetPermalinkPost(postId string, userId string) (*model.PostList, *model.AppError) { 564 list, err := a.Srv().Store.Post().Get(postId, false) 565 if err != nil { 566 return nil, err 567 } 568 569 if len(list.Order) != 1 { 570 return nil, model.NewAppError("getPermalinkTmp", "api.post_get_post_by_id.get.app_error", nil, "", http.StatusNotFound) 571 } 572 post := list.Posts[list.Order[0]] 573 574 class, err := a.GetClass(post.ClassId) 575 if err != nil { 576 return nil, err 577 } 578 579 if err = a.JoinClass(class, userId); err != nil { 580 return nil, err 581 } 582 583 return list, nil 584 } 585 586 func (a *App) GetPostsBeforePost(options model.GetPostsOptions) (*model.PostList, *model.AppError) { 587 return a.Srv().Store.Post().GetPostsBefore(options) 588 } 589 590 func (a *App) GetPostsAfterPost(options model.GetPostsOptions) (*model.PostList, *model.AppError) { 591 return a.Srv().Store.Post().GetPostsAfter(options) 592 } 593 594 func (a *App) GetPostsAroundPost(before bool, options model.GetPostsOptions) (*model.PostList, *model.AppError) { 595 if before { 596 return a.Srv().Store.Post().GetPostsBefore(options) 597 } 598 return a.Srv().Store.Post().GetPostsAfter(options) 599 } 600 601 func (a *App) GetPostAfterTime(classId string, time int64) (*model.Post, *model.AppError) { 602 return a.Srv().Store.Post().GetPostAfterTime(classId, time) 603 } 604 605 func (a *App) GetPostIdAfterTime(classId string, time int64) (string, *model.AppError) { 606 return a.Srv().Store.Post().GetPostIdAfterTime(classId, time) 607 } 608 609 func (a *App) GetPostIdBeforeTime(classId string, time int64) (string, *model.AppError) { 610 return a.Srv().Store.Post().GetPostIdBeforeTime(classId, time) 611 } 612 613 func (a *App) GetNextPostIdFromPostList(postList *model.PostList) string { 614 if len(postList.Order) > 0 { 615 firstPostId := postList.Order[0] 616 firstPost := postList.Posts[firstPostId] 617 nextPostId, err := a.GetPostIdAfterTime(firstPost.ClassId, firstPost.CreateAt) 618 if err != nil { 619 mlog.Warn("GetNextPostIdFromPostList: failed in getting next post", mlog.Err(err)) 620 } 621 622 return nextPostId 623 } 624 625 return "" 626 } 627 628 func (a *App) GetPrevPostIdFromPostList(postList *model.PostList) string { 629 if len(postList.Order) > 0 { 630 lastPostId := postList.Order[len(postList.Order)-1] 631 lastPost := postList.Posts[lastPostId] 632 previousPostId, err := a.GetPostIdBeforeTime(lastPost.ClassId, lastPost.CreateAt) 633 if err != nil { 634 mlog.Warn("GetPrevPostIdFromPostList: failed in getting previous post", mlog.Err(err)) 635 } 636 637 return previousPostId 638 } 639 640 return "" 641 } 642 643 // AddCursorIdsForPostList adds NextPostId and PrevPostId as cursor to the PostList. 644 // The conditional blocks ensure that it sets those cursor IDs immediately as afterPost, beforePost or empty, 645 // and only query to database whenever necessary. 646 func (a *App) AddCursorIdsForPostList(originalList *model.PostList, afterPost, beforePost string, since int64, page, perPage int) { 647 prevPostIdSet := false 648 prevPostId := "" 649 nextPostIdSet := false 650 nextPostId := "" 651 652 if since > 0 { // "since" query to return empty NextPostId and PrevPostId 653 nextPostIdSet = true 654 prevPostIdSet = true 655 } else if afterPost != "" { 656 if page == 0 { 657 prevPostId = afterPost 658 prevPostIdSet = true 659 } 660 661 if len(originalList.Order) < perPage { 662 nextPostIdSet = true 663 } 664 } else if beforePost != "" { 665 if page == 0 { 666 nextPostId = beforePost 667 nextPostIdSet = true 668 } 669 670 if len(originalList.Order) < perPage { 671 prevPostIdSet = true 672 } 673 } 674 675 if !nextPostIdSet { 676 nextPostId = a.GetNextPostIdFromPostList(originalList) 677 } 678 679 if !prevPostIdSet { 680 prevPostId = a.GetPrevPostIdFromPostList(originalList) 681 } 682 683 originalList.NextPostId = nextPostId 684 originalList.PrevPostId = prevPostId 685 } 686 687 func (a *App) DeletePost(postId, deleteByID string) (*model.Post, *model.AppError) { 688 post, err := a.Srv().Store.Post().GetSingle(postId) 689 if err != nil { 690 err.StatusCode = http.StatusBadRequest 691 return nil, err 692 } 693 694 class, err := a.GetClass(post.ClassId) 695 if err != nil { 696 return nil, err 697 } 698 699 if class.DeleteAt != 0 { 700 err := model.NewAppError("DeletePost", "api.post.delete_post.can_not_delete_post_in_deleted.error", nil, "", http.StatusBadRequest) 701 return nil, err 702 } 703 704 if err := a.Srv().Store.Post().Delete(postId, model.GetMillis(), deleteByID); err != nil { 705 return nil, err 706 } 707 708 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", post.ClassId, "", nil) 709 message.Add("post", a.PreparePostForClient(post, false, false).ToJson()) 710 a.Publish(message) 711 712 a.Srv().Go(func() { 713 a.DeletePostFiles(post) 714 }) 715 a.Srv().Go(func() { 716 a.DeleteFlaggedPosts(post.Id) 717 }) 718 719 // a.invalidateCacheForClassPosts(post.ClassId) 720 721 return post, nil 722 } 723 724 func (a *App) DeleteFlaggedPosts(postId string) { 725 if err := a.Srv().Store.Preference().DeleteCategoryAndName(model.PREFERENCE_CATEGORY_FLAGGED_POST, postId); err != nil { 726 mlog.Warn("Unable to delete flagged post preference when deleting post.", mlog.Err(err)) 727 return 728 } 729 } 730 731 func (a *App) DeletePostFiles(post *model.Post) { 732 if len(post.FileIds) == 0 { 733 return 734 } 735 736 if _, err := a.Srv().Store.FileInfo().DeleteForPost(post.Id); err != nil { 737 mlog.Warn("Encountered error when deleting files for post", mlog.String("post_id", post.Id), mlog.Err(err)) 738 } 739 } 740 741 func (a *App) convertUserNameToUserIds(usernames []string) []string { 742 for idx, username := range usernames { 743 if user, err := a.GetUserByUsername(username); err != nil { 744 mlog.Error("error getting user by username", mlog.String("user_name", username), mlog.Err(err)) 745 } else { 746 usernames[idx] = user.Id 747 } 748 } 749 return usernames 750 } 751 752 func (a *App) GetFileInfosForPostWithMigration(postId string) ([]*model.FileInfo, *model.AppError) { 753 754 pchan := make(chan store.StoreResult, 1) 755 go func() { 756 post, err := a.Srv().Store.Post().GetSingle(postId) 757 pchan <- store.StoreResult{Data: post, Err: err} 758 close(pchan) 759 }() 760 761 infos, err := a.GetFileInfosForPost(postId, false) 762 if err != nil { 763 return nil, err 764 } 765 766 if len(infos) == 0 { 767 // No FileInfos were returned so check if they need to be created for this post 768 result := <-pchan 769 if result.Err != nil { 770 return nil, result.Err 771 } 772 post := result.Data.(*model.Post) 773 774 if len(post.Filenames) > 0 { 775 a.Srv().Store.FileInfo().InvalidateFileInfosForPostCache(postId, false) 776 a.Srv().Store.FileInfo().InvalidateFileInfosForPostCache(postId, true) 777 // The post has Filenames that need to be replaced with FileInfos 778 infos = a.MigrateFilenamesToFileInfos(post) 779 } 780 } 781 782 return infos, nil 783 } 784 785 func (a *App) GetFileInfosForPost(postId string, fromMaster bool) ([]*model.FileInfo, *model.AppError) { 786 return a.Srv().Store.FileInfo().GetForPost(postId, fromMaster, false, true) 787 } 788 789 func (a *App) PostWithProxyAddedToImageURLs(post *model.Post) *model.Post { 790 if f := a.ImageProxyAdder(); f != nil { 791 return post.WithRewrittenImageURLs(f) 792 } 793 return post 794 } 795 796 func (a *App) PostWithProxyRemovedFromImageURLs(post *model.Post) *model.Post { 797 if f := a.ImageProxyRemover(); f != nil { 798 return post.WithRewrittenImageURLs(f) 799 } 800 return post 801 } 802 803 func (a *App) PostPatchWithProxyRemovedFromImageURLs(patch *model.PostPatch) *model.PostPatch { 804 if f := a.ImageProxyRemover(); f != nil { 805 return patch.WithRewrittenImageURLs(f) 806 } 807 return patch 808 } 809 810 func (a *App) ImageProxyAdder() func(string) string { 811 if !*a.Config().ImageProxySettings.Enable { 812 return nil 813 } 814 815 return func(url string) string { 816 return a.Srv().ImageProxy.GetProxiedImageURL(url) 817 } 818 } 819 820 func (a *App) ImageProxyRemover() (f func(string) string) { 821 if !*a.Config().ImageProxySettings.Enable { 822 return nil 823 } 824 825 return func(url string) string { 826 return a.Srv().ImageProxy.GetUnproxiedImageURL(url) 827 } 828 } 829 830 func (a *App) MaxPostSize() int { 831 maxPostSize := a.Srv().Store.Post().GetMaxPostSize() 832 if maxPostSize == 0 { 833 return model.POST_MESSAGE_MAX_RUNES_V1 834 } 835 836 return maxPostSize 837 }