[MM-55143] Disallow reacting with an emoji that does not exist, limit the total number of unique reactions per post (#25331)
* [MM-55143] Disallow reacting with an emoji that does not exist * WIP for server limit on emoji reactions * WIP * Implement default limit of 25 unique emoji reactions * Add modal for reaction limit * Fix test * PR feedback * Fix i18n * Update admin string * Merge'd * Fixing some issues, check limits correctly based on other users reactions * Fix typos * Fix lint/test * Add tests, fix other tests --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
0a38042d58
Коммит
eaa5cce3ce
@@ -29,18 +29,20 @@ func TestReactionsOfPost(t *testing.T) {
|
||||
reactionObject := model.Reaction{
|
||||
UserId: th.BasicUser.Id,
|
||||
PostId: post.Id,
|
||||
EmojiName: "emoji",
|
||||
EmojiName: "smile",
|
||||
CreateAt: model.GetMillis(),
|
||||
}
|
||||
reactionObjectDeleted := model.Reaction{
|
||||
UserId: th.BasicUser2.Id,
|
||||
PostId: post.Id,
|
||||
EmojiName: "emoji",
|
||||
EmojiName: "smile",
|
||||
CreateAt: model.GetMillis(),
|
||||
}
|
||||
|
||||
th.App.SaveReactionForPost(th.Context, &reactionObject)
|
||||
th.App.SaveReactionForPost(th.Context, &reactionObjectDeleted)
|
||||
_, err := th.App.SaveReactionForPost(th.Context, &reactionObject)
|
||||
require.Nil(t, err)
|
||||
_, err = th.App.SaveReactionForPost(th.Context, &reactionObjectDeleted)
|
||||
require.Nil(t, err)
|
||||
reactionsOfPost, err := th.App.BuildPostReactions(th.Context, post.Id)
|
||||
require.Nil(t, err)
|
||||
|
||||
|
||||
@@ -20,6 +20,30 @@ func (a *App) SaveReactionForPost(c request.CTX, reaction *model.Reaction) (*mod
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check whether this is a valid emoji
|
||||
if _, ok := model.GetSystemEmojiId(reaction.EmojiName); !ok {
|
||||
if _, emojiErr := a.GetEmojiByName(c, reaction.EmojiName); emojiErr != nil {
|
||||
return nil, emojiErr
|
||||
}
|
||||
}
|
||||
|
||||
existing, dErr := a.Srv().Store().Reaction().ExistsOnPost(reaction.PostId, reaction.EmojiName)
|
||||
if dErr != nil {
|
||||
return nil, model.NewAppError("SaveReactionForPost", "app.reaction.save.save.app_error", nil, "", http.StatusInternalServerError).Wrap(dErr)
|
||||
}
|
||||
|
||||
// If it exists already, we don't need to check for the limit
|
||||
if !existing {
|
||||
count, dErr := a.Srv().Store().Reaction().GetUniqueCountForPost(reaction.PostId)
|
||||
if dErr != nil {
|
||||
return nil, model.NewAppError("SaveReactionForPost", "app.reaction.save.save.app_error", nil, "", http.StatusInternalServerError).Wrap(dErr)
|
||||
}
|
||||
|
||||
if count >= *a.Config().ServiceSettings.UniqueEmojiReactionLimitPerPost {
|
||||
return nil, model.NewAppError("SaveReactionForPost", "app.reaction.save.save.too_many_reactions", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
channel, err := a.GetChannel(c, post.ChannelId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -13,6 +13,89 @@ import (
|
||||
"github.com/mattermost/mattermost/server/v8/channels/testlib"
|
||||
)
|
||||
|
||||
func TestSaveReactionForPost(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
|
||||
post := th.CreatePost(th.BasicChannel)
|
||||
reaction1, err := th.App.SaveReactionForPost(th.Context, &model.Reaction{
|
||||
UserId: th.BasicUser.Id,
|
||||
PostId: post.Id,
|
||||
EmojiName: "cry",
|
||||
})
|
||||
require.NotNil(t, reaction1)
|
||||
require.Nil(t, err)
|
||||
reaction2, err := th.App.SaveReactionForPost(th.Context, &model.Reaction{
|
||||
UserId: th.BasicUser.Id,
|
||||
PostId: post.Id,
|
||||
EmojiName: "smile",
|
||||
})
|
||||
require.NotNil(t, reaction2)
|
||||
require.Nil(t, err)
|
||||
reaction3, err := th.App.SaveReactionForPost(th.Context, &model.Reaction{
|
||||
UserId: th.BasicUser.Id,
|
||||
PostId: post.Id,
|
||||
EmojiName: "rofl",
|
||||
})
|
||||
require.NotNil(t, reaction3)
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Run("should not add reaction if it does not exist on the system", func(t *testing.T) {
|
||||
reaction := &model.Reaction{
|
||||
UserId: th.BasicUser.Id,
|
||||
PostId: th.BasicPost.Id,
|
||||
EmojiName: "definitely-not-a-real-emoji",
|
||||
}
|
||||
|
||||
result, err := th.App.SaveReactionForPost(th.Context, reaction)
|
||||
require.NotNil(t, err)
|
||||
require.Nil(t, result)
|
||||
})
|
||||
|
||||
t.Run("should not add reaction if we are over the limit", func(t *testing.T) {
|
||||
var originalLimit *int
|
||||
th.UpdateConfig(func(cfg *model.Config) {
|
||||
originalLimit = cfg.ServiceSettings.UniqueEmojiReactionLimitPerPost
|
||||
*cfg.ServiceSettings.UniqueEmojiReactionLimitPerPost = 3
|
||||
})
|
||||
defer th.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.ServiceSettings.UniqueEmojiReactionLimitPerPost = originalLimit
|
||||
})
|
||||
|
||||
reaction := &model.Reaction{
|
||||
UserId: th.BasicUser.Id,
|
||||
PostId: post.Id,
|
||||
EmojiName: "joy",
|
||||
}
|
||||
|
||||
result, err := th.App.SaveReactionForPost(th.Context, reaction)
|
||||
require.NotNil(t, err)
|
||||
require.Nil(t, result)
|
||||
})
|
||||
|
||||
t.Run("should always add reaction if we are over the limit but the reaction is not unique", func(t *testing.T) {
|
||||
user := th.CreateUser()
|
||||
|
||||
var originalLimit *int
|
||||
th.UpdateConfig(func(cfg *model.Config) {
|
||||
originalLimit = cfg.ServiceSettings.UniqueEmojiReactionLimitPerPost
|
||||
*cfg.ServiceSettings.UniqueEmojiReactionLimitPerPost = 3
|
||||
})
|
||||
defer th.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.ServiceSettings.UniqueEmojiReactionLimitPerPost = originalLimit
|
||||
})
|
||||
|
||||
reaction := &model.Reaction{
|
||||
UserId: user.Id,
|
||||
PostId: post.Id,
|
||||
EmojiName: "cry",
|
||||
}
|
||||
|
||||
result, err := th.App.SaveReactionForPost(th.Context, reaction)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSharedChannelSyncForReactionActions(t *testing.T) {
|
||||
t.Run("adding a reaction in a shared channel performs a content sync when sync service is running on that node", func(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
@@ -84,3 +167,15 @@ func TestSharedChannelSyncForReactionActions(t *testing.T) {
|
||||
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[1])
|
||||
})
|
||||
}
|
||||
|
||||
func (th *TestHelper) UpdateConfig(f func(*model.Config)) {
|
||||
if th.ConfigStore.IsReadOnly() {
|
||||
return
|
||||
}
|
||||
old := th.ConfigStore.Get()
|
||||
updated := old.Clone()
|
||||
f(updated)
|
||||
if _, _, err := th.ConfigStore.Set(updated); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user