github.com/xzl8028/xenia-server@v0.0.0-20190809101854-18450a97da63/app/post.go (about)

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