github.com/haalcala/mattermost-server-change-repo@v0.0.0-20210713015153-16753fbeee5f/app/post.go (about)

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