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  }