Merge branch 'master' into mark-as-unread

Этот коммит содержится в:
Harrison Healey
2019-09-18 13:16:23 -04:00
родитель 42e927cc3f c7b583ccdd
Коммит 5f28ce9de0
190 изменённых файлов: 11424 добавлений и 2337 удалений

Просмотреть файл

@@ -87,7 +87,7 @@ PLUGIN_PACKAGES += mattermost-plugin-github-v0.10.2
PLUGIN_PACKAGES += mattermost-plugin-welcomebot-v1.1.0 PLUGIN_PACKAGES += mattermost-plugin-welcomebot-v1.1.0
PLUGIN_PACKAGES += mattermost-plugin-aws-SNS-v1.0.2 PLUGIN_PACKAGES += mattermost-plugin-aws-SNS-v1.0.2
PLUGIN_PACKAGES += mattermost-plugin-antivirus-v0.1.1 PLUGIN_PACKAGES += mattermost-plugin-antivirus-v0.1.1
PLUGIN_PACKAGES += mattermost-plugin-jira-v2.1.1 PLUGIN_PACKAGES += mattermost-plugin-jira-v2.1.3
PLUGIN_PACKAGES += mattermost-plugin-gitlab-v1.0.0 PLUGIN_PACKAGES += mattermost-plugin-gitlab-v1.0.0
PLUGIN_PACKAGES += mattermost-plugin-jenkins-v1.0.0 PLUGIN_PACKAGES += mattermost-plugin-jenkins-v1.0.0
@@ -144,6 +144,7 @@ govet: ## Runs govet against all packages.
$(GO) get golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow $(GO) get golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
$(GO) vet $(GOFLAGS) $(ALL_PACKAGES) || exit 1 $(GO) vet $(GOFLAGS) $(ALL_PACKAGES) || exit 1
$(GO) vet -vettool=$(GOPATH)/bin/shadow $(GOFLAGS) $(ALL_PACKAGES) || exit 1 $(GO) vet -vettool=$(GOPATH)/bin/shadow $(GOFLAGS) $(ALL_PACKAGES) || exit 1
$(GO) run plugin/checker/main.go
gofmt: ## Runs gofmt against all packages. gofmt: ## Runs gofmt against all packages.
@echo Running GOFMT @echo Running GOFMT

Просмотреть файл

@@ -9,7 +9,6 @@ import (
"net" "net"
"net/http" "net/http"
"os" "os"
"path/filepath"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
@@ -138,16 +137,6 @@ func setupTestHelper(enterprise bool, updateConfig func(*model.Config)) *TestHel
th.tempWorkspace = dir th.tempWorkspace = dir
} }
pluginDir := filepath.Join(th.tempWorkspace, "plugins")
webappDir := filepath.Join(th.tempWorkspace, "webapp")
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.Directory = pluginDir
*cfg.PluginSettings.ClientDirectory = webappDir
})
th.App.InitPlugins(pluginDir, webappDir)
return th return th
} }

Просмотреть файл

@@ -35,6 +35,7 @@ func (api *API) InitChannel() {
api.BaseRoutes.Channel.Handle("", api.ApiSessionRequired(updateChannel)).Methods("PUT") api.BaseRoutes.Channel.Handle("", api.ApiSessionRequired(updateChannel)).Methods("PUT")
api.BaseRoutes.Channel.Handle("/patch", api.ApiSessionRequired(patchChannel)).Methods("PUT") api.BaseRoutes.Channel.Handle("/patch", api.ApiSessionRequired(patchChannel)).Methods("PUT")
api.BaseRoutes.Channel.Handle("/convert", api.ApiSessionRequired(convertChannelToPrivate)).Methods("POST") api.BaseRoutes.Channel.Handle("/convert", api.ApiSessionRequired(convertChannelToPrivate)).Methods("POST")
api.BaseRoutes.Channel.Handle("/privacy", api.ApiSessionRequired(updateChannelPrivacy)).Methods("PUT")
api.BaseRoutes.Channel.Handle("/restore", api.ApiSessionRequired(restoreChannel)).Methods("POST") api.BaseRoutes.Channel.Handle("/restore", api.ApiSessionRequired(restoreChannel)).Methods("POST")
api.BaseRoutes.Channel.Handle("", api.ApiSessionRequired(deleteChannel)).Methods("DELETE") api.BaseRoutes.Channel.Handle("", api.ApiSessionRequired(deleteChannel)).Methods("DELETE")
api.BaseRoutes.Channel.Handle("/stats", api.ApiSessionRequired(getChannelStats)).Methods("GET") api.BaseRoutes.Channel.Handle("/stats", api.ApiSessionRequired(getChannelStats)).Methods("GET")
@@ -229,6 +230,54 @@ func convertChannelToPrivate(c *Context, w http.ResponseWriter, r *http.Request)
w.Write([]byte(rchannel.ToJson())) w.Write([]byte(rchannel.ToJson()))
} }
func updateChannelPrivacy(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireChannelId()
if c.Err != nil {
return
}
props := model.StringInterfaceFromJson(r.Body)
privacy, ok := props["privacy"].(string)
if !ok || (privacy != model.CHANNEL_OPEN && privacy != model.CHANNEL_PRIVATE) {
c.SetInvalidParam("privacy")
return
}
channel, err := c.App.GetChannel(c.Params.ChannelId)
if err != nil {
c.Err = err
return
}
if !c.App.SessionHasPermissionToTeam(c.App.Session, channel.TeamId, model.PERMISSION_MANAGE_TEAM) {
c.SetPermissionError(model.PERMISSION_MANAGE_TEAM)
return
}
if channel.Name == model.DEFAULT_CHANNEL && privacy == model.CHANNEL_PRIVATE {
c.Err = model.NewAppError("updateChannelPrivacy", "api.channel.update_channel_privacy.default_channel_error", nil, "", http.StatusBadRequest)
return
}
user, err := c.App.GetUser(c.App.Session.UserId)
if err != nil {
c.Err = err
return
}
channel.Type = privacy
updatedChannel, err := c.App.UpdateChannelPrivacy(channel, user)
if err != nil {
c.Err = err
return
}
c.LogAudit("name=" + updatedChannel.Name)
w.Write([]byte(updatedChannel.ToJson()))
}
func patchChannel(c *Context, w http.ResponseWriter, r *http.Request) { func patchChannel(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireChannelId() c.RequireChannelId()
if c.Err != nil { if c.Err != nil {

Просмотреть файл

@@ -1387,6 +1387,68 @@ func TestConvertChannelToPrivate(t *testing.T) {
} }
} }
func TestUpdateChannelPrivacy(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
Client := th.Client
type testTable []struct {
name string
channel *model.Channel
expectedPrivacy string
}
defaultChannel, _ := th.App.GetChannelByName(model.DEFAULT_CHANNEL, th.BasicTeam.Id, false)
privateChannel := th.CreatePrivateChannel()
publicChannel := th.CreatePublicChannel()
tt := testTable{
{"Updating default channel should fail with forbidden status if not logged in", defaultChannel, model.CHANNEL_OPEN},
{"Updating private channel should fail with forbidden status if not logged in", privateChannel, model.CHANNEL_PRIVATE},
{"Updating public channel should fail with forbidden status if not logged in", publicChannel, model.CHANNEL_OPEN},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
_, resp := Client.UpdateChannelPrivacy(tc.channel.Id, tc.expectedPrivacy)
CheckForbiddenStatus(t, resp)
})
}
th.LoginTeamAdmin()
tt = testTable{
{"Converting default channel to private should fail", defaultChannel, model.CHANNEL_PRIVATE},
{"Updating privacy to an invalid setting should fail", publicChannel, "invalid"},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
_, resp := Client.UpdateChannelPrivacy(tc.channel.Id, tc.expectedPrivacy)
CheckBadRequestStatus(t, resp)
})
}
tt = testTable{
{"Default channel should stay public", defaultChannel, model.CHANNEL_OPEN},
{"Public channel should stay public", publicChannel, model.CHANNEL_OPEN},
{"Private channel should stay private", privateChannel, model.CHANNEL_PRIVATE},
{"Public channel should convert to private", publicChannel, model.CHANNEL_PRIVATE},
{"Private channel should convert to public", privateChannel, model.CHANNEL_OPEN},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
updatedChannel, resp := Client.UpdateChannelPrivacy(tc.channel.Id, tc.expectedPrivacy)
CheckNoError(t, resp)
assert.Equal(t, updatedChannel.Type, tc.expectedPrivacy)
updatedChannel, err := th.App.GetChannel(tc.channel.Id)
require.Nil(t, err)
assert.Equal(t, updatedChannel.Type, tc.expectedPrivacy)
})
}
}
func TestRestoreChannel(t *testing.T) { func TestRestoreChannel(t *testing.T) {
th := Setup().InitBasic() th := Setup().InitBasic()
defer th.TearDown() defer th.TearDown()

25
api4/helpers.go Обычный файл
Просмотреть файл

@@ -0,0 +1,25 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
import (
"net/url"
"strconv"
"github.com/pkg/errors"
)
func parseInt(u *url.URL, name string, defaultValue int) (int, error) {
valueStr := u.Query().Get(name)
if valueStr == "" {
return defaultValue, nil
}
value, err := strconv.Atoi(valueStr)
if err != nil {
return 0, errors.Wrapf(err, "failed to parse %s as integer", name)
}
return value, nil
}

Просмотреть файл

@@ -142,8 +142,20 @@ func TestOpenDialog(t *testing.T) {
CheckBadRequestStatus(t, resp) CheckBadRequestStatus(t, resp)
assert.False(t, pass) assert.False(t, pass)
// Should pass with no elements // Should pass with markdown formatted introduction text
request.URL = "http://localhost:8065" request.URL = "http://localhost:8065"
request.Dialog.IntroductionText = "**Some** _introduction text"
pass, resp = Client.OpenInteractiveDialog(request)
CheckNoError(t, resp)
assert.True(t, pass)
// Should pass with empty introduction text
request.Dialog.IntroductionText = ""
pass, resp = Client.OpenInteractiveDialog(request)
CheckNoError(t, resp)
assert.True(t, pass)
// Should pass with no elements
request.Dialog.Elements = nil request.Dialog.Elements = nil
pass, resp = Client.OpenInteractiveDialog(request) pass, resp = Client.OpenInteractiveDialog(request)
CheckNoError(t, resp) CheckNoError(t, resp)

Просмотреть файл

@@ -7,6 +7,7 @@ package api4
import ( import (
"bytes" "bytes"
"encoding/json"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
@@ -36,6 +37,8 @@ func (api *API) InitPlugin() {
api.BaseRoutes.Plugin.Handle("/disable", api.ApiSessionRequired(disablePlugin)).Methods("POST") api.BaseRoutes.Plugin.Handle("/disable", api.ApiSessionRequired(disablePlugin)).Methods("POST")
api.BaseRoutes.Plugins.Handle("/webapp", api.ApiHandler(getWebappPlugins)).Methods("GET") api.BaseRoutes.Plugins.Handle("/webapp", api.ApiHandler(getWebappPlugins)).Methods("GET")
api.BaseRoutes.Plugins.Handle("/marketplace", api.ApiSessionRequired(getMarketplacePlugins)).Methods("GET")
} }
func uploadPlugin(c *Context, w http.ResponseWriter, r *http.Request) { func uploadPlugin(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -239,6 +242,43 @@ func getWebappPlugins(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.ManifestListToJson(clientManifests))) w.Write([]byte(model.ManifestListToJson(clientManifests)))
} }
func getMarketplacePlugins(c *Context, w http.ResponseWriter, r *http.Request) {
if !*c.App.Config().PluginSettings.Enable {
c.Err = model.NewAppError("getMarketplacePlugins", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
return
}
if !*c.App.Config().PluginSettings.EnableMarketplace {
c.Err = model.NewAppError("getMarketplacePlugins", "app.plugin.marketplace_disabled.app_error", nil, "", http.StatusNotImplemented)
return
}
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return
}
filter, err := parseMarketplacePluginFilter(r.URL)
if err != nil {
c.Err = model.NewAppError("getMarketplacePlugins", "app.plugin.marshal.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
plugins, appErr := c.App.GetMarketplacePlugins(filter)
if appErr != nil {
c.Err = appErr
return
}
json, err := json.Marshal(plugins)
if err != nil {
c.Err = model.NewAppError("getMarketplacePlugins", "app.plugin.marshal.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
w.Write(json)
}
func enablePlugin(c *Context, w http.ResponseWriter, r *http.Request) { func enablePlugin(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequirePluginId() c.RequirePluginId()
if c.Err != nil { if c.Err != nil {
@@ -286,3 +326,25 @@ func disablePlugin(c *Context, w http.ResponseWriter, r *http.Request) {
ReturnStatusOK(w) ReturnStatusOK(w)
} }
func parseMarketplacePluginFilter(u *url.URL) (*model.MarketplacePluginFilter, error) {
page, err := parseInt(u, "page", 0)
if err != nil {
return nil, err
}
perPage, err := parseInt(u, "per_page", 100)
if err != nil {
return nil, err
}
filter := u.Query().Get("filter")
serverVersion := u.Query().Get("server_version")
return &model.MarketplacePluginFilter{
Page: page,
PerPage: perPage,
Filter: filter,
ServerVersion: serverVersion,
}, nil
}

Просмотреть файл

@@ -11,6 +11,8 @@ import (
"net/http/httptest" "net/http/httptest"
"os" "os"
"path/filepath" "path/filepath"
"sort"
"strings"
"testing" "testing"
"time" "time"
@@ -36,9 +38,7 @@ func TestPlugin(t *testing.T) {
path, _ := fileutils.FindDir("tests") path, _ := fileutils.FindDir("tests")
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz")) tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
// Install from URL // Install from URL
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
@@ -270,9 +270,7 @@ func TestNotifyClusterPluginEvent(t *testing.T) {
path, _ := fileutils.FindDir("tests") path, _ := fileutils.FindDir("tests")
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz")) tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
testCluster.ClearMessages() testCluster.ClearMessages()
@@ -331,9 +329,7 @@ func TestNotifyClusterPluginEvent(t *testing.T) {
func TestDisableOnRemove(t *testing.T) { func TestDisableOnRemove(t *testing.T) {
path, _ := fileutils.FindDir("tests") path, _ := fileutils.FindDir("tests")
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz")) tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
testCases := []struct { testCases := []struct {
Description string Description string
@@ -432,6 +428,324 @@ func TestDisableOnRemove(t *testing.T) {
} }
} }
func TestGetMarketplacePlugins(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.Enable = true
*cfg.PluginSettings.EnableUploads = true
*cfg.PluginSettings.EnableMarketplace = false
})
t.Run("marketplace disabled", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableMarketplace = false
*cfg.PluginSettings.MarketplaceUrl = "invalid.com"
})
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckNotImplementedStatus(t, resp)
require.Nil(t, plugins)
})
t.Run("no server", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableMarketplace = true
*cfg.PluginSettings.MarketplaceUrl = "invalid.com"
})
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckInternalErrorStatus(t, resp)
require.Nil(t, plugins)
})
t.Run("no permission", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableMarketplace = true
*cfg.PluginSettings.MarketplaceUrl = "invalid.com"
})
plugins, resp := th.Client.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckForbiddenStatus(t, resp)
require.Nil(t, plugins)
})
t.Run("empty response from server", func(t *testing.T) {
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusOK)
json, err := json.Marshal([]*model.MarketplacePlugin{})
require.NoError(t, err)
res.Write(json)
}))
defer func() { testServer.Close() }()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableMarketplace = true
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
})
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckNoError(t, resp)
require.Len(t, plugins, 0)
})
t.Run("verify server version is passed through", func(t *testing.T) {
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
serverVersion, ok := req.URL.Query()["server_version"]
require.True(t, ok)
require.Len(t, serverVersion, 1)
require.Equal(t, model.CurrentVersion, serverVersion[0])
require.NotEqual(t, 0, len(serverVersion[0]))
res.WriteHeader(http.StatusOK)
json, err := json.Marshal([]*model.MarketplacePlugin{})
require.NoError(t, err)
res.Write(json)
}))
defer func() { testServer.Close() }()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.EnableMarketplace = true
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
})
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckNoError(t, resp)
require.Len(t, plugins, 0)
})
}
func TestGetInstalledMarketplacePlugins(t *testing.T) {
samplePlugins := []*model.MarketplacePlugin{
{
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
HomepageURL: "https://github.com/mattermost/mattermost-plugin-nps",
IconData: "http://example.com/icon.svg",
DownloadURL: "https://github.com/mattermost/mattermost-plugin-nps/releases/download/v1.0.3/com.mattermost.nps-1.0.3.tar.gz",
Manifest: &model.Manifest{
Id: "com.mattermost.nps",
Name: "User Satisfaction Surveys",
Description: "This plugin sends quarterly user satisfaction surveys to gather feedback and help improve Mattermost.",
Version: "1.0.3",
MinServerVersion: "5.14.0",
},
},
InstalledVersion: "",
},
}
path, _ := fileutils.FindDir("tests")
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
require.NoError(t, err)
t.Run("marketplace client returns not-installed plugin", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusOK)
json, err := json.Marshal(samplePlugins)
require.NoError(t, err)
res.Write(json)
}))
defer func() { testServer.Close() }()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.Enable = true
*cfg.PluginSettings.EnableUploads = true
*cfg.PluginSettings.EnableMarketplace = true
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
})
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckNoError(t, resp)
require.Equal(t, samplePlugins, plugins)
manifest, resp := th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
CheckNoError(t, resp)
expectedPlugins := append(samplePlugins, &model.MarketplacePlugin{
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
HomepageURL: "",
IconData: "",
DownloadURL: "",
Manifest: manifest,
},
InstalledVersion: manifest.Version,
})
sort.SliceStable(expectedPlugins, func(i, j int) bool {
return strings.ToLower(expectedPlugins[i].Manifest.Name) < strings.ToLower(expectedPlugins[j].Manifest.Name)
})
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckNoError(t, resp)
require.Equal(t, expectedPlugins, plugins)
ok, resp := th.SystemAdminClient.RemovePlugin(manifest.Id)
CheckNoError(t, resp)
assert.True(t, ok)
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckNoError(t, resp)
require.Equal(t, samplePlugins, plugins)
})
t.Run("marketplace client returns installed plugin", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.Enable = true
*cfg.PluginSettings.EnableUploads = true
*cfg.PluginSettings.EnableMarketplace = true
})
manifest, resp := th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
CheckNoError(t, resp)
newPlugin := &model.MarketplacePlugin{
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
HomepageURL: "HomepageURL",
IconData: "IconData",
DownloadURL: "DownloadURL",
Manifest: manifest,
},
InstalledVersion: manifest.Version,
}
expectedPlugins := append(samplePlugins, newPlugin)
sort.SliceStable(expectedPlugins, func(i, j int) bool {
return strings.ToLower(expectedPlugins[i].Manifest.Name) < strings.ToLower(expectedPlugins[j].Manifest.Name)
})
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusOK)
json, err := json.Marshal([]*model.MarketplacePlugin{samplePlugins[0], newPlugin})
require.NoError(t, err)
res.Write(json)
}))
defer func() { testServer.Close() }()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
})
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckNoError(t, resp)
require.Equal(t, expectedPlugins, plugins)
ok, resp := th.SystemAdminClient.RemovePlugin(manifest.Id)
CheckNoError(t, resp)
assert.True(t, ok)
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckNoError(t, resp)
newPlugin.InstalledVersion = manifest.Version
require.Equal(t, expectedPlugins, plugins)
})
}
func TestSearchGetMarketplacePlugins(t *testing.T) {
samplePlugins := []*model.MarketplacePlugin{
{
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
HomepageURL: "https://github.com/mattermost/mattermost-plugin-nps",
IconData: "Cjxzdmcgdmlld0JveD0nMCAwIDEwNSA5MycgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJz4KPHBhdGggZD0nTTY2LDBoMzl2OTN6TTM4LDBoLTM4djkzek01MiwzNWwyNSw1OGgtMTZsLTgtMThoLTE4eicgZmlsbD0nI0VEMUMyNCcvPgo8L3N2Zz4K",
DownloadURL: "https://github.com/mattermost/mattermost-plugin-nps/releases/download/v1.0.3/com.mattermost.nps-1.0.3.tar.gz",
Manifest: &model.Manifest{
Id: "com.mattermost.nps",
Name: "User Satisfaction Surveys",
Description: "This plugin sends quarterly user satisfaction surveys to gather feedback and help improve Mattermost.",
Version: "1.0.3",
MinServerVersion: "5.14.0",
},
},
InstalledVersion: "",
},
}
path, _ := fileutils.FindDir("tests")
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
require.NoError(t, err)
tarDataV2, err := ioutil.ReadFile(filepath.Join(path, "testpluginv2.tar.gz"))
require.NoError(t, err)
t.Run("search installed plugin", func(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusOK)
json, err := json.Marshal(samplePlugins)
require.NoError(t, err)
res.Write(json)
}))
defer func() { testServer.Close() }()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.Enable = true
*cfg.PluginSettings.EnableUploads = true
*cfg.PluginSettings.EnableMarketplace = true
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
})
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckNoError(t, resp)
require.Equal(t, samplePlugins, plugins)
manifest, resp := th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
CheckNoError(t, resp)
newPluginV1 := &model.MarketplacePlugin{
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
HomepageURL: "",
IconData: "",
DownloadURL: "",
Manifest: manifest,
},
InstalledVersion: manifest.Version,
}
expectedPlugins := append(samplePlugins, newPluginV1)
manifest, resp = th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarDataV2))
CheckNoError(t, resp)
newPluginV2 := &model.MarketplacePlugin{
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
HomepageURL: "",
IconData: "",
DownloadURL: "",
Manifest: manifest,
},
InstalledVersion: manifest.Version,
}
expectedPlugins = append(expectedPlugins, newPluginV2)
sort.SliceStable(expectedPlugins, func(i, j int) bool {
return strings.ToLower(expectedPlugins[i].Manifest.Name) < strings.ToLower(expectedPlugins[j].Manifest.Name)
})
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
CheckNoError(t, resp)
require.Equal(t, expectedPlugins, plugins)
// Search for plugins from the server
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{Filter: "testplugin_v2"})
CheckNoError(t, resp)
require.Equal(t, []*model.MarketplacePlugin{newPluginV2}, plugins)
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{Filter: "dsgsdg_v2"})
CheckNoError(t, resp)
require.Equal(t, []*model.MarketplacePlugin{newPluginV2}, plugins)
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{Filter: "User Satisfaction Surveys"})
CheckNoError(t, resp)
require.Equal(t, samplePlugins, plugins)
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{Filter: "NOFILTER"})
CheckNoError(t, resp)
require.Nil(t, plugins)
})
}
func findClusterMessages(event string, msgs []*model.ClusterMessage) []*model.ClusterMessage { func findClusterMessages(event string, msgs []*model.ClusterMessage) []*model.ClusterMessage {
var result []*model.ClusterMessage var result []*model.ClusterMessage
for _, msg := range msgs { for _, msg := range msgs {

Просмотреть файл

@@ -134,6 +134,10 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
} }
skipFetchThreads := false
if r.URL.Query().Get("fetchThreads") == "false" {
skipFetchThreads = true
}
channelId := c.Params.ChannelId channelId := c.Params.ChannelId
page := c.Params.Page page := c.Params.Page
@@ -149,7 +153,7 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
etag := "" etag := ""
if since > 0 { if since > 0 {
list, err = c.App.GetPostsSince(channelId, since) list, err = c.App.GetPostsSince(model.GetPostsSinceOptions{ChannelId: channelId, Time: since, SkipFetchThreads: skipFetchThreads})
} else if len(afterPost) > 0 { } else if len(afterPost) > 0 {
etag = c.App.GetPostsEtag(channelId) etag = c.App.GetPostsEtag(channelId)
@@ -157,7 +161,7 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
list, err = c.App.GetPostsAfterPost(channelId, afterPost, page, perPage) list, err = c.App.GetPostsAfterPost(model.GetPostsOptions{ChannelId: channelId, PostId: afterPost, Page: page, PerPage: perPage, SkipFetchThreads: skipFetchThreads})
} else if len(beforePost) > 0 { } else if len(beforePost) > 0 {
etag = c.App.GetPostsEtag(channelId) etag = c.App.GetPostsEtag(channelId)
@@ -165,7 +169,7 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
list, err = c.App.GetPostsBeforePost(channelId, beforePost, page, perPage) list, err = c.App.GetPostsBeforePost(model.GetPostsOptions{ChannelId: channelId, PostId: beforePost, Page: page, PerPage: perPage, SkipFetchThreads: skipFetchThreads})
} else { } else {
etag = c.App.GetPostsEtag(channelId) etag = c.App.GetPostsEtag(channelId)
@@ -173,7 +177,7 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
list, err = c.App.GetPostsPage(channelId, page, perPage) list, err = c.App.GetPostsPage(model.GetPostsOptions{ChannelId: channelId, Page: page, PerPage: perPage, SkipFetchThreads: skipFetchThreads})
} }
if err != nil { if err != nil {
@@ -209,7 +213,11 @@ func getPostsForChannelAroundLastUnread(c *Context, w http.ResponseWriter, r *ht
return return
} }
postList, err := c.App.GetPostsForChannelAroundLastUnread(channelId, userId, c.Params.LimitBefore, c.Params.LimitAfter) skipFetchThreads := false
if r.URL.Query().Get("fetchThreads") == "false" {
skipFetchThreads = true
}
postList, err := c.App.GetPostsForChannelAroundLastUnread(channelId, userId, c.Params.LimitBefore, c.Params.LimitAfter, skipFetchThreads)
if err != nil { if err != nil {
c.Err = err c.Err = err
return return
@@ -223,7 +231,11 @@ func getPostsForChannelAroundLastUnread(c *Context, w http.ResponseWriter, r *ht
return return
} }
postList, err = c.App.GetPostsPage(channelId, app.PAGE_DEFAULT, c.Params.LimitBefore) postList, err = c.App.GetPostsPage(model.GetPostsOptions{ChannelId: channelId, Page: app.PAGE_DEFAULT, PerPage: c.Params.LimitBefore, SkipFetchThreads: skipFetchThreads})
if err != nil {
c.Err = err
return
}
} }
postList.NextPostId = c.App.GetNextPostIdFromPostList(postList) postList.NextPostId = c.App.GetNextPostIdFromPostList(postList)

Просмотреть файл

@@ -56,7 +56,7 @@ func getSystemPing(c *Context, w http.ResponseWriter, r *http.Request) {
actualGoroutines := runtime.NumGoroutine() actualGoroutines := runtime.NumGoroutine()
if *c.App.Config().ServiceSettings.GoroutineHealthThreshold > 0 && actualGoroutines >= *c.App.Config().ServiceSettings.GoroutineHealthThreshold { if *c.App.Config().ServiceSettings.GoroutineHealthThreshold > 0 && actualGoroutines >= *c.App.Config().ServiceSettings.GoroutineHealthThreshold {
mlog.Warn(fmt.Sprintf("The number of running goroutines (%v) is over the health threshold (%v)", actualGoroutines, *c.App.Config().ServiceSettings.GoroutineHealthThreshold)) mlog.Warn("The number of running goroutines is over the health threshold", mlog.Int("goroutines", actualGoroutines), mlog.Int("health_threshold", *c.App.Config().ServiceSettings.GoroutineHealthThreshold))
s[model.STATUS] = model.STATUS_UNHEALTHY s[model.STATUS] = model.STATUS_UNHEALTHY
} }
@@ -76,17 +76,17 @@ func getSystemPing(c *Context, w http.ResponseWriter, r *http.Request) {
Value: currentTime, Value: currentTime,
}) })
if writeErr != nil { if writeErr != nil {
mlog.Debug(fmt.Sprintf("Unable to write to database: %s", writeErr.Error())) mlog.Debug("Unable to write to database.", mlog.Err(writeErr))
s[dbStatusKey] = model.STATUS_UNHEALTHY s[dbStatusKey] = model.STATUS_UNHEALTHY
s[model.STATUS] = model.STATUS_UNHEALTHY s[model.STATUS] = model.STATUS_UNHEALTHY
} else { } else {
healthCheck, readErr := c.App.Srv.Store.System().GetByName(healthCheckKey) healthCheck, readErr := c.App.Srv.Store.System().GetByName(healthCheckKey)
if readErr != nil { if readErr != nil {
mlog.Debug(fmt.Sprintf("Unable to read from database: %s", readErr.Error())) mlog.Debug("Unable to read from database.", mlog.Err(readErr))
s[dbStatusKey] = model.STATUS_UNHEALTHY s[dbStatusKey] = model.STATUS_UNHEALTHY
s[model.STATUS] = model.STATUS_UNHEALTHY s[model.STATUS] = model.STATUS_UNHEALTHY
} else if healthCheck.Value != currentTime { } else if healthCheck.Value != currentTime {
mlog.Debug(fmt.Sprintf("Incorrect healthcheck value, expected %s, got %s", currentTime, healthCheck.Value)) mlog.Debug("Incorrect healthcheck value", mlog.String("expected", currentTime), mlog.String("got", healthCheck.Value))
s[dbStatusKey] = model.STATUS_UNHEALTHY s[dbStatusKey] = model.STATUS_UNHEALTHY
s[model.STATUS] = model.STATUS_UNHEALTHY s[model.STATUS] = model.STATUS_UNHEALTHY
} else { } else {
@@ -105,7 +105,7 @@ func getSystemPing(c *Context, w http.ResponseWriter, r *http.Request) {
s[model.STATUS] = model.STATUS_UNHEALTHY s[model.STATUS] = model.STATUS_UNHEALTHY
} }
} else { } else {
mlog.Debug(fmt.Sprintf("Unable to get filestore for ping status: %s", appErr.Error())) mlog.Debug("Unable to get filestore for ping status.", mlog.Err(appErr))
s[filestoreStatusKey] = model.STATUS_UNHEALTHY s[filestoreStatusKey] = model.STATUS_UNHEALTHY
s[model.STATUS] = model.STATUS_UNHEALTHY s[model.STATUS] = model.STATUS_UNHEALTHY
} }
@@ -269,7 +269,7 @@ func postLog(c *Context, w http.ResponseWriter, r *http.Request) {
err.Where = "client" err.Where = "client"
c.LogError(err) c.LogError(err)
} else { } else {
mlog.Debug(fmt.Sprint(msg)) mlog.Debug("message", mlog.String("message", msg))
} }
m["message"] = msg m["message"] = msg

Просмотреть файл

@@ -557,7 +557,7 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
profiles, err = c.App.GetUsersWithoutTeamPage(c.Params.Page, c.Params.PerPage, c.IsSystemAdmin(), restrictions) profiles, err = c.App.GetUsersWithoutTeamPage(userGetOptions, c.IsSystemAdmin())
} else if len(notInChannelId) > 0 { } else if len(notInChannelId) > 0 {
if !c.App.SessionHasPermissionToChannel(c.App.Session, notInChannelId, model.PERMISSION_READ_CHANNEL) { if !c.App.SessionHasPermissionToChannel(c.App.Session, notInChannelId, model.PERMISSION_READ_CHANNEL) {
c.SetPermissionError(model.PERMISSION_READ_CHANNEL) c.SetPermissionError(model.PERMISSION_READ_CHANNEL)

Просмотреть файл

@@ -4,7 +4,6 @@
package api4 package api4
import ( import (
"fmt"
"net/http" "net/http"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
@@ -25,7 +24,7 @@ func connectWebSocket(c *Context, w http.ResponseWriter, r *http.Request) {
ws, err := upgrader.Upgrade(w, r, nil) ws, err := upgrader.Upgrade(w, r, nil)
if err != nil { if err != nil {
mlog.Error(fmt.Sprintf("websocket connect err: %v", err)) mlog.Error("websocket connect err.", mlog.Err(err))
c.Err = model.NewAppError("connect", "api.web_socket.connect.upgrade.app_error", nil, "", http.StatusInternalServerError) c.Err = model.NewAppError("connect", "api.web_socket.connect.upgrade.app_error", nil, "", http.StatusInternalServerError)
return return
} }

Просмотреть файл

@@ -4,8 +4,6 @@
package app package app
import ( import (
"fmt"
"github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/store"
@@ -25,7 +23,7 @@ func (a *App) GetAnalytics(name string, teamId string) (model.AnalyticsRows, *mo
} }
if systemUserCount > int64(*a.Config().AnalyticsSettings.MaxUsersForStatistics) { if systemUserCount > int64(*a.Config().AnalyticsSettings.MaxUsersForStatistics) {
mlog.Debug(fmt.Sprintf("More than %v users on the system, intensive queries skipped", *a.Config().AnalyticsSettings.MaxUsersForStatistics)) mlog.Debug("More than limit users are on the system, intensive queries skipped", mlog.Int("limit", *a.Config().AnalyticsSettings.MaxUsersForStatistics))
skipIntensiveQueries = true skipIntensiveQueries = true
} }

Просмотреть файл

@@ -66,6 +66,10 @@ func (cfg *AutoPostCreator) UploadTestFile() ([]string, bool) {
} }
func (cfg *AutoPostCreator) CreateRandomPost() (*model.Post, bool) { func (cfg *AutoPostCreator) CreateRandomPost() (*model.Post, bool) {
return cfg.CreateRandomPostNested("", "")
}
func (cfg *AutoPostCreator) CreateRandomPostNested(parentId, rootId string) (*model.Post, bool) {
var fileIds []string var fileIds []string
if cfg.HasImage { if cfg.HasImage {
var err1 bool var err1 bool
@@ -84,6 +88,8 @@ func (cfg *AutoPostCreator) CreateRandomPost() (*model.Post, bool) {
post := &model.Post{ post := &model.Post{
ChannelId: cfg.channelid, ChannelId: cfg.channelid,
ParentId: parentId,
RootId: rootId,
Message: postText, Message: postText,
FileIds: fileIds} FileIds: fileIds}
rpost, err2 := cfg.client.CreatePost(post) rpost, err2 := cfg.client.CreatePost(post)

Просмотреть файл

@@ -526,7 +526,7 @@ func TestAddChannelMemberNoUserRequestor(t *testing.T) {
} }
assert.Equal(t, groupUserIds, channelMemberHistoryUserIds) assert.Equal(t, groupUserIds, channelMemberHistoryUserIds)
postList, err := th.App.Srv.Store.Post().GetPosts(channel.Id, 0, 1, false) postList, err := th.App.Srv.Store.Post().GetPosts(model.GetPostsOptions{ChannelId: channel.Id, Page: 0, PerPage: 1}, false)
require.Nil(t, err) require.Nil(t, err)
if assert.Len(t, postList.Order, 1) { if assert.Len(t, postList.Order, 1) {

Просмотреть файл

@@ -4,7 +4,6 @@
package app package app
import ( import (
"fmt"
"time" "time"
"github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/mlog"
@@ -34,41 +33,41 @@ func (a *App) NewClusterDiscoveryService() *ClusterDiscoveryService {
func (me *ClusterDiscoveryService) Start() { func (me *ClusterDiscoveryService) Start() {
err := me.app.Srv.Store.ClusterDiscovery().Cleanup() err := me.app.Srv.Store.ClusterDiscovery().Cleanup()
if err != nil { if err != nil {
mlog.Error(fmt.Sprintf("ClusterDiscoveryService failed to cleanup the outdated cluster discovery information err=%v", err)) mlog.Error("ClusterDiscoveryService failed to cleanup the outdated cluster discovery information", mlog.Err(err))
} }
exists, err := me.app.Srv.Store.ClusterDiscovery().Exists(&me.ClusterDiscovery) exists, err := me.app.Srv.Store.ClusterDiscovery().Exists(&me.ClusterDiscovery)
if err != nil { if err != nil {
mlog.Error(fmt.Sprintf("ClusterDiscoveryService failed to check if row exists for %v with err=%v", me.ClusterDiscovery.ToJson(), err)) mlog.Error("ClusterDiscoveryService failed to check if row exists", mlog.String("ClusterDiscovery", me.ClusterDiscovery.ToJson()), mlog.Err(err))
} else { } else {
if exists { if exists {
if _, err := me.app.Srv.Store.ClusterDiscovery().Delete(&me.ClusterDiscovery); err != nil { if _, err := me.app.Srv.Store.ClusterDiscovery().Delete(&me.ClusterDiscovery); err != nil {
mlog.Error(fmt.Sprintf("ClusterDiscoveryService failed to start clean for %v with err=%v", me.ClusterDiscovery.ToJson(), err)) mlog.Error("ClusterDiscoveryService failed to start clean", mlog.String("ClusterDiscovery", me.ClusterDiscovery.ToJson()), mlog.Err(err))
} }
} }
} }
if err := me.app.Srv.Store.ClusterDiscovery().Save(&me.ClusterDiscovery); err != nil { if err := me.app.Srv.Store.ClusterDiscovery().Save(&me.ClusterDiscovery); err != nil {
mlog.Error(fmt.Sprintf("ClusterDiscoveryService failed to save for %v with err=%v", me.ClusterDiscovery.ToJson(), err)) mlog.Error("ClusterDiscoveryService failed to save", mlog.String("ClusterDiscovery", me.ClusterDiscovery.ToJson()), mlog.Err(err))
return return
} }
go func() { go func() {
mlog.Debug(fmt.Sprintf("ClusterDiscoveryService ping writer started for %v", me.ClusterDiscovery.ToJson())) mlog.Debug("ClusterDiscoveryService ping writer started", mlog.String("ClusterDiscovery", me.ClusterDiscovery.ToJson()))
ticker := time.NewTicker(DISCOVERY_SERVICE_WRITE_PING) ticker := time.NewTicker(DISCOVERY_SERVICE_WRITE_PING)
defer func() { defer func() {
ticker.Stop() ticker.Stop()
if _, err := me.app.Srv.Store.ClusterDiscovery().Delete(&me.ClusterDiscovery); err != nil { if _, err := me.app.Srv.Store.ClusterDiscovery().Delete(&me.ClusterDiscovery); err != nil {
mlog.Error(fmt.Sprintf("ClusterDiscoveryService failed to cleanup for %v with err=%v", me.ClusterDiscovery.ToJson(), err)) mlog.Error("ClusterDiscoveryService failed to cleanup", mlog.String("ClusterDiscovery", me.ClusterDiscovery.ToJson()), mlog.Err(err))
} }
mlog.Debug(fmt.Sprintf("ClusterDiscoveryService ping writer stopped for %v", me.ClusterDiscovery.ToJson())) mlog.Debug("ClusterDiscoveryService ping writer stopped", mlog.String("ClusterDiscovery", me.ClusterDiscovery.ToJson()))
}() }()
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
if err := me.app.Srv.Store.ClusterDiscovery().SetLastPingAt(&me.ClusterDiscovery); err != nil { if err := me.app.Srv.Store.ClusterDiscovery().SetLastPingAt(&me.ClusterDiscovery); err != nil {
mlog.Error(fmt.Sprintf("ClusterDiscoveryService failed to write ping for %v with err=%v", me.ClusterDiscovery.ToJson(), err)) mlog.Error("ClusterDiscoveryService failed to write ping", mlog.String("ClusterDiscovery", me.ClusterDiscovery.ToJson()), mlog.Err(err))
} }
case <-me.stop: case <-me.stop:
return return

Просмотреть файл

@@ -47,14 +47,26 @@ func (me *groupmsgProvider) DoCommand(a *App, args *model.CommandArgs, message s
for _, username := range users { for _, username := range users {
username = strings.TrimSpace(username) username = strings.TrimSpace(username)
username = strings.TrimPrefix(username, "@") username = strings.TrimPrefix(username, "@")
if targetUser, err := a.Srv.Store.User().GetByUsername(username); err != nil { targetUser, err := a.Srv.Store.User().GetByUsername(username)
if err != nil {
invalidUsernames = append(invalidUsernames, username) invalidUsernames = append(invalidUsernames, username)
} else { continue
_, exists := targetUsers[targetUser.Id] }
if !exists && targetUser.Id != args.UserId {
targetUsers[targetUser.Id] = targetUser canSee, err := a.UserCanSeeOtherUser(args.UserId, targetUser.Id)
targetUsersSlice = append(targetUsersSlice, targetUser.Id) if err != nil {
} return &model.CommandResponse{Text: args.T("api.command_groupmsg.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
if !canSee {
invalidUsernames = append(invalidUsernames, username)
continue
}
_, exists := targetUsers[targetUser.Id]
if !exists && targetUser.Id != args.UserId {
targetUsers[targetUser.Id] = targetUser
targetUsersSlice = append(targetUsersSlice, targetUser.Id)
} }
} }

Просмотреть файл

@@ -52,46 +52,67 @@ func TestGroupMsgProvider(t *testing.T) {
th.LinkUserToTeam(th.BasicUser, team) th.LinkUserToTeam(th.BasicUser, team)
cmd := &groupmsgProvider{} cmd := &groupmsgProvider{}
// Check without permission to create a GM channel. t.Run("Check without permission to create a GM channel.", func(t *testing.T) {
resp := cmd.DoCommand(th.App, &model.CommandArgs{ resp := cmd.DoCommand(th.App, &model.CommandArgs{
T: i18n.IdentityTfunc(), T: i18n.IdentityTfunc(),
SiteURL: "http://test.url", SiteURL: "http://test.url",
TeamId: team.Id, TeamId: team.Id,
UserId: th.BasicUser.Id, UserId: th.BasicUser.Id,
Session: model.Session{ Session: model.Session{
Roles: "", Roles: "",
}, },
}, targetUsers+"hello") }, targetUsers+"hello")
channelName := model.GetGroupNameFromUserIds([]string{th.BasicUser.Id, th.BasicUser2.Id, user3.Id}) assert.Equal(t, "api.command_groupmsg.permission.app_error", resp.Text)
assert.Equal(t, "api.command_groupmsg.permission.app_error", resp.Text) assert.Equal(t, "", resp.GotoLocation)
assert.Equal(t, "", resp.GotoLocation) })
// Check with permission to create a GM channel. t.Run("Check without permissions to view a user in the list.", func(t *testing.T) {
resp = cmd.DoCommand(th.App, &model.CommandArgs{ th.RemovePermissionFromRole(model.PERMISSION_VIEW_MEMBERS.Id, model.SYSTEM_USER_ROLE_ID)
T: i18n.IdentityTfunc(), defer th.AddPermissionToRole(model.PERMISSION_VIEW_MEMBERS.Id, model.SYSTEM_USER_ROLE_ID)
SiteURL: "http://test.url", resp := cmd.DoCommand(th.App, &model.CommandArgs{
TeamId: team.Id, T: i18n.IdentityTfunc(),
UserId: th.BasicUser.Id, SiteURL: "http://test.url",
Session: model.Session{ TeamId: team.Id,
Roles: model.SYSTEM_USER_ROLE_ID, UserId: th.BasicUser.Id,
}, Session: model.Session{
}, targetUsers+"hello") Roles: model.SYSTEM_USER_ROLE_ID,
},
}, targetUsers+"hello")
assert.Equal(t, "", resp.Text) assert.Equal(t, "api.command_groupmsg.invalid_user.app_error", resp.Text)
assert.Equal(t, "http://test.url/"+team.Name+"/channels/"+channelName, resp.GotoLocation) assert.Equal(t, "", resp.GotoLocation)
})
// Check without permission to post to an existing GM channel. t.Run("Check with permission to create a GM channel.", func(t *testing.T) {
resp = cmd.DoCommand(th.App, &model.CommandArgs{ resp := cmd.DoCommand(th.App, &model.CommandArgs{
T: i18n.IdentityTfunc(), T: i18n.IdentityTfunc(),
SiteURL: "http://test.url", SiteURL: "http://test.url",
TeamId: team.Id, TeamId: team.Id,
UserId: th.BasicUser.Id, UserId: th.BasicUser.Id,
Session: model.Session{ Session: model.Session{
Roles: "", Roles: model.SYSTEM_USER_ROLE_ID,
}, },
}, targetUsers+"hello") }, targetUsers+"hello")
assert.Equal(t, "", resp.Text) channelName := model.GetGroupNameFromUserIds([]string{th.BasicUser.Id, th.BasicUser2.Id, user3.Id})
assert.Equal(t, "http://test.url/"+team.Name+"/channels/"+channelName, resp.GotoLocation) assert.Equal(t, "", resp.Text)
assert.Equal(t, "http://test.url/"+team.Name+"/channels/"+channelName, resp.GotoLocation)
})
t.Run("Check without permission to post to an existing GM channel.", func(t *testing.T) {
resp := cmd.DoCommand(th.App, &model.CommandArgs{
T: i18n.IdentityTfunc(),
SiteURL: "http://test.url",
TeamId: team.Id,
UserId: th.BasicUser.Id,
Session: model.Session{
Roles: "",
},
}, targetUsers+"hello")
channelName := model.GetGroupNameFromUserIds([]string{th.BasicUser.Id, th.BasicUser2.Id, user3.Id})
assert.Equal(t, "", resp.Text)
assert.Equal(t, "http://test.url/"+team.Name+"/channels/"+channelName, resp.GotoLocation)
})
} }

Просмотреть файл

@@ -39,6 +39,9 @@ var usage = `Mattermost testing commands to help configure the system
Example: Example:
/test channels fuzz 5 10 /test channels fuzz 5 10
ThreadedPost - create a large threaded post
/test threaded_post
Posts - Add some random posts with fuzz text to current channel. Posts - Add some random posts with fuzz text to current channel.
/test posts [fuzz] <Min Posts> <Max Posts> <Max Images> /test posts [fuzz] <Min Posts> <Max Posts> <Max Images>
@@ -127,6 +130,10 @@ func (me *LoadTestProvider) DoCommand(a *App, args *model.CommandArgs, message s
return me.PostCommand(a, args, message) return me.PostCommand(a, args, message)
} }
if strings.HasPrefix(message, "threaded_post") {
return me.ThreadedPostCommand(a, args, message)
}
if strings.HasPrefix(message, "url") { if strings.HasPrefix(message, "url") {
return me.UrlCommand(a, args, message) return me.UrlCommand(a, args, message)
} }
@@ -277,6 +284,34 @@ func (me *LoadTestProvider) ChannelsCommand(a *App, args *model.CommandArgs, mes
return &model.CommandResponse{Text: "Added channels", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} return &model.CommandResponse{Text: "Added channels", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
} }
func (me *LoadTestProvider) ThreadedPostCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
var usernames []string
options := &model.UserGetOptions{InTeamId: args.TeamId, Page: 0, PerPage: 1000}
if profileUsers, err := a.Srv.Store.User().GetProfiles(options); err == nil {
usernames = make([]string, len(profileUsers))
i := 0
for _, userprof := range profileUsers {
usernames[i] = userprof.Username
i++
}
}
client := model.NewAPIv4Client(args.SiteURL)
client.MockSession(args.Session.Token)
testPoster := NewAutoPostCreator(client, args.ChannelId)
testPoster.Fuzzy = true
testPoster.Users = usernames
rpost, ok := testPoster.CreateRandomPost()
if !ok {
return &model.CommandResponse{Text: "Cannot create a post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
for i := 0; i < 1000; i++ {
testPoster.CreateRandomPostNested(rpost.Id, rpost.Id)
}
return &model.CommandResponse{Text: "Added threaded post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
func (me *LoadTestProvider) PostsCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse { func (me *LoadTestProvider) PostsCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse {
cmd := strings.TrimSpace(strings.TrimPrefix(message, "posts")) cmd := strings.TrimSpace(strings.TrimPrefix(message, "posts"))

Просмотреть файл

@@ -602,6 +602,8 @@ func (a *App) trackConfig() {
"enable_uploads": *cfg.PluginSettings.EnableUploads, "enable_uploads": *cfg.PluginSettings.EnableUploads,
"allow_insecure_download_url": *cfg.PluginSettings.AllowInsecureDownloadUrl, "allow_insecure_download_url": *cfg.PluginSettings.AllowInsecureDownloadUrl,
"enable_health_check": *cfg.PluginSettings.EnableHealthCheck, "enable_health_check": *cfg.PluginSettings.EnableHealthCheck,
"enable_marketplace": *cfg.PluginSettings.EnableMarketplace,
"is_default_marketplace_url": isDefault(*cfg.PluginSettings.MarketplaceUrl, model.PLUGIN_SETTINGS_DEFAULT_MARKETPLACE_URL),
}) })
a.SendDiagnostic(TRACK_CONFIG_DATA_RETENTION, map[string]interface{}{ a.SendDiagnostic(TRACK_CONFIG_DATA_RETENTION, map[string]interface{}{

Просмотреть файл

@@ -68,7 +68,7 @@ func TestDiagnostics(t *testing.T) {
case identifyMessage := <-data: case identifyMessage := <-data:
require.Contains(t, identifyMessage, diagnosticID) require.Contains(t, identifyMessage, diagnosticID)
case <-time.After(time.Second * 1): case <-time.After(time.Second * 1):
t.Fatal("Did not receive ID message") require.Fail(t,"Did not receive ID message")
} }
t.Run("Send", func(t *testing.T) { t.Run("Send", func(t *testing.T) {
@@ -80,7 +80,7 @@ func TestDiagnostics(t *testing.T) {
case result := <-data: case result := <-data:
require.Contains(t, result, testValue) require.Contains(t, result, testValue)
case <-time.After(time.Second * 1): case <-time.After(time.Second * 1):
t.Fatal("Did not receive diagnostic") require.Fail(t,"Did not receive diagnostic")
} }
}) })
@@ -137,7 +137,7 @@ func TestDiagnostics(t *testing.T) {
select { select {
case <-data: case <-data:
t.Fatal("Should not send diagnostics when the segment key is not set") require.Fail(t,"Should not send diagnostics when the segment key is not set")
case <-time.After(time.Second * 1): case <-time.After(time.Second * 1):
// Did not receive diagnostics // Did not receive diagnostics
} }
@@ -150,7 +150,7 @@ func TestDiagnostics(t *testing.T) {
select { select {
case <-data: case <-data:
t.Fatal("Should not send diagnostics when they are disabled") require.Fail(t,"Should not send diagnostics when they are disabled")
case <-time.After(time.Second * 1): case <-time.After(time.Second * 1):
// Did not receive diagnostics // Did not receive diagnostics
} }

Просмотреть файл

@@ -142,7 +142,7 @@ func (job *EmailBatchingJob) checkPendingNotifications(now time.Time, handler fu
team, err := job.server.Store.Team().GetByName(notifications[0].teamName) team, err := job.server.Store.Team().GetByName(notifications[0].teamName)
if err != nil { if err != nil {
mlog.Error(fmt.Sprint("Unable to find Team id for notification", err)) mlog.Error("Unable to find Team id for notification", mlog.Err(err))
continue continue
} }
@@ -154,13 +154,13 @@ func (job *EmailBatchingJob) checkPendingNotifications(now time.Time, handler fu
// all queued notifications // all queued notifications
channelMembers, err := job.server.Store.Channel().GetMembersForUser(inspectedTeamNames[notification.teamName], userId) channelMembers, err := job.server.Store.Channel().GetMembersForUser(inspectedTeamNames[notification.teamName], userId)
if err != nil { if err != nil {
mlog.Error(fmt.Sprint("Unable to find ChannelMembers for user", err)) mlog.Error("Unable to find ChannelMembers for user", mlog.Err(err))
continue continue
} }
for _, channelMember := range *channelMembers { for _, channelMember := range *channelMembers {
if channelMember.LastViewedAt >= batchStartTime { if channelMember.LastViewedAt >= batchStartTime {
mlog.Debug(fmt.Sprintf("Deleted notifications for user %s", userId), mlog.String("user_id", userId)) mlog.Debug("Deleted notifications for user", mlog.String("user_id", userId))
delete(job.pendingNotifications, userId) delete(job.pendingNotifications, userId)
break break
} }
@@ -241,7 +241,7 @@ func (s *Server) sendBatchedEmailNotification(userId string, notifications []*ba
body.Props["BodyText"] = translateFunc("api.email_batching.send_batched_email_notification.body_text", len(notifications)) body.Props["BodyText"] = translateFunc("api.email_batching.send_batched_email_notification.body_text", len(notifications))
if err := s.FakeApp().SendNotificationMail(user.Email, subject, body.Render()); err != nil { if err := s.FakeApp().SendNotificationMail(user.Email, subject, body.Render()); err != nil {
mlog.Warn(fmt.Sprintf("Unable to send batched email notification err=%v", err), mlog.String("email", user.Email)) mlog.Warn("Unable to send batched email notification", mlog.String("email", user.Email), mlog.Err(err))
} }
} }

Просмотреть файл

@@ -5,7 +5,6 @@ package app
import ( import (
"bytes" "bytes"
"fmt"
"image" "image"
"image/draw" "image/draw"
"image/gif" "image/gif"
@@ -285,13 +284,12 @@ func imageToPaletted(img image.Image) *image.Paletted {
func (a *App) deleteEmojiImage(id string) { func (a *App) deleteEmojiImage(id string) {
if err := a.MoveFile(getEmojiImagePath(id), "emoji/"+id+"/image_deleted"); err != nil { if err := a.MoveFile(getEmojiImagePath(id), "emoji/"+id+"/image_deleted"); err != nil {
mlog.Error(fmt.Sprintf("Failed to rename image when deleting emoji %v", id)) mlog.Error("Failed to rename image when deleting emoji", mlog.String("emoji_id", id))
} }
} }
func (a *App) deleteReactionsForEmoji(emojiName string) { func (a *App) deleteReactionsForEmoji(emojiName string) {
if err := a.Srv.Store.Reaction().DeleteAllWithEmojiName(emojiName); err != nil { if err := a.Srv.Store.Reaction().DeleteAllWithEmojiName(emojiName); err != nil {
mlog.Warn(fmt.Sprintf("Unable to delete reactions when deleting emoji with emoji name %v", emojiName)) mlog.Warn("Unable to delete reactions when deleting emoji", mlog.String("emoji_name", emojiName), mlog.Err(err))
mlog.Warn(fmt.Sprint(err))
} }
} }

Просмотреть файл

@@ -115,9 +115,8 @@ func TestDirCreationForEmoji(t *testing.T) {
pathToDir := th.App.createDirForEmoji("test.json", "exported_emoji_test") pathToDir := th.App.createDirForEmoji("test.json", "exported_emoji_test")
defer os.Remove(pathToDir) defer os.Remove(pathToDir)
if _, err := os.Stat(pathToDir); os.IsNotExist(err) { _, err := os.Stat(pathToDir)
t.Fatal("Directory exported_emoji_test should exist") require.False(t, os.IsNotExist(err), "Directory exported_emoji_test should exist")
}
} }
func TestCopyEmojiImages(t *testing.T) { func TestCopyEmojiImages(t *testing.T) {
@@ -147,13 +146,10 @@ func TestCopyEmojiImages(t *testing.T) {
defer os.RemoveAll(filePath) defer os.RemoveAll(filePath)
copyError := th.App.copyEmojiImages(emoji.Id, emojiImagePath, pathToDir) copyError := th.App.copyEmojiImages(emoji.Id, emojiImagePath, pathToDir)
if copyError != nil { require.Nil(t, copyError)
t.Fatal(copyError)
}
if _, err := os.Stat(pathToDir + "/" + emoji.Id + "/image"); os.IsNotExist(err) { _, err = os.Stat(pathToDir + "/" + emoji.Id + "/image")
t.Fatal("File should exist ", err) require.False(t, os.IsNotExist(err), "File should exist ")
}
} }
func TestExportCustomEmoji(t *testing.T) { func TestExportCustomEmoji(t *testing.T) {
@@ -170,9 +166,8 @@ func TestExportCustomEmoji(t *testing.T) {
dirNameToExportEmoji := "exported_emoji_test" dirNameToExportEmoji := "exported_emoji_test"
defer os.RemoveAll("../" + dirNameToExportEmoji) defer os.RemoveAll("../" + dirNameToExportEmoji)
if err := th.App.ExportCustomEmoji(fileWriter, filePath, pathToEmojiDir, dirNameToExportEmoji); err != nil { err = th.App.ExportCustomEmoji(fileWriter, filePath, pathToEmojiDir, dirNameToExportEmoji)
t.Fatal(err) require.Nil(t, err, "should not have failed")
}
} }
func TestExportAllUsers(t *testing.T) { func TestExportAllUsers(t *testing.T) {

Просмотреть файл

@@ -139,7 +139,11 @@ func (a *App) GetInfoForFilename(post *model.Post, teamId string, filename strin
// Find the path from the Filename of the form /{channelId}/{userId}/{uid}/{nameWithExtension} // Find the path from the Filename of the form /{channelId}/{userId}/{uid}/{nameWithExtension}
split := strings.SplitN(filename, "/", 5) split := strings.SplitN(filename, "/", 5)
if len(split) < 5 { if len(split) < 5 {
mlog.Error("Unable to decipher filename when migrating post to use FileInfos", mlog.String("post_id", post.Id), mlog.String("filename", filename)) mlog.Error(
"Unable to decipher filename when migrating post to use FileInfos",
mlog.String("post_id", post.Id),
mlog.String("filename", filename),
)
return nil return nil
} }
@@ -176,9 +180,10 @@ func (a *App) GetInfoForFilename(post *model.Post, teamId string, filename strin
info, err := model.GetInfoForBytes(name, data) info, err := model.GetInfoForBytes(name, data)
if err != nil { if err != nil {
mlog.Warn( mlog.Warn(
fmt.Sprintf("Unable to fully decode file info when migrating post to use FileInfos, err=%v", err), "Unable to fully decode file info when migrating post to use FileInfos",
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("filename", filename), mlog.String("filename", filename),
mlog.Err(err),
) )
} }
@@ -207,7 +212,7 @@ func (a *App) FindTeamIdForFilename(post *model.Post, filename string) string {
// This post is in a direct channel so we need to figure out what team the files are stored under. // This post is in a direct channel so we need to figure out what team the files are stored under.
teams, err := a.Srv.Store.Team().GetTeamsByUserId(post.UserId) teams, err := a.Srv.Store.Team().GetTeamsByUserId(post.UserId)
if err != nil { if err != nil {
mlog.Error(fmt.Sprintf("Unable to get teams when migrating post to use FileInfo, err=%v", err), mlog.String("post_id", post.Id)) mlog.Error("Unable to get teams when migrating post to use FileInfo", mlog.Err(err), mlog.String("post_id", post.Id))
return "" return ""
} }
@@ -241,9 +246,10 @@ func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo {
filenames := utils.RemoveDuplicatesFromStringArray(post.Filenames) filenames := utils.RemoveDuplicatesFromStringArray(post.Filenames)
if errCh != nil { if errCh != nil {
mlog.Error( mlog.Error(
fmt.Sprintf("Unable to get channel when migrating post to use FileInfos, err=%v", errCh), "Unable to get channel when migrating post to use FileInfos",
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("channel_id", post.ChannelId), mlog.String("channel_id", post.ChannelId),
mlog.Err(errCh),
) )
return []*model.FileInfo{} return []*model.FileInfo{}
} }
@@ -261,7 +267,8 @@ func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo {
infos := make([]*model.FileInfo, 0, len(filenames)) infos := make([]*model.FileInfo, 0, len(filenames))
if teamId == "" { if teamId == "" {
mlog.Error( mlog.Error(
fmt.Sprintf("Unable to find team id for files when migrating post to use FileInfos, filenames=%v", filenames), "Unable to find team id for files when migrating post to use FileInfos",
mlog.String("filenames", strings.Join(filenames, ",")),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
) )
} else { } else {
@@ -279,9 +286,9 @@ func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo {
fileMigrationLock.Lock() fileMigrationLock.Lock()
defer fileMigrationLock.Unlock() defer fileMigrationLock.Unlock()
result, err := a.Srv.Store.Post().Get(post.Id) result, err := a.Srv.Store.Post().Get(post.Id, false)
if err != nil { if err != nil {
mlog.Error(fmt.Sprintf("Unable to get post when migrating post to use FileInfos, err=%v", err), mlog.String("post_id", post.Id)) mlog.Error("Unable to get post when migrating post to use FileInfos", mlog.Err(err), mlog.String("post_id", post.Id))
return []*model.FileInfo{} return []*model.FileInfo{}
} }
@@ -290,7 +297,7 @@ func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo {
var fileInfos []*model.FileInfo var fileInfos []*model.FileInfo
fileInfos, err = a.Srv.Store.FileInfo().GetForPost(post.Id, true, false, false) fileInfos, err = a.Srv.Store.FileInfo().GetForPost(post.Id, true, false, false)
if err != nil { if err != nil {
mlog.Error(fmt.Sprintf("Unable to get FileInfos for migrated post, err=%v", err), mlog.String("post_id", post.Id)) mlog.Error("Unable to get FileInfos for migrated post", mlog.Err(err), mlog.String("post_id", post.Id))
return []*model.FileInfo{} return []*model.FileInfo{}
} }
@@ -305,10 +312,11 @@ func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo {
for _, info := range infos { for _, info := range infos {
if _, err = a.Srv.Store.FileInfo().Save(info); err != nil { if _, err = a.Srv.Store.FileInfo().Save(info); err != nil {
mlog.Error( mlog.Error(
fmt.Sprintf("Unable to save file info when migrating post to use FileInfos, err=%v", err), "Unable to save file info when migrating post to use FileInfos",
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("file_info_id", info.Id), mlog.String("file_info_id", info.Id),
mlog.String("file_info_path", info.Path), mlog.String("file_info_path", info.Path),
mlog.Err(err),
) )
continue continue
} }
@@ -326,7 +334,13 @@ func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo {
// Update Posts to clear Filenames and set FileIds // Update Posts to clear Filenames and set FileIds
if _, err = a.Srv.Store.Post().Update(newPost, post); err != nil { if _, err = a.Srv.Store.Post().Update(newPost, post); err != nil {
mlog.Error(fmt.Sprintf("Unable to save migrated post when migrating to use FileInfos, new_file_ids=%v, old_filenames=%v, err=%v", newPost.FileIds, post.Filenames, err), mlog.String("post_id", post.Id)) mlog.Error(
"Unable to save migrated post when migrating to use FileInfos",
mlog.String("new_file_ids", strings.Join(newPost.FileIds, ",")),
mlog.String("old_filenames", strings.Join(post.Filenames, ",")),
mlog.String("post_id", post.Id),
mlog.Err(err),
)
return []*model.FileInfo{} return []*model.FileInfo{}
} }
return savedInfos return savedInfos
@@ -755,7 +769,7 @@ func (t *uploadFileTask) postprocessImage() {
var err error var err error
decoded, typ, err = image.Decode(t.newReader()) decoded, typ, err = image.Decode(t.newReader())
if err != nil { if err != nil {
mlog.Error(fmt.Sprintf("Unable to decode image err=%v", err)) mlog.Error("Unable to decode image", mlog.Err(err))
return return
} }
} }
@@ -779,14 +793,14 @@ func (t *uploadFileTask) postprocessImage() {
go func() { go func() {
_, aerr := t.writeFile(r, path) _, aerr := t.writeFile(r, path)
if aerr != nil { if aerr != nil {
mlog.Error(fmt.Sprintf("Unable to upload path=%v err=%v", path, aerr)) mlog.Error("Unable to upload", mlog.String("path", path), mlog.Err(aerr))
return return
} }
}() }()
err := jpeg.Encode(w, img, &jpeg.Options{Quality: 90}) err := jpeg.Encode(w, img, &jpeg.Options{Quality: 90})
if err != nil { if err != nil {
mlog.Error(fmt.Sprintf("Unable to encode image as jpeg path=%v err=%v", path, err)) mlog.Error("Unable to encode image as jpeg", mlog.String("path", path), mlog.Err(err))
w.CloseWithError(err) w.CloseWithError(err)
} else { } else {
w.Close() w.Close()
@@ -959,7 +973,7 @@ func prepareImage(fileData []byte) (image.Image, int, int) {
// Decode image bytes into Image object // Decode image bytes into Image object
img, imgType, err := image.Decode(bytes.NewReader(fileData)) img, imgType, err := image.Decode(bytes.NewReader(fileData))
if err != nil { if err != nil {
mlog.Error(fmt.Sprintf("Unable to decode image err=%v", err)) mlog.Error("Unable to decode image", mlog.Err(err))
return nil, 0, 0 return nil, 0, 0
} }
@@ -1038,12 +1052,12 @@ func (a *App) generateThumbnailImage(img image.Image, thumbnailPath string, widt
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
if err := jpeg.Encode(buf, thumbnail, &jpeg.Options{Quality: 90}); err != nil { if err := jpeg.Encode(buf, thumbnail, &jpeg.Options{Quality: 90}); err != nil {
mlog.Error(fmt.Sprintf("Unable to encode image as jpeg path=%v err=%v", thumbnailPath, err)) mlog.Error("Unable to encode image as jpeg", mlog.String("path", thumbnailPath), mlog.Err(err))
return return
} }
if _, err := a.WriteFile(buf, thumbnailPath); err != nil { if _, err := a.WriteFile(buf, thumbnailPath); err != nil {
mlog.Error(fmt.Sprintf("Unable to upload thumbnail path=%v err=%v", thumbnailPath, err)) mlog.Error("Unable to upload thumbnail", mlog.String("path", thumbnailPath), mlog.Err(err))
return return
} }
} }
@@ -1060,12 +1074,12 @@ func (a *App) generatePreviewImage(img image.Image, previewPath string, width in
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
if err := jpeg.Encode(buf, preview, &jpeg.Options{Quality: 90}); err != nil { if err := jpeg.Encode(buf, preview, &jpeg.Options{Quality: 90}); err != nil {
mlog.Error(fmt.Sprintf("Unable to encode image as preview jpg err=%v", err), mlog.String("path", previewPath)) mlog.Error("Unable to encode image as preview jpg", mlog.Err(err), mlog.String("path", previewPath))
return return
} }
if _, err := a.WriteFile(buf, previewPath); err != nil { if _, err := a.WriteFile(buf, previewPath); err != nil {
mlog.Error(fmt.Sprintf("Unable to upload preview err=%v", err), mlog.String("path", previewPath)) mlog.Error("Unable to upload preview", mlog.Err(err), mlog.String("path", previewPath))
return return
} }
} }

Просмотреть файл

@@ -27,17 +27,12 @@ func TestGeneratePublicLinkHash(t *testing.T) {
hash2 := GeneratePublicLinkHash(filename2, salt1) hash2 := GeneratePublicLinkHash(filename2, salt1)
hash3 := GeneratePublicLinkHash(filename1, salt2) hash3 := GeneratePublicLinkHash(filename1, salt2)
if hash1 != GeneratePublicLinkHash(filename1, salt1) { hash := GeneratePublicLinkHash(filename1, salt1)
t.Fatal("hash should be equal for the same file name and salt") assert.Equal(t, hash, hash1, "hash should be equal for the same file name and salt")
}
if hash1 == hash2 { assert.NotEqual(t, hash1, hash2, "hashes for different files should not be equal")
t.Fatal("hashes for different files should not be equal")
}
if hash1 == hash3 { assert.NotEqual(t, hash1, hash3, "hashes for the same file with different salts should not be equal")
t.Fatal("hashes for the same file with different salts should not be equal")
}
} }
func TestDoUploadFile(t *testing.T) { func TestDoUploadFile(t *testing.T) {
@@ -51,60 +46,44 @@ func TestDoUploadFile(t *testing.T) {
data := []byte("abcd") data := []byte("abcd")
info1, err := th.App.DoUploadFile(time.Date(2007, 2, 4, 1, 2, 3, 4, time.Local), teamId, channelId, userId, filename, data) info1, err := th.App.DoUploadFile(time.Date(2007, 2, 4, 1, 2, 3, 4, time.Local), teamId, channelId, userId, filename, data)
if err != nil { require.Nil(t, err, "DoUploadFile should succeed with valid data")
t.Fatal(err) defer func() {
} else { th.App.Srv.Store.FileInfo().PermanentDelete(info1.Id)
defer func() { th.App.RemoveFile(info1.Path)
th.App.Srv.Store.FileInfo().PermanentDelete(info1.Id) }()
th.App.RemoveFile(info1.Path)
}()
}
if info1.Path != fmt.Sprintf("20070204/teams/%v/channels/%v/users/%v/%v/%v", teamId, channelId, userId, info1.Id, filename) { value := fmt.Sprintf("20070204/teams/%v/channels/%v/users/%v/%v/%v", teamId, channelId, userId, info1.Id, filename)
t.Fatal("stored file at incorrect path", info1.Path) assert.Equal(t, value, info1.Path, "stored file at incorrect path" )
}
info2, err := th.App.DoUploadFile(time.Date(2007, 2, 4, 1, 2, 3, 4, time.Local), teamId, channelId, userId, filename, data) info2, err := th.App.DoUploadFile(time.Date(2007, 2, 4, 1, 2, 3, 4, time.Local), teamId, channelId, userId, filename, data)
if err != nil { require.Nil(t, err, "DoUploadFile should succeed with valid data")
t.Fatal(err) defer func() {
} else { th.App.Srv.Store.FileInfo().PermanentDelete(info2.Id)
defer func() { th.App.RemoveFile(info2.Path)
th.App.Srv.Store.FileInfo().PermanentDelete(info2.Id) }()
th.App.RemoveFile(info2.Path)
}()
}
if info2.Path != fmt.Sprintf("20070204/teams/%v/channels/%v/users/%v/%v/%v", teamId, channelId, userId, info2.Id, filename) { value = fmt.Sprintf("20070204/teams/%v/channels/%v/users/%v/%v/%v", teamId, channelId, userId, info2.Id, filename)
t.Fatal("stored file at incorrect path", info2.Path) assert.Equal(t, value, info2.Path, "stored file at incorrect path")
}
info3, err := th.App.DoUploadFile(time.Date(2008, 3, 5, 1, 2, 3, 4, time.Local), teamId, channelId, userId, filename, data) info3, err := th.App.DoUploadFile(time.Date(2008, 3, 5, 1, 2, 3, 4, time.Local), teamId, channelId, userId, filename, data)
if err != nil { require.Nil(t, err, "DoUploadFile should succeed with valid data")
t.Fatal(err) defer func() {
} else { th.App.Srv.Store.FileInfo().PermanentDelete(info3.Id)
defer func() { th.App.RemoveFile(info3.Path)
th.App.Srv.Store.FileInfo().PermanentDelete(info3.Id) }()
th.App.RemoveFile(info3.Path)
}()
}
if info3.Path != fmt.Sprintf("20080305/teams/%v/channels/%v/users/%v/%v/%v", teamId, channelId, userId, info3.Id, filename) { value = fmt.Sprintf("20080305/teams/%v/channels/%v/users/%v/%v/%v", teamId, channelId, userId, info3.Id, filename)
t.Fatal("stored file at incorrect path", info3.Path) assert.Equal(t, value, info3.Path, "stored file at incorrect path")
}
info4, err := th.App.DoUploadFile(time.Date(2009, 3, 5, 1, 2, 3, 4, time.Local), "../../"+teamId, "../../"+channelId, "../../"+userId, "../../"+filename, data) info4, err := th.App.DoUploadFile(time.Date(2009, 3, 5, 1, 2, 3, 4, time.Local), "../../"+teamId, "../../"+channelId, "../../"+userId, "../../"+filename, data)
if err != nil { require.Nil(t, err, "DoUploadFile should succeed with valid data")
t.Fatal(err) defer func() {
} else { th.App.Srv.Store.FileInfo().PermanentDelete(info4.Id)
defer func() { th.App.RemoveFile(info4.Path)
th.App.Srv.Store.FileInfo().PermanentDelete(info4.Id) }()
th.App.RemoveFile(info4.Path)
}()
}
if info4.Path != fmt.Sprintf("20090305/teams/%v/channels/%v/users/%v/%v/%v", teamId, channelId, userId, info4.Id, filename) { value = fmt.Sprintf("20090305/teams/%v/channels/%v/users/%v/%v/%v", teamId, channelId, userId, info4.Id, filename)
t.Fatal("stored file at incorrect path", info4.Path) assert.Equal(t, value, info4.Path, "stored file at incorrect path")
}
} }
func TestUploadFile(t *testing.T) { func TestUploadFile(t *testing.T) {
@@ -116,19 +95,15 @@ func TestUploadFile(t *testing.T) {
data := []byte("abcd") data := []byte("abcd")
info1, err := th.App.UploadFile(data, channelId, filename) info1, err := th.App.UploadFile(data, channelId, filename)
if err != nil { require.Nil(t, err, "UploadFile should succeed with valid data")
t.Fatal(err) defer func() {
} else { th.App.Srv.Store.FileInfo().PermanentDelete(info1.Id)
defer func() { th.App.RemoveFile(info1.Path)
th.App.Srv.Store.FileInfo().PermanentDelete(info1.Id) }()
th.App.RemoveFile(info1.Path)
}()
}
if info1.Path != fmt.Sprintf("%v/teams/noteam/channels/%v/users/nouser/%v/%v", value := fmt.Sprintf("%v/teams/noteam/channels/%v/users/nouser/%v/%v",
time.Now().Format("20060102"), channelId, info1.Id, filename) { time.Now().Format("20060102"), channelId, info1.Id, filename)
t.Fatal("stored file at incorrect path", info1.Path) assert.Equal(t, value, info1.Path, "Stored file at incorrect path")
}
} }
func TestGetInfoForFilename(t *testing.T) { func TestGetInfoForFilename(t *testing.T) {

Просмотреть файл

@@ -16,6 +16,7 @@ import (
"github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/utils"
"github.com/stretchr/testify/require"
) )
type TestHelper struct { type TestHelper struct {
@@ -499,22 +500,14 @@ func (me *TestHelper) ResetEmojisMigration() {
func (me *TestHelper) CheckTeamCount(t *testing.T, expected int64) { func (me *TestHelper) CheckTeamCount(t *testing.T, expected int64) {
teamCount, err := me.App.Srv.Store.Team().AnalyticsTeamCount() teamCount, err := me.App.Srv.Store.Team().AnalyticsTeamCount()
if err != nil { require.Nil(t, err, "Failed to get team count.")
t.Fatalf("Failed to get team count.") require.Equalf(t, teamCount, expected, "Unexpected number of teams. Expected: %v, found: %v", expected, teamCount)
}
if teamCount != expected {
t.Fatalf("Unexpected number of teams. Expected: %v, found: %v", expected, teamCount)
}
} }
func (me *TestHelper) CheckChannelsCount(t *testing.T, expected int64) { func (me *TestHelper) CheckChannelsCount(t *testing.T, expected int64) {
if count, err := me.App.Srv.Store.Channel().AnalyticsTypeCount("", model.CHANNEL_OPEN); err == nil { count, err := me.App.Srv.Store.Channel().AnalyticsTypeCount("", model.CHANNEL_OPEN)
if count != expected { require.Nilf(t, err, "Failed to get channel count.")
t.Fatalf("Unexpected number of channels. Expected: %v, found: %v", expected, count) require.Equalf(t, count, expected, "Unexpected number of channels. Expected: %v, found: %v", expected, count)
}
} else {
t.Fatalf("Failed to get channel count.")
}
} }
func (me *TestHelper) SetupTeamScheme() *model.Scheme { func (me *TestHelper) SetupTeamScheme() *model.Scheme {

Просмотреть файл

@@ -430,6 +430,7 @@ func TestSubmitInteractiveDialog(t *testing.T) {
assert.Equal(t, "value1", val) assert.Equal(t, "value1", val)
resp := model.SubmitDialogResponse{ resp := model.SubmitDialogResponse{
Error: "some generic error",
Errors: map[string]string{"name1": "some error"}, Errors: map[string]string{"name1": "some error"},
} }
@@ -444,6 +445,7 @@ func TestSubmitInteractiveDialog(t *testing.T) {
resp, err := th.App.SubmitInteractiveDialog(submit) resp, err := th.App.SubmitInteractiveDialog(submit)
assert.Nil(t, err) assert.Nil(t, err)
require.NotNil(t, resp) require.NotNil(t, resp)
assert.Equal(t, "some generic error", resp.Error)
assert.Equal(t, "some error", resp.Errors["name1"]) assert.Equal(t, "some error", resp.Errors["name1"])
submit.URL = "" submit.URL = ""

Просмотреть файл

@@ -29,7 +29,7 @@ func (a *App) LoadLicense() {
if license != nil { if license != nil {
if _, err = a.SaveLicense(licenseBytes); err != nil { if _, err = a.SaveLicense(licenseBytes); err != nil {
mlog.Info(fmt.Sprintf("Failed to save license key loaded from disk err=%v", err.Error())) mlog.Info("Failed to save license key loaded from disk.", mlog.Err(err))
} else { } else {
licenseId = license.Id licenseId = license.Id
} }

Просмотреть файл

@@ -8,6 +8,7 @@ import (
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestLoadLicense(t *testing.T) { func TestLoadLicense(t *testing.T) {
@@ -15,9 +16,7 @@ func TestLoadLicense(t *testing.T) {
defer th.TearDown() defer th.TearDown()
th.App.LoadLicense() th.App.LoadLicense()
if th.App.License() != nil { require.Nil(t, th.App.License(), "shouldn't have a valid license")
t.Fatal("shouldn't have a valid license")
}
} }
func TestSaveLicense(t *testing.T) { func TestSaveLicense(t *testing.T) {
@@ -26,18 +25,16 @@ func TestSaveLicense(t *testing.T) {
b1 := []byte("junk") b1 := []byte("junk")
if _, err := th.App.SaveLicense(b1); err == nil { _, err := th.App.SaveLicense(b1)
t.Fatal("shouldn't have saved license") require.NotNil(t, err, "shouldn't have saved license")
}
} }
func TestRemoveLicense(t *testing.T) { func TestRemoveLicense(t *testing.T) {
th := Setup(t) th := Setup(t)
defer th.TearDown() defer th.TearDown()
if err := th.App.RemoveLicense(); err != nil { err := th.App.RemoveLicense()
t.Fatal("should have removed license") require.Nil(t, err, "should have removed license")
}
} }
func TestSetLicense(t *testing.T) { func TestSetLicense(t *testing.T) {
@@ -49,18 +46,16 @@ func TestSetLicense(t *testing.T) {
l1.Customer = &model.Customer{} l1.Customer = &model.Customer{}
l1.StartsAt = model.GetMillis() - 1000 l1.StartsAt = model.GetMillis() - 1000
l1.ExpiresAt = model.GetMillis() + 100000 l1.ExpiresAt = model.GetMillis() + 100000
if ok := th.App.SetLicense(l1); !ok { ok := th.App.SetLicense(l1)
t.Fatal("license should have worked") require.True(t, ok, "license should have worked")
}
l3 := &model.License{} l3 := &model.License{}
l3.Features = &model.Features{} l3.Features = &model.Features{}
l3.Customer = &model.Customer{} l3.Customer = &model.Customer{}
l3.StartsAt = model.GetMillis() + 10000 l3.StartsAt = model.GetMillis() + 10000
l3.ExpiresAt = model.GetMillis() + 100000 l3.ExpiresAt = model.GetMillis() + 100000
if ok := th.App.SetLicense(l3); !ok { ok = th.App.SetLicense(l3)
t.Fatal("license should have passed") require.True(t, ok, "license should have passed")
}
} }
func TestClientLicenseEtag(t *testing.T) { func TestClientLicenseEtag(t *testing.T) {
@@ -72,16 +67,12 @@ func TestClientLicenseEtag(t *testing.T) {
th.App.SetClientLicense(map[string]string{"SomeFeature": "true", "IsLicensed": "true"}) th.App.SetClientLicense(map[string]string{"SomeFeature": "true", "IsLicensed": "true"})
etag2 := th.App.GetClientLicenseEtag(false) etag2 := th.App.GetClientLicenseEtag(false)
if etag1 == etag2 { require.NotEqual(t, etag1, etag2, "etags should not match")
t.Fatal("etags should not match")
}
th.App.SetClientLicense(map[string]string{"SomeFeature": "true", "IsLicensed": "false"}) th.App.SetClientLicense(map[string]string{"SomeFeature": "true", "IsLicensed": "false"})
etag3 := th.App.GetClientLicenseEtag(false) etag3 := th.App.GetClientLicenseEtag(false)
if etag2 == etag3 { require.NotEqual(t, etag2, etag3, "etags should not match")
t.Fatal("etags should not match")
}
} }
func TestGetSanitizedClientLicense(t *testing.T) { func TestGetSanitizedClientLicense(t *testing.T) {

Просмотреть файл

@@ -4,7 +4,6 @@
package app package app
import ( import (
"fmt"
"sort" "sort"
"strings" "strings"
"unicode" "unicode"
@@ -175,14 +174,14 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
// Remove the user as recipient when the user has muted the channel. // Remove the user as recipient when the user has muted the channel.
if channelMuted, ok := channelMemberNotifyPropsMap[id][model.MARK_UNREAD_NOTIFY_PROP]; ok { if channelMuted, ok := channelMemberNotifyPropsMap[id][model.MARK_UNREAD_NOTIFY_PROP]; ok {
if channelMuted == model.CHANNEL_MARK_UNREAD_MENTION { if channelMuted == model.CHANNEL_MARK_UNREAD_MENTION {
mlog.Debug(fmt.Sprintf("Channel muted for user_id %v, channel_mute %v", id, channelMuted)) mlog.Debug("Channel muted for user", mlog.String("user_id", id), mlog.String("channel_mute", channelMuted))
userAllowsEmails = false userAllowsEmails = false
} }
} }
//If email verification is required and user email is not verified don't send email. //If email verification is required and user email is not verified don't send email.
if *a.Config().EmailSettings.RequireEmailVerification && !profileMap[id].EmailVerified { if *a.Config().EmailSettings.RequireEmailVerification && !profileMap[id].EmailVerified {
mlog.Error(fmt.Sprintf("Skipped sending notification email to %v, address not verified. [details: user_id=%v]", profileMap[id].Email, id)) mlog.Error("Skipped sending notification email, address not verified.", mlog.String("user_email", profileMap[id].Email), mlog.String("user_id", id))
continue continue
} }
@@ -250,7 +249,12 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
// MUST be completed before push notifications send // MUST be completed before push notifications send
for _, umc := range updateMentionChans { for _, umc := range updateMentionChans {
if err := <-umc; err != nil { if err := <-umc; err != nil {
mlog.Warn(fmt.Sprintf("Failed to update mention count, post_id=%v channel_id=%v err=%v", post.Id, post.ChannelId, result.Err), mlog.String("post_id", post.Id)) mlog.Warn(
"Failed to update mention count",
mlog.String("post_id", post.Id),
mlog.String("channel_id", post.ChannelId),
mlog.Err(err),
)
} }
} }
@@ -352,7 +356,7 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
var infos []*model.FileInfo var infos []*model.FileInfo
if result := <-fchan; result.Err != nil { if result := <-fchan; result.Err != nil {
mlog.Warn(fmt.Sprint("Unable to get fileInfo for push notifications.", post.Id, result.Err), mlog.String("post_id", post.Id)) mlog.Warn("Unable to get fileInfo for push notifications.", mlog.String("post_id", post.Id), mlog.Err(result.Err))
} else { } else {
infos = result.Data.([]*model.FileInfo) infos = result.Data.([]*model.FileInfo)
} }

Просмотреть файл

@@ -109,8 +109,8 @@ func TestGetNotificationEmailBodyFullNotificationPublicChannel(t *testing.T) {
if !strings.Contains(body, "Channel: "+channel.DisplayName) { if !strings.Contains(body, "Channel: "+channel.DisplayName) {
t.Fatal("Expected email text 'Channel: " + channel.DisplayName + "'. Got " + body) t.Fatal("Expected email text 'Channel: " + channel.DisplayName + "'. Got " + body)
} }
if !strings.Contains(body, "@"+senderName+" - ") { if !strings.Contains(body, senderName+" - ") {
t.Fatal("Expected email text '@" + senderName + " - '. Got " + body) t.Fatal("Expected email text '" + senderName + " - '. Got " + body)
} }
if !strings.Contains(body, post.Message) { if !strings.Contains(body, post.Message) {
t.Fatal("Expected email text '" + post.Message + "'. Got " + body) t.Fatal("Expected email text '" + post.Message + "'. Got " + body)
@@ -146,8 +146,8 @@ func TestGetNotificationEmailBodyFullNotificationGroupChannel(t *testing.T) {
if !strings.Contains(body, "Channel: ChannelName") { if !strings.Contains(body, "Channel: ChannelName") {
t.Fatal("Expected email text 'Channel: ChannelName'. Got " + body) t.Fatal("Expected email text 'Channel: ChannelName'. Got " + body)
} }
if !strings.Contains(body, "@"+senderName+" - ") { if !strings.Contains(body, senderName+" - ") {
t.Fatal("Expected email text '@" + senderName + " - '. Got " + body) t.Fatal("Expected email text '" + senderName + " - '. Got " + body)
} }
if !strings.Contains(body, post.Message) { if !strings.Contains(body, post.Message) {
t.Fatal("Expected email text '" + post.Message + "'. Got " + body) t.Fatal("Expected email text '" + post.Message + "'. Got " + body)
@@ -183,8 +183,8 @@ func TestGetNotificationEmailBodyFullNotificationPrivateChannel(t *testing.T) {
if !strings.Contains(body, "Channel: "+channel.DisplayName) { if !strings.Contains(body, "Channel: "+channel.DisplayName) {
t.Fatal("Expected email text 'Channel: " + channel.DisplayName + "'. Got " + body) t.Fatal("Expected email text 'Channel: " + channel.DisplayName + "'. Got " + body)
} }
if !strings.Contains(body, "@"+senderName+" - ") { if !strings.Contains(body, senderName+" - ") {
t.Fatal("Expected email text '@" + senderName + " - '. Got " + body) t.Fatal("Expected email text '" + senderName + " - '. Got " + body)
} }
if !strings.Contains(body, post.Message) { if !strings.Contains(body, post.Message) {
t.Fatal("Expected email text '" + post.Message + "'. Got " + body) t.Fatal("Expected email text '" + post.Message + "'. Got " + body)
@@ -217,8 +217,8 @@ func TestGetNotificationEmailBodyFullNotificationDirectChannel(t *testing.T) {
if !strings.Contains(body, "You have a new Direct Message.") { if !strings.Contains(body, "You have a new Direct Message.") {
t.Fatal("Expected email text 'You have a new Direct Message. Got " + body) t.Fatal("Expected email text 'You have a new Direct Message. Got " + body)
} }
if !strings.Contains(body, "@"+senderName+" - ") { if !strings.Contains(body, senderName+" - ") {
t.Fatal("Expected email text '@" + senderName + " - '. Got " + body) t.Fatal("Expected email text '" + senderName + " - '. Got " + body)
} }
if !strings.Contains(body, post.Message) { if !strings.Contains(body, post.Message) {
t.Fatal("Expected email text '" + post.Message + "'. Got " + body) t.Fatal("Expected email text '" + post.Message + "'. Got " + body)
@@ -386,8 +386,8 @@ func TestGetNotificationEmailBodyGenericNotificationPublicChannel(t *testing.T)
translateFunc := utils.GetUserTranslations("en") translateFunc := utils.GetUserTranslations("en")
body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
if !strings.Contains(body, "You have a new notification from @"+senderName) { if !strings.Contains(body, "You have a new notification from "+senderName) {
t.Fatal("Expected email text 'You have a new notification from @" + senderName + "'. Got " + body) t.Fatal("Expected email text 'You have a new notification from " + senderName + "'. Got " + body)
} }
if strings.Contains(body, "Channel: "+channel.DisplayName) { if strings.Contains(body, "Channel: "+channel.DisplayName) {
t.Fatal("Did not expect email text 'Channel: " + channel.DisplayName + "'. Got " + body) t.Fatal("Did not expect email text 'Channel: " + channel.DisplayName + "'. Got " + body)
@@ -420,8 +420,8 @@ func TestGetNotificationEmailBodyGenericNotificationGroupChannel(t *testing.T) {
translateFunc := utils.GetUserTranslations("en") translateFunc := utils.GetUserTranslations("en")
body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
if !strings.Contains(body, "You have a new Group Message from @"+senderName) { if !strings.Contains(body, "You have a new Group Message from "+senderName) {
t.Fatal("Expected email text 'You have a new Group Message from @" + senderName + "'. Got " + body) t.Fatal("Expected email text 'You have a new Group Message from " + senderName + "'. Got " + body)
} }
if strings.Contains(body, "CHANNEL: "+channel.DisplayName) { if strings.Contains(body, "CHANNEL: "+channel.DisplayName) {
t.Fatal("Did not expect email text 'CHANNEL: " + channel.DisplayName + "'. Got " + body) t.Fatal("Did not expect email text 'CHANNEL: " + channel.DisplayName + "'. Got " + body)
@@ -454,8 +454,8 @@ func TestGetNotificationEmailBodyGenericNotificationPrivateChannel(t *testing.T)
translateFunc := utils.GetUserTranslations("en") translateFunc := utils.GetUserTranslations("en")
body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
if !strings.Contains(body, "You have a new notification from @"+senderName) { if !strings.Contains(body, "You have a new notification from "+senderName) {
t.Fatal("Expected email text 'You have a new notification from @" + senderName + "'. Got " + body) t.Fatal("Expected email text 'You have a new notification from " + senderName + "'. Got " + body)
} }
if strings.Contains(body, "CHANNEL: "+channel.DisplayName) { if strings.Contains(body, "CHANNEL: "+channel.DisplayName) {
t.Fatal("Did not expect email text 'CHANNEL: " + channel.DisplayName + "'. Got " + body) t.Fatal("Did not expect email text 'CHANNEL: " + channel.DisplayName + "'. Got " + body)
@@ -488,8 +488,8 @@ func TestGetNotificationEmailBodyGenericNotificationDirectChannel(t *testing.T)
translateFunc := utils.GetUserTranslations("en") translateFunc := utils.GetUserTranslations("en")
body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
if !strings.Contains(body, "You have a new Direct Message from @"+senderName) { if !strings.Contains(body, "You have a new Direct Message from "+senderName) {
t.Fatal("Expected email text 'You have a new Direct Message from @" + senderName + "'. Got " + body) t.Fatal("Expected email text 'You have a new Direct Message from " + senderName + "'. Got " + body)
} }
if strings.Contains(body, "CHANNEL: "+channel.DisplayName) { if strings.Contains(body, "CHANNEL: "+channel.DisplayName) {
t.Fatal("Did not expect email text 'CHANNEL: " + channel.DisplayName + "'. Got " + body) t.Fatal("Did not expect email text 'CHANNEL: " + channel.DisplayName + "'. Got " + body)

Просмотреть файл

@@ -7,12 +7,14 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"sort"
"strings" "strings"
"github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/plugin" "github.com/mattermost/mattermost-server/plugin"
"github.com/mattermost/mattermost-server/services/filesstore" "github.com/mattermost/mattermost-server/services/filesstore"
"github.com/mattermost/mattermost-server/services/marketplace"
"github.com/mattermost/mattermost-server/utils/fileutils" "github.com/mattermost/mattermost-server/utils/fileutils"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@@ -400,6 +402,103 @@ func (a *App) GetPlugins() (*model.PluginsResponse, *model.AppError) {
return resp, nil return resp, nil
} }
// GetMarketplacePlugins returns a list of plugins from the marketplace-server,
// and plugins that are installed locally.
func (a *App) GetMarketplacePlugins(filter *model.MarketplacePluginFilter) ([]*model.MarketplacePlugin, *model.AppError) {
var result []*model.MarketplacePlugin
pluginSet := map[string]bool{}
pluginsEnvironment := a.GetPluginsEnvironment()
if pluginsEnvironment == nil {
return nil, model.NewAppError("GetMarketplacePlugins", "app.plugin.config.app_error", nil, "", http.StatusInternalServerError)
}
marketplaceClient, err := marketplace.NewClient(
*a.Config().PluginSettings.MarketplaceUrl,
a.HTTPService,
)
if err != nil {
return nil, model.NewAppError("GetMarketplacePlugins", "app.plugin.marketplace_client.app_error", nil, err.Error(), http.StatusInternalServerError)
}
// Fetch all plugins from marketplace.
marketplacePlugins, err := marketplaceClient.GetPlugins(&model.MarketplacePluginFilter{
PerPage: -1,
ServerVersion: model.CurrentVersion,
})
if err != nil {
return nil, model.NewAppError("GetMarketplacePlugins", "app.plugin.marketplace_plugins.app_error", nil, err.Error(), http.StatusInternalServerError)
}
for _, p := range marketplacePlugins {
if p.Manifest == nil || !pluginMatchesFilter(p.Manifest, filter.Filter) {
continue
}
marketplacePlugin := &model.MarketplacePlugin{
BaseMarketplacePlugin: p,
}
var manifest *model.Manifest
if manifest, err = pluginsEnvironment.GetManifest(p.Manifest.Id); err != nil && err != plugin.ErrNotFound {
return nil, model.NewAppError("GetMarketplacePlugins", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError)
} else if err == nil {
// Plugin is installed.
marketplacePlugin.InstalledVersion = manifest.Version
}
pluginSet[p.Manifest.Id] = true
result = append(result, marketplacePlugin)
}
// Include all other installed plugins.
plugins, err := pluginsEnvironment.Available()
if err != nil {
return nil, model.NewAppError("GetMarketplacePlugins", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError)
}
for _, plugin := range plugins {
if plugin.Manifest == nil || pluginSet[plugin.Manifest.Id] || !pluginMatchesFilter(plugin.Manifest, filter.Filter) {
continue
}
result = append(result, &model.MarketplacePlugin{
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
Manifest: plugin.Manifest,
},
InstalledVersion: plugin.Manifest.Version,
})
}
// Sort result alphabetically.
sort.SliceStable(result, func(i, j int) bool {
return strings.ToLower(result[i].Manifest.Name) < strings.ToLower(result[j].Manifest.Name)
})
return result, nil
}
func pluginMatchesFilter(manifest *model.Manifest, filter string) bool {
filter = strings.TrimSpace(strings.ToLower(filter))
if filter == "" {
return true
}
if strings.ToLower(manifest.Id) == filter {
return true
}
if strings.Contains(strings.ToLower(manifest.Name), filter) {
return true
}
if strings.Contains(strings.ToLower(manifest.Description), filter) {
return true
}
return false
}
// notifyPluginEnabled notifies connected websocket clients across all peers if the version of the given // notifyPluginEnabled notifies connected websocket clients across all peers if the version of the given
// plugin is same across them. // plugin is same across them.
// //

Просмотреть файл

@@ -474,19 +474,19 @@ func (api *PluginAPI) GetPost(postId string) (*model.Post, *model.AppError) {
} }
func (api *PluginAPI) GetPostsSince(channelId string, time int64) (*model.PostList, *model.AppError) { func (api *PluginAPI) GetPostsSince(channelId string, time int64) (*model.PostList, *model.AppError) {
return api.app.GetPostsSince(channelId, time) return api.app.GetPostsSince(model.GetPostsSinceOptions{ChannelId: channelId, Time: time})
} }
func (api *PluginAPI) GetPostsAfter(channelId, postId string, page, perPage int) (*model.PostList, *model.AppError) { func (api *PluginAPI) GetPostsAfter(channelId, postId string, page, perPage int) (*model.PostList, *model.AppError) {
return api.app.GetPostsAfterPost(channelId, postId, page, perPage) return api.app.GetPostsAfterPost(model.GetPostsOptions{ChannelId: channelId, PostId: postId, Page: page, PerPage: perPage})
} }
func (api *PluginAPI) GetPostsBefore(channelId, postId string, page, perPage int) (*model.PostList, *model.AppError) { func (api *PluginAPI) GetPostsBefore(channelId, postId string, page, perPage int) (*model.PostList, *model.AppError) {
return api.app.GetPostsBeforePost(channelId, postId, page, perPage) return api.app.GetPostsBeforePost(model.GetPostsOptions{ChannelId: channelId, PostId: postId, Page: page, PerPage: perPage})
} }
func (api *PluginAPI) GetPostsForChannel(channelId string, page, perPage int) (*model.PostList, *model.AppError) { func (api *PluginAPI) GetPostsForChannel(channelId string, page, perPage int) (*model.PostList, *model.AppError) {
return api.app.GetPostsPage(channelId, page, perPage) return api.app.GetPostsPage(model.GetPostsOptions{ChannelId: channelId, Page: perPage, PerPage: page})
} }
func (api *PluginAPI) UpdatePost(post *model.Post) (*model.Post, *model.AppError) { func (api *PluginAPI) UpdatePost(post *model.Post) (*model.Post, *model.AppError) {

Просмотреть файл

@@ -4,6 +4,7 @@
package app package app
import ( import (
"github.com/stretchr/testify/require"
"os" "os"
"strings" "strings"
"testing" "testing"
@@ -94,7 +95,7 @@ func TestPluginDeadlock(t *testing.T) {
select { select {
case <-done: case <-done:
case <-time.After(30 * time.Second): case <-time.After(30 * time.Second):
t.Fatal("plugin failed to activate: likely deadlocked") require.Fail(t, "plugin failed to activate: likely deadlocked")
go func() { go func() {
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
os.Exit(1) os.Exit(1)
@@ -201,7 +202,7 @@ func TestPluginDeadlock(t *testing.T) {
select { select {
case <-done: case <-done:
case <-time.After(30 * time.Second): case <-time.After(30 * time.Second):
t.Fatal("plugin failed to activate: likely deadlocked") require.Fail(t, "plugin failed to activate: likely deadlocked")
go func() { go func() {
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
os.Exit(1) os.Exit(1)

Просмотреть файл

@@ -75,7 +75,12 @@ func (a *App) CreatePostAsUser(post *model.Post, currentSessionId string) (*mode
// Update the LastViewAt only if the post does not have from_webhook prop set (eg. Zapier app) // Update the LastViewAt only if the post does not have from_webhook prop set (eg. Zapier app)
if _, ok := post.Props["from_webhook"]; !ok { if _, ok := post.Props["from_webhook"]; !ok {
if _, err := a.MarkChannelsAsViewed([]string{post.ChannelId}, post.UserId, currentSessionId); err != nil { if _, err := a.MarkChannelsAsViewed([]string{post.ChannelId}, post.UserId, currentSessionId); err != nil {
mlog.Error(fmt.Sprintf("Encountered error updating last viewed, channel_id=%s, user_id=%s, err=%v", post.ChannelId, post.UserId, err)) mlog.Error(
"Encountered error updating last viewed",
mlog.String("channel_id", post.ChannelId),
mlog.String("user_id", post.UserId),
mlog.Err(err),
)
} }
} }
@@ -162,7 +167,7 @@ func (a *App) CreatePost(post *model.Post, channel *model.Channel, triggerWebhoo
if len(post.RootId) > 0 { if len(post.RootId) > 0 {
pchan = make(chan store.StoreResult, 1) pchan = make(chan store.StoreResult, 1)
go func() { go func() {
r, pErr := a.Srv.Store.Post().Get(post.RootId) r, pErr := a.Srv.Store.Post().Get(post.RootId, true)
pchan <- store.StoreResult{Data: r, Err: pErr} pchan <- store.StoreResult{Data: r, Err: pErr}
close(pchan) close(pchan)
}() }()
@@ -228,7 +233,7 @@ func (a *App) CreatePost(post *model.Post, channel *model.Channel, triggerWebhoo
post.Props["attachments"] = attachmentsInterface post.Props["attachments"] = attachmentsInterface
} }
if err != nil { if err != nil {
mlog.Error("Could not convert post attachments to map interface, err=%s" + err.Error()) mlog.Error("Could not convert post attachments to map interface.", mlog.Err(err))
} }
} }
@@ -470,7 +475,7 @@ func (a *App) DeleteEphemeralPost(userId, postId string) {
func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model.AppError) { func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model.AppError) {
post.SanitizeProps() post.SanitizeProps()
postLists, err := a.Srv.Store.Post().Get(post.Id) postLists, err := a.Srv.Store.Post().Get(post.Id, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -563,7 +568,7 @@ func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model
a.Srv.Go(func() { a.Srv.Go(func() {
channel, chanErr := a.Srv.Store.Channel().GetForPost(rpost.Id) channel, chanErr := a.Srv.Store.Channel().GetForPost(rpost.Id)
if chanErr != nil { if chanErr != nil {
mlog.Error(fmt.Sprintf("Couldn't get channel %v for post %v for Elasticsearch indexing.", rpost.ChannelId, rpost.Id)) mlog.Error("Couldn't get channel for post for Elasticsearch indexing.", mlog.String("channel_id", rpost.ChannelId), mlog.String("post_id", rpost.Id))
return return
} }
if err := a.Elasticsearch.IndexPost(rpost, channel.TeamId); err != nil { if err := a.Elasticsearch.IndexPost(rpost, channel.TeamId); err != nil {
@@ -609,20 +614,20 @@ func (a *App) PatchPost(postId string, patch *model.PostPatch) (*model.Post, *mo
return updatedPost, nil return updatedPost, nil
} }
func (a *App) GetPostsPage(channelId string, page int, perPage int) (*model.PostList, *model.AppError) { func (a *App) GetPostsPage(options model.GetPostsOptions) (*model.PostList, *model.AppError) {
return a.Srv.Store.Post().GetPosts(channelId, page*perPage, perPage, true) return a.Srv.Store.Post().GetPosts(options, false)
} }
func (a *App) GetPosts(channelId string, offset int, limit int) (*model.PostList, *model.AppError) { func (a *App) GetPosts(channelId string, offset int, limit int) (*model.PostList, *model.AppError) {
return a.Srv.Store.Post().GetPosts(channelId, offset, limit, true) return a.Srv.Store.Post().GetPosts(model.GetPostsOptions{ChannelId: channelId, Page: offset, PerPage: limit}, true)
} }
func (a *App) GetPostsEtag(channelId string) string { func (a *App) GetPostsEtag(channelId string) string {
return a.Srv.Store.Post().GetEtag(channelId, true) return a.Srv.Store.Post().GetEtag(channelId, true)
} }
func (a *App) GetPostsSince(channelId string, time int64) (*model.PostList, *model.AppError) { func (a *App) GetPostsSince(options model.GetPostsSinceOptions) (*model.PostList, *model.AppError) {
return a.Srv.Store.Post().GetPostsSince(channelId, time, true) return a.Srv.Store.Post().GetPostsSince(options, true)
} }
func (a *App) GetSinglePost(postId string) (*model.Post, *model.AppError) { func (a *App) GetSinglePost(postId string) (*model.Post, *model.AppError) {
@@ -630,7 +635,7 @@ func (a *App) GetSinglePost(postId string) (*model.Post, *model.AppError) {
} }
func (a *App) GetPostThread(postId string) (*model.PostList, *model.AppError) { func (a *App) GetPostThread(postId string) (*model.PostList, *model.AppError) {
return a.Srv.Store.Post().Get(postId) return a.Srv.Store.Post().Get(postId, false)
} }
func (a *App) GetFlaggedPosts(userId string, offset int, limit int) (*model.PostList, *model.AppError) { func (a *App) GetFlaggedPosts(userId string, offset int, limit int) (*model.PostList, *model.AppError) {
@@ -646,7 +651,7 @@ func (a *App) GetFlaggedPostsForChannel(userId, channelId string, offset int, li
} }
func (a *App) GetPermalinkPost(postId string, userId string) (*model.PostList, *model.AppError) { func (a *App) GetPermalinkPost(postId string, userId string) (*model.PostList, *model.AppError) {
list, err := a.Srv.Store.Post().Get(postId) list, err := a.Srv.Store.Post().Get(postId, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -668,19 +673,19 @@ func (a *App) GetPermalinkPost(postId string, userId string) (*model.PostList, *
return list, nil return list, nil
} }
func (a *App) GetPostsBeforePost(channelId, postId string, page, perPage int) (*model.PostList, *model.AppError) { func (a *App) GetPostsBeforePost(options model.GetPostsOptions) (*model.PostList, *model.AppError) {
return a.Srv.Store.Post().GetPostsBefore(channelId, postId, perPage, page*perPage) return a.Srv.Store.Post().GetPostsBefore(options)
} }
func (a *App) GetPostsAfterPost(channelId, postId string, page, perPage int) (*model.PostList, *model.AppError) { func (a *App) GetPostsAfterPost(options model.GetPostsOptions) (*model.PostList, *model.AppError) {
return a.Srv.Store.Post().GetPostsAfter(channelId, postId, perPage, page*perPage) return a.Srv.Store.Post().GetPostsAfter(options)
} }
func (a *App) GetPostsAroundPost(postId, channelId string, offset, limit int, before bool) (*model.PostList, *model.AppError) { func (a *App) GetPostsAroundPost(before bool, options model.GetPostsOptions) (*model.PostList, *model.AppError) {
if before { if before {
return a.Srv.Store.Post().GetPostsBefore(channelId, postId, limit, offset) return a.Srv.Store.Post().GetPostsBefore(options)
} }
return a.Srv.Store.Post().GetPostsAfter(channelId, postId, limit, offset) return a.Srv.Store.Post().GetPostsAfter(options)
} }
func (a *App) GetPostAfterTime(channelId string, time int64) (*model.Post, *model.AppError) { func (a *App) GetPostAfterTime(channelId string, time int64) (*model.Post, *model.AppError) {
@@ -768,8 +773,7 @@ func (a *App) AddCursorIdsForPostList(originalList *model.PostList, afterPost, b
originalList.NextPostId = nextPostId originalList.NextPostId = nextPostId
originalList.PrevPostId = prevPostId originalList.PrevPostId = prevPostId
} }
func (a *App) GetPostsForChannelAroundLastUnread(channelId, userId string, limitBefore, limitAfter int, skipFetchThreads bool) (*model.PostList, *model.AppError) {
func (a *App) GetPostsForChannelAroundLastUnread(channelId, userId string, limitBefore, limitAfter int) (*model.PostList, *model.AppError) {
var member *model.ChannelMember var member *model.ChannelMember
var err *model.AppError var err *model.AppError
if member, err = a.GetChannelMember(channelId, userId); err != nil { if member, err = a.GetChannelMember(channelId, userId); err != nil {
@@ -793,13 +797,13 @@ func (a *App) GetPostsForChannelAroundLastUnread(channelId, userId string, limit
// channel organically, those replies will be added below. // channel organically, those replies will be added below.
postList.Order = []string{lastUnreadPostId} postList.Order = []string{lastUnreadPostId}
if postListBefore, err := a.GetPostsBeforePost(channelId, lastUnreadPostId, PAGE_DEFAULT, limitBefore); err != nil { if postListBefore, err := a.GetPostsBeforePost(model.GetPostsOptions{ChannelId: channelId, PostId: lastUnreadPostId, Page: PAGE_DEFAULT, PerPage: limitBefore, SkipFetchThreads: skipFetchThreads}); err != nil {
return nil, err return nil, err
} else if postListBefore != nil { } else if postListBefore != nil {
postList.Extend(postListBefore) postList.Extend(postListBefore)
} }
if postListAfter, err := a.GetPostsAfterPost(channelId, lastUnreadPostId, PAGE_DEFAULT, limitAfter-1); err != nil { if postListAfter, err := a.GetPostsAfterPost(model.GetPostsOptions{ChannelId: channelId, PostId: lastUnreadPostId, Page: PAGE_DEFAULT, PerPage: limitAfter - 1, SkipFetchThreads: skipFetchThreads}); err != nil {
return nil, err return nil, err
} else if postListAfter != nil { } else if postListAfter != nil {
postList.Extend(postListAfter) postList.Extend(postListAfter)
@@ -856,7 +860,7 @@ func (a *App) DeletePost(postId, deleteByID string) (*model.Post, *model.AppErro
func (a *App) DeleteFlaggedPosts(postId string) { func (a *App) DeleteFlaggedPosts(postId string) {
if err := a.Srv.Store.Preference().DeleteCategoryAndName(model.PREFERENCE_CATEGORY_FLAGGED_POST, postId); err != nil { if err := a.Srv.Store.Preference().DeleteCategoryAndName(model.PREFERENCE_CATEGORY_FLAGGED_POST, postId); err != nil {
mlog.Warn(fmt.Sprintf("Unable to delete flagged post preference when deleting post, err=%v", err)) mlog.Warn("Unable to delete flagged post preference when deleting post.", mlog.Err(err))
return return
} }
} }
@@ -867,7 +871,7 @@ func (a *App) DeletePostFiles(post *model.Post) {
} }
if _, err := a.Srv.Store.FileInfo().DeleteForPost(post.Id); err != nil { if _, err := a.Srv.Store.FileInfo().DeleteForPost(post.Id); err != nil {
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)) mlog.Warn("Encountered error when deleting files for post", mlog.String("post_id", post.Id), mlog.Err(err))
} }
} }
@@ -949,7 +953,7 @@ func (a *App) convertChannelNamesToChannelIds(channels []string, userId string,
for idx, channelName := range channels { for idx, channelName := range channels {
channel, err := a.parseAndFetchChannelIdByNameFromInFilter(channelName, userId, teamId, includeDeletedChannels) channel, err := a.parseAndFetchChannelIdByNameFromInFilter(channelName, userId, teamId, includeDeletedChannels)
if err != nil { if err != nil {
mlog.Error(fmt.Sprint(err)) mlog.Error("error getting channel id by name from in filter", mlog.Err(err))
continue continue
} }
channels[idx] = channel.Id channels[idx] = channel.Id
@@ -960,7 +964,7 @@ func (a *App) convertChannelNamesToChannelIds(channels []string, userId string,
func (a *App) convertUserNameToUserIds(usernames []string) []string { func (a *App) convertUserNameToUserIds(usernames []string) []string {
for idx, username := range usernames { for idx, username := range usernames {
if user, err := a.GetUserByUsername(username); err != nil { if user, err := a.GetUserByUsername(username); err != nil {
mlog.Error(fmt.Sprint(err)) mlog.Error("error getting user by username", mlog.String("user_name", username), mlog.Err(err))
} else { } else {
usernames[idx] = user.Id usernames[idx] = user.Id
} }
@@ -1005,7 +1009,7 @@ func (a *App) esSearchPostsInTeamForUser(paramsList []*model.SearchParams, userI
// We only allow the user to search in channels they are a member of. // We only allow the user to search in channels they are a member of.
userChannels, err := a.GetChannelsForUser(teamId, userId, includeDeleted) userChannels, err := a.GetChannelsForUser(teamId, userId, includeDeleted)
if err != nil { if err != nil {
mlog.Error(fmt.Sprint(err)) mlog.Error("error getting channel for user", mlog.Err(err))
return nil, err return nil, err
} }
@@ -1062,7 +1066,7 @@ func (a *App) SearchPostsInTeamForUser(terms string, userId string, teamId strin
if strings.HasPrefix(channelName, "@") { if strings.HasPrefix(channelName, "@") {
channel, err := a.parseAndFetchChannelIdByNameFromInFilter(channelName, userId, teamId, includeDeletedChannels) channel, err := a.parseAndFetchChannelIdByNameFromInFilter(channelName, userId, teamId, includeDeletedChannels)
if err != nil { if err != nil {
mlog.Error(fmt.Sprint(err)) mlog.Error("error getting channel_id by name from in filter", mlog.Err(err))
continue continue
} }
params.InChannels[idx] = channel.Name params.InChannels[idx] = channel.Name
@@ -1072,7 +1076,7 @@ func (a *App) SearchPostsInTeamForUser(terms string, userId string, teamId strin
if strings.HasPrefix(channelName, "@") { if strings.HasPrefix(channelName, "@") {
channel, err := a.parseAndFetchChannelIdByNameFromInFilter(channelName, userId, teamId, includeDeletedChannels) channel, err := a.parseAndFetchChannelIdByNameFromInFilter(channelName, userId, teamId, includeDeletedChannels)
if err != nil { if err != nil {
mlog.Error(fmt.Sprint(err)) mlog.Error("error getting channel_id by name from in filter", mlog.Err(err))
continue continue
} }
params.ExcludedChannels[idx] = channel.Name params.ExcludedChannels[idx] = channel.Name

Просмотреть файл

@@ -544,7 +544,7 @@ func TestGetEmbedForPost(t *testing.T) {
</head> </head>
</html>`)) </html>`))
} else { } else {
t.Fatal("Invalid path", r.URL.Path) require.Fail(t, "Invalid path", r.URL.Path)
} }
})) }))
defer server.Close() defer server.Close()

Просмотреть файл

@@ -4,7 +4,6 @@
package app package app
import ( import (
"fmt"
"math" "math"
"net/http" "net/http"
"strconv" "strconv"
@@ -77,14 +76,14 @@ func (rl *RateLimiter) GenerateKey(r *http.Request) string {
func (rl *RateLimiter) RateLimitWriter(key string, w http.ResponseWriter) bool { func (rl *RateLimiter) RateLimitWriter(key string, w http.ResponseWriter) bool {
limited, context, err := rl.throttledRateLimiter.RateLimit(key, 1) limited, context, err := rl.throttledRateLimiter.RateLimit(key, 1)
if err != nil { if err != nil {
mlog.Critical("Internal server error when rate limiting. Rate Limiting broken. Error:" + err.Error()) mlog.Critical("Internal server error when rate limiting. Rate Limiting broken.", mlog.Err(err))
return false return false
} }
setRateLimitHeaders(w, context) setRateLimitHeaders(w, context)
if limited { if limited {
mlog.Error(fmt.Sprintf("Denied due to throttling settings code=429 key=%v", key)) mlog.Error("Denied due to throttling settings code=429", mlog.String("key", key))
http.Error(w, "limit exceeded", 429) http.Error(w, "limit exceeded", 429)
} }

Просмотреть файл

@@ -4,7 +4,6 @@
package app package app
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
@@ -114,7 +113,7 @@ func (s *Server) DoSecurityUpdateCheck() {
} }
for _, user := range users { for _, user := range users {
mlog.Info(fmt.Sprintf("Sending security bulletin for %v to %v", bulletin.Id, user.Email)) mlog.Info("Sending security bulletin", mlog.String("bulletin_id", bulletin.Id), mlog.String("user_email", user.Email))
license := s.License() license := s.License()
mailservice.SendMailUsingConfig(user.Email, utils.T("mattermost.bulletin.subject"), string(body), s.Config(), license != nil && *license.Features.Compliance) mailservice.SendMailUsingConfig(user.Email, utils.T("mattermost.bulletin.subject"), string(body), s.Config(), license != nil && *license.Features.Compliance)
} }

Просмотреть файл

@@ -7,7 +7,6 @@ import (
"archive/zip" "archive/zip"
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
@@ -168,7 +167,7 @@ func (a *App) SlackAddUsers(teamId string, slackusers []SlackUser, importerLog *
if email == "" { if email == "" {
email = sUser.Username + "@example.com" email = sUser.Username + "@example.com"
importerLog.WriteString(utils.T("api.slackimport.slack_add_users.missing_email_address", map[string]interface{}{"Email": email, "Username": sUser.Username})) importerLog.WriteString(utils.T("api.slackimport.slack_add_users.missing_email_address", map[string]interface{}{"Email": email, "Username": sUser.Username}))
mlog.Warn(fmt.Sprintf("Slack Import: User %v does not have an email address in the Slack export. Used %v as a placeholder. The user should update their email address once logged in to the system.", email, sUser.Username)) mlog.Warn("Slack Import: User does not have an email address in the Slack export. Used username as a placeholder. The user should update their email address once logged in to the system.", mlog.String("user_email", email), mlog.String("user_name", sUser.Username))
} }
password := model.NewId() password := model.NewId()
@@ -246,7 +245,7 @@ func (a *App) SlackAddPosts(teamId string, channel *model.Channel, posts []Slack
continue continue
} }
if users[sPost.User] == nil { if users[sPost.User] == nil {
mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) mlog.Debug("Slack Import: Unable to add the message as the Slack user does not exist in Mattermost.", mlog.String("user", sPost.User))
continue continue
} }
newPost := model.Post{ newPost := model.Post{
@@ -288,7 +287,7 @@ func (a *App) SlackAddPosts(teamId string, channel *model.Channel, posts []Slack
continue continue
} }
if users[sPost.Comment.User] == nil { if users[sPost.Comment.User] == nil {
mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) mlog.Debug("Slack Import: Unable to add the message as the Slack user does not exist in Mattermost.", mlog.String("user", sPost.User))
continue continue
} }
newPost := model.Post{ newPost := model.Post{
@@ -333,7 +332,7 @@ func (a *App) SlackAddPosts(teamId string, channel *model.Channel, posts []Slack
continue continue
} }
if users[sPost.User] == nil { if users[sPost.User] == nil {
mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) mlog.Debug("Slack Import: Unable to add the message as the Slack user does not exist in Mattermost.", mlog.String("user", sPost.User))
continue continue
} }
@@ -361,7 +360,7 @@ func (a *App) SlackAddPosts(teamId string, channel *model.Channel, posts []Slack
continue continue
} }
if users[sPost.User] == nil { if users[sPost.User] == nil {
mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) mlog.Debug("Slack Import: Unable to add the message as the Slack user does not exist in Mattermost.", mlog.String("user", sPost.User))
continue continue
} }
newPost := model.Post{ newPost := model.Post{
@@ -381,7 +380,7 @@ func (a *App) SlackAddPosts(teamId string, channel *model.Channel, posts []Slack
continue continue
} }
if users[sPost.User] == nil { if users[sPost.User] == nil {
mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) mlog.Debug("Slack Import: Unable to add the message as the Slack user does not exist in Mattermost.", mlog.String("user", sPost.User))
continue continue
} }
newPost := model.Post{ newPost := model.Post{
@@ -398,7 +397,7 @@ func (a *App) SlackAddPosts(teamId string, channel *model.Channel, posts []Slack
continue continue
} }
if users[sPost.User] == nil { if users[sPost.User] == nil {
mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) mlog.Debug("Slack Import: Unable to add the message as the Slack user does not exist in Mattermost.", mlog.String("user", sPost.User))
continue continue
} }
newPost := model.Post{ newPost := model.Post{
@@ -415,7 +414,7 @@ func (a *App) SlackAddPosts(teamId string, channel *model.Channel, posts []Slack
continue continue
} }
if users[sPost.User] == nil { if users[sPost.User] == nil {
mlog.Debug(fmt.Sprintf("Slack Import: Unable to add the message as the Slack user %v does not exist in Mattermost.", sPost.User)) mlog.Debug("Slack Import: Unable to add the message as the Slack user does not exist in Mattermost.", mlog.String("user", sPost.User))
continue continue
} }
newPost := model.Post{ newPost := model.Post{
@@ -427,7 +426,11 @@ func (a *App) SlackAddPosts(teamId string, channel *model.Channel, posts []Slack
} }
a.OldImportPost(&newPost) a.OldImportPost(&newPost)
default: default:
mlog.Warn(fmt.Sprintf("Slack Import: Unable to import the message as its type is not supported: post_type=%v, post_subtype=%v.", sPost.Type, sPost.SubType)) mlog.Warn(
"Slack Import: Unable to import the message as its type is not supported",
mlog.String("post_type", sPost.Type),
mlog.String("post_subtype", sPost.SubType),
)
} }
} }
} }
@@ -439,12 +442,12 @@ func (a *App) SlackUploadFile(slackPostFile *SlackFile, uploads map[string]*zip.
} }
file, ok := uploads[slackPostFile.Id] file, ok := uploads[slackPostFile.Id]
if !ok { if !ok {
mlog.Warn(fmt.Sprintf("Slack Import: Unable to import file %v as the file is missing from the Slack export zip file.", slackPostFile.Id)) mlog.Warn("Slack Import: Unable to import file as the file is missing from the Slack export zip file.", mlog.String("file_id", slackPostFile.Id))
return nil, false return nil, false
} }
openFile, err := file.Open() openFile, err := file.Open()
if err != nil { if err != nil {
mlog.Warn(fmt.Sprintf("Slack Import: Unable to open the file %v from the Slack export: %v.", slackPostFile.Id, err.Error())) mlog.Warn("Slack Import: Unable to open the file from the Slack export.", mlog.String("file_id", slackPostFile.Id), mlog.Err(err))
return nil, false return nil, false
} }
defer openFile.Close() defer openFile.Close()
@@ -452,7 +455,7 @@ func (a *App) SlackUploadFile(slackPostFile *SlackFile, uploads map[string]*zip.
timestamp := utils.TimeFromMillis(SlackConvertTimeStamp(slackTimestamp)) timestamp := utils.TimeFromMillis(SlackConvertTimeStamp(slackTimestamp))
uploadedFile, err := a.OldImportFile(timestamp, openFile, teamId, channelId, userId, filepath.Base(file.Name)) uploadedFile, err := a.OldImportFile(timestamp, openFile, teamId, channelId, userId, filepath.Base(file.Name))
if err != nil { if err != nil {
mlog.Warn(fmt.Sprintf("Slack Import: An error occurred when uploading file %v: %v.", slackPostFile.Id, err.Error())) mlog.Warn("Slack Import: An error occurred when uploading file.", mlog.String("file_id", slackPostFile.Id), mlog.Err(err))
return nil, false return nil, false
} }
@@ -480,22 +483,22 @@ func (a *App) addSlackUsersToChannel(members []string, users map[string]*model.U
func SlackSanitiseChannelProperties(channel model.Channel) model.Channel { func SlackSanitiseChannelProperties(channel model.Channel) model.Channel {
if utf8.RuneCountInString(channel.DisplayName) > model.CHANNEL_DISPLAY_NAME_MAX_RUNES { if utf8.RuneCountInString(channel.DisplayName) > model.CHANNEL_DISPLAY_NAME_MAX_RUNES {
mlog.Warn(fmt.Sprintf("Slack Import: Channel %v display name exceeds the maximum length. It will be truncated when imported.", channel.DisplayName)) mlog.Warn("Slack Import: Channel display name exceeds the maximum length. It will be truncated when imported.", mlog.String("channel_display_name", channel.DisplayName))
channel.DisplayName = truncateRunes(channel.DisplayName, model.CHANNEL_DISPLAY_NAME_MAX_RUNES) channel.DisplayName = truncateRunes(channel.DisplayName, model.CHANNEL_DISPLAY_NAME_MAX_RUNES)
} }
if len(channel.Name) > model.CHANNEL_NAME_MAX_LENGTH { if len(channel.Name) > model.CHANNEL_NAME_MAX_LENGTH {
mlog.Warn(fmt.Sprintf("Slack Import: Channel %v handle exceeds the maximum length. It will be truncated when imported.", channel.DisplayName)) mlog.Warn("Slack Import: Channel handle exceeds the maximum length. It will be truncated when imported.", mlog.String("channel_display_name", channel.DisplayName))
channel.Name = channel.Name[0:model.CHANNEL_NAME_MAX_LENGTH] channel.Name = channel.Name[0:model.CHANNEL_NAME_MAX_LENGTH]
} }
if utf8.RuneCountInString(channel.Purpose) > model.CHANNEL_PURPOSE_MAX_RUNES { if utf8.RuneCountInString(channel.Purpose) > model.CHANNEL_PURPOSE_MAX_RUNES {
mlog.Warn(fmt.Sprintf("Slack Import: Channel %v purpose exceeds the maximum length. It will be truncated when imported.", channel.DisplayName)) mlog.Warn("Slack Import: Channel purpose exceeds the maximum length. It will be truncated when imported.", mlog.String("channel_display_name", channel.DisplayName))
channel.Purpose = truncateRunes(channel.Purpose, model.CHANNEL_PURPOSE_MAX_RUNES) channel.Purpose = truncateRunes(channel.Purpose, model.CHANNEL_PURPOSE_MAX_RUNES)
} }
if utf8.RuneCountInString(channel.Header) > model.CHANNEL_HEADER_MAX_RUNES { if utf8.RuneCountInString(channel.Header) > model.CHANNEL_HEADER_MAX_RUNES {
mlog.Warn(fmt.Sprintf("Slack Import: Channel %v header exceeds the maximum length. It will be truncated when imported.", channel.DisplayName)) mlog.Warn("Slack Import: Channel header exceeds the maximum length. It will be truncated when imported.", mlog.String("channel_display_name", channel.DisplayName))
channel.Header = truncateRunes(channel.Header, model.CHANNEL_HEADER_MAX_RUNES) channel.Header = truncateRunes(channel.Header, model.CHANNEL_HEADER_MAX_RUNES)
} }
@@ -540,7 +543,7 @@ func (a *App) SlackAddChannels(teamId string, slackchannels []SlackChannel, post
// Haven't found an existing channel to merge with. Try importing it as a new one. // Haven't found an existing channel to merge with. Try importing it as a new one.
mChannel = a.OldImportChannel(&newChannel, sChannel, users) mChannel = a.OldImportChannel(&newChannel, sChannel, users)
if mChannel == nil { if mChannel == nil {
mlog.Warn(fmt.Sprintf("Slack Import: Unable to import Slack channel: %s.", newChannel.DisplayName)) mlog.Warn("Slack Import: Unable to import Slack channel.", mlog.String("channel_display_name", newChannel.DisplayName))
importerLog.WriteString(utils.T("api.slackimport.slack_add_channels.import_failed", map[string]interface{}{"DisplayName": newChannel.DisplayName})) importerLog.WriteString(utils.T("api.slackimport.slack_add_channels.import_failed", map[string]interface{}{"DisplayName": newChannel.DisplayName}))
continue continue
} }
@@ -563,7 +566,7 @@ func SlackConvertUserMentions(users []SlackUser, posts map[string][]SlackPost) m
for _, user := range users { for _, user := range users {
r, err := regexp.Compile("<@" + user.Id + `(\|` + user.Username + ")?>") r, err := regexp.Compile("<@" + user.Id + `(\|` + user.Username + ")?>")
if err != nil { if err != nil {
mlog.Warn(fmt.Sprintf("Slack Import: Unable to compile the @mention, matching regular expression for the Slack user %v (id=%v).", user.Id, user.Username), mlog.String("user_id", user.Id)) mlog.Warn("Slack Import: Unable to compile the @mention, matching regular expression for the Slack user.", mlog.String("user_name", user.Username), mlog.String("user_id", user.Id))
continue continue
} }
regexes["@"+user.Username] = r regexes["@"+user.Username] = r
@@ -591,7 +594,7 @@ func SlackConvertChannelMentions(channels []SlackChannel, posts map[string][]Sla
for _, channel := range channels { for _, channel := range channels {
r, err := regexp.Compile("<#" + channel.Id + `(\|` + channel.Name + ")?>") r, err := regexp.Compile("<#" + channel.Id + `(\|` + channel.Name + ")?>")
if err != nil { if err != nil {
mlog.Warn(fmt.Sprintf("Slack Import: Unable to compile the !channel, matching regular expression for the Slack channel %v (id=%v).", channel.Id, channel.Name)) mlog.Warn("Slack Import: Unable to compile the !channel, matching regular expression for the Slack channel.", mlog.String("channel_id", channel.Id), mlog.String("channel_name", channel.Name))
continue continue
} }
regexes["~"+channel.Name] = r regexes["~"+channel.Name] = r
@@ -783,7 +786,7 @@ func (a *App) OldImportPost(post *model.Post) string {
_, err := a.Srv.Store.Post().Save(post) _, err := a.Srv.Store.Post().Save(post)
if err != nil { if err != nil {
mlog.Debug(fmt.Sprintf("Error saving post. user=%v, message=%v", post.UserId, post.Message)) mlog.Debug("Error saving post.", mlog.String("user_id", post.UserId), mlog.String("message", post.Message))
} }
if firstIteration { if firstIteration {
@@ -792,7 +795,13 @@ func (a *App) OldImportPost(post *model.Post) string {
} }
for _, fileId := range post.FileIds { for _, fileId := range post.FileIds {
if err := a.Srv.Store.FileInfo().AttachToPost(fileId, post.Id, post.UserId); err != nil { if err := a.Srv.Store.FileInfo().AttachToPost(fileId, post.Id, post.UserId); err != nil {
mlog.Error(fmt.Sprintf("Error attaching files to post. postId=%v, fileIds=%v, message=%v", post.Id, post.FileIds, err), mlog.String("post_id", post.Id)) mlog.Error(
"Error attaching files to post.",
mlog.String("post_id", post.Id),
mlog.String("file_ids", strings.Join(post.FileIds, ",")),
mlog.String("user_id", post.UserId),
mlog.Err(err),
)
} }
} }
post.FileIds = nil post.FileIds = nil
@@ -813,16 +822,16 @@ func (a *App) OldImportUser(team *model.Team, user *model.User) *model.User {
ruser, err := a.Srv.Store.User().Save(user) ruser, err := a.Srv.Store.User().Save(user)
if err != nil { if err != nil {
mlog.Error(fmt.Sprintf("Error saving user. err=%v", err)) mlog.Error("Error saving user.", mlog.Err(err))
return nil return nil
} }
if _, err = a.Srv.Store.User().VerifyEmail(ruser.Id, ruser.Email); err != nil { if _, err = a.Srv.Store.User().VerifyEmail(ruser.Id, ruser.Email); err != nil {
mlog.Error(fmt.Sprintf("Failed to set email verified err=%v", err)) mlog.Error("Failed to set email verified.", mlog.Err(err))
} }
if err = a.JoinUserToTeam(team, user, ""); err != nil { if err = a.JoinUserToTeam(team, user, ""); err != nil {
mlog.Error(fmt.Sprintf("Failed to join team when importing err=%v", err)) mlog.Error("Failed to join team when importing.", mlog.Err(err))
} }
return ruser return ruser

Просмотреть файл

@@ -26,14 +26,11 @@ func TestCreateTeam(t *testing.T) {
Type: model.TEAM_OPEN, Type: model.TEAM_OPEN,
} }
if _, err := th.App.CreateTeam(team); err != nil { _, err := th.App.CreateTeam(team)
t.Log(err) require.Nil(t, err, "Should create a new team")
t.Fatal("Should create a new team")
}
if _, err := th.App.CreateTeam(th.BasicTeam); err == nil { _, err = th.App.CreateTeam(th.BasicTeam)
t.Fatal("Should not create a new team - team already exist") require.NotNil(t, err, "Should not create a new team - team already exist")
}
} }
func TestCreateTeamWithUser(t *testing.T) { func TestCreateTeamWithUser(t *testing.T) {
@@ -48,13 +45,11 @@ func TestCreateTeamWithUser(t *testing.T) {
Type: model.TEAM_OPEN, Type: model.TEAM_OPEN,
} }
if _, err := th.App.CreateTeamWithUser(team, th.BasicUser.Id); err != nil { _, err := th.App.CreateTeamWithUser(team, th.BasicUser.Id)
t.Fatal("Should create a new team with existing user", err) require.Nil(t, err, "Should create a new team with existing user")
}
if _, err := th.App.CreateTeamWithUser(team, model.NewId()); err == nil { _, err = th.App.CreateTeamWithUser(team, model.NewId())
t.Fatal("Should not create a new team - user does not exist") require.NotNil(t, err, "Should not create a new team - user does not exist")
}
} }
func TestUpdateTeam(t *testing.T) { func TestUpdateTeam(t *testing.T) {
@@ -63,14 +58,9 @@ func TestUpdateTeam(t *testing.T) {
th.BasicTeam.DisplayName = "Testing 123" th.BasicTeam.DisplayName = "Testing 123"
if updatedTeam, err := th.App.UpdateTeam(th.BasicTeam); err != nil { updatedTeam, err := th.App.UpdateTeam(th.BasicTeam)
t.Log(err) require.Nil(t, err, "Should update the team")
t.Fatal("Should update the team") require.Equal(t, "Testing 123", updatedTeam.DisplayName, "Wrong Team DisplayName")
} else {
if updatedTeam.DisplayName != "Testing 123" {
t.Fatal("Wrong Team DisplayName")
}
}
} }
func TestAddUserToTeam(t *testing.T) { func TestAddUserToTeam(t *testing.T) {
@@ -82,59 +72,45 @@ func TestAddUserToTeam(t *testing.T) {
ruser, _ := th.App.CreateUser(&user) ruser, _ := th.App.CreateUser(&user)
defer th.App.PermanentDeleteUser(&user) defer th.App.PermanentDeleteUser(&user)
if _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, ""); err != nil { _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, "")
t.Log(err) require.Nil(t, err, "Should add user to the team")
t.Fatal("Should add user to the team")
}
}) })
t.Run("allow user by domain", func(t *testing.T) { t.Run("allow user by domain", func(t *testing.T) {
th.BasicTeam.AllowedDomains = "example.com" th.BasicTeam.AllowedDomains = "example.com"
if _, err := th.App.UpdateTeam(th.BasicTeam); err != nil { _, err := th.App.UpdateTeam(th.BasicTeam)
t.Log(err) require.Nil(t, err, "Should update the team")
t.Fatal("Should update the team")
}
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
ruser, _ := th.App.CreateUser(&user) ruser, _ := th.App.CreateUser(&user)
defer th.App.PermanentDeleteUser(&user) defer th.App.PermanentDeleteUser(&user)
if _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, ""); err != nil { _, err = th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, "")
t.Log(err) require.Nil(t, err, "Should have allowed whitelisted user")
t.Fatal("Should have allowed whitelisted user")
}
}) })
t.Run("block user by domain but allow bot", func(t *testing.T) { t.Run("block user by domain but allow bot", func(t *testing.T) {
th.BasicTeam.AllowedDomains = "example.com" th.BasicTeam.AllowedDomains = "example.com"
if _, err := th.App.UpdateTeam(th.BasicTeam); err != nil { _, err := th.App.UpdateTeam(th.BasicTeam)
t.Log(err) require.Nil(t, err, "Should update the team")
t.Fatal("Should update the team")
}
user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
ruser, err := th.App.CreateUser(&user) ruser, err := th.App.CreateUser(&user)
if err != nil { require.Nil(t, err, "Error creating user: %s", err)
t.Fatalf("Error creating user: %s", err)
}
defer th.App.PermanentDeleteUser(&user) defer th.App.PermanentDeleteUser(&user)
if _, err = th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, ""); err == nil || err.Where != "JoinUserToTeam" { _, err = th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, "")
t.Log(err) require.NotNil(t, err, "Should not add restricted user")
t.Fatal("Should not add restricted user") require.Equal(t, "JoinUserToTeam", err.Where, "Error should be JoinUserToTeam")
}
user = model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), AuthService: "notnil", AuthData: model.NewString("notnil")} user = model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), AuthService: "notnil", AuthData: model.NewString("notnil")}
ruser, err = th.App.CreateUser(&user) ruser, err = th.App.CreateUser(&user)
if err != nil { require.Nil(t, err, "Error creating authservice user: %s", err)
t.Fatalf("Error creating authservice user: %s", err)
}
defer th.App.PermanentDeleteUser(&user) defer th.App.PermanentDeleteUser(&user)
if _, err = th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, ""); err == nil || err.Where != "JoinUserToTeam" { _, err = th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, "")
t.Log(err) require.NotNil(t, err, "Should not add authservice user")
t.Fatal("Should not add authservice user") require.Equal(t, "JoinUserToTeam", err.Where, "Error should be JoinUserToTeam")
}
bot, err := th.App.CreateBot(&model.Bot{ bot, err := th.App.CreateBot(&model.Bot{
Username: "somebot", Username: "somebot",
@@ -149,50 +125,45 @@ func TestAddUserToTeam(t *testing.T) {
t.Run("block user with subdomain", func(t *testing.T) { t.Run("block user with subdomain", func(t *testing.T) {
th.BasicTeam.AllowedDomains = "example.com" th.BasicTeam.AllowedDomains = "example.com"
if _, err := th.App.UpdateTeam(th.BasicTeam); err != nil { _, err := th.App.UpdateTeam(th.BasicTeam)
t.Log(err) require.Nil(t, err, "Should update the team")
t.Fatal("Should update the team")
}
user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
ruser, _ := th.App.CreateUser(&user) ruser, _ := th.App.CreateUser(&user)
defer th.App.PermanentDeleteUser(&user) defer th.App.PermanentDeleteUser(&user)
if _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, ""); err == nil || err.Where != "JoinUserToTeam" { _, err = th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, "")
t.Log(err) require.NotNil(t, err, "Should not add restricted user")
t.Fatal("Should not add restricted user") require.Equal(t, "JoinUserToTeam", err.Where, "Error should be JoinUserToTeam")
}
}) })
t.Run("allow users by multiple domains", func(t *testing.T) { t.Run("allow users by multiple domains", func(t *testing.T) {
th.BasicTeam.AllowedDomains = "foo.com, bar.com" th.BasicTeam.AllowedDomains = "foo.com, bar.com"
if _, err := th.App.UpdateTeam(th.BasicTeam); err != nil { _, err := th.App.UpdateTeam(th.BasicTeam)
t.Log(err) require.Nil(t, err, "Should update the team")
t.Fatal("Should update the team")
}
user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@foo.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@foo.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
ruser1, _ := th.App.CreateUser(&user1) ruser1, _ := th.App.CreateUser(&user1)
user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@bar.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@bar.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
ruser2, _ := th.App.CreateUser(&user2) ruser2, _ := th.App.CreateUser(&user2)
user3 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} user3 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
ruser3, _ := th.App.CreateUser(&user3) ruser3, _ := th.App.CreateUser(&user3)
defer th.App.PermanentDeleteUser(&user1) defer th.App.PermanentDeleteUser(&user1)
defer th.App.PermanentDeleteUser(&user2) defer th.App.PermanentDeleteUser(&user2)
defer th.App.PermanentDeleteUser(&user3) defer th.App.PermanentDeleteUser(&user3)
if _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser1.Id, ""); err != nil { _, err = th.App.AddUserToTeam(th.BasicTeam.Id, ruser1.Id, "")
t.Log(err) require.Nil(t, err, "Should have allowed whitelisted user1")
t.Fatal("Should have allowed whitelisted user1")
} _, err = th.App.AddUserToTeam(th.BasicTeam.Id, ruser2.Id, "")
if _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser2.Id, ""); err != nil { require.Nil(t, err, "Should have allowed whitelisted user2")
t.Log(err)
t.Fatal("Should have allowed whitelisted user2") _, err = th.App.AddUserToTeam(th.BasicTeam.Id, ruser3.Id, "")
} require.NotNil(t, err, "Should not have allowed restricted user3")
if _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser3.Id, ""); err == nil || err.Where != "JoinUserToTeam" { require.Equal(t, "JoinUserToTeam", err.Where, "Error should be JoinUserToTeam")
t.Log(err)
t.Fatal("Should not have allowed restricted user3")
}
}) })
} }
@@ -206,9 +177,8 @@ func TestAddUserToTeamByToken(t *testing.T) {
rguest := th.CreateGuest() rguest := th.CreateGuest()
t.Run("invalid token", func(t *testing.T) { t.Run("invalid token", func(t *testing.T) {
if _, err := th.App.AddUserToTeamByToken(ruser.Id, "123"); err == nil { _, err := th.App.AddUserToTeamByToken(ruser.Id, "123")
t.Fatal("Should fail on unexisting token") require.NotNil(t, err, "Should fail on unexisting token")
}
}) })
t.Run("invalid token type", func(t *testing.T) { t.Run("invalid token type", func(t *testing.T) {
@@ -216,11 +186,12 @@ func TestAddUserToTeamByToken(t *testing.T) {
TOKEN_TYPE_VERIFY_EMAIL, TOKEN_TYPE_VERIFY_EMAIL,
model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id}), model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id}),
) )
require.Nil(t, th.App.Srv.Store.Token().Save(token)) require.Nil(t, th.App.Srv.Store.Token().Save(token))
defer th.App.DeleteToken(token) defer th.App.DeleteToken(token)
if _, err := th.App.AddUserToTeamByToken(ruser.Id, token.Token); err == nil {
t.Fatal("Should fail on bad token type") _, err := th.App.AddUserToTeamByToken(ruser.Id, token.Token)
} require.NotNil(t, err, "Should fail on bad token type")
}) })
t.Run("expired token", func(t *testing.T) { t.Run("expired token", func(t *testing.T) {
@@ -228,12 +199,13 @@ func TestAddUserToTeamByToken(t *testing.T) {
TOKEN_TYPE_TEAM_INVITATION, TOKEN_TYPE_TEAM_INVITATION,
model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id}), model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id}),
) )
token.CreateAt = model.GetMillis() - INVITATION_EXPIRY_TIME - 1 token.CreateAt = model.GetMillis() - INVITATION_EXPIRY_TIME - 1
require.Nil(t, th.App.Srv.Store.Token().Save(token)) require.Nil(t, th.App.Srv.Store.Token().Save(token))
defer th.App.DeleteToken(token) defer th.App.DeleteToken(token)
if _, err := th.App.AddUserToTeamByToken(ruser.Id, token.Token); err == nil {
t.Fatal("Should fail on expired token") _, err := th.App.AddUserToTeamByToken(ruser.Id, token.Token)
} require.NotNil(t, err, "Should fail on expired token")
}) })
t.Run("invalid team id", func(t *testing.T) { t.Run("invalid team id", func(t *testing.T) {
@@ -243,9 +215,9 @@ func TestAddUserToTeamByToken(t *testing.T) {
) )
require.Nil(t, th.App.Srv.Store.Token().Save(token)) require.Nil(t, th.App.Srv.Store.Token().Save(token))
defer th.App.DeleteToken(token) defer th.App.DeleteToken(token)
if _, err := th.App.AddUserToTeamByToken(ruser.Id, token.Token); err == nil {
t.Fatal("Should fail on bad team id") _, err := th.App.AddUserToTeamByToken(ruser.Id, token.Token)
} require.NotNil(t, err, "Should fail on bad team id")
}) })
t.Run("invalid user id", func(t *testing.T) { t.Run("invalid user id", func(t *testing.T) {
@@ -255,9 +227,9 @@ func TestAddUserToTeamByToken(t *testing.T) {
) )
require.Nil(t, th.App.Srv.Store.Token().Save(token)) require.Nil(t, th.App.Srv.Store.Token().Save(token))
defer th.App.DeleteToken(token) defer th.App.DeleteToken(token)
if _, err := th.App.AddUserToTeamByToken(model.NewId(), token.Token); err == nil {
t.Fatal("Should fail on bad user id") _, err := th.App.AddUserToTeamByToken(model.NewId(), token.Token)
} require.NotNil(t, err, "Should fail on bad user id")
}) })
t.Run("valid request", func(t *testing.T) { t.Run("valid request", func(t *testing.T) {
@@ -266,11 +238,10 @@ func TestAddUserToTeamByToken(t *testing.T) {
model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id}), model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id}),
) )
require.Nil(t, th.App.Srv.Store.Token().Save(token)) require.Nil(t, th.App.Srv.Store.Token().Save(token))
if _, err := th.App.AddUserToTeamByToken(ruser.Id, token.Token); err != nil { _, err := th.App.AddUserToTeamByToken(ruser.Id, token.Token)
t.Log(err) require.Nil(t, err, "Should add user to the team")
t.Fatal("Should add user to the team")
} _, err = th.App.Srv.Store.Token().GetByToken(token.Token)
_, err := th.App.Srv.Store.Token().GetByToken(token.Token)
require.NotNil(t, err, "The token must be deleted after be used") require.NotNil(t, err, "The token must be deleted after be used")
members, err := th.App.GetChannelMembersForUser(th.BasicTeam.Id, ruser.Id) members, err := th.App.GetChannelMembersForUser(th.BasicTeam.Id, ruser.Id)
@@ -304,11 +275,11 @@ func TestAddUserToTeamByToken(t *testing.T) {
model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id, "channels": th.BasicChannel.Id}), model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id, "channels": th.BasicChannel.Id}),
) )
require.Nil(t, th.App.Srv.Store.Token().Save(token)) require.Nil(t, th.App.Srv.Store.Token().Save(token))
if _, err := th.App.AddUserToTeamByToken(rguest.Id, token.Token); err != nil {
t.Log(err) _, err := th.App.AddUserToTeamByToken(rguest.Id, token.Token)
t.Fatal("Should add user to the team") require.Nil(t, err, "Should add user to the team")
}
_, err := th.App.Srv.Store.Token().GetByToken(token.Token) _, err = th.App.Srv.Store.Token().GetByToken(token.Token)
require.NotNil(t, err, "The token must be deleted after be used") require.NotNil(t, err, "The token must be deleted after be used")
members, err := th.App.GetChannelMembersForUser(th.BasicTeam.Id, rguest.Id) members, err := th.App.GetChannelMembersForUser(th.BasicTeam.Id, rguest.Id)
@@ -319,35 +290,28 @@ func TestAddUserToTeamByToken(t *testing.T) {
t.Run("group-constrained team", func(t *testing.T) { t.Run("group-constrained team", func(t *testing.T) {
th.BasicTeam.GroupConstrained = model.NewBool(true) th.BasicTeam.GroupConstrained = model.NewBool(true)
if _, err := th.App.UpdateTeam(th.BasicTeam); err != nil { _, err := th.App.UpdateTeam(th.BasicTeam)
t.Log(err) require.Nil(t, err, "Should update the team")
t.Fatal("Should update the team")
}
token := model.NewToken( token := model.NewToken(
TOKEN_TYPE_TEAM_INVITATION, TOKEN_TYPE_TEAM_INVITATION,
model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id}), model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id}),
) )
require.Nil(t, th.App.Srv.Store.Token().Save(token)) require.Nil(t, th.App.Srv.Store.Token().Save(token))
if _, err := th.App.AddUserToTeamByToken(ruser.Id, token.Token); err == nil {
t.Fatal("Should return an error when trying to join a group-constrained team.") _, err = th.App.AddUserToTeamByToken(ruser.Id, token.Token)
} else { require.NotNil(t, err, "Should return an error when trying to join a group-constrained team.")
require.Equal(t, "app.team.invite_token.group_constrained.error", err.Id) require.Equal(t, "app.team.invite_token.group_constrained.error", err.Id)
}
th.BasicTeam.GroupConstrained = model.NewBool(false) th.BasicTeam.GroupConstrained = model.NewBool(false)
if _, err := th.App.UpdateTeam(th.BasicTeam); err != nil { _, err = th.App.UpdateTeam(th.BasicTeam)
t.Log(err) require.Nil(t, err, "Should update the team")
t.Fatal("Should update the team")
}
}) })
t.Run("block user", func(t *testing.T) { t.Run("block user", func(t *testing.T) {
th.BasicTeam.AllowedDomains = "example.com" th.BasicTeam.AllowedDomains = "example.com"
if _, err := th.App.UpdateTeam(th.BasicTeam); err != nil { _, err := th.App.UpdateTeam(th.BasicTeam)
t.Log(err) require.Nil(t, err, "Should update the team")
t.Fatal("Should update the team")
}
user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
ruser, _ := th.App.CreateUser(&user) ruser, _ := th.App.CreateUser(&user)
@@ -359,10 +323,9 @@ func TestAddUserToTeamByToken(t *testing.T) {
) )
require.Nil(t, th.App.Srv.Store.Token().Save(token)) require.Nil(t, th.App.Srv.Store.Token().Save(token))
if _, err := th.App.AddUserToTeamByToken(ruser.Id, token.Token); err == nil || err.Where != "JoinUserToTeam" { _, err = th.App.AddUserToTeamByToken(ruser.Id, token.Token)
t.Log(err) require.NotNil(t, err, "Should not add restricted user")
t.Fatal("Should not add restricted user") require.Equal(t, "JoinUserToTeam", err.Where, "Error should be JoinUserToTeam")
}
}) })
} }
@@ -374,27 +337,22 @@ func TestAddUserToTeamByTeamId(t *testing.T) {
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
ruser, _ := th.App.CreateUser(&user) ruser, _ := th.App.CreateUser(&user)
if err := th.App.AddUserToTeamByTeamId(th.BasicTeam.Id, ruser); err != nil { err := th.App.AddUserToTeamByTeamId(th.BasicTeam.Id, ruser)
t.Log(err) require.Nil(t, err, "Should add user to the team")
t.Fatal("Should add user to the team")
}
}) })
t.Run("block user", func(t *testing.T) { t.Run("block user", func(t *testing.T) {
th.BasicTeam.AllowedDomains = "example.com" th.BasicTeam.AllowedDomains = "example.com"
if _, err := th.App.UpdateTeam(th.BasicTeam); err != nil { _, err := th.App.UpdateTeam(th.BasicTeam)
t.Log(err) require.Nil(t, err, "Should update the team")
t.Fatal("Should update the team")
}
user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
ruser, _ := th.App.CreateUser(&user) ruser, _ := th.App.CreateUser(&user)
defer th.App.PermanentDeleteUser(&user) defer th.App.PermanentDeleteUser(&user)
if err := th.App.AddUserToTeamByTeamId(th.BasicTeam.Id, ruser); err == nil || err.Where != "JoinUserToTeam" { err = th.App.AddUserToTeamByTeamId(th.BasicTeam.Id, ruser)
t.Log(err) require.NotNil(t, err, "Should not add restricted user")
t.Fatal("Should not add restricted user") require.Equal(t, "JoinUserToTeam", err.Where, "Error should be JoinUserToTeam")
}
}) })
} }
@@ -409,9 +367,8 @@ func TestPermanentDeleteTeam(t *testing.T) {
Email: "foo@foo.com", Email: "foo@foo.com",
Type: model.TEAM_OPEN, Type: model.TEAM_OPEN,
}) })
if err != nil { require.Nil(t, err, "Should create a team")
t.Fatal(err.Error())
}
defer func() { defer func() {
th.App.PermanentDeleteTeam(team) th.App.PermanentDeleteTeam(team)
}() }()
@@ -423,21 +380,19 @@ func TestPermanentDeleteTeam(t *testing.T) {
URL: "http://foo", URL: "http://foo",
Method: model.COMMAND_METHOD_POST, Method: model.COMMAND_METHOD_POST,
}) })
if err != nil { require.Nil(t, err, "Should create a command")
t.Fatal(err.Error())
}
defer th.App.DeleteCommand(command.Id) defer th.App.DeleteCommand(command.Id)
if command, err = th.App.GetCommand(command.Id); command == nil || err != nil { command, err = th.App.GetCommand(command.Id)
t.Fatal("unable to get new command") require.NotNil(t, command, "command should not be nil")
} require.Nil(t, err, "unable to get new command")
err = th.App.PermanentDeleteTeam(team) err = th.App.PermanentDeleteTeam(team)
require.Nil(t, err) require.Nil(t, err)
if command, err = th.App.GetCommand(command.Id); command != nil || err == nil { command, err = th.App.GetCommand(command.Id)
t.Fatal("command wasn't deleted") require.Nil(t, command, "command wasn't deleted")
} require.NotNil(t, err, "should not return an error")
// Test deleting a team with no channels. // Test deleting a team with no channels.
team = th.CreateTeam() team = th.CreateTeam()
@@ -445,19 +400,16 @@ func TestPermanentDeleteTeam(t *testing.T) {
th.App.PermanentDeleteTeam(team) th.App.PermanentDeleteTeam(team)
}() }()
if channels, err := th.App.GetPublicChannelsForTeam(team.Id, 0, 1000); err != nil { channels, err := th.App.GetPublicChannelsForTeam(team.Id, 0, 1000)
t.Fatal(err) require.Nil(t, err)
} else {
for _, channel := range *channels { for _, channel := range *channels {
if err2 := th.App.PermanentDeleteChannel(channel); err2 != nil { err2 := th.App.PermanentDeleteChannel(channel)
t.Fatal(err) require.Nil(t, err2)
}
}
} }
if err := th.App.PermanentDeleteTeam(team); err != nil { err = th.App.PermanentDeleteTeam(team)
t.Fatal(err) require.Nil(t, err)
}
} }
func TestSanitizeTeam(t *testing.T) { func TestSanitizeTeam(t *testing.T) {
@@ -469,6 +421,7 @@ func TestSanitizeTeam(t *testing.T) {
Email: th.MakeEmail(), Email: th.MakeEmail(),
AllowedDomains: "example.com", AllowedDomains: "example.com",
} }
copyTeam := func() *model.Team { copyTeam := func() *model.Team {
copy := &model.Team{} copy := &model.Team{}
*copy = *team *copy = *team
@@ -489,9 +442,7 @@ func TestSanitizeTeam(t *testing.T) {
} }
sanitized := th.App.SanitizeTeam(session, copyTeam()) sanitized := th.App.SanitizeTeam(session, copyTeam())
if sanitized.Email != "" { require.Empty(t, sanitized.Email, "should've sanitized team")
t.Fatal("should've sanitized team")
}
}) })
t.Run("user of the team", func(t *testing.T) { t.Run("user of the team", func(t *testing.T) {
@@ -508,9 +459,7 @@ func TestSanitizeTeam(t *testing.T) {
} }
sanitized := th.App.SanitizeTeam(session, copyTeam()) sanitized := th.App.SanitizeTeam(session, copyTeam())
if sanitized.Email != "" { require.Empty(t, sanitized.Email, "should've sanitized team")
t.Fatal("should've sanitized team")
}
}) })
t.Run("team admin", func(t *testing.T) { t.Run("team admin", func(t *testing.T) {
@@ -527,9 +476,7 @@ func TestSanitizeTeam(t *testing.T) {
} }
sanitized := th.App.SanitizeTeam(session, copyTeam()) sanitized := th.App.SanitizeTeam(session, copyTeam())
if sanitized.Email == "" { require.NotEmpty(t, sanitized.Email, "shouldn't have sanitized team")
t.Fatal("shouldn't have sanitized team")
}
}) })
t.Run("team admin of another team", func(t *testing.T) { t.Run("team admin of another team", func(t *testing.T) {
@@ -546,9 +493,7 @@ func TestSanitizeTeam(t *testing.T) {
} }
sanitized := th.App.SanitizeTeam(session, copyTeam()) sanitized := th.App.SanitizeTeam(session, copyTeam())
if sanitized.Email != "" { require.Empty(t, sanitized.Email, "should've sanitized team")
t.Fatal("should've sanitized team")
}
}) })
t.Run("system admin, not a user of team", func(t *testing.T) { t.Run("system admin, not a user of team", func(t *testing.T) {
@@ -565,9 +510,7 @@ func TestSanitizeTeam(t *testing.T) {
} }
sanitized := th.App.SanitizeTeam(session, copyTeam()) sanitized := th.App.SanitizeTeam(session, copyTeam())
if sanitized.Email == "" { require.NotEmpty(t, sanitized.Email, "shouldn't have sanitized team")
t.Fatal("shouldn't have sanitized team")
}
}) })
t.Run("system admin, user of team", func(t *testing.T) { t.Run("system admin, user of team", func(t *testing.T) {
@@ -584,9 +527,7 @@ func TestSanitizeTeam(t *testing.T) {
} }
sanitized := th.App.SanitizeTeam(session, copyTeam()) sanitized := th.App.SanitizeTeam(session, copyTeam())
if sanitized.Email == "" { require.NotEmpty(t, sanitized.Email, "shouldn't have sanitized team")
t.Fatal("shouldn't have sanitized team")
}
}) })
} }
@@ -627,13 +568,8 @@ func TestSanitizeTeams(t *testing.T) {
sanitized := th.App.SanitizeTeams(session, teams) sanitized := th.App.SanitizeTeams(session, teams)
if sanitized[0].Email != "" { require.Empty(t, sanitized[0].Email, "should've sanitized first team")
t.Fatal("should've sanitized first team") require.NotEmpty(t, sanitized[1].Email, "shouldn't have sanitized second team")
}
if sanitized[1].Email == "" {
t.Fatal("shouldn't have sanitized second team")
}
}) })
t.Run("system admin", func(t *testing.T) { t.Run("system admin", func(t *testing.T) {
@@ -663,14 +599,8 @@ func TestSanitizeTeams(t *testing.T) {
} }
sanitized := th.App.SanitizeTeams(session, teams) sanitized := th.App.SanitizeTeams(session, teams)
assert.NotEmpty(t, sanitized[0].Email, "shouldn't have sanitized first team")
if sanitized[0].Email == "" { assert.NotEmpty(t, sanitized[1].Email, "shouldn't have sanitized second team")
t.Fatal("shouldn't have sanitized first team")
}
if sanitized[1].Email == "" {
t.Fatal("shouldn't have sanitized second team")
}
}) })
} }
@@ -686,10 +616,8 @@ func TestJoinUserToTeam(t *testing.T) {
Type: model.TEAM_OPEN, Type: model.TEAM_OPEN,
} }
if _, err := th.App.CreateTeam(team); err != nil { _, err := th.App.CreateTeam(team)
t.Log(err) require.Nil(t, err, "Should create a new team")
t.Fatal("Should create a new team")
}
maxUsersPerTeam := th.App.Config().TeamSettings.MaxUsersPerTeam maxUsersPerTeam := th.App.Config().TeamSettings.MaxUsersPerTeam
defer func() { defer func() {
@@ -704,9 +632,9 @@ func TestJoinUserToTeam(t *testing.T) {
ruser, _ := th.App.CreateUser(&user) ruser, _ := th.App.CreateUser(&user)
defer th.App.PermanentDeleteUser(&user) defer th.App.PermanentDeleteUser(&user)
if _, alreadyAdded, err := th.App.joinUserToTeam(team, ruser); alreadyAdded || err != nil { _, alreadyAdded, err := th.App.joinUserToTeam(team, ruser)
t.Fatal("Should return already added equal to false and no error") require.False(t, alreadyAdded, "Should return already added equal to false")
} require.Nil(t, err, "Should return no error")
}) })
t.Run("join when you are a member", func(t *testing.T) { t.Run("join when you are a member", func(t *testing.T) {
@@ -715,9 +643,10 @@ func TestJoinUserToTeam(t *testing.T) {
defer th.App.PermanentDeleteUser(&user) defer th.App.PermanentDeleteUser(&user)
th.App.joinUserToTeam(team, ruser) th.App.joinUserToTeam(team, ruser)
if _, alreadyAdded, err := th.App.joinUserToTeam(team, ruser); !alreadyAdded || err != nil {
t.Fatal("Should return already added and no error") _, alreadyAdded, err := th.App.joinUserToTeam(team, ruser)
} require.True(t, alreadyAdded, "Should return already added")
require.Nil(t, err, "Should return no error")
}) })
t.Run("re-join after leaving", func(t *testing.T) { t.Run("re-join after leaving", func(t *testing.T) {
@@ -727,9 +656,10 @@ func TestJoinUserToTeam(t *testing.T) {
th.App.joinUserToTeam(team, ruser) th.App.joinUserToTeam(team, ruser)
th.App.LeaveTeam(team, ruser, ruser.Id) th.App.LeaveTeam(team, ruser, ruser.Id)
if _, alreadyAdded, err := th.App.joinUserToTeam(team, ruser); alreadyAdded || err != nil {
t.Fatal("Should return already added equal to false and no error") _, alreadyAdded, err := th.App.joinUserToTeam(team, ruser)
} require.False(t, alreadyAdded, "Should return already added equal to false")
require.Nil(t, err, "Should return no error")
}) })
t.Run("new join with limit problem", func(t *testing.T) { t.Run("new join with limit problem", func(t *testing.T) {
@@ -737,28 +667,31 @@ func TestJoinUserToTeam(t *testing.T) {
ruser1, _ := th.App.CreateUser(&user1) ruser1, _ := th.App.CreateUser(&user1)
user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
ruser2, _ := th.App.CreateUser(&user2) ruser2, _ := th.App.CreateUser(&user2)
defer th.App.PermanentDeleteUser(&user1) defer th.App.PermanentDeleteUser(&user1)
defer th.App.PermanentDeleteUser(&user2) defer th.App.PermanentDeleteUser(&user2)
th.App.joinUserToTeam(team, ruser1) th.App.joinUserToTeam(team, ruser1)
if _, _, err := th.App.joinUserToTeam(team, ruser2); err == nil {
t.Fatal("Should fail") _, _, err := th.App.joinUserToTeam(team, ruser2)
} require.NotNil(t, err, "Should fail")
}) })
t.Run("re-join alfter leaving with limit problem", func(t *testing.T) { t.Run("re-join alfter leaving with limit problem", func(t *testing.T) {
user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
ruser1, _ := th.App.CreateUser(&user1) ruser1, _ := th.App.CreateUser(&user1)
user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
ruser2, _ := th.App.CreateUser(&user2) ruser2, _ := th.App.CreateUser(&user2)
defer th.App.PermanentDeleteUser(&user1) defer th.App.PermanentDeleteUser(&user1)
defer th.App.PermanentDeleteUser(&user2) defer th.App.PermanentDeleteUser(&user2)
th.App.joinUserToTeam(team, ruser1) th.App.joinUserToTeam(team, ruser1)
th.App.LeaveTeam(team, ruser1, ruser1.Id) th.App.LeaveTeam(team, ruser1, ruser1.Id)
th.App.joinUserToTeam(team, ruser2) th.App.joinUserToTeam(team, ruser2)
if _, _, err := th.App.joinUserToTeam(team, ruser1); err == nil {
t.Fatal("Should fail") _, _, err := th.App.joinUserToTeam(team, ruser1)
} require.NotNil(t, err, "Should fail")
}) })
} }
@@ -771,13 +704,8 @@ func TestAppUpdateTeamScheme(t *testing.T) {
team.SchemeId = mockID team.SchemeId = mockID
updatedTeam, err := th.App.UpdateTeamScheme(th.BasicTeam) updatedTeam, err := th.App.UpdateTeamScheme(th.BasicTeam)
if err != nil { require.Nil(t, err)
t.Fatal(err) require.Equal(t, mockID, updatedTeam.SchemeId, "Wrong Team SchemeId")
}
if updatedTeam.SchemeId != mockID {
t.Fatal("Wrong Team SchemeId")
}
} }
func TestGetTeamMembers(t *testing.T) { func TestGetTeamMembers(t *testing.T) {
@@ -811,6 +739,7 @@ func TestGetTeamMembers(t *testing.T) {
// Fetch team members multipile times // Fetch team members multipile times
members, err := th.App.GetTeamMembers(th.BasicTeam.Id, 0, 5, nil) members, err := th.App.GetTeamMembers(th.BasicTeam.Id, 0, 5, nil)
require.Nil(t, err) require.Nil(t, err)
// This should return 5 members // This should return 5 members
members2, err := th.App.GetTeamMembers(th.BasicTeam.Id, 5, 6, nil) members2, err := th.App.GetTeamMembers(th.BasicTeam.Id, 5, 6, nil)
require.Nil(t, err) require.Nil(t, err)
@@ -897,9 +826,8 @@ func TestUpdateTeamMemberRolesChangingGuest(t *testing.T) {
_, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, "") _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, "")
require.Nil(t, err) require.Nil(t, err)
if _, err := th.App.UpdateTeamMemberRoles(th.BasicTeam.Id, ruser.Id, "team_user"); err == nil { _, err = th.App.UpdateTeamMemberRoles(th.BasicTeam.Id, ruser.Id, "team_user")
t.Fatal("Should fail when try to modify the guest role") require.NotNil(t, err, "Should fail when try to modify the guest role")
}
}) })
t.Run("from user to guest", func(t *testing.T) { t.Run("from user to guest", func(t *testing.T) {
@@ -909,9 +837,8 @@ func TestUpdateTeamMemberRolesChangingGuest(t *testing.T) {
_, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, "") _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, "")
require.Nil(t, err) require.Nil(t, err)
if _, err := th.App.UpdateTeamMemberRoles(th.BasicTeam.Id, ruser.Id, "team_guest"); err == nil { _, err = th.App.UpdateTeamMemberRoles(th.BasicTeam.Id, ruser.Id, "team_guest")
t.Fatal("Should fail when try to modify the guest role") require.NotNil(t, err, "Should fail when try to modify the guest role")
}
}) })
t.Run("from user to admin", func(t *testing.T) { t.Run("from user to admin", func(t *testing.T) {
@@ -921,9 +848,8 @@ func TestUpdateTeamMemberRolesChangingGuest(t *testing.T) {
_, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, "") _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, "")
require.Nil(t, err) require.Nil(t, err)
if _, err := th.App.UpdateTeamMemberRoles(th.BasicTeam.Id, ruser.Id, "team_user team_admin"); err != nil { _, err = th.App.UpdateTeamMemberRoles(th.BasicTeam.Id, ruser.Id, "team_user team_admin")
t.Fatal("Should work when you not modify guest role") require.Nil(t, err, "Should work when you not modify guest role")
}
}) })
t.Run("from guest to guest plus custom", func(t *testing.T) { t.Run("from guest to guest plus custom", func(t *testing.T) {
@@ -936,9 +862,8 @@ func TestUpdateTeamMemberRolesChangingGuest(t *testing.T) {
_, err = th.App.CreateRole(&model.Role{Name: "custom", DisplayName: "custom", Description: "custom"}) _, err = th.App.CreateRole(&model.Role{Name: "custom", DisplayName: "custom", Description: "custom"})
require.Nil(t, err) require.Nil(t, err)
if _, err := th.App.UpdateTeamMemberRoles(th.BasicTeam.Id, ruser.Id, "team_guest custom"); err != nil { _, err = th.App.UpdateTeamMemberRoles(th.BasicTeam.Id, ruser.Id, "team_guest custom")
t.Fatal("Should work when you not modify guest role") require.Nil(t, err, "Should work when you not modify guest role")
}
}) })
t.Run("a guest cant have user role", func(t *testing.T) { t.Run("a guest cant have user role", func(t *testing.T) {
@@ -948,9 +873,8 @@ func TestUpdateTeamMemberRolesChangingGuest(t *testing.T) {
_, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, "") _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, "")
require.Nil(t, err) require.Nil(t, err)
if _, err := th.App.UpdateTeamMemberRoles(th.BasicTeam.Id, ruser.Id, "team_guest team_user"); err == nil { _, err = th.App.UpdateTeamMemberRoles(th.BasicTeam.Id, ruser.Id, "team_guest team_user")
t.Fatal("Should work when you not modify guest role") require.NotNil(t, err, "Should work when you not modify guest role")
}
}) })
} }

Просмотреть файл

@@ -95,7 +95,7 @@ func (a *App) CreateUserWithToken(user *model.User, token *model.Token) (*model.
for _, channel := range channels { for _, channel := range channels {
_, err := a.AddChannelMember(ruser.Id, channel, "", "") _, err := a.AddChannelMember(ruser.Id, channel, "", "")
if err != nil { if err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to add channel member", mlog.Err(err))
} }
} }
} }
@@ -135,7 +135,7 @@ func (a *App) CreateUserWithInviteId(user *model.User, inviteId string) (*model.
a.AddDirectChannels(team.Id, ruser) a.AddDirectChannels(team.Id, ruser)
if err := a.SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.Locale, a.GetSiteURL()); err != nil { if err := a.SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to send welcome email on create user with inviteId", mlog.Err(err))
} }
return ruser, nil return ruser, nil
@@ -148,7 +148,7 @@ func (a *App) CreateUserAsAdmin(user *model.User) (*model.User, *model.AppError)
} }
if err := a.SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.Locale, a.GetSiteURL()); err != nil { if err := a.SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to send welcome email on create admin user", mlog.Err(err))
} }
return ruser, nil return ruser, nil
@@ -172,7 +172,7 @@ func (a *App) CreateUserFromSignup(user *model.User) (*model.User, *model.AppErr
} }
if err := a.SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.Locale, a.GetSiteURL()); err != nil { if err := a.SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to send welcome email on create user from signup", mlog.Err(err))
} }
return ruser, nil return ruser, nil
@@ -190,7 +190,7 @@ func (a *App) IsFirstUserAccount() bool {
if a.SessionCacheLength() == 0 { if a.SessionCacheLength() == 0 {
count, err := a.Srv.Store.User().Count(model.UserCountOptions{IncludeDeleted: true}) count, err := a.Srv.Store.User().Count(model.UserCountOptions{IncludeDeleted: true})
if err != nil { if err != nil {
mlog.Error(fmt.Sprint(err)) mlog.Error("There was a error fetching if first user account", mlog.Err(err))
return false return false
} }
if count <= 0 { if count <= 0 {
@@ -314,19 +314,19 @@ func (a *App) createUser(user *model.User) (*model.User, *model.AppError) {
ruser, err := a.Srv.Store.User().Save(user) ruser, err := a.Srv.Store.User().Save(user)
if err != nil { if err != nil {
mlog.Error(fmt.Sprintf("Couldn't save the user err=%v", err)) mlog.Error("Couldn't save the user", mlog.Err(err))
return nil, err return nil, err
} }
if user.EmailVerified { if user.EmailVerified {
if err := a.VerifyUserEmail(ruser.Id, user.Email); err != nil { if err := a.VerifyUserEmail(ruser.Id, user.Email); err != nil {
mlog.Error(fmt.Sprintf("Failed to set email verified err=%v", err)) mlog.Error("Failed to set email verified", mlog.Err(err))
} }
} }
pref := model.Preference{UserId: ruser.Id, Category: model.PREFERENCE_CATEGORY_TUTORIAL_STEPS, Name: ruser.Id, Value: "0"} pref := model.Preference{UserId: ruser.Id, Category: model.PREFERENCE_CATEGORY_TUTORIAL_STEPS, Name: ruser.Id, Value: "0"}
if err := a.Srv.Store.Preference().Save(&model.Preferences{pref}); err != nil { if err := a.Srv.Store.Preference().Save(&model.Preferences{pref}); err != nil {
mlog.Error(fmt.Sprintf("Encountered error saving tutorial preference, err=%v", err.Message)) mlog.Error("Encountered error saving tutorial preference", mlog.Err(err))
} }
ruser.Sanitize(map[string]bool{}) ruser.Sanitize(map[string]bool{})
@@ -397,7 +397,7 @@ func (a *App) CreateOAuthUser(service string, userData io.Reader, teamId string)
err = a.AddDirectChannels(teamId, user) err = a.AddDirectChannels(teamId, user)
if err != nil { if err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to add direct channels", mlog.Err(err))
} }
} }
@@ -589,8 +589,8 @@ func (a *App) GetUsersNotInChannelPage(teamId string, channelId string, groupCon
return a.sanitizeProfiles(users, asAdmin), nil return a.sanitizeProfiles(users, asAdmin), nil
} }
func (a *App) GetUsersWithoutTeamPage(page int, perPage int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) { func (a *App) GetUsersWithoutTeamPage(options *model.UserGetOptions, asAdmin bool) ([]*model.User, *model.AppError) {
users, err := a.GetUsersWithoutTeam(page*perPage, perPage, viewRestrictions) users, err := a.GetUsersWithoutTeam(options)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -598,8 +598,8 @@ func (a *App) GetUsersWithoutTeamPage(page int, perPage int, asAdmin bool, viewR
return a.sanitizeProfiles(users, asAdmin), nil return a.sanitizeProfiles(users, asAdmin), nil
} }
func (a *App) GetUsersWithoutTeam(offset int, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) { func (a *App) GetUsersWithoutTeam(options *model.UserGetOptions) ([]*model.User, *model.AppError) {
return a.Srv.Store.User().GetProfilesWithoutTeam(offset, limit, viewRestrictions) return a.Srv.Store.User().GetProfilesWithoutTeam(options)
} }
// GetTeamGroupUsers returns the users who are associated to the team via GroupTeams and GroupMembers. // GetTeamGroupUsers returns the users who are associated to the team via GroupTeams and GroupMembers.
@@ -835,14 +835,14 @@ func (a *App) SetDefaultProfileImage(user *model.User) *model.AppError {
} }
if err := a.Srv.Store.User().ResetLastPictureUpdate(user.Id); err != nil { if err := a.Srv.Store.User().ResetLastPictureUpdate(user.Id); err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to reset last picture update", mlog.Err(err))
} }
a.InvalidateCacheForUser(user.Id) a.InvalidateCacheForUser(user.Id)
updatedUser, appErr := a.GetUser(user.Id) updatedUser, appErr := a.GetUser(user.Id)
if appErr != nil { if appErr != nil {
mlog.Error(fmt.Sprintf("Error in getting users profile for id=%v forcing logout", user.Id), mlog.String("user_id", user.Id)) mlog.Error("Error in getting users profile forcing logout", mlog.String("user_id", user.Id), mlog.Err(appErr))
return nil return nil
} }
@@ -908,7 +908,7 @@ func (a *App) SetProfileImageFromFile(userId string, file io.Reader) *model.AppE
} }
if err := a.Srv.Store.User().UpdateLastPictureUpdate(userId); err != nil { if err := a.Srv.Store.User().UpdateLastPictureUpdate(userId); err != nil {
mlog.Error(err.Error()) mlog.Error("Error with updating last picture update", mlog.Err(err))
} }
a.invalidateUserCacheAndPublish(userId) a.invalidateUserCacheAndPublish(userId)
@@ -978,10 +978,11 @@ func (a *App) invalidateUserChannelMembersCaches(user *model.User) *model.AppErr
} }
func (a *App) UpdateActive(user *model.User, active bool) (*model.User, *model.AppError) { func (a *App) UpdateActive(user *model.User, active bool) (*model.User, *model.AppError) {
user.UpdateAt = model.GetMillis()
if active { if active {
user.DeleteAt = 0 user.DeleteAt = 0
} else { } else {
user.DeleteAt = model.GetMillis() user.DeleteAt = user.UpdateAt
} }
userUpdate, err := a.Srv.Store.User().Update(user, true) userUpdate, err := a.Srv.Store.User().Update(user, true)
@@ -997,6 +998,7 @@ func (a *App) UpdateActive(user *model.User, active bool) (*model.User, *model.A
} }
a.invalidateUserChannelMembersCaches(user) a.invalidateUserChannelMembersCaches(user)
a.InvalidateCacheForUser(user.Id)
a.sendUpdatedUserEvent(*ruser) a.sendUpdatedUserEvent(*ruser)
@@ -1135,13 +1137,13 @@ func (a *App) UpdateUser(user *model.User, sendNotifications bool) (*model.User,
if *a.Config().EmailSettings.RequireEmailVerification { if *a.Config().EmailSettings.RequireEmailVerification {
a.Srv.Go(func() { a.Srv.Go(func() {
if err := a.SendEmailVerification(userUpdate.New, newEmail); err != nil { if err := a.SendEmailVerification(userUpdate.New, newEmail); err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to send email verification", mlog.Err(err))
} }
}) })
} else { } else {
a.Srv.Go(func() { a.Srv.Go(func() {
if err := a.SendEmailChangeEmail(userUpdate.Old.Email, userUpdate.New.Email, userUpdate.New.Locale, a.GetSiteURL()); err != nil { if err := a.SendEmailChangeEmail(userUpdate.Old.Email, userUpdate.New.Email, userUpdate.New.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to send email change email", mlog.Err(err))
} }
}) })
} }
@@ -1150,7 +1152,7 @@ func (a *App) UpdateUser(user *model.User, sendNotifications bool) (*model.User,
if userUpdate.New.Username != userUpdate.Old.Username { if userUpdate.New.Username != userUpdate.Old.Username {
a.Srv.Go(func() { a.Srv.Go(func() {
if err := a.SendChangeUsernameEmail(userUpdate.Old.Username, userUpdate.New.Username, userUpdate.New.Email, userUpdate.New.Locale, a.GetSiteURL()); err != nil { if err := a.SendChangeUsernameEmail(userUpdate.Old.Username, userUpdate.New.Username, userUpdate.New.Email, userUpdate.New.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to send change username email", mlog.Err(err))
} }
}) })
} }
@@ -1212,12 +1214,12 @@ func (a *App) UpdateMfa(activate bool, userId, token string) *model.AppError {
a.Srv.Go(func() { a.Srv.Go(func() {
user, err := a.GetUser(userId) user, err := a.GetUser(userId)
if err != nil { if err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to get user", mlog.Err(err))
return return
} }
if err := a.SendMfaChangeEmail(user.Email, activate, user.Locale, a.GetSiteURL()); err != nil { if err := a.SendMfaChangeEmail(user.Email, activate, user.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to send mfa change email", mlog.Err(err))
} }
}) })
@@ -1254,7 +1256,7 @@ func (a *App) UpdatePasswordSendEmail(user *model.User, newPassword, method stri
a.Srv.Go(func() { a.Srv.Go(func() {
if err := a.SendPasswordChangeEmail(user.Email, method, user.Locale, a.GetSiteURL()); err != nil { if err := a.SendPasswordChangeEmail(user.Email, method, user.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to send password change email", mlog.Err(err))
} }
}) })
@@ -1300,7 +1302,7 @@ func (a *App) ResetPasswordFromToken(userSuppliedTokenString, newPassword string
} }
if err := a.DeleteToken(token); err != nil { if err := a.DeleteToken(token); err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to delete token", mlog.Err(err))
} }
return nil return nil
@@ -1397,7 +1399,7 @@ func (a *App) UpdateUserRoles(userId string, newRoles string, sendWebSocketEvent
if result := <-schan; result.Err != nil { if result := <-schan; result.Err != nil {
// soft error since the user roles were still updated // soft error since the user roles were still updated
mlog.Error(fmt.Sprint(result.Err)) mlog.Error("Failed during updating user roles", mlog.Err(result.Err))
} }
a.ClearSessionCacheForUser(user.Id) a.ClearSessionCacheForUser(user.Id)
@@ -1413,9 +1415,9 @@ func (a *App) UpdateUserRoles(userId string, newRoles string, sendWebSocketEvent
} }
func (a *App) PermanentDeleteUser(user *model.User) *model.AppError { func (a *App) PermanentDeleteUser(user *model.User) *model.AppError {
mlog.Warn(fmt.Sprintf("Attempting to permanently delete account %v id=%v", user.Email, user.Id), mlog.String("user_id", user.Id)) mlog.Warn("Attempting to permanently delete account", mlog.String("user_id", user.Id), mlog.String("user_email", user.Email))
if user.IsInRole(model.SYSTEM_ADMIN_ROLE_ID) { if user.IsInRole(model.SYSTEM_ADMIN_ROLE_ID) {
mlog.Warn(fmt.Sprintf("You are deleting %v that is a system administrator. You may need to set another account as the system administrator using the command line tools.", user.Email)) mlog.Warn("You are deleting a user that is a system administrator. You may need to set another account as the system administrator using the command line tools.", mlog.String("user_email", user.Email))
} }
if _, err := a.UpdateActive(user, false); err != nil { if _, err := a.UpdateActive(user, false); err != nil {
@@ -1464,7 +1466,7 @@ func (a *App) PermanentDeleteUser(user *model.User) *model.AppError {
infos, err := a.Srv.Store.FileInfo().GetForUser(user.Id) infos, err := a.Srv.Store.FileInfo().GetForUser(user.Id)
if err != nil { if err != nil {
mlog.Warn("Error getting file list for user from FileInfoStore") mlog.Warn("Error getting file list for user from FileInfoStore", mlog.Err(err))
} }
for _, info := range infos { for _, info := range infos {
@@ -1510,7 +1512,7 @@ func (a *App) PermanentDeleteUser(user *model.User) *model.AppError {
return err return err
} }
mlog.Warn(fmt.Sprintf("Permanently deleted account %v id=%v", user.Email, user.Id), mlog.String("user_id", user.Id)) mlog.Warn("Permanently deleted account", mlog.String("user_email", user.Email), mlog.String("user_id", user.Id))
if a.IsESIndexingEnabled() { if a.IsESIndexingEnabled() {
a.Srv.Go(func() { a.Srv.Go(func() {
@@ -1578,13 +1580,13 @@ func (a *App) VerifyEmailFromToken(userSuppliedTokenString string) *model.AppErr
if user.Email != tokenData.Email { if user.Email != tokenData.Email {
a.Srv.Go(func() { a.Srv.Go(func() {
if err := a.SendEmailChangeEmail(user.Email, tokenData.Email, user.Locale, a.GetSiteURL()); err != nil { if err := a.SendEmailChangeEmail(user.Email, tokenData.Email, user.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to send email change email", mlog.Err(err))
} }
}) })
} }
if err := a.DeleteToken(token); err != nil { if err := a.DeleteToken(token); err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to delete token", mlog.Err(err))
} }
return nil return nil
@@ -2240,13 +2242,13 @@ func (a *App) PromoteGuestToUser(user *model.User, requestorId string) *model.Ap
for _, team := range userTeams { for _, team := range userTeams {
// Soft error if there is an issue joining the default channels // Soft error if there is an issue joining the default channels
if err = a.JoinDefaultChannels(team.Id, user, false, requestorId); err != nil { if err = a.JoinDefaultChannels(team.Id, user, false, requestorId); err != nil {
mlog.Error(fmt.Sprintf("Encountered an issue joining default channels err=%v", err), mlog.String("user_id", user.Id), mlog.String("team_id", team.Id), mlog.String("requestor_id", requestorId)) mlog.Error("Failed to join default channels", mlog.String("user_id", user.Id), mlog.String("team_id", team.Id), mlog.String("requestor_id", requestorId), mlog.Err(err))
} }
} }
promotedUser, err := a.GetUser(user.Id) promotedUser, err := a.GetUser(user.Id)
if err != nil { if err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to get user on promote guest to user", mlog.Err(err))
} else { } else {
a.sendUpdatedUserEvent(*promotedUser) a.sendUpdatedUserEvent(*promotedUser)
a.UpdateSessionsIsGuest(promotedUser.Id, promotedUser.IsGuest()) a.UpdateSessionsIsGuest(promotedUser.Id, promotedUser.IsGuest())
@@ -2254,7 +2256,7 @@ func (a *App) PromoteGuestToUser(user *model.User, requestorId string) *model.Ap
teamMembers, err := a.GetTeamMembersForUser(user.Id) teamMembers, err := a.GetTeamMembersForUser(user.Id)
if err != nil { if err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to get team members for user on promote guest to user", mlog.Err(err))
} }
for _, member := range teamMembers { for _, member := range teamMembers {
@@ -2262,7 +2264,7 @@ func (a *App) PromoteGuestToUser(user *model.User, requestorId string) *model.Ap
channelMembers, err := a.GetChannelMembersForUser(member.TeamId, user.Id) channelMembers, err := a.GetChannelMembersForUser(member.TeamId, user.Id)
if err != nil { if err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to get channel members for user on promote guest to user", mlog.Err(err))
} }
for _, member := range *channelMembers { for _, member := range *channelMembers {
@@ -2285,7 +2287,7 @@ func (a *App) DemoteUserToGuest(user *model.User) *model.AppError {
demotedUser, err := a.GetUser(user.Id) demotedUser, err := a.GetUser(user.Id)
if err != nil { if err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to get user on demote user to guest", mlog.Err(err))
} else { } else {
a.sendUpdatedUserEvent(*demotedUser) a.sendUpdatedUserEvent(*demotedUser)
a.UpdateSessionsIsGuest(demotedUser.Id, demotedUser.IsGuest()) a.UpdateSessionsIsGuest(demotedUser.Id, demotedUser.IsGuest())
@@ -2293,7 +2295,7 @@ func (a *App) DemoteUserToGuest(user *model.User) *model.AppError {
teamMembers, err := a.GetTeamMembersForUser(user.Id) teamMembers, err := a.GetTeamMembersForUser(user.Id)
if err != nil { if err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to get team members for users on demote user to guest", mlog.Err(err))
} }
for _, member := range teamMembers { for _, member := range teamMembers {
@@ -2301,7 +2303,7 @@ func (a *App) DemoteUserToGuest(user *model.User) *model.AppError {
channelMembers, err := a.GetChannelMembersForUser(member.TeamId, user.Id) channelMembers, err := a.GetChannelMembersForUser(member.TeamId, user.Id)
if err != nil { if err != nil {
mlog.Error(err.Error()) mlog.Error("Failed to get channel members for users on demote user to guest", mlog.Err(err))
} }
for _, member := range *channelMembers { for _, member := range *channelMembers {
@@ -2320,7 +2322,7 @@ func (a *App) invalidateUserCacheAndPublish(userId string) {
user, userErr := a.GetUser(userId) user, userErr := a.GetUser(userId)
if userErr != nil { if userErr != nil {
mlog.Error(fmt.Sprintf("Error in getting users profile for id=%v, err=%v", userId, userErr.Error()), mlog.String("user_id", userId)) mlog.Error("Error in getting users profile", mlog.String("user_id", userId), mlog.Err(userErr))
return return
} }

Просмотреть файл

@@ -618,7 +618,7 @@ func TestResctrictedViewMembers(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) { t.Run(tc.Name, func(t *testing.T) {
results, err := th.App.GetUsersWithoutTeam(0, 100, tc.Restrictions) results, err := th.App.GetUsersWithoutTeam(&model.UserGetOptions{Page: 0, PerPage: 100, ViewRestrictions: tc.Restrictions})
require.Nil(t, err) require.Nil(t, err)
ids := []string{} ids := []string{}
for _, result := range results { for _, result := range results {

Просмотреть файл

@@ -144,9 +144,9 @@ func (c *WebConn) readPump() {
if err := c.WebSocket.ReadJSON(&req); err != nil { if err := c.WebSocket.ReadJSON(&req); err != nil {
// browsers will appear as CloseNoStatusReceived // browsers will appear as CloseNoStatusReceived
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
mlog.Debug(fmt.Sprintf("websocket.read: client side closed socket userId=%v", c.UserId)) mlog.Debug("websocket.read: client side closed socket", mlog.String("user_id", c.UserId))
} else { } else {
mlog.Debug(fmt.Sprintf("websocket.read: closing websocket for userId=%v error=%v", c.UserId, err.Error())) mlog.Debug("websocket.read: closing websocket", mlog.String("user_id", c.UserId), mlog.Err(err))
} }
return return
} }
@@ -181,7 +181,12 @@ func (c *WebConn) writePump() {
if msg.EventType() == model.WEBSOCKET_EVENT_TYPING || if msg.EventType() == model.WEBSOCKET_EVENT_TYPING ||
msg.EventType() == model.WEBSOCKET_EVENT_STATUS_CHANGE || msg.EventType() == model.WEBSOCKET_EVENT_STATUS_CHANGE ||
msg.EventType() == model.WEBSOCKET_EVENT_CHANNEL_VIEWED { msg.EventType() == model.WEBSOCKET_EVENT_CHANNEL_VIEWED {
mlog.Info(fmt.Sprintf("websocket.slow: dropping message userId=%v type=%v channelId=%v", c.UserId, msg.EventType(), evt.Broadcast.ChannelId)) mlog.Info(
"websocket.slow: dropping message",
mlog.String("user_id", c.UserId),
mlog.String("type", msg.EventType()),
mlog.String("channel_id", evt.Broadcast.ChannelId),
)
skipSend = true skipSend = true
} }
} }
@@ -200,9 +205,20 @@ func (c *WebConn) writePump() {
if len(c.Send) >= SEND_DEADLOCK_WARN { if len(c.Send) >= SEND_DEADLOCK_WARN {
if evtOk { if evtOk {
mlog.Warn(fmt.Sprintf("websocket.full: message userId=%v type=%v channelId=%v size=%v", c.UserId, msg.EventType(), evt.Broadcast.ChannelId, len(msg.ToJson()))) mlog.Warn(
"websocket.full",
mlog.String("user_id", c.UserId),
mlog.String("type", msg.EventType()),
mlog.String("channel_id", evt.Broadcast.ChannelId),
mlog.Int("size", len(msg.ToJson())),
)
} else { } else {
mlog.Warn(fmt.Sprintf("websocket.full: message userId=%v type=%v size=%v", c.UserId, msg.EventType(), len(msg.ToJson()))) mlog.Warn(
"websocket.full",
mlog.String("user_id", c.UserId),
mlog.String("type", msg.EventType()),
mlog.Int("size", len(msg.ToJson())),
)
} }
} }
@@ -210,9 +226,9 @@ func (c *WebConn) writePump() {
if err := c.WebSocket.WriteMessage(websocket.TextMessage, msgBytes); err != nil { if err := c.WebSocket.WriteMessage(websocket.TextMessage, msgBytes); err != nil {
// browsers will appear as CloseNoStatusReceived // browsers will appear as CloseNoStatusReceived
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
mlog.Debug(fmt.Sprintf("websocket.send: client side closed socket userId=%v", c.UserId)) mlog.Debug("websocket.send: client side closed socket", mlog.String("user_id", c.UserId))
} else { } else {
mlog.Debug(fmt.Sprintf("websocket.send: closing websocket for userId=%v, error=%v", c.UserId, err.Error())) mlog.Debug("websocket.send: closing websocket", mlog.String("user_id", c.UserId), mlog.Err(err))
} }
return return
} }
@@ -229,9 +245,9 @@ func (c *WebConn) writePump() {
if err := c.WebSocket.WriteMessage(websocket.PingMessage, []byte{}); err != nil { if err := c.WebSocket.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
// browsers will appear as CloseNoStatusReceived // browsers will appear as CloseNoStatusReceived
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
mlog.Debug(fmt.Sprintf("websocket.ticker: client side closed socket userId=%v", c.UserId)) mlog.Debug("websocket.ticker: client side closed socket", mlog.String("user_id", c.UserId))
} else { } else {
mlog.Debug(fmt.Sprintf("websocket.ticker: closing websocket for userId=%v error=%v", c.UserId, err.Error())) mlog.Debug("websocket.ticker: closing websocket", mlog.String("user_id", c.UserId), mlog.Err(err))
} }
return return
} }
@@ -241,7 +257,7 @@ func (c *WebConn) writePump() {
case <-authTicker.C: case <-authTicker.C:
if c.GetSessionToken() == "" { if c.GetSessionToken() == "" {
mlog.Debug(fmt.Sprintf("websocket.authTicker: did not authenticate ip=%v", c.WebSocket.RemoteAddr())) mlog.Debug("websocket.authTicker: did not authenticate", mlog.Any("ip_address", c.WebSocket.RemoteAddr()))
return return
} }
authTicker.Stop() authTicker.Stop()
@@ -265,7 +281,7 @@ func (webCon *WebConn) IsAuthenticated() bool {
session, err := webCon.App.GetSession(webCon.GetSessionToken()) session, err := webCon.App.GetSession(webCon.GetSessionToken())
if err != nil { if err != nil {
mlog.Error(fmt.Sprintf("Invalid session err=%v", err.Error())) mlog.Error("Invalid session.", mlog.Err(err))
webCon.SetSessionToken("") webCon.SetSessionToken("")
webCon.SetSession(nil) webCon.SetSession(nil)
webCon.SetSessionExpiresAt(0) webCon.SetSessionExpiresAt(0)
@@ -338,7 +354,7 @@ func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool {
if webCon.AllChannelMembers == nil { if webCon.AllChannelMembers == nil {
result, err := webCon.App.Srv.Store.Channel().GetAllChannelMembersForUser(webCon.UserId, true, false) result, err := webCon.App.Srv.Store.Channel().GetAllChannelMembersForUser(webCon.UserId, true, false)
if err != nil { if err != nil {
mlog.Error("webhub.shouldSendEvent: " + err.Error()) mlog.Error("webhub.shouldSendEvent.", mlog.Err(err))
return false return false
} }
webCon.AllChannelMembers = result webCon.AllChannelMembers = result
@@ -359,7 +375,7 @@ func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool {
if msg.Event == model.WEBSOCKET_EVENT_USER_UPDATED && webCon.GetSession().Props[model.SESSION_PROP_IS_GUEST] == "true" { if msg.Event == model.WEBSOCKET_EVENT_USER_UPDATED && webCon.GetSession().Props[model.SESSION_PROP_IS_GUEST] == "true" {
canSee, err := webCon.App.UserCanSeeOtherUser(webCon.UserId, msg.Data["user"].(*model.User).Id) canSee, err := webCon.App.UserCanSeeOtherUser(webCon.UserId, msg.Data["user"].(*model.User).Id)
if err != nil { if err != nil {
mlog.Error("webhub.shouldSendEvent: " + err.Error()) mlog.Error("webhub.shouldSendEvent.", mlog.Err(err))
return false return false
} }
return canSee return canSee
@@ -374,7 +390,7 @@ func (webCon *WebConn) IsMemberOfTeam(teamId string) bool {
if currentSession == nil || len(currentSession.Token) == 0 { if currentSession == nil || len(currentSession.Token) == 0 {
session, err := webCon.App.GetSession(webCon.GetSessionToken()) session, err := webCon.App.GetSession(webCon.GetSessionToken())
if err != nil { if err != nil {
mlog.Error(fmt.Sprintf("Invalid session err=%v", err.Error())) mlog.Error("Invalid session.", mlog.Err(err))
return false return false
} }
webCon.SetSession(session) webCon.SetSession(session)

Просмотреть файл

@@ -4,7 +4,6 @@
package app package app
import ( import (
"fmt"
"io" "io"
"net/http" "net/http"
"regexp" "regexp"
@@ -108,7 +107,7 @@ func (a *App) TriggerWebhook(payload *model.OutgoingWebhookPayload, hook *model.
a.Srv.Go(func() { a.Srv.Go(func() {
webhookResp, err := a.doOutgoingWebhookRequest(url, body, contentType) webhookResp, err := a.doOutgoingWebhookRequest(url, body, contentType)
if err != nil { if err != nil {
mlog.Error(fmt.Sprintf("Event POST failed, err=%s", err.Error())) mlog.Error("Event POST failed.", mlog.Err(err))
return return
} }
@@ -139,7 +138,7 @@ func (a *App) TriggerWebhook(payload *model.OutgoingWebhookPayload, hook *model.
webhookResp.IconURL = hook.IconURL webhookResp.IconURL = hook.IconURL
} }
if _, err := a.CreateWebhookPost(hook.CreatorId, channel, text, webhookResp.Username, webhookResp.IconURL, "", webhookResp.Props, webhookResp.Type, postRootId); err != nil { if _, err := a.CreateWebhookPost(hook.CreatorId, channel, text, webhookResp.Username, webhookResp.IconURL, "", webhookResp.Props, webhookResp.Type, postRootId); err != nil {
mlog.Error(fmt.Sprintf("Failed to create response post, err=%v", err)) mlog.Error("Failed to create response post.", mlog.Err(err))
} }
} }
}) })

Просмотреть файл

@@ -4,7 +4,6 @@
package app package app
import ( import (
"fmt"
"net/http" "net/http"
"github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/mlog"
@@ -89,7 +88,13 @@ func (wr *WebSocketRouter) ServeWebSocket(conn *WebConn, r *model.WebSocketReque
} }
func ReturnWebSocketError(conn *WebConn, r *model.WebSocketRequest, err *model.AppError) { func ReturnWebSocketError(conn *WebConn, r *model.WebSocketRequest, err *model.AppError) {
mlog.Error(fmt.Sprintf("websocket routing error: seq=%v uid=%v %v [details: %v]", r.Seq, conn.UserId, err.SystemMessage(utils.T), err.DetailedError)) mlog.Error(
"websocket routing error.",
mlog.Int64("seq", r.Seq),
mlog.String("user_id", conn.UserId),
mlog.String("system_message", err.SystemMessage(utils.T)),
mlog.Err(err),
)
err.DetailedError = "" err.DetailedError = ""
errorResp := model.NewWebSocketError(r.Seq, err) errorResp := model.NewWebSocketError(r.Seq, err)

Просмотреть файл

@@ -136,16 +136,16 @@ func TestConfigSet(t *testing.T) {
}) })
t.Run("Error when the wrong value is set", func(t *testing.T) { t.Run("Error when the wrong value is set", func(t *testing.T) {
assert.Error(t, th.RunCommand(t, "config", "set", "EmailSettings.ConnectionSecurity", "invalid")) assert.Error(t, th.RunCommand(t, "config", "set", "EmailSettings.ConnectionSecurity", "invalid-key"))
output := th.CheckCommand(t, "config", "get", "EmailSettings.ConnectionSecurity") output := th.CheckCommand(t, "config", "get", "EmailSettings.ConnectionSecurity")
assert.NotContains(t, string(output), "invalid") assert.NotContains(t, string(output), "invalid-key")
}) })
t.Run("Error when the wrong locale is set", func(t *testing.T) { t.Run("Error when the wrong locale is set", func(t *testing.T) {
th.CheckCommand(t, "config", "set", "LocalizationSettings.DefaultServerLocale", "es") th.CheckCommand(t, "config", "set", "LocalizationSettings.DefaultServerLocale", "es")
assert.Error(t, th.RunCommand(t, "config", "set", "LocalizationSettings.DefaultServerLocale", "invalid")) assert.Error(t, th.RunCommand(t, "config", "set", "LocalizationSettings.DefaultServerLocale", "invalid-key"))
output := th.CheckCommand(t, "config", "get", "LocalizationSettings.DefaultServerLocale") output := th.CheckCommand(t, "config", "get", "LocalizationSettings.DefaultServerLocale")
assert.NotContains(t, string(output), "invalid") assert.NotContains(t, string(output), "invalid-key")
assert.NotContains(t, string(output), "\"en\"") assert.NotContains(t, string(output), "\"en\"")
}) })

Просмотреть файл

@@ -22,7 +22,7 @@ func prettyPrintStruct(t interface{}) string {
func structToMap(t interface{}) map[string]interface{} { func structToMap(t interface{}) map[string]interface{} {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
mlog.Error(fmt.Sprintf("Panicked in structToMap. This should never happen. %v", r)) mlog.Error("Panicked in structToMap. This should never happen.", mlog.Any("recover", r))
} }
}() }()

Просмотреть файл

@@ -16,9 +16,9 @@ import (
// Enterprise Deps // Enterprise Deps
_ "github.com/dgryski/dgoogauth" _ "github.com/dgryski/dgoogauth"
_ "github.com/go-ldap/ldap"
_ "github.com/hako/durafmt" _ "github.com/hako/durafmt"
_ "github.com/hashicorp/memberlist" _ "github.com/hashicorp/memberlist"
_ "github.com/mattermost/ldap"
_ "github.com/mattermost/rsc/qr" _ "github.com/mattermost/rsc/qr"
_ "github.com/prometheus/client_golang/prometheus" _ "github.com/prometheus/client_golang/prometheus"
_ "github.com/prometheus/client_golang/prometheus/promhttp" _ "github.com/prometheus/client_golang/prometheus/promhttp"

Просмотреть файл

@@ -43,6 +43,7 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L
props["ExperimentalEnableDefaultChannelLeaveJoinMessages"] = strconv.FormatBool(*c.ServiceSettings.ExperimentalEnableDefaultChannelLeaveJoinMessages) props["ExperimentalEnableDefaultChannelLeaveJoinMessages"] = strconv.FormatBool(*c.ServiceSettings.ExperimentalEnableDefaultChannelLeaveJoinMessages)
props["ExperimentalGroupUnreadChannels"] = *c.ServiceSettings.ExperimentalGroupUnreadChannels props["ExperimentalGroupUnreadChannels"] = *c.ServiceSettings.ExperimentalGroupUnreadChannels
props["EnableSVGs"] = strconv.FormatBool(*c.ServiceSettings.EnableSVGs) props["EnableSVGs"] = strconv.FormatBool(*c.ServiceSettings.EnableSVGs)
props["EnableMarketplace"] = strconv.FormatBool(*c.PluginSettings.EnableMarketplace)
// This setting is only temporary, so keep using the old setting name for the mobile and web apps // This setting is only temporary, so keep using the old setting name for the mobile and web apps
props["ExperimentalEnablePostMetadata"] = "true" props["ExperimentalEnablePostMetadata"] = "true"

4
go.mod
Просмотреть файл

@@ -16,7 +16,7 @@ require (
github.com/fortytw2/leaktest v1.3.0 // indirect github.com/fortytw2/leaktest v1.3.0 // indirect
github.com/fsnotify/fsnotify v1.4.7 github.com/fsnotify/fsnotify v1.4.7
github.com/go-gorp/gorp v2.0.0+incompatible // indirect github.com/go-gorp/gorp v2.0.0+incompatible // indirect
github.com/go-ldap/ldap v3.0.3+incompatible github.com/go-ldap/ldap v3.0.3+incompatible // indirect
github.com/go-redis/redis v6.15.2+incompatible github.com/go-redis/redis v6.15.2+incompatible
github.com/go-sql-driver/mysql v1.4.1 github.com/go-sql-driver/mysql v1.4.1
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
@@ -45,6 +45,7 @@ require (
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect
github.com/mattermost/go-i18n v1.11.0 github.com/mattermost/go-i18n v1.11.0
github.com/mattermost/gorp v2.0.1-0.20190301154413-3b31e9a39d05+incompatible github.com/mattermost/gorp v2.0.1-0.20190301154413-3b31e9a39d05+incompatible
github.com/mattermost/ldap v3.0.4+incompatible
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0 github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0
github.com/mattermost/viper v1.0.4 github.com/mattermost/viper v1.0.4
github.com/mattn/go-runewidth v0.0.4 // indirect github.com/mattn/go-runewidth v0.0.4 // indirect
@@ -87,6 +88,7 @@ require (
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 golang.org/x/net v0.0.0-20190628185345-da137c7871d7
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 // indirect golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 // indirect
golang.org/x/text v0.3.2 golang.org/x/text v0.3.2
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b
google.golang.org/appengine v1.6.1 // indirect google.golang.org/appengine v1.6.1 // indirect
google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610 // indirect google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610 // indirect
google.golang.org/grpc v1.22.0 // indirect google.golang.org/grpc v1.22.0 // indirect

4
go.sum
Просмотреть файл

@@ -24,6 +24,7 @@ github.com/a8m/mark v0.1.1-0.20170507133748-44f2db618845/go.mod h1:c8Mh99Cw82nrs
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I=
@@ -239,6 +240,8 @@ github.com/mattermost/go-i18n v1.11.0 h1:1hLKqn/ZvhZ80OekjVPGYcCrBfMz+YxNNgqS+be
github.com/mattermost/go-i18n v1.11.0/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34= github.com/mattermost/go-i18n v1.11.0/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34=
github.com/mattermost/gorp v2.0.1-0.20190301154413-3b31e9a39d05+incompatible h1:FN4zK2wNig7MVVsOsGEZ+LeIq0gUcudn3LEGgbodMq8= github.com/mattermost/gorp v2.0.1-0.20190301154413-3b31e9a39d05+incompatible h1:FN4zK2wNig7MVVsOsGEZ+LeIq0gUcudn3LEGgbodMq8=
github.com/mattermost/gorp v2.0.1-0.20190301154413-3b31e9a39d05+incompatible/go.mod h1:0kX1qa3DOpaPJyOdMLeo7TcBN0QmUszj9a/VygOhDe0= github.com/mattermost/gorp v2.0.1-0.20190301154413-3b31e9a39d05+incompatible/go.mod h1:0kX1qa3DOpaPJyOdMLeo7TcBN0QmUszj9a/VygOhDe0=
github.com/mattermost/ldap v3.0.4+incompatible h1:SOeNnz+JNR+foQ3yHkYqijb9MLPhXN2BZP/PdX23VDU=
github.com/mattermost/ldap v3.0.4+incompatible/go.mod h1:b4reDCcGpBxJ4WX0f224KFY+OR0npin7or7EFpeIko4=
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0 h1:G9tL6JXRBMzjuD1kkBtcnd42kUiT6QDwxfFYu7adM6o= github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0 h1:G9tL6JXRBMzjuD1kkBtcnd42kUiT6QDwxfFYu7adM6o=
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0/go.mod h1:nV5bfVpT//+B1RPD2JvRnxbkLmJEYXmRaaVl15fsXjs= github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0/go.mod h1:nV5bfVpT//+B1RPD2JvRnxbkLmJEYXmRaaVl15fsXjs=
github.com/mattermost/viper v1.0.4 h1:cMYOz4PhguscGSPxrSokUtib5HrG4gCpiUh27wyA3d0= github.com/mattermost/viper v1.0.4 h1:cMYOz4PhguscGSPxrSokUtib5HrG4gCpiUh27wyA3d0=
@@ -506,6 +509,7 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b h1:mSUCVIwDx4hfXJfWsOPfdzEHxzb2Xjl6BQ8YgPnazQA=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=

Просмотреть файл

@@ -6022,10 +6022,6 @@
"id": "store.sql_post.permanent_delete_by_user.too_many.app_error", "id": "store.sql_post.permanent_delete_by_user.too_many.app_error",
"translation": "Konnte die zu löschenden Nachrichten für den Benutzer nicht auswählen (zu viele), bitte wiederholen." "translation": "Konnte die zu löschenden Nachrichten für den Benutzer nicht auswählen (zu viele), bitte wiederholen."
}, },
{
"id": "store.sql_post.query_max_post_size.error",
"translation": "Konnte die maximal unterstützte Nachrichtengröße nicht bestimmen."
},
{ {
"id": "store.sql_post.save.app_error", "id": "store.sql_post.save.app_error",
"translation": "Konnte die Nachricht nicht speichern." "translation": "Konnte die Nachricht nicht speichern."

Просмотреть файл

@@ -387,6 +387,10 @@
"id": "api.channel.update_channel_member_roles.scheme_role.app_error", "id": "api.channel.update_channel_member_roles.scheme_role.app_error",
"translation": "The provided role is managed by a Scheme and therefore cannot be applied directly to a Channel Member" "translation": "The provided role is managed by a Scheme and therefore cannot be applied directly to a Channel Member"
}, },
{
"id": "api.channel.update_channel_privacy.default_channel_error",
"translation": "The default channel cannot be made private."
},
{ {
"id": "api.channel.update_channel_scheme.license.error", "id": "api.channel.update_channel_scheme.license.error",
"translation": "Your license does not support updating a channel's scheme" "translation": "Your license does not support updating a channel's scheme"
@@ -3376,7 +3380,7 @@
}, },
{ {
"id": "app.notification.body.intro.direct.generic", "id": "app.notification.body.intro.direct.generic",
"translation": "You have a new Direct Message from @{{.SenderName}}" "translation": "You have a new Direct Message from {{.SenderName}}"
}, },
{ {
"id": "app.notification.body.intro.group_message.full", "id": "app.notification.body.intro.group_message.full",
@@ -3384,7 +3388,7 @@
}, },
{ {
"id": "app.notification.body.intro.group_message.generic", "id": "app.notification.body.intro.group_message.generic",
"translation": "You have a new Group Message from @{{.SenderName}}" "translation": "You have a new Group Message from {{.SenderName}}"
}, },
{ {
"id": "app.notification.body.intro.notification.full", "id": "app.notification.body.intro.notification.full",
@@ -3392,11 +3396,11 @@
}, },
{ {
"id": "app.notification.body.intro.notification.generic", "id": "app.notification.body.intro.notification.generic",
"translation": "You have a new notification from @{{.SenderName}}" "translation": "You have a new notification from {{.SenderName}}"
}, },
{ {
"id": "app.notification.body.text.direct.full", "id": "app.notification.body.text.direct.full",
"translation": "@{{.SenderName}} - {{.Hour}}:{{.Minute}} {{.TimeZone}}, {{.Month}} {{.Day}}" "translation": "{{.SenderName}} - {{.Hour}}:{{.Minute}} {{.TimeZone}}, {{.Month}} {{.Day}}"
}, },
{ {
"id": "app.notification.body.text.direct.generic", "id": "app.notification.body.text.direct.generic",
@@ -3408,7 +3412,7 @@
}, },
{ {
"id": "app.notification.body.text.group_message.full2", "id": "app.notification.body.text.group_message.full2",
"translation": "@{{.SenderName}} - {{.Hour}}:{{.Minute}} {{.TimeZone}}, {{.Month}} {{.Day}}" "translation": "{{.SenderName}} - {{.Hour}}:{{.Minute}} {{.TimeZone}}, {{.Month}} {{.Day}}"
}, },
{ {
"id": "app.notification.body.text.group_message.generic", "id": "app.notification.body.text.group_message.generic",
@@ -3420,7 +3424,7 @@
}, },
{ {
"id": "app.notification.body.text.notification.full2", "id": "app.notification.body.text.notification.full2",
"translation": "@{{.SenderName}} - {{.Hour}}:{{.Minute}} {{.TimeZone}}, {{.Month}} {{.Day}}" "translation": "{{.SenderName}} - {{.Hour}}:{{.Minute}} {{.TimeZone}}, {{.Month}} {{.Day}}"
}, },
{ {
"id": "app.notification.body.text.notification.generic", "id": "app.notification.body.text.notification.generic",
@@ -3502,6 +3506,22 @@
"id": "app.plugin.manifest.app_error", "id": "app.plugin.manifest.app_error",
"translation": "Unable to find manifest for extracted plugin" "translation": "Unable to find manifest for extracted plugin"
}, },
{
"id": "app.plugin.marketplace_client.app_error",
"translation": "Failed to create marketplace client."
},
{
"id": "app.plugin.marketplace_disabled.app_error",
"translation": "Marketplace has been disabled. Please check your logs for details."
},
{
"id": "app.plugin.marketplace_plugins.app_error",
"translation": "Failed to get plugins from the marketplace server."
},
{
"id": "app.plugin.marshal.app_error",
"translation": "Failed to marshal marketplace plugins."
},
{ {
"id": "app.plugin.mvdir.app_error", "id": "app.plugin.mvdir.app_error",
"translation": "Unable to move plugin from temporary directory to final destination. Another plugin may be using the same directory name." "translation": "Unable to move plugin from temporary directory to final destination. Another plugin may be using the same directory name."
@@ -6362,10 +6382,6 @@
"id": "store.sql_post.permanent_delete_by_user.too_many.app_error", "id": "store.sql_post.permanent_delete_by_user.too_many.app_error",
"translation": "Unable to select the posts to delete for the user (too many), please re-run" "translation": "Unable to select the posts to delete for the user (too many), please re-run"
}, },
{
"id": "store.sql_post.query_max_post_size.error",
"translation": "Unable to determine the maximum supported post size"
},
{ {
"id": "store.sql_post.save.app_error", "id": "store.sql_post.save.app_error",
"translation": "Unable to save the Post" "translation": "Unable to save the Post"

Просмотреть файл

@@ -6022,10 +6022,6 @@
"id": "store.sql_post.permanent_delete_by_user.too_many.app_error", "id": "store.sql_post.permanent_delete_by_user.too_many.app_error",
"translation": "No se puede seleccionar los mensajes a eliminar del usuario (son demasiados), por favor ejecuta de nuevo" "translation": "No se puede seleccionar los mensajes a eliminar del usuario (son demasiados), por favor ejecuta de nuevo"
}, },
{
"id": "store.sql_post.query_max_post_size.error",
"translation": "No se puede determinar el tamaño máximo soportado de los mensajes"
},
{ {
"id": "store.sql_post.save.app_error", "id": "store.sql_post.save.app_error",
"translation": "No se puede guardar el mensaje" "translation": "No se puede guardar el mensaje"

Просмотреть файл

@@ -6022,10 +6022,6 @@
"id": "store.sql_post.permanent_delete_by_user.too_many.app_error", "id": "store.sql_post.permanent_delete_by_user.too_many.app_error",
"translation": "Impossible de sélectionner les messages à supprimer pour l'utilisateur (ils sont trop nombreux), veuillez relancer l'opération" "translation": "Impossible de sélectionner les messages à supprimer pour l'utilisateur (ils sont trop nombreux), veuillez relancer l'opération"
}, },
{
"id": "store.sql_post.query_max_post_size.error",
"translation": "Impossible de déterminer la taille maximale supportée pour les messages"
},
{ {
"id": "store.sql_post.save.app_error", "id": "store.sql_post.save.app_error",
"translation": "Impossible de sauvegarder le message" "translation": "Impossible de sauvegarder le message"

Просмотреть файл

@@ -6022,10 +6022,6 @@
"id": "store.sql_post.permanent_delete_by_user.too_many.app_error", "id": "store.sql_post.permanent_delete_by_user.too_many.app_error",
"translation": "Non è stato possibile selezionare le pubblicazioni da eliminare per l'utente (troppe pubblicazioni), per favore rilancia" "translation": "Non è stato possibile selezionare le pubblicazioni da eliminare per l'utente (troppe pubblicazioni), per favore rilancia"
}, },
{
"id": "store.sql_post.query_max_post_size.error",
"translation": "Impossibile determinare la dimensione massima supportata per le pubblicazioni"
},
{ {
"id": "store.sql_post.save.app_error", "id": "store.sql_post.save.app_error",
"translation": "Impossibile salvare la pubblicazione" "translation": "Impossibile salvare la pubblicazione"

Просмотреть файл

@@ -6022,10 +6022,6 @@
"id": "store.sql_post.permanent_delete_by_user.too_many.app_error", "id": "store.sql_post.permanent_delete_by_user.too_many.app_error",
"translation": "ユーザーの削除すべき投稿を選択できませんでした(数が多過ぎます)。再度実行してください" "translation": "ユーザーの削除すべき投稿を選択できませんでした(数が多過ぎます)。再度実行してください"
}, },
{
"id": "store.sql_post.query_max_post_size.error",
"translation": "投稿サイズの最大値を定義できませんでした"
},
{ {
"id": "store.sql_post.save.app_error", "id": "store.sql_post.save.app_error",
"translation": "投稿を保存できませんでした" "translation": "投稿を保存できませんでした"

Просмотреть файл

@@ -6022,10 +6022,6 @@
"id": "store.sql_post.permanent_delete_by_user.too_many.app_error", "id": "store.sql_post.permanent_delete_by_user.too_many.app_error",
"translation": "We couldn't select the posts to delete for the user (too many), please re-run" "translation": "We couldn't select the posts to delete for the user (too many), please re-run"
}, },
{
"id": "store.sql_post.query_max_post_size.error",
"translation": "Unable to determine the maximum supported post size"
},
{ {
"id": "store.sql_post.save.app_error", "id": "store.sql_post.save.app_error",
"translation": "내용을 가져올수 없습니다." "translation": "내용을 가져올수 없습니다."

Просмотреть файл

@@ -6022,10 +6022,6 @@
"id": "store.sql_post.permanent_delete_by_user.too_many.app_error", "id": "store.sql_post.permanent_delete_by_user.too_many.app_error",
"translation": "We kunnen de geselecteerde berichten voor de gebruiker niet verwijderen (het zijn er te veel), probeer opnieuw" "translation": "We kunnen de geselecteerde berichten voor de gebruiker niet verwijderen (het zijn er te veel), probeer opnieuw"
}, },
{
"id": "store.sql_post.query_max_post_size.error",
"translation": "Unable to determine the maximum supported post size"
},
{ {
"id": "store.sql_post.save.app_error", "id": "store.sql_post.save.app_error",
"translation": "Bericht kan niet opgehaald worden" "translation": "Bericht kan niet opgehaald worden"

Просмотреть файл

@@ -6022,10 +6022,6 @@
"id": "store.sql_post.permanent_delete_by_user.too_many.app_error", "id": "store.sql_post.permanent_delete_by_user.too_many.app_error",
"translation": "Nie można wybrać postów do usunięcia dla użytkownika (zbyt wielu), uruchom ponownie" "translation": "Nie można wybrać postów do usunięcia dla użytkownika (zbyt wielu), uruchom ponownie"
}, },
{
"id": "store.sql_post.query_max_post_size.error",
"translation": "Nie można określić maksymalnego obsługiwanego rozmiaru postu"
},
{ {
"id": "store.sql_post.save.app_error", "id": "store.sql_post.save.app_error",
"translation": "Nie można zapisać wpisu" "translation": "Nie można zapisać wpisu"

Просмотреть файл

@@ -6022,10 +6022,6 @@
"id": "store.sql_post.permanent_delete_by_user.too_many.app_error", "id": "store.sql_post.permanent_delete_by_user.too_many.app_error",
"translation": "Não foi possível selecionar as publicações para excluir do usuário (muitos), por favor, re-executar" "translation": "Não foi possível selecionar as publicações para excluir do usuário (muitos), por favor, re-executar"
}, },
{
"id": "store.sql_post.query_max_post_size.error",
"translation": "Não foi possível determinar o tamanho máximo de publicação suportado"
},
{ {
"id": "store.sql_post.save.app_error", "id": "store.sql_post.save.app_error",
"translation": "Não foi possível salvar a Publicação" "translation": "Não foi possível salvar a Publicação"

Просмотреть файл

@@ -6022,10 +6022,6 @@
"id": "store.sql_post.permanent_delete_by_user.too_many.app_error", "id": "store.sql_post.permanent_delete_by_user.too_many.app_error",
"translation": "Am putut selectaţi mesaje pentru a şterge pentru utilizator (prea multe), vă rugăm să re-rula" "translation": "Am putut selectaţi mesaje pentru a şterge pentru utilizator (prea multe), vă rugăm să re-rula"
}, },
{
"id": "store.sql_post.query_max_post_size.error",
"translation": "Noi nu a putut determina dimensiunea maximă suportate de post"
},
{ {
"id": "store.sql_post.save.app_error", "id": "store.sql_post.save.app_error",
"translation": "Imposibil de salvat postarea" "translation": "Imposibil de salvat postarea"

Просмотреть файл

@@ -6022,10 +6022,6 @@
"id": "store.sql_post.permanent_delete_by_user.too_many.app_error", "id": "store.sql_post.permanent_delete_by_user.too_many.app_error",
"translation": "Не удалось выбрать для удаления посты пользователя (слишком много), пожалуйста, запустите повторно" "translation": "Не удалось выбрать для удаления посты пользователя (слишком много), пожалуйста, запустите повторно"
}, },
{
"id": "store.sql_post.query_max_post_size.error",
"translation": "Unable to determine the maximum supported post size"
},
{ {
"id": "store.sql_post.save.app_error", "id": "store.sql_post.save.app_error",
"translation": "Не удалось удалить плагин" "translation": "Не удалось удалить плагин"

Просмотреть файл

@@ -6022,10 +6022,6 @@
"id": "store.sql_post.permanent_delete_by_user.too_many.app_error", "id": "store.sql_post.permanent_delete_by_user.too_many.app_error",
"translation": "Kullanıcının silinecek iletileri seçilemedi (çok fazla), lütfen yeniden çalıştırın" "translation": "Kullanıcının silinecek iletileri seçilemedi (çok fazla), lütfen yeniden çalıştırın"
}, },
{
"id": "store.sql_post.query_max_post_size.error",
"translation": "Desteklenen en büyük ileti boyutu belirlenemedi"
},
{ {
"id": "store.sql_post.save.app_error", "id": "store.sql_post.save.app_error",
"translation": "İleti kaydedilemedi" "translation": "İleti kaydedilemedi"

Просмотреть файл

@@ -6022,10 +6022,6 @@
"id": "store.sql_post.permanent_delete_by_user.too_many.app_error", "id": "store.sql_post.permanent_delete_by_user.too_many.app_error",
"translation": "Не вдається вибрати повідомлення, які потрібно видалити для користувача (забагато), будь ласка, повторно запустіть" "translation": "Не вдається вибрати повідомлення, які потрібно видалити для користувача (забагато), будь ласка, повторно запустіть"
}, },
{
"id": "store.sql_post.query_max_post_size.error",
"translation": "Ми не змогли визначити максимально підтримуваний розмір повідомлення"
},
{ {
"id": "store.sql_post.save.app_error", "id": "store.sql_post.save.app_error",
"translation": "Не вдається зберегти публікацію" "translation": "Не вдається зберегти публікацію"

Просмотреть файл

@@ -6022,10 +6022,6 @@
"id": "store.sql_post.permanent_delete_by_user.too_many.app_error", "id": "store.sql_post.permanent_delete_by_user.too_many.app_error",
"translation": "无法删除该用户被选择的信息(数量太多),请重新运行" "translation": "无法删除该用户被选择的信息(数量太多),请重新运行"
}, },
{
"id": "store.sql_post.query_max_post_size.error",
"translation": "无法判断最大支持的消息大小"
},
{ {
"id": "store.sql_post.save.app_error", "id": "store.sql_post.save.app_error",
"translation": "无法保存消息" "translation": "无法保存消息"

Просмотреть файл

@@ -6022,10 +6022,6 @@
"id": "store.sql_post.permanent_delete_by_user.too_many.app_error", "id": "store.sql_post.permanent_delete_by_user.too_many.app_error",
"translation": "無法選擇該使用者的訊息以刪除 (數量太多),請重新執行" "translation": "無法選擇該使用者的訊息以刪除 (數量太多),請重新執行"
}, },
{
"id": "store.sql_post.query_max_post_size.error",
"translation": "無法判定最大支援的訊息大小"
},
{ {
"id": "store.sql_post.save.app_error", "id": "store.sql_post.save.app_error",
"translation": "無法儲存訊息" "translation": "無法儲存訊息"

Просмотреть файл

@@ -5,7 +5,6 @@ package jobs
import ( import (
"context" "context"
"fmt"
"time" "time"
"net/http" "net/http"
@@ -135,10 +134,10 @@ func (srv *JobServer) CancellationWatcher(ctx context.Context, jobId string, can
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
mlog.Debug(fmt.Sprintf("CancellationWatcher for Job: %v Aborting as job has finished.", jobId)) mlog.Debug("CancellationWatcher for Job Aborting as job has finished.", mlog.String("job_id", jobId))
return return
case <-time.After(CANCEL_WATCHER_POLLING_INTERVAL * time.Millisecond): case <-time.After(CANCEL_WATCHER_POLLING_INTERVAL * time.Millisecond):
mlog.Debug(fmt.Sprintf("CancellationWatcher for Job: %v polling.", jobId)) mlog.Debug("CancellationWatcher for Job started polling.", mlog.String("job_id", jobId))
if jobStatus, err := srv.Store.Job().Get(jobId); err == nil { if jobStatus, err := srv.Store.Job().Get(jobId); err == nil {
if jobStatus.Status == model.JOB_STATUS_CANCEL_REQUESTED { if jobStatus.Status == model.JOB_STATUS_CANCEL_REQUESTED {
close(cancelChan) close(cancelChan)

Просмотреть файл

@@ -4,7 +4,6 @@
package jobs package jobs
import ( import (
"fmt"
"math/rand" "math/rand"
"time" "time"
@@ -68,7 +67,7 @@ func (watcher *Watcher) Stop() {
func (watcher *Watcher) PollAndNotify() { func (watcher *Watcher) PollAndNotify() {
jobs, err := watcher.srv.Store.Job().GetAllByStatus(model.JOB_STATUS_PENDING) jobs, err := watcher.srv.Store.Job().GetAllByStatus(model.JOB_STATUS_PENDING)
if err != nil { if err != nil {
mlog.Error(fmt.Sprintf("Error occurred getting all pending statuses: %v", err.Error())) mlog.Error("Error occurred getting all pending statuses.", mlog.Err(err))
return return
} }

Просмотреть файл

@@ -28,6 +28,7 @@ const (
type Field = zapcore.Field type Field = zapcore.Field
var Int64 = zap.Int64 var Int64 = zap.Int64
var Int32 = zap.Int32
var Int = zap.Int var Int = zap.Int
var Uint32 = zap.Uint32 var Uint32 = zap.Uint32
var String = zap.String var String = zap.String

Просмотреть файл

@@ -2100,6 +2100,17 @@ func (c *Client4) ConvertChannelToPrivate(channelId string) (*Channel, *Response
return ChannelFromJson(r.Body), BuildResponse(r) return ChannelFromJson(r.Body), BuildResponse(r)
} }
// UpdateChannelPrivacy updates channel privacy
func (c *Client4) UpdateChannelPrivacy(channelId string, privacy string) (*Channel, *Response) {
requestBody := map[string]string{"privacy": privacy}
r, err := c.DoApiPut(c.GetChannelRoute(channelId)+"/privacy", MapToJson(requestBody))
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
return ChannelFromJson(r.Body), BuildResponse(r)
}
// RestoreChannel restores a previously deleted channel. Any missing fields are not updated. // RestoreChannel restores a previously deleted channel. Any missing fields are not updated.
func (c *Client4) RestoreChannel(channelId string) (*Channel, *Response) { func (c *Client4) RestoreChannel(channelId string) (*Channel, *Response) {
r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/restore", "") r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/restore", "")
@@ -4518,6 +4529,31 @@ func (c *Client4) DisablePlugin(id string) (bool, *Response) {
return CheckStatusOK(r), BuildResponse(r) return CheckStatusOK(r), BuildResponse(r)
} }
// GetMarketplacePlugins will return a list of plugins that an admin can install.
// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE.
func (c *Client4) GetMarketplacePlugins(filter *MarketplacePluginFilter) ([]*MarketplacePlugin, *Response) {
route := c.GetPluginsRoute() + "/marketplace"
u, parseErr := url.Parse(route)
if parseErr != nil {
return nil, &Response{Error: NewAppError("GetMarketplacePlugins", "model.client.parse_plugins.app_error", nil, parseErr.Error(), http.StatusBadRequest)}
}
filter.ApplyToURL(u)
r, err := c.DoApiGet(u.String(), "")
if err != nil {
return nil, BuildErrorResponse(r, err)
}
defer closeBody(r)
plugins, readerErr := MarketplacePluginsFromReader(r.Body)
if readerErr != nil {
return nil, BuildErrorResponse(r, NewAppError(route, "model.client.parse_plugins.app_error", nil, err.Error(), http.StatusBadRequest))
}
return plugins, BuildResponse(r)
}
// UpdateChannelScheme will update a channel's scheme. // UpdateChannelScheme will update a channel's scheme.
func (c *Client4) UpdateChannelScheme(channelId, schemeId string) (bool, *Response) { func (c *Client4) UpdateChannelScheme(channelId, schemeId string) (bool, *Response) {
sip := &SchemeIDPatch{SchemeID: &schemeId} sip := &SchemeIDPatch{SchemeID: &schemeId}

Просмотреть файл

@@ -17,7 +17,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/go-ldap/ldap" "github.com/mattermost/ldap"
) )
const ( const (
@@ -171,8 +171,10 @@ const (
DATA_RETENTION_SETTINGS_DEFAULT_FILE_RETENTION_DAYS = 365 DATA_RETENTION_SETTINGS_DEFAULT_FILE_RETENTION_DAYS = 365
DATA_RETENTION_SETTINGS_DEFAULT_DELETION_JOB_START_TIME = "02:00" DATA_RETENTION_SETTINGS_DEFAULT_DELETION_JOB_START_TIME = "02:00"
PLUGIN_SETTINGS_DEFAULT_DIRECTORY = "./plugins" PLUGIN_SETTINGS_DEFAULT_DIRECTORY = "./plugins"
PLUGIN_SETTINGS_DEFAULT_CLIENT_DIRECTORY = "./client/plugins" PLUGIN_SETTINGS_DEFAULT_CLIENT_DIRECTORY = "./client/plugins"
PLUGIN_SETTINGS_DEFAULT_ENABLE_MARKETPLACE = true
PLUGIN_SETTINGS_DEFAULT_MARKETPLACE_URL = "https://marketplace.integrations.mattermost.com"
COMPLIANCE_EXPORT_TYPE_CSV = "csv" COMPLIANCE_EXPORT_TYPE_CSV = "csv"
COMPLIANCE_EXPORT_TYPE_ACTIANCE = "actiance" COMPLIANCE_EXPORT_TYPE_ACTIANCE = "actiance"
@@ -2201,6 +2203,8 @@ type PluginSettings struct {
ClientDirectory *string `restricted:"true"` ClientDirectory *string `restricted:"true"`
Plugins map[string]map[string]interface{} Plugins map[string]map[string]interface{}
PluginStates map[string]*PluginState PluginStates map[string]*PluginState
EnableMarketplace *bool
MarketplaceUrl *string
} }
func (s *PluginSettings) SetDefaults(ls LogSettings) { func (s *PluginSettings) SetDefaults(ls LogSettings) {
@@ -2220,22 +2224,14 @@ func (s *PluginSettings) SetDefaults(ls LogSettings) {
s.EnableHealthCheck = NewBool(true) s.EnableHealthCheck = NewBool(true)
} }
if s.Directory == nil { if s.Directory == nil || *s.Directory == "" {
s.Directory = NewString(PLUGIN_SETTINGS_DEFAULT_DIRECTORY) s.Directory = NewString(PLUGIN_SETTINGS_DEFAULT_DIRECTORY)
} }
if *s.Directory == "" { if s.ClientDirectory == nil || *s.ClientDirectory == "" {
*s.Directory = PLUGIN_SETTINGS_DEFAULT_DIRECTORY
}
if s.ClientDirectory == nil {
s.ClientDirectory = NewString(PLUGIN_SETTINGS_DEFAULT_CLIENT_DIRECTORY) s.ClientDirectory = NewString(PLUGIN_SETTINGS_DEFAULT_CLIENT_DIRECTORY)
} }
if *s.ClientDirectory == "" {
*s.ClientDirectory = PLUGIN_SETTINGS_DEFAULT_CLIENT_DIRECTORY
}
if s.Plugins == nil { if s.Plugins == nil {
s.Plugins = make(map[string]map[string]interface{}) s.Plugins = make(map[string]map[string]interface{})
} }
@@ -2248,6 +2244,14 @@ func (s *PluginSettings) SetDefaults(ls LogSettings) {
// Enable the NPS plugin by default if diagnostics are enabled // Enable the NPS plugin by default if diagnostics are enabled
s.PluginStates["com.mattermost.nps"] = &PluginState{Enable: ls.EnableDiagnostics == nil || *ls.EnableDiagnostics} s.PluginStates["com.mattermost.nps"] = &PluginState{Enable: ls.EnableDiagnostics == nil || *ls.EnableDiagnostics}
} }
if s.EnableMarketplace == nil {
s.EnableMarketplace = NewBool(PLUGIN_SETTINGS_DEFAULT_ENABLE_MARKETPLACE)
}
if s.MarketplaceUrl == nil || *s.MarketplaceUrl == "" {
s.MarketplaceUrl = NewString(PLUGIN_SETTINGS_DEFAULT_MARKETPLACE_URL)
}
} }
type GlobalRelayMessageExportSettings struct { type GlobalRelayMessageExportSettings struct {

Просмотреть файл

@@ -178,13 +178,14 @@ type PostActionAPIResponse struct {
} }
type Dialog struct { type Dialog struct {
CallbackId string `json:"callback_id"` CallbackId string `json:"callback_id"`
Title string `json:"title"` Title string `json:"title"`
IconURL string `json:"icon_url"` IntroductionText string `json:"introduction_text"`
Elements []DialogElement `json:"elements"` IconURL string `json:"icon_url"`
SubmitLabel string `json:"submit_label"` Elements []DialogElement `json:"elements"`
NotifyOnCancel bool `json:"notify_on_cancel"` SubmitLabel string `json:"submit_label"`
State string `json:"state"` NotifyOnCancel bool `json:"notify_on_cancel"`
State string `json:"state"`
} }
type DialogElement struct { type DialogElement struct {
@@ -221,6 +222,7 @@ type SubmitDialogRequest struct {
} }
type SubmitDialogResponse struct { type SubmitDialogResponse struct {
Error string `json:"error,omitempty"`
Errors map[string]string `json:"errors,omitempty"` Errors map[string]string `json:"errors,omitempty"`
} }

Просмотреть файл

@@ -141,6 +141,7 @@ func TestSubmitDialogRequestToJson(t *testing.T) {
func TestSubmitDialogResponseToJson(t *testing.T) { func TestSubmitDialogResponseToJson(t *testing.T) {
t.Run("all fine", func(t *testing.T) { t.Run("all fine", func(t *testing.T) {
request := SubmitDialogResponse{ request := SubmitDialogResponse{
Error: "some generic error",
Errors: map[string]string{ Errors: map[string]string{
"text": "some text", "text": "some text",
"float": "1.2", "float": "1.2",

69
model/marketplace_plugin.go Обычный файл
Просмотреть файл

@@ -0,0 +1,69 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"io"
"net/url"
"strconv"
)
// BaseMarketplacePlugin is a Mattermost plugin received from the marketplace server.
type BaseMarketplacePlugin struct {
HomepageURL string `json:"homepage_url"`
DownloadURL string `json:"download_url"`
IconData string `json:"icon_data"`
Manifest *Manifest `json:"manifest"`
}
// MarketplacePlugin is a state aware marketplace plugin.
type MarketplacePlugin struct {
*BaseMarketplacePlugin
InstalledVersion string `json:"installed_version"`
}
// BaseMarketplacePluginsFromReader decodes a json-encoded list of plugins from the given io.Reader.
func BaseMarketplacePluginsFromReader(reader io.Reader) ([]*BaseMarketplacePlugin, error) {
plugins := []*BaseMarketplacePlugin{}
decoder := json.NewDecoder(reader)
if err := decoder.Decode(&plugins); err != nil && err != io.EOF {
return nil, err
}
return plugins, nil
}
// MarketplacePluginsFromReader decodes a json-encoded list of plugins from the given io.Reader.
func MarketplacePluginsFromReader(reader io.Reader) ([]*MarketplacePlugin, error) {
plugins := []*MarketplacePlugin{}
decoder := json.NewDecoder(reader)
if err := decoder.Decode(&plugins); err != nil && err != io.EOF {
return nil, err
}
return plugins, nil
}
// MarketplacePluginFilter describes the parameters to request a list of plugins.
type MarketplacePluginFilter struct {
Page int
PerPage int
Filter string
ServerVersion string
}
// ApplyToURL modifies the given url to include query string parameters for the request.
func (filter *MarketplacePluginFilter) ApplyToURL(u *url.URL) {
q := u.Query()
q.Add("page", strconv.Itoa(filter.Page))
if filter.PerPage > 0 {
q.Add("per_page", strconv.Itoa(filter.PerPage))
}
q.Add("filter", filter.Filter)
q.Add("server_version", filter.ServerVersion)
u.RawQuery = q.Encode()
}

Просмотреть файл

@@ -20,6 +20,7 @@ type MessageExport struct {
PostId *string PostId *string
PostCreateAt *int64 PostCreateAt *int64
PostUpdateAt *int64
PostMessage *string PostMessage *string
PostType *string PostType *string
PostRootId *string PostRootId *string

Просмотреть файл

@@ -73,7 +73,6 @@ type Post struct {
OriginalId string `json:"original_id"` OriginalId string `json:"original_id"`
Message string `json:"message"` Message string `json:"message"`
// MessageSource will contain the message as submitted by the user if Message has been modified // MessageSource will contain the message as submitted by the user if Message has been modified
// by Mattermost for presentation (e.g if an image proxy is being used). It should be used to // by Mattermost for presentation (e.g if an image proxy is being used). It should be used to
// populate edit boxes if present. // populate edit boxes if present.
@@ -88,7 +87,8 @@ type Post struct {
HasReactions bool `json:"has_reactions,omitempty"` HasReactions bool `json:"has_reactions,omitempty"`
// Transient data populated before sending a post to the client // Transient data populated before sending a post to the client
Metadata *PostMetadata `json:"metadata,omitempty" db:"-"` ReplyCount int64 `json:"reply_count" db:"-"`
Metadata *PostMetadata `json:"metadata,omitempty" db:"-"`
} }
type PostEphemeral struct { type PostEphemeral struct {
@@ -170,6 +170,20 @@ func (o *Post) ToUnsanitizedJson() string {
return string(b) return string(b)
} }
type GetPostsSinceOptions struct {
ChannelId string
Time int64
SkipFetchThreads bool
}
type GetPostsOptions struct {
ChannelId string
PostId string
Page int
PerPage int
SkipFetchThreads bool
}
func PostFromJson(data io.Reader) *Post { func PostFromJson(data io.Reader) *Post {
var o *Post var o *Post
json.NewDecoder(data).Decode(&o) json.NewDecoder(data).Decode(&o)

Просмотреть файл

@@ -33,14 +33,14 @@ func (u *UserSearch) ToJson() []byte {
// UserSearchFromJson will decode the input and return a User // UserSearchFromJson will decode the input and return a User
func UserSearchFromJson(data io.Reader) *UserSearch { func UserSearchFromJson(data io.Reader) *UserSearch {
var us *UserSearch us := UserSearch{}
json.NewDecoder(data).Decode(&us) json.NewDecoder(data).Decode(&us)
if us.Limit == 0 { if us.Limit == 0 {
us.Limit = USER_SEARCH_DEFAULT_LIMIT us.Limit = USER_SEARCH_DEFAULT_LIMIT
} }
return us return &us
} }
// UserSearchOptions captures internal parameters derived from the user's permissions and a // UserSearchOptions captures internal parameters derived from the user's permissions and a

Просмотреть файл

@@ -13,6 +13,7 @@ import (
// It should be maintained in chronological order with most current // It should be maintained in chronological order with most current
// release at the front of the list. // release at the front of the list.
var versions = []string{ var versions = []string{
"5.16.0",
"5.15.0", "5.15.0",
"5.14.0", "5.14.0",
"5.13.0", "5.13.0",

Просмотреть файл

@@ -16,19 +16,29 @@ import (
type API interface { type API interface {
// LoadPluginConfiguration loads the plugin's configuration. dest should be a pointer to a // LoadPluginConfiguration loads the plugin's configuration. dest should be a pointer to a
// struct that the configuration JSON can be unmarshalled to. // struct that the configuration JSON can be unmarshalled to.
//
// Minimum server version: 5.2
LoadPluginConfiguration(dest interface{}) error LoadPluginConfiguration(dest interface{}) error
// RegisterCommand registers a custom slash command. When the command is triggered, your plugin // RegisterCommand registers a custom slash command. When the command is triggered, your plugin
// can fulfill it via the ExecuteCommand hook. // can fulfill it via the ExecuteCommand hook.
//
// Minimum server version: 5.2
RegisterCommand(command *model.Command) error RegisterCommand(command *model.Command) error
// UnregisterCommand unregisters a command previously registered via RegisterCommand. // UnregisterCommand unregisters a command previously registered via RegisterCommand.
//
// Minimum server version: 5.2
UnregisterCommand(teamId, trigger string) error UnregisterCommand(teamId, trigger string) error
// GetSession returns the session object for the Session ID // GetSession returns the session object for the Session ID
//
// Minimum server version: 5.2
GetSession(sessionId string) (*model.Session, *model.AppError) GetSession(sessionId string) (*model.Session, *model.AppError)
// GetConfig fetches the currently persisted config // GetConfig fetches the currently persisted config
//
// Minimum server version: 5.2
GetConfig() *model.Config GetConfig() *model.Config
// GetUnsanitizedConfig fetches the currently persisted config without removing secrets. // GetUnsanitizedConfig fetches the currently persisted config without removing secrets.
@@ -37,6 +47,8 @@ type API interface {
GetUnsanitizedConfig() *model.Config GetUnsanitizedConfig() *model.Config
// SaveConfig sets the given config and persists the changes // SaveConfig sets the given config and persists the changes
//
// Minimum server version: 5.2
SaveConfig(config *model.Config) *model.AppError SaveConfig(config *model.Config) *model.AppError
// GetPluginConfig fetches the currently persisted config of plugin // GetPluginConfig fetches the currently persisted config of plugin
@@ -76,9 +88,13 @@ type API interface {
GetDiagnosticId() string GetDiagnosticId() string
// CreateUser creates a user. // CreateUser creates a user.
//
// Minimum server version: 5.2
CreateUser(user *model.User) (*model.User, *model.AppError) CreateUser(user *model.User) (*model.User, *model.AppError)
// DeleteUser deletes a user. // DeleteUser deletes a user.
//
// Minimum server version: 5.2
DeleteUser(userId string) *model.AppError DeleteUser(userId string) *model.AppError
// GetUsers a list of users based on search options. // GetUsers a list of users based on search options.
@@ -87,12 +103,18 @@ type API interface {
GetUsers(options *model.UserGetOptions) ([]*model.User, *model.AppError) GetUsers(options *model.UserGetOptions) ([]*model.User, *model.AppError)
// GetUser gets a user. // GetUser gets a user.
//
// Minimum server version: 5.2
GetUser(userId string) (*model.User, *model.AppError) GetUser(userId string) (*model.User, *model.AppError)
// GetUserByEmail gets a user by their email address. // GetUserByEmail gets a user by their email address.
//
// Minimum server version: 5.2
GetUserByEmail(email string) (*model.User, *model.AppError) GetUserByEmail(email string) (*model.User, *model.AppError)
// GetUserByUsername gets a user by their username. // GetUserByUsername gets a user by their username.
//
// Minimum server version: 5.2
GetUserByUsername(name string) (*model.User, *model.AppError) GetUserByUsername(name string) (*model.User, *model.AppError)
// GetUsersByUsernames gets users by their usernames. // GetUsersByUsernames gets users by their usernames.
@@ -121,16 +143,24 @@ type API interface {
RemoveTeamIcon(teamId string) *model.AppError RemoveTeamIcon(teamId string) *model.AppError
// UpdateUser updates a user. // UpdateUser updates a user.
//
// Minimum server version: 5.2
UpdateUser(user *model.User) (*model.User, *model.AppError) UpdateUser(user *model.User) (*model.User, *model.AppError)
// GetUserStatus will get a user's status. // GetUserStatus will get a user's status.
//
// Minimum server version: 5.2
GetUserStatus(userId string) (*model.Status, *model.AppError) GetUserStatus(userId string) (*model.Status, *model.AppError)
// GetUserStatusesByIds will return a list of user statuses based on the provided slice of user IDs. // GetUserStatusesByIds will return a list of user statuses based on the provided slice of user IDs.
//
// Minimum server version: 5.2
GetUserStatusesByIds(userIds []string) ([]*model.Status, *model.AppError) GetUserStatusesByIds(userIds []string) ([]*model.Status, *model.AppError)
// UpdateUserStatus will set a user's status until the user, or another integration/plugin, sets it back to online. // UpdateUserStatus will set a user's status until the user, or another integration/plugin, sets it back to online.
// The status parameter can be: "online", "away", "dnd", or "offline". // The status parameter can be: "online", "away", "dnd", or "offline".
//
// Minimum server version: 5.2
UpdateUserStatus(userId, status string) (*model.Status, *model.AppError) UpdateUserStatus(userId, status string) (*model.Status, *model.AppError)
// UpdateUserActive deactivates or reactivates an user. // UpdateUserActive deactivates or reactivates an user.
@@ -153,18 +183,28 @@ type API interface {
GetLDAPUserAttributes(userId string, attributes []string) (map[string]string, *model.AppError) GetLDAPUserAttributes(userId string, attributes []string) (map[string]string, *model.AppError)
// CreateTeam creates a team. // CreateTeam creates a team.
//
// Minimum server version: 5.2
CreateTeam(team *model.Team) (*model.Team, *model.AppError) CreateTeam(team *model.Team) (*model.Team, *model.AppError)
// DeleteTeam deletes a team. // DeleteTeam deletes a team.
//
// Minimum server version: 5.2
DeleteTeam(teamId string) *model.AppError DeleteTeam(teamId string) *model.AppError
// GetTeam gets all teams. // GetTeam gets all teams.
//
// Minimum server version: 5.2
GetTeams() ([]*model.Team, *model.AppError) GetTeams() ([]*model.Team, *model.AppError)
// GetTeam gets a team. // GetTeam gets a team.
//
// Minimum server version: 5.2
GetTeam(teamId string) (*model.Team, *model.AppError) GetTeam(teamId string) (*model.Team, *model.AppError)
// GetTeamByName gets a team by its name. // GetTeamByName gets a team by its name.
//
// Minimum server version: 5.2
GetTeamByName(name string) (*model.Team, *model.AppError) GetTeamByName(name string) (*model.Team, *model.AppError)
// GetTeamsUnreadForUser gets the unread message and mention counts for each team to which the given user belongs. // GetTeamsUnreadForUser gets the unread message and mention counts for each team to which the given user belongs.
@@ -173,6 +213,8 @@ type API interface {
GetTeamsUnreadForUser(userId string) ([]*model.TeamUnread, *model.AppError) GetTeamsUnreadForUser(userId string) ([]*model.TeamUnread, *model.AppError)
// UpdateTeam updates a team. // UpdateTeam updates a team.
//
// Minimum server version: 5.2
UpdateTeam(team *model.Team) (*model.Team, *model.AppError) UpdateTeam(team *model.Team) (*model.Team, *model.AppError)
// SearchTeams search a team. // SearchTeams search a team.
@@ -186,18 +228,28 @@ type API interface {
GetTeamsForUser(userId string) ([]*model.Team, *model.AppError) GetTeamsForUser(userId string) ([]*model.Team, *model.AppError)
// CreateTeamMember creates a team membership. // CreateTeamMember creates a team membership.
//
// Minimum server version: 5.2
CreateTeamMember(teamId, userId string) (*model.TeamMember, *model.AppError) CreateTeamMember(teamId, userId string) (*model.TeamMember, *model.AppError)
// CreateTeamMember creates a team membership for all provided user ids. // CreateTeamMember creates a team membership for all provided user ids.
//
// Minimum server version: 5.2
CreateTeamMembers(teamId string, userIds []string, requestorId string) ([]*model.TeamMember, *model.AppError) CreateTeamMembers(teamId string, userIds []string, requestorId string) ([]*model.TeamMember, *model.AppError)
// DeleteTeamMember deletes a team membership. // DeleteTeamMember deletes a team membership.
//
// Minimum server version: 5.2
DeleteTeamMember(teamId, userId, requestorId string) *model.AppError DeleteTeamMember(teamId, userId, requestorId string) *model.AppError
// GetTeamMembers returns the memberships of a specific team. // GetTeamMembers returns the memberships of a specific team.
//
// Minimum server version: 5.2
GetTeamMembers(teamId string, page, perPage int) ([]*model.TeamMember, *model.AppError) GetTeamMembers(teamId string, page, perPage int) ([]*model.TeamMember, *model.AppError)
// GetTeamMember returns a specific membership. // GetTeamMember returns a specific membership.
//
// Minimum server version: 5.2
GetTeamMember(teamId, userId string) (*model.TeamMember, *model.AppError) GetTeamMember(teamId, userId string) (*model.TeamMember, *model.AppError)
// GetTeamMembersForUser returns all team memberships for a user. // GetTeamMembersForUser returns all team memberships for a user.
@@ -206,24 +258,38 @@ type API interface {
GetTeamMembersForUser(userId string, page int, perPage int) ([]*model.TeamMember, *model.AppError) GetTeamMembersForUser(userId string, page int, perPage int) ([]*model.TeamMember, *model.AppError)
// UpdateTeamMemberRoles updates the role for a team membership. // UpdateTeamMemberRoles updates the role for a team membership.
//
// Minimum server version: 5.2
UpdateTeamMemberRoles(teamId, userId, newRoles string) (*model.TeamMember, *model.AppError) UpdateTeamMemberRoles(teamId, userId, newRoles string) (*model.TeamMember, *model.AppError)
// CreateChannel creates a channel. // CreateChannel creates a channel.
//
// Minimum server version: 5.2
CreateChannel(channel *model.Channel) (*model.Channel, *model.AppError) CreateChannel(channel *model.Channel) (*model.Channel, *model.AppError)
// DeleteChannel deletes a channel. // DeleteChannel deletes a channel.
//
// Minimum server version: 5.2
DeleteChannel(channelId string) *model.AppError DeleteChannel(channelId string) *model.AppError
// GetPublicChannelsForTeam gets a list of all channels. // GetPublicChannelsForTeam gets a list of all channels.
//
// Minimum server version: 5.2
GetPublicChannelsForTeam(teamId string, page, perPage int) ([]*model.Channel, *model.AppError) GetPublicChannelsForTeam(teamId string, page, perPage int) ([]*model.Channel, *model.AppError)
// GetChannel gets a channel. // GetChannel gets a channel.
//
// Minimum server version: 5.2
GetChannel(channelId string) (*model.Channel, *model.AppError) GetChannel(channelId string) (*model.Channel, *model.AppError)
// GetChannelByName gets a channel by its name, given a team id. // GetChannelByName gets a channel by its name, given a team id.
//
// Minimum server version: 5.2
GetChannelByName(teamId, name string, includeDeleted bool) (*model.Channel, *model.AppError) GetChannelByName(teamId, name string, includeDeleted bool) (*model.Channel, *model.AppError)
// GetChannelByNameForTeamName gets a channel by its name, given a team name. // GetChannelByNameForTeamName gets a channel by its name, given a team name.
//
// Minimum server version: 5.2
GetChannelByNameForTeamName(teamName, channelName string, includeDeleted bool) (*model.Channel, *model.AppError) GetChannelByNameForTeamName(teamName, channelName string, includeDeleted bool) (*model.Channel, *model.AppError)
// GetChannelsForTeamForUser gets a list of channels for given user ID in given team ID. // GetChannelsForTeamForUser gets a list of channels for given user ID in given team ID.
@@ -238,13 +304,19 @@ type API interface {
// GetDirectChannel gets a direct message channel. // GetDirectChannel gets a direct message channel.
// If the channel does not exist it will create it. // If the channel does not exist it will create it.
//
// Minimum server version: 5.2
GetDirectChannel(userId1, userId2 string) (*model.Channel, *model.AppError) GetDirectChannel(userId1, userId2 string) (*model.Channel, *model.AppError)
// GetGroupChannel gets a group message channel. // GetGroupChannel gets a group message channel.
// If the channel does not exist it will create it. // If the channel does not exist it will create it.
//
// Minimum server version: 5.2
GetGroupChannel(userIds []string) (*model.Channel, *model.AppError) GetGroupChannel(userIds []string) (*model.Channel, *model.AppError)
// UpdateChannel updates a channel. // UpdateChannel updates a channel.
//
// Minimum server version: 5.2
UpdateChannel(channel *model.Channel) (*model.Channel, *model.AppError) UpdateChannel(channel *model.Channel) (*model.Channel, *model.AppError)
// SearchChannels returns the channels on a team matching the provided search term. // SearchChannels returns the channels on a team matching the provided search term.
@@ -263,9 +335,13 @@ type API interface {
SearchPostsInTeam(teamId string, paramsList []*model.SearchParams) ([]*model.Post, *model.AppError) SearchPostsInTeam(teamId string, paramsList []*model.SearchParams) ([]*model.Post, *model.AppError)
// AddChannelMember creates a channel membership for a user. // AddChannelMember creates a channel membership for a user.
//
// Minimum server version: 5.2
AddChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError) AddChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError)
// GetChannelMember gets a channel membership for a user. // GetChannelMember gets a channel membership for a user.
//
// Minimum server version: 5.2
GetChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError) GetChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError)
// GetChannelMembers gets a channel membership for all users. // GetChannelMembers gets a channel membership for all users.
@@ -284,15 +360,23 @@ type API interface {
GetChannelMembersForUser(teamId, userId string, page, perPage int) ([]*model.ChannelMember, *model.AppError) GetChannelMembersForUser(teamId, userId string, page, perPage int) ([]*model.ChannelMember, *model.AppError)
// UpdateChannelMemberRoles updates a user's roles for a channel. // UpdateChannelMemberRoles updates a user's roles for a channel.
//
// Minimum server version: 5.2
UpdateChannelMemberRoles(channelId, userId, newRoles string) (*model.ChannelMember, *model.AppError) UpdateChannelMemberRoles(channelId, userId, newRoles string) (*model.ChannelMember, *model.AppError)
// UpdateChannelMemberNotifications updates a user's notification properties for a channel. // UpdateChannelMemberNotifications updates a user's notification properties for a channel.
//
// Minimum server version: 5.2
UpdateChannelMemberNotifications(channelId, userId string, notifications map[string]string) (*model.ChannelMember, *model.AppError) UpdateChannelMemberNotifications(channelId, userId string, notifications map[string]string) (*model.ChannelMember, *model.AppError)
// DeleteChannelMember deletes a channel membership for a user. // DeleteChannelMember deletes a channel membership for a user.
//
// Minimum server version: 5.2
DeleteChannelMember(channelId, userId string) *model.AppError DeleteChannelMember(channelId, userId string) *model.AppError
// CreatePost creates a post. // CreatePost creates a post.
//
// Minimum server version: 5.2
CreatePost(post *model.Post) (*model.Post, *model.AppError) CreatePost(post *model.Post) (*model.Post, *model.AppError)
// AddReaction add a reaction to a post. // AddReaction add a reaction to a post.
@@ -311,17 +395,25 @@ type API interface {
GetReactions(postId string) ([]*model.Reaction, *model.AppError) GetReactions(postId string) ([]*model.Reaction, *model.AppError)
// SendEphemeralPost creates an ephemeral post. // SendEphemeralPost creates an ephemeral post.
//
// Minimum server version: 5.2
SendEphemeralPost(userId string, post *model.Post) *model.Post SendEphemeralPost(userId string, post *model.Post) *model.Post
// UpdateEphemeralPost updates an ephemeral message previously sent to the user. // UpdateEphemeralPost updates an ephemeral message previously sent to the user.
// EXPERIMENTAL: This API is experimental and can be changed without advance notice. // EXPERIMENTAL: This API is experimental and can be changed without advance notice.
//
// Minimum server version: 5.2
UpdateEphemeralPost(userId string, post *model.Post) *model.Post UpdateEphemeralPost(userId string, post *model.Post) *model.Post
// DeleteEphemeralPost deletes an ephemeral message previously sent to the user. // DeleteEphemeralPost deletes an ephemeral message previously sent to the user.
// EXPERIMENTAL: This API is experimental and can be changed without advance notice. // EXPERIMENTAL: This API is experimental and can be changed without advance notice.
//
// Minimum server version: 5.2
DeleteEphemeralPost(userId, postId string) DeleteEphemeralPost(userId, postId string)
// DeletePost deletes a post. // DeletePost deletes a post.
//
// Minimum server version: 5.2
DeletePost(postId string) *model.AppError DeletePost(postId string) *model.AppError
// GetPostThread gets a post with all the other posts in the same thread. // GetPostThread gets a post with all the other posts in the same thread.
@@ -330,6 +422,8 @@ type API interface {
GetPostThread(postId string) (*model.PostList, *model.AppError) GetPostThread(postId string) (*model.PostList, *model.AppError)
// GetPost gets a post. // GetPost gets a post.
//
// Minimum server version: 5.2
GetPost(postId string) (*model.Post, *model.AppError) GetPost(postId string) (*model.Post, *model.AppError)
// GetPostsSince gets posts created after a specified time as Unix time in milliseconds. // GetPostsSince gets posts created after a specified time as Unix time in milliseconds.
@@ -358,6 +452,8 @@ type API interface {
GetTeamStats(teamId string) (*model.TeamStats, *model.AppError) GetTeamStats(teamId string) (*model.TeamStats, *model.AppError)
// UpdatePost updates a post. // UpdatePost updates a post.
//
// Minimum server version: 5.2
UpdatePost(post *model.Post) (*model.Post, *model.AppError) UpdatePost(post *model.Post) (*model.Post, *model.AppError)
// GetProfileImage gets user's profile image. // GetProfileImage gets user's profile image.
@@ -393,6 +489,8 @@ type API interface {
// The duplicate FileInfo objects are not initially linked to a post, but may now be passed // The duplicate FileInfo objects are not initially linked to a post, but may now be passed
// to CreatePost. Use this API to duplicate a post and its file attachments without // to CreatePost. Use this API to duplicate a post and its file attachments without
// actually duplicating the uploaded files. // actually duplicating the uploaded files.
//
// Minimum server version: 5.2
CopyFileInfos(userId string, fileIds []string) ([]string, *model.AppError) CopyFileInfos(userId string, fileIds []string) ([]string, *model.AppError)
// GetFileInfo gets a File Info for a specific fileId // GetFileInfo gets a File Info for a specific fileId
@@ -402,7 +500,7 @@ type API interface {
// GetFile gets content of a file by it's ID // GetFile gets content of a file by it's ID
// //
// Minimum Server version: 5.8 // Minimum server version: 5.8
GetFile(fileId string) ([]byte, *model.AppError) GetFile(fileId string) ([]byte, *model.AppError)
// GetFileLink gets the public link to a file by fileId. // GetFileLink gets the public link to a file by fileId.
@@ -463,6 +561,8 @@ type API interface {
// KVSet stores a key-value pair, unique per plugin. // KVSet stores a key-value pair, unique per plugin.
// Provided helper functions and internal plugin code will use the prefix `mmi_` before keys. Do not use this prefix. // Provided helper functions and internal plugin code will use the prefix `mmi_` before keys. Do not use this prefix.
//
// Minimum server version: 5.2
KVSet(key string, value []byte) *model.AppError KVSet(key string, value []byte) *model.AppError
// KVCompareAndSet updates a key-value pair, unique per plugin, but only if the current value matches the given oldValue. // KVCompareAndSet updates a key-value pair, unique per plugin, but only if the current value matches the given oldValue.
@@ -488,9 +588,13 @@ type API interface {
KVSetWithExpiry(key string, value []byte, expireInSeconds int64) *model.AppError KVSetWithExpiry(key string, value []byte, expireInSeconds int64) *model.AppError
// KVGet retrieves a value based on the key, unique per plugin. Returns nil for non-existent keys. // KVGet retrieves a value based on the key, unique per plugin. Returns nil for non-existent keys.
//
// Minimum server version: 5.2
KVGet(key string) ([]byte, *model.AppError) KVGet(key string) ([]byte, *model.AppError)
// KVDelete removes a key-value pair, unique per plugin. Returns nil for non-existent keys. // KVDelete removes a key-value pair, unique per plugin. Returns nil for non-existent keys.
//
// Minimum server version: 5.2
KVDelete(key string) *model.AppError KVDelete(key string) *model.AppError
// KVDeleteAll removes all key-value pairs for a plugin. // KVDeleteAll removes all key-value pairs for a plugin.
@@ -507,6 +611,8 @@ type API interface {
// event is the type and will be prepended with "custom_<pluginid>_". // event is the type and will be prepended with "custom_<pluginid>_".
// payload is the data sent with the event. Interface values must be primitive Go types or mattermost-server/model types. // payload is the data sent with the event. Interface values must be primitive Go types or mattermost-server/model types.
// broadcast determines to which users to send the event. // broadcast determines to which users to send the event.
//
// Minimum server version: 5.2
PublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *model.WebsocketBroadcast) PublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *model.WebsocketBroadcast)
// HasPermissionTo check if the user has the permission at system scope. // HasPermissionTo check if the user has the permission at system scope.
@@ -528,24 +634,32 @@ type API interface {
// Appropriate context such as the plugin name will already be added as fields so plugins // Appropriate context such as the plugin name will already be added as fields so plugins
// do not need to add that info. // do not need to add that info.
// keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob // keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob
//
// Minimum server version: 5.2
LogDebug(msg string, keyValuePairs ...interface{}) LogDebug(msg string, keyValuePairs ...interface{})
// LogInfo writes a log message to the Mattermost server log file. // LogInfo writes a log message to the Mattermost server log file.
// Appropriate context such as the plugin name will already be added as fields so plugins // Appropriate context such as the plugin name will already be added as fields so plugins
// do not need to add that info. // do not need to add that info.
// keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob // keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob
//
// Minimum server version: 5.2
LogInfo(msg string, keyValuePairs ...interface{}) LogInfo(msg string, keyValuePairs ...interface{})
// LogError writes a log message to the Mattermost server log file. // LogError writes a log message to the Mattermost server log file.
// Appropriate context such as the plugin name will already be added as fields so plugins // Appropriate context such as the plugin name will already be added as fields so plugins
// do not need to add that info. // do not need to add that info.
// keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob // keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob
//
// Minimum server version: 5.2
LogError(msg string, keyValuePairs ...interface{}) LogError(msg string, keyValuePairs ...interface{})
// LogWarn writes a log message to the Mattermost server log file. // LogWarn writes a log message to the Mattermost server log file.
// Appropriate context such as the plugin name will already be added as fields so plugins // Appropriate context such as the plugin name will already be added as fields so plugins
// do not need to add that info. // do not need to add that info.
// keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob // keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob
//
// Minimum server version: 5.2
LogWarn(msg string, keyValuePairs ...interface{}) LogWarn(msg string, keyValuePairs ...interface{})
// SendMail sends an email to a specific address // SendMail sends an email to a specific address

126
plugin/checker/main.go Обычный файл
Просмотреть файл

@@ -0,0 +1,126 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"bytes"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"go/ast"
"golang.org/x/tools/go/packages"
"github.com/pkg/errors"
)
const pluginPackagePath = "github.com/mattermost/mattermost-server/plugin"
func main() {
if err := runCheck(pluginPackagePath); err != nil {
fmt.Fprintln(os.Stderr, "#", pluginPackagePath)
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func runCheck(pkgPath string) error {
pkg, err := getPackage(pkgPath)
if err != nil {
return err
}
apiInterface := findAPIInterface(pkg.Syntax)
if apiInterface == nil {
return errors.Errorf("could not find API interface in package %s", pkgPath)
}
invalidMethods := findInvalidMethods(apiInterface.Methods.List)
if len(invalidMethods) > 0 {
return errors.New(renderErrorMessage(pkg, invalidMethods))
}
return nil
}
func getPackage(pkgPath string) (*packages.Package, error) {
cfg := &packages.Config{
Mode: packages.NeedName | packages.NeedTypes | packages.NeedSyntax,
}
pkgs, err := packages.Load(cfg, pkgPath)
if err != nil {
return nil, err
}
if len(pkgs) == 0 {
return nil, errors.Errorf("could not find package %s", pkgPath)
}
return pkgs[0], nil
}
func findAPIInterface(files []*ast.File) *ast.InterfaceType {
for _, f := range files {
var iface *ast.InterfaceType
ast.Inspect(f, func(n ast.Node) bool {
if t, ok := n.(*ast.TypeSpec); ok {
if i, ok := t.Type.(*ast.InterfaceType); ok && t.Name.Name == "API" {
iface = i
return false
}
}
return true
})
if iface != nil {
return iface
}
}
return nil
}
func findInvalidMethods(methods []*ast.Field) []*ast.Field {
var invalid []*ast.Field
for _, m := range methods {
if !hasValidMinimumVersionComment(m.Doc.Text()) {
invalid = append(invalid, m)
}
}
return invalid
}
var versionRequirementRE = regexp.MustCompile(`^Minimum server version: \d+\.\d+(\.\d+)?$`)
func hasValidMinimumVersionComment(s string) bool {
lines := strings.Split(strings.TrimSpace(s), "\n")
if len(lines) > 0 {
lastLine := lines[len(lines)-1]
return versionRequirementRE.MatchString(lastLine)
}
return false
}
func renderErrorMessage(pkg *packages.Package, methods []*ast.Field) string {
cwd, _ := os.Getwd()
out := &bytes.Buffer{}
for _, m := range methods {
pos := pkg.Fset.Position(m.Pos())
filename, err := filepath.Rel(cwd, pos.Filename)
if err != nil {
// If deriving a relative path fails for some reason,
// we prefer to still print the absolute path to the file.
filename = pos.Filename
}
fmt.Fprintf(out,
"%s:%d:%d: missing a minimum server version comment\n",
filename,
pos.Line,
pos.Column,
)
}
return out.String()
}

49
plugin/checker/main_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,49 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRunCheck(t *testing.T) {
testCases := []struct {
name, pkgPath, err string
}{
{
name: "valid comments",
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/test/valid",
err: "",
},
{
name: "invalid comments",
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/test/invalid",
err: "test/invalid/invalid.go:15:2: missing a minimum server version comment\n",
},
{
name: "missing API interface",
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/test/missing",
err: "could not find API interface in package github.com/mattermost/mattermost-server/plugin/checker/test/missing",
},
{
name: "non-existent package path",
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/test/does_not_exist",
err: "could not find API interface in package github.com/mattermost/mattermost-server/plugin/checker/test/does_not_exist",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := runCheck(tc.pkgPath)
if tc.err != "" {
assert.EqualError(t, err, tc.err)
} else {
assert.NoError(t, err)
}
})
}
}

16
plugin/checker/test/invalid/invalid.go Обычный файл
Просмотреть файл

@@ -0,0 +1,16 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package invalid
type API interface {
// ValidMethod is a fake method for testing the
// plugin comment checker with a valid comment.
//
// Minimum server version: 1.2.3
ValidMethod()
// InvalidMethod is a fake method for testing the
// plugin comment checker with an invalid comment.
InvalidMethod()
}

8
plugin/checker/test/missing/missing.go Обычный файл
Просмотреть файл

@@ -0,0 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package missing
// SomeType is a fake interface for testing the plugin comment checker.
type SomeType interface {
}

12
plugin/checker/test/valid/valid.go Обычный файл
Просмотреть файл

@@ -0,0 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package valid
type API interface {
// ValidMethod is a fake method for testing the
// plugin comment checker with a valid comment.
//
// Minimum server version: 1.2.3
ValidMethod()
}

Просмотреть файл

@@ -18,6 +18,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
var ErrNotFound = errors.New("Item not found")
type apiImplCreatorFunc func(*model.Manifest) API type apiImplCreatorFunc func(*model.Manifest) API
// registeredPlugin stores the state for a given plugin that has been activated // registeredPlugin stores the state for a given plugin that has been activated
@@ -162,6 +164,23 @@ func (env *Environment) Statuses() (model.PluginStatuses, error) {
return pluginStatuses, nil return pluginStatuses, nil
} }
// GetManifest returns a manifest for a given pluginId.
// Returns ErrNotFound if plugin is not found.
func (env *Environment) GetManifest(pluginId string) (*model.Manifest, error) {
plugins, err := env.Available()
if err != nil {
return nil, errors.Wrap(err, "unable to get plugin statuses")
}
for _, plugin := range plugins {
if plugin.Manifest != nil && plugin.Manifest.Id == pluginId {
return plugin.Manifest, nil
}
}
return nil, ErrNotFound
}
func (env *Environment) Activate(id string) (manifest *model.Manifest, activated bool, reterr error) { func (env *Environment) Activate(id string) (manifest *model.Manifest, activated bool, reterr error) {
// Check if we are already active // Check if we are already active
if env.IsActive(id) { if env.IsActive(id) {

Просмотреть файл

@@ -118,7 +118,7 @@ func (sup *supervisor) PerformHealthCheck() error {
} }
} }
if pingErr != nil { if pingErr != nil {
mlog.Debug(fmt.Sprintf("Error pinging plugin, error: %s", pingErr.Error())) mlog.Debug("Error pinging plugin", mlog.Err(pingErr))
return fmt.Errorf("Plugin RPC connection is not responding") return fmt.Errorf("Plugin RPC connection is not responding")
} }
} }

79
services/marketplace/client.go Обычный файл
Просмотреть файл

@@ -0,0 +1,79 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package marketplace
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/services/httpservice"
"github.com/pkg/errors"
)
// Client is the programmatic interface to the marketplace server API.
type Client struct {
address string
httpClient *http.Client
}
// NewClient creates a client to the marketplace server at the given address.
func NewClient(address string, httpService httpservice.HTTPService) (*Client, error) {
var httpClient *http.Client
addressUrl, err := url.Parse(address)
if err != nil {
return nil, errors.Wrap(err, "failed to parse marketplace address")
}
if addressUrl.Hostname() == "localhost" || addressUrl.Hostname() == "127.0.0.1" {
httpClient = httpService.MakeClient(true)
} else {
httpClient = httpService.MakeClient(false)
}
return &Client{
address: address,
httpClient: httpClient,
}, nil
}
// GetPlugins fetches the list of plugins from the configured server.
func (c *Client) GetPlugins(request *model.MarketplacePluginFilter) ([]*model.BaseMarketplacePlugin, error) {
u, err := url.Parse(c.buildURL("/api/v1/plugins"))
if err != nil {
return nil, err
}
request.ApplyToURL(u)
resp, err := c.doGet(u.String())
if err != nil {
return nil, err
}
defer closeBody(resp)
switch resp.StatusCode {
case http.StatusOK:
return model.BaseMarketplacePluginsFromReader(resp.Body)
default:
return nil, errors.Errorf("failed with status code %d", resp.StatusCode)
}
}
// closeBody ensures the Body of an http.Response is properly closed.
func closeBody(r *http.Response) {
if r.Body != nil {
_, _ = ioutil.ReadAll(r.Body)
_ = r.Body.Close()
}
}
func (c *Client) buildURL(urlPath string, args ...interface{}) string {
return fmt.Sprintf("%s%s", c.address, fmt.Sprintf(urlPath, args...))
}
func (c *Client) doGet(u string) (*http.Response, error) {
return c.httpClient.Get(u)
}

Просмотреть файл

@@ -8,7 +8,6 @@ import (
"github.com/mattermost/mattermost-server/einterfaces" "github.com/mattermost/mattermost-server/einterfaces"
"github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model"
) )
const ( const (
@@ -22,8 +21,6 @@ type LayeredStoreDatabaseLayer interface {
type LayeredStore struct { type LayeredStore struct {
TmpContext context.Context TmpContext context.Context
RoleStore RoleStore
SchemeStore SchemeStore
DatabaseLayer LayeredStoreDatabaseLayer DatabaseLayer LayeredStoreDatabaseLayer
LocalCacheLayer *LocalCacheSupplier LocalCacheLayer *LocalCacheSupplier
RedisLayer *RedisSupplier RedisLayer *RedisSupplier
@@ -37,9 +34,6 @@ func NewLayeredStore(db LayeredStoreDatabaseLayer, metrics einterfaces.MetricsIn
LocalCacheLayer: NewLocalCacheSupplier(metrics, cluster), LocalCacheLayer: NewLocalCacheSupplier(metrics, cluster),
} }
store.RoleStore = &LayeredRoleStore{store}
store.SchemeStore = &LayeredSchemeStore{store}
// Setup the chain // Setup the chain
if ENABLE_EXPERIMENTAL_REDIS { if ENABLE_EXPERIMENTAL_REDIS {
mlog.Debug("Experimental redis enabled.") mlog.Debug("Experimental redis enabled.")
@@ -161,7 +155,7 @@ func (s *LayeredStore) Plugin() PluginStore {
} }
func (s *LayeredStore) Role() RoleStore { func (s *LayeredStore) Role() RoleStore {
return s.RoleStore return s.DatabaseLayer.Role()
} }
func (s *LayeredStore) TermsOfService() TermsOfServiceStore { func (s *LayeredStore) TermsOfService() TermsOfServiceStore {
@@ -173,7 +167,7 @@ func (s *LayeredStore) UserTermsOfService() UserTermsOfServiceStore {
} }
func (s *LayeredStore) Scheme() SchemeStore { func (s *LayeredStore) Scheme() SchemeStore {
return s.SchemeStore return s.DatabaseLayer.Scheme()
} }
func (s *LayeredStore) Group() GroupStore { func (s *LayeredStore) Group() GroupStore {
@@ -220,63 +214,3 @@ func (s *LayeredStore) TotalSearchDbConnections() int {
func (s *LayeredStore) CheckIntegrity() <-chan IntegrityCheckResult { func (s *LayeredStore) CheckIntegrity() <-chan IntegrityCheckResult {
return s.DatabaseLayer.CheckIntegrity() return s.DatabaseLayer.CheckIntegrity()
} }
type LayeredRoleStore struct {
*LayeredStore
}
func (s *LayeredRoleStore) Save(role *model.Role) (*model.Role, *model.AppError) {
return s.LayerChainHead.RoleSave(s.TmpContext, role)
}
func (s *LayeredRoleStore) Get(roleId string) (*model.Role, *model.AppError) {
return s.LayerChainHead.RoleGet(s.TmpContext, roleId)
}
func (s *LayeredRoleStore) GetAll() ([]*model.Role, *model.AppError) {
return s.LayerChainHead.RoleGetAll(s.TmpContext)
}
func (s *LayeredRoleStore) GetByName(name string) (*model.Role, *model.AppError) {
return s.LayerChainHead.RoleGetByName(s.TmpContext, name)
}
func (s *LayeredRoleStore) GetByNames(names []string) ([]*model.Role, *model.AppError) {
return s.LayerChainHead.RoleGetByNames(s.TmpContext, names)
}
func (s *LayeredRoleStore) Delete(roldId string) (*model.Role, *model.AppError) {
return s.LayerChainHead.RoleDelete(s.TmpContext, roldId)
}
func (s *LayeredRoleStore) PermanentDeleteAll() *model.AppError {
return s.LayerChainHead.RolePermanentDeleteAll(s.TmpContext)
}
type LayeredSchemeStore struct {
*LayeredStore
}
func (s *LayeredSchemeStore) Save(scheme *model.Scheme) (*model.Scheme, *model.AppError) {
return s.LayerChainHead.SchemeSave(s.TmpContext, scheme)
}
func (s *LayeredSchemeStore) Get(schemeId string) (*model.Scheme, *model.AppError) {
return s.LayerChainHead.SchemeGet(s.TmpContext, schemeId)
}
func (s *LayeredSchemeStore) GetByName(schemeName string) (*model.Scheme, *model.AppError) {
return s.LayerChainHead.SchemeGetByName(s.TmpContext, schemeName)
}
func (s *LayeredSchemeStore) Delete(schemeId string) (*model.Scheme, *model.AppError) {
return s.LayerChainHead.SchemeDelete(s.TmpContext, schemeId)
}
func (s *LayeredSchemeStore) GetAllPage(scope string, offset int, limit int) ([]*model.Scheme, *model.AppError) {
return s.LayerChainHead.SchemeGetAllPage(s.TmpContext, scope, offset, limit)
}
func (s *LayeredSchemeStore) PermanentDeleteAll() *model.AppError {
return s.LayerChainHead.SchemePermanentDeleteAll(s.TmpContext)
}

Просмотреть файл

@@ -3,9 +3,6 @@
package store package store
import "github.com/mattermost/mattermost-server/model"
import "context"
type LayeredStoreSupplierResult struct { type LayeredStoreSupplierResult struct {
StoreResult StoreResult
} }
@@ -20,21 +17,4 @@ type LayeredStoreSupplier interface {
// //
SetChainNext(LayeredStoreSupplier) SetChainNext(LayeredStoreSupplier)
Next() LayeredStoreSupplier Next() LayeredStoreSupplier
// Roles
RoleSave(ctx context.Context, role *model.Role, hints ...LayeredStoreHint) (*model.Role, *model.AppError)
RoleGet(ctx context.Context, roleId string, hints ...LayeredStoreHint) (*model.Role, *model.AppError)
RoleGetAll(ctx context.Context, hints ...LayeredStoreHint) ([]*model.Role, *model.AppError)
RoleGetByName(ctx context.Context, name string, hints ...LayeredStoreHint) (*model.Role, *model.AppError)
RoleGetByNames(ctx context.Context, names []string, hints ...LayeredStoreHint) ([]*model.Role, *model.AppError)
RoleDelete(ctx context.Context, roldId string, hints ...LayeredStoreHint) (*model.Role, *model.AppError)
RolePermanentDeleteAll(ctx context.Context, hints ...LayeredStoreHint) *model.AppError
// Schemes
SchemeSave(ctx context.Context, scheme *model.Scheme, hints ...LayeredStoreHint) (*model.Scheme, *model.AppError)
SchemeGet(ctx context.Context, schemeId string, hints ...LayeredStoreHint) (*model.Scheme, *model.AppError)
SchemeGetByName(ctx context.Context, schemeName string, hints ...LayeredStoreHint) (*model.Scheme, *model.AppError)
SchemeDelete(ctx context.Context, schemeId string, hints ...LayeredStoreHint) (*model.Scheme, *model.AppError)
SchemeGetAllPage(ctx context.Context, scope string, offset int, limit int, hints ...LayeredStoreHint) ([]*model.Scheme, *model.AppError)
SchemePermanentDeleteAll(ctx context.Context, hints ...LayeredStoreHint) *model.AppError
} }

Просмотреть файл

@@ -8,28 +8,16 @@ import (
"github.com/mattermost/mattermost-server/einterfaces" "github.com/mattermost/mattermost-server/einterfaces"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
) )
const ( const (
ROLE_CACHE_SIZE = 20000
ROLE_CACHE_SEC = 30 * 60
SCHEME_CACHE_SIZE = 20000
SCHEME_CACHE_SEC = 30 * 60
GROUP_CACHE_SIZE = 20000
GROUP_CACHE_SEC = 30 * 60
CLEAR_CACHE_MESSAGE_DATA = "" CLEAR_CACHE_MESSAGE_DATA = ""
) )
type LocalCacheSupplier struct { type LocalCacheSupplier struct {
next LayeredStoreSupplier next LayeredStoreSupplier
roleCache *utils.Cache metrics einterfaces.MetricsInterface
schemeCache *utils.Cache cluster einterfaces.ClusterInterface
metrics einterfaces.MetricsInterface
cluster einterfaces.ClusterInterface
} }
// Caching Interface // Caching Interface
@@ -46,14 +34,8 @@ type ObjectCache interface {
func NewLocalCacheSupplier(metrics einterfaces.MetricsInterface, cluster einterfaces.ClusterInterface) *LocalCacheSupplier { func NewLocalCacheSupplier(metrics einterfaces.MetricsInterface, cluster einterfaces.ClusterInterface) *LocalCacheSupplier {
supplier := &LocalCacheSupplier{ supplier := &LocalCacheSupplier{
roleCache: utils.NewLruWithParams(ROLE_CACHE_SIZE, "Role", ROLE_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES), metrics: metrics,
schemeCache: utils.NewLruWithParams(SCHEME_CACHE_SIZE, "Scheme", SCHEME_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_SCHEMES), cluster: cluster,
metrics: metrics,
cluster: cluster,
}
if cluster != nil {
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES, supplier.handleClusterInvalidateRole)
} }
return supplier return supplier
@@ -122,6 +104,4 @@ func (s *LocalCacheSupplier) doClearCacheCluster(cache ObjectCache) {
} }
func (s *LocalCacheSupplier) Invalidate() { func (s *LocalCacheSupplier) Invalidate() {
s.doClearCacheCluster(s.roleCache)
s.doClearCacheCluster(s.schemeCache)
} }

Просмотреть файл

@@ -1,96 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"context"
"github.com/mattermost/mattermost-server/model"
)
func (s *LocalCacheSupplier) handleClusterInvalidateRole(msg *model.ClusterMessage) {
if msg.Data == CLEAR_CACHE_MESSAGE_DATA {
s.roleCache.Purge()
} else {
s.roleCache.Remove(msg.Data)
}
}
func (s *LocalCacheSupplier) RoleSave(ctx context.Context, role *model.Role, hints ...LayeredStoreHint) (*model.Role, *model.AppError) {
if len(role.Name) != 0 {
defer s.doInvalidateCacheCluster(s.roleCache, role.Name)
}
return s.Next().RoleSave(ctx, role, hints...)
}
func (s *LocalCacheSupplier) RoleGet(ctx context.Context, roleId string, hints ...LayeredStoreHint) (*model.Role, *model.AppError) {
// Roles are cached by name, as that is most commonly how they are looked up.
// This means that no caching is supported on roles being looked up by ID.
return s.Next().RoleGet(ctx, roleId, hints...)
}
func (s *LocalCacheSupplier) RoleGetAll(ctx context.Context, hints ...LayeredStoreHint) ([]*model.Role, *model.AppError) {
// Roles are cached by name, as that is most commonly how they are looked up.
// This means that no caching is supported on roles being listed.
return s.Next().RoleGetAll(ctx, hints...)
}
func (s *LocalCacheSupplier) RoleGetByName(ctx context.Context, name string, hints ...LayeredStoreHint) (*model.Role, *model.AppError) {
if result := s.doStandardReadCache(ctx, s.roleCache, name, hints...); result != nil {
return result.Data.(*model.Role), nil
}
role, err := s.Next().RoleGetByName(ctx, name, hints...)
if err != nil {
return nil, err
}
result := NewSupplierResult()
result.Data = role
s.doStandardAddToCache(ctx, s.roleCache, name, result, hints...)
return role, nil
}
func (s *LocalCacheSupplier) RoleGetByNames(ctx context.Context, roleNames []string, hints ...LayeredStoreHint) ([]*model.Role, *model.AppError) {
var foundRoles []*model.Role
var rolesToQuery []string
for _, roleName := range roleNames {
if result := s.doStandardReadCache(ctx, s.roleCache, roleName, hints...); result != nil {
foundRoles = append(foundRoles, result.Data.(*model.Role))
} else {
rolesToQuery = append(rolesToQuery, roleName)
}
}
rolesFound, err := s.Next().RoleGetByNames(ctx, rolesToQuery, hints...)
for _, role := range rolesFound {
res := NewSupplierResult()
res.Data = role
s.doStandardAddToCache(ctx, s.roleCache, role.Name, res, hints...)
}
foundRoles = append(foundRoles, rolesFound...)
return foundRoles, err
}
func (s *LocalCacheSupplier) RoleDelete(ctx context.Context, roleId string, hints ...LayeredStoreHint) (*model.Role, *model.AppError) {
role, err := s.Next().RoleDelete(ctx, roleId, hints...)
if err != nil {
return nil, err
}
s.doInvalidateCacheCluster(s.roleCache, role.Name)
return role, nil
}
func (s *LocalCacheSupplier) RolePermanentDeleteAll(ctx context.Context, hints ...LayeredStoreHint) *model.AppError {
defer s.roleCache.Purge()
defer s.doClearCacheCluster(s.roleCache)
return s.Next().RolePermanentDeleteAll(ctx, hints...)
}

Просмотреть файл

@@ -1,64 +0,0 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"context"
"github.com/mattermost/mattermost-server/model"
)
func (s *LocalCacheSupplier) handleClusterInvalidateScheme(msg *model.ClusterMessage) {
if msg.Data == CLEAR_CACHE_MESSAGE_DATA {
s.schemeCache.Purge()
} else {
s.schemeCache.Remove(msg.Data)
}
}
func (s *LocalCacheSupplier) SchemeSave(ctx context.Context, scheme *model.Scheme, hints ...LayeredStoreHint) (*model.Scheme, *model.AppError) {
if len(scheme.Id) != 0 {
defer s.doInvalidateCacheCluster(s.schemeCache, scheme.Id)
}
return s.Next().SchemeSave(ctx, scheme, hints...)
}
func (s *LocalCacheSupplier) SchemeGet(ctx context.Context, schemeId string, hints ...LayeredStoreHint) (*model.Scheme, *model.AppError) {
if result := s.doStandardReadCache(ctx, s.schemeCache, schemeId, hints...); result != nil {
return result.Data.(*model.Scheme), nil
}
scheme, err := s.Next().SchemeGet(ctx, schemeId, hints...)
if err != nil {
return nil, err
}
result := NewSupplierResult()
result.Data = scheme
s.doStandardAddToCache(ctx, s.schemeCache, schemeId, result, hints...)
return scheme, nil
}
func (s *LocalCacheSupplier) SchemeGetByName(ctx context.Context, schemeName string, hints ...LayeredStoreHint) (*model.Scheme, *model.AppError) {
return s.Next().SchemeGetByName(ctx, schemeName, hints...)
}
func (s *LocalCacheSupplier) SchemeDelete(ctx context.Context, schemeId string, hints ...LayeredStoreHint) (*model.Scheme, *model.AppError) {
defer s.doInvalidateCacheCluster(s.schemeCache, schemeId)
defer s.doClearCacheCluster(s.roleCache)
return s.Next().SchemeDelete(ctx, schemeId, hints...)
}
func (s *LocalCacheSupplier) SchemeGetAllPage(ctx context.Context, scope string, offset int, limit int, hints ...LayeredStoreHint) ([]*model.Scheme, *model.AppError) {
return s.Next().SchemeGetAllPage(ctx, scope, offset, limit, hints...)
}
func (s *LocalCacheSupplier) SchemePermanentDeleteAll(ctx context.Context, hints ...LayeredStoreHint) *model.AppError {
defer s.doClearCacheCluster(s.schemeCache)
defer s.doClearCacheCluster(s.roleCache)
return s.Next().SchemePermanentDeleteAll(ctx, hints...)
}

Просмотреть файл

@@ -14,6 +14,12 @@ const (
REACTION_CACHE_SIZE = 20000 REACTION_CACHE_SIZE = 20000
REACTION_CACHE_SEC = 30 * 60 REACTION_CACHE_SEC = 30 * 60
ROLE_CACHE_SIZE = 20000
ROLE_CACHE_SEC = 30 * 60
SCHEME_CACHE_SIZE = 20000
SCHEME_CACHE_SEC = 30 * 60
CLEAR_CACHE_MESSAGE_DATA = "" CLEAR_CACHE_MESSAGE_DATA = ""
) )
@@ -23,6 +29,10 @@ type LocalCacheStore struct {
cluster einterfaces.ClusterInterface cluster einterfaces.ClusterInterface
reaction LocalCacheReactionStore reaction LocalCacheReactionStore
reactionCache *utils.Cache reactionCache *utils.Cache
role LocalCacheRoleStore
roleCache *utils.Cache
scheme LocalCacheSchemeStore
schemeCache *utils.Cache
} }
func NewLocalCacheLayer(baseStore store.Store, metrics einterfaces.MetricsInterface, cluster einterfaces.ClusterInterface) LocalCacheStore { func NewLocalCacheLayer(baseStore store.Store, metrics einterfaces.MetricsInterface, cluster einterfaces.ClusterInterface) LocalCacheStore {
@@ -33,9 +43,15 @@ func NewLocalCacheLayer(baseStore store.Store, metrics einterfaces.MetricsInterf
} }
localCacheStore.reactionCache = utils.NewLruWithParams(REACTION_CACHE_SIZE, "Reaction", REACTION_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS) localCacheStore.reactionCache = utils.NewLruWithParams(REACTION_CACHE_SIZE, "Reaction", REACTION_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS)
localCacheStore.reaction = LocalCacheReactionStore{ReactionStore: baseStore.Reaction(), rootStore: &localCacheStore} localCacheStore.reaction = LocalCacheReactionStore{ReactionStore: baseStore.Reaction(), rootStore: &localCacheStore}
localCacheStore.roleCache = utils.NewLruWithParams(ROLE_CACHE_SIZE, "Role", ROLE_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES)
localCacheStore.role = LocalCacheRoleStore{RoleStore: baseStore.Role(), rootStore: &localCacheStore}
localCacheStore.schemeCache = utils.NewLruWithParams(SCHEME_CACHE_SIZE, "Scheme", SCHEME_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_SCHEMES)
localCacheStore.scheme = LocalCacheSchemeStore{SchemeStore: baseStore.Scheme(), rootStore: &localCacheStore}
if cluster != nil { if cluster != nil {
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS, localCacheStore.reaction.handleClusterInvalidateReaction) cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS, localCacheStore.reaction.handleClusterInvalidateReaction)
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES, localCacheStore.role.handleClusterInvalidateRole)
cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_SCHEMES, localCacheStore.scheme.handleClusterInvalidateScheme)
} }
return localCacheStore return localCacheStore
} }
@@ -44,6 +60,14 @@ func (s LocalCacheStore) Reaction() store.ReactionStore {
return s.reaction return s.reaction
} }
func (s LocalCacheStore) Role() store.RoleStore {
return s.role
}
func (s LocalCacheStore) Scheme() store.SchemeStore {
return s.scheme
}
func (s LocalCacheStore) DropAllTables() { func (s LocalCacheStore) DropAllTables() {
s.Invalidate() s.Invalidate()
s.Store.DropAllTables() s.Store.DropAllTables()

Просмотреть файл

@@ -6,11 +6,44 @@ package localcachelayer
import ( import (
"testing" "testing"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store/storetest/mocks"
"github.com/mattermost/mattermost-server/testlib" "github.com/mattermost/mattermost-server/testlib"
) )
var mainHelper *testlib.MainHelper var mainHelper *testlib.MainHelper
func getMockStore() *mocks.Store {
mockStore := mocks.Store{}
fakeReaction := model.Reaction{PostId: "123"}
mockReactionsStore := mocks.ReactionStore{}
mockReactionsStore.On("Save", &fakeReaction).Return(&model.Reaction{}, nil)
mockReactionsStore.On("Delete", &fakeReaction).Return(&model.Reaction{}, nil)
mockReactionsStore.On("GetForPost", "123", false).Return([]*model.Reaction{&fakeReaction}, nil)
mockReactionsStore.On("GetForPost", "123", true).Return([]*model.Reaction{&fakeReaction}, nil)
mockStore.On("Reaction").Return(&mockReactionsStore)
fakeRole := model.Role{Id: "123", Name: "role-name"}
mockRolesStore := mocks.RoleStore{}
mockRolesStore.On("Save", &fakeRole).Return(&model.Role{}, nil)
mockRolesStore.On("Delete", "123").Return(&fakeRole, nil)
mockRolesStore.On("GetByName", "role-name").Return(&fakeRole, nil)
mockRolesStore.On("GetByNames", []string{"role-name"}).Return([]*model.Role{&fakeRole}, nil)
mockRolesStore.On("PermanentDeleteAll").Return(nil)
mockStore.On("Role").Return(&mockRolesStore)
fakeScheme := model.Scheme{Id: "123", Name: "scheme-name"}
mockSchemesStore := mocks.SchemeStore{}
mockSchemesStore.On("Save", &fakeScheme).Return(&model.Scheme{}, nil)
mockSchemesStore.On("Delete", "123").Return(&model.Scheme{}, nil)
mockSchemesStore.On("Get", "123").Return(&fakeScheme, nil)
mockSchemesStore.On("PermanentDeleteAll").Return(nil)
mockStore.On("Scheme").Return(&mockSchemesStore)
return &mockStore
}
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
mainHelper = testlib.NewMainHelperWithOptions(nil) mainHelper = testlib.NewMainHelperWithOptions(nil)
defer mainHelper.Close() defer mainHelper.Close()

Просмотреть файл

@@ -21,72 +21,48 @@ func TestReactionStoreCache(t *testing.T) {
fakeReaction := model.Reaction{PostId: "123"} fakeReaction := model.Reaction{PostId: "123"}
t.Run("first call not cached, second cached and returning same data", func(t *testing.T) { t.Run("first call not cached, second cached and returning same data", func(t *testing.T) {
mockStore := mocks.Store{} mockStore := getMockStore()
mockReactionsStore := mocks.ReactionStore{} cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
mockReactionsStore.On("Save", &fakeReaction).Return(&model.Reaction{}, nil)
mockReactionsStore.On("Delete", &fakeReaction).Return(&model.Reaction{}, nil)
mockReactionsStore.On("GetForPost", "123", false).Return([]*model.Reaction{&fakeReaction}, nil)
mockReactionsStore.On("GetForPost", "123", true).Return([]*model.Reaction{&fakeReaction}, nil)
mockStore.On("Reaction").Return(&mockReactionsStore)
cachedStore := NewLocalCacheLayer(&mockStore, nil, nil)
reaction, err := cachedStore.Reaction().GetForPost("123", true) reaction, err := cachedStore.Reaction().GetForPost("123", true)
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, reaction, []*model.Reaction{&fakeReaction}) assert.Equal(t, reaction, []*model.Reaction{&fakeReaction})
mockReactionsStore.AssertNumberOfCalls(t, "GetForPost", 1) mockStore.Reaction().(*mocks.ReactionStore).AssertNumberOfCalls(t, "GetForPost", 1)
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, reaction, []*model.Reaction{&fakeReaction}) assert.Equal(t, reaction, []*model.Reaction{&fakeReaction})
cachedStore.Reaction().GetForPost("123", true) cachedStore.Reaction().GetForPost("123", true)
mockReactionsStore.AssertNumberOfCalls(t, "GetForPost", 1) mockStore.Reaction().(*mocks.ReactionStore).AssertNumberOfCalls(t, "GetForPost", 1)
}) })
t.Run("first call not cached, second force no cached", func(t *testing.T) { t.Run("first call not cached, second force no cached", func(t *testing.T) {
mockStore := mocks.Store{} mockStore := getMockStore()
mockReactionsStore := mocks.ReactionStore{} cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
mockReactionsStore.On("Save", &fakeReaction).Return(&model.Reaction{}, nil)
mockReactionsStore.On("Delete", &fakeReaction).Return(&model.Reaction{}, nil)
mockReactionsStore.On("GetForPost", "123", false).Return([]*model.Reaction{&fakeReaction}, nil)
mockReactionsStore.On("GetForPost", "123", true).Return([]*model.Reaction{&fakeReaction}, nil)
mockStore.On("Reaction").Return(&mockReactionsStore)
cachedStore := NewLocalCacheLayer(&mockStore, nil, nil)
cachedStore.Reaction().GetForPost("123", true) cachedStore.Reaction().GetForPost("123", true)
mockReactionsStore.AssertNumberOfCalls(t, "GetForPost", 1) mockStore.Reaction().(*mocks.ReactionStore).AssertNumberOfCalls(t, "GetForPost", 1)
cachedStore.Reaction().GetForPost("123", false) cachedStore.Reaction().GetForPost("123", false)
mockReactionsStore.AssertNumberOfCalls(t, "GetForPost", 2) mockStore.Reaction().(*mocks.ReactionStore).AssertNumberOfCalls(t, "GetForPost", 2)
}) })
t.Run("first call not cached, save, and then not cached again", func(t *testing.T) { t.Run("first call not cached, save, and then not cached again", func(t *testing.T) {
mockStore := mocks.Store{} mockStore := getMockStore()
mockReactionsStore := mocks.ReactionStore{} cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
mockReactionsStore.On("Save", &fakeReaction).Return(&model.Reaction{}, nil)
mockReactionsStore.On("Delete", &fakeReaction).Return(&model.Reaction{}, nil)
mockReactionsStore.On("GetForPost", "123", false).Return([]*model.Reaction{&fakeReaction}, nil)
mockReactionsStore.On("GetForPost", "123", true).Return([]*model.Reaction{&fakeReaction}, nil)
mockStore.On("Reaction").Return(&mockReactionsStore)
cachedStore := NewLocalCacheLayer(&mockStore, nil, nil)
cachedStore.Reaction().GetForPost("123", true) cachedStore.Reaction().GetForPost("123", true)
mockReactionsStore.AssertNumberOfCalls(t, "GetForPost", 1) mockStore.Reaction().(*mocks.ReactionStore).AssertNumberOfCalls(t, "GetForPost", 1)
cachedStore.Reaction().Save(&fakeReaction) cachedStore.Reaction().Save(&fakeReaction)
cachedStore.Reaction().GetForPost("123", true) cachedStore.Reaction().GetForPost("123", true)
mockReactionsStore.AssertNumberOfCalls(t, "GetForPost", 2) mockStore.Reaction().(*mocks.ReactionStore).AssertNumberOfCalls(t, "GetForPost", 2)
}) })
t.Run("first call not cached, delete, and then not cached again", func(t *testing.T) { t.Run("first call not cached, delete, and then not cached again", func(t *testing.T) {
mockStore := mocks.Store{} mockStore := getMockStore()
mockReactionsStore := mocks.ReactionStore{} cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
mockReactionsStore.On("Save", &fakeReaction).Return(&model.Reaction{}, nil)
mockReactionsStore.On("Delete", &fakeReaction).Return(&model.Reaction{}, nil)
mockReactionsStore.On("GetForPost", "123", false).Return([]*model.Reaction{&fakeReaction}, nil)
mockReactionsStore.On("GetForPost", "123", true).Return([]*model.Reaction{&fakeReaction}, nil)
mockStore.On("Reaction").Return(&mockReactionsStore)
cachedStore := NewLocalCacheLayer(&mockStore, nil, nil)
cachedStore.Reaction().GetForPost("123", true) cachedStore.Reaction().GetForPost("123", true)
mockReactionsStore.AssertNumberOfCalls(t, "GetForPost", 1) mockStore.Reaction().(*mocks.ReactionStore).AssertNumberOfCalls(t, "GetForPost", 1)
cachedStore.Reaction().Delete(&fakeReaction) cachedStore.Reaction().Delete(&fakeReaction)
cachedStore.Reaction().GetForPost("123", true) cachedStore.Reaction().GetForPost("123", true)
mockReactionsStore.AssertNumberOfCalls(t, "GetForPost", 2) mockStore.Reaction().(*mocks.ReactionStore).AssertNumberOfCalls(t, "GetForPost", 2)
}) })
} }

80
store/localcachelayer/role_layer.go Обычный файл
Просмотреть файл

@@ -0,0 +1,80 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package localcachelayer
import (
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
)
type LocalCacheRoleStore struct {
store.RoleStore
rootStore *LocalCacheStore
}
func (s *LocalCacheRoleStore) handleClusterInvalidateRole(msg *model.ClusterMessage) {
if msg.Data == CLEAR_CACHE_MESSAGE_DATA {
s.rootStore.roleCache.Purge()
} else {
s.rootStore.roleCache.Remove(msg.Data)
}
}
func (s LocalCacheRoleStore) Save(role *model.Role) (*model.Role, *model.AppError) {
if len(role.Name) != 0 {
defer s.rootStore.doInvalidateCacheCluster(s.rootStore.roleCache, role.Name)
}
return s.RoleStore.Save(role)
}
func (s LocalCacheRoleStore) GetByName(name string) (*model.Role, *model.AppError) {
if role := s.rootStore.doStandardReadCache(s.rootStore.roleCache, name); role != nil {
return role.(*model.Role), nil
}
role, err := s.RoleStore.GetByName(name)
if err != nil {
return nil, err
}
s.rootStore.doStandardAddToCache(s.rootStore.roleCache, name, role)
return role, nil
}
func (s LocalCacheRoleStore) GetByNames(names []string) ([]*model.Role, *model.AppError) {
var foundRoles []*model.Role
var rolesToQuery []string
for _, roleName := range names {
if role := s.rootStore.doStandardReadCache(s.rootStore.roleCache, roleName); role != nil {
foundRoles = append(foundRoles, role.(*model.Role))
} else {
rolesToQuery = append(rolesToQuery, roleName)
}
}
roles, _ := s.RoleStore.GetByNames(rolesToQuery)
if roles != nil {
for _, role := range roles {
s.rootStore.doStandardAddToCache(s.rootStore.roleCache, role.Name, role)
}
}
return append(foundRoles, roles...), nil
}
func (s LocalCacheRoleStore) Delete(roleId string) (*model.Role, *model.AppError) {
role, err := s.RoleStore.Delete(roleId)
if err == nil {
s.rootStore.doInvalidateCacheCluster(s.rootStore.roleCache, role.Name)
}
return role, err
}
func (s LocalCacheRoleStore) PermanentDeleteAll() *model.AppError {
defer s.rootStore.roleCache.Purge()
defer s.rootStore.doClearCacheCluster(s.rootStore.roleCache)
return s.RoleStore.PermanentDeleteAll()
}

69
store/localcachelayer/role_layer_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,69 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package localcachelayer
import (
"testing"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store/storetest"
"github.com/mattermost/mattermost-server/store/storetest/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRoleStore(t *testing.T) {
StoreTest(t, storetest.TestRoleStore)
}
func TestRoleStoreCache(t *testing.T) {
fakeRole := model.Role{Id: "123", Name: "role-name"}
t.Run("first call not cached, second cached and returning same data", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
role, err := cachedStore.Role().GetByName("role-name")
require.Nil(t, err)
assert.Equal(t, role, &fakeRole)
mockStore.Role().(*mocks.RoleStore).AssertNumberOfCalls(t, "GetByName", 1)
require.Nil(t, err)
assert.Equal(t, role, &fakeRole)
cachedStore.Role().GetByName("role-name")
mockStore.Role().(*mocks.RoleStore).AssertNumberOfCalls(t, "GetByName", 1)
})
t.Run("first call not cached, save, and then not cached again", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
cachedStore.Role().GetByName("role-name")
mockStore.Role().(*mocks.RoleStore).AssertNumberOfCalls(t, "GetByName", 1)
cachedStore.Role().Save(&fakeRole)
cachedStore.Role().GetByName("role-name")
mockStore.Role().(*mocks.RoleStore).AssertNumberOfCalls(t, "GetByName", 2)
})
t.Run("first call not cached, delete, and then not cached again", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
cachedStore.Role().GetByName("role-name")
mockStore.Role().(*mocks.RoleStore).AssertNumberOfCalls(t, "GetByName", 1)
cachedStore.Role().Delete("123")
cachedStore.Role().GetByName("role-name")
mockStore.Role().(*mocks.RoleStore).AssertNumberOfCalls(t, "GetByName", 2)
})
t.Run("first call not cached, permanent delete all, and then not cached again", func(t *testing.T) {
mockStore := getMockStore()
cachedStore := NewLocalCacheLayer(mockStore, nil, nil)
cachedStore.Role().GetByName("role-name")
mockStore.Role().(*mocks.RoleStore).AssertNumberOfCalls(t, "GetByName", 1)
cachedStore.Role().PermanentDeleteAll()
cachedStore.Role().GetByName("role-name")
mockStore.Role().(*mocks.RoleStore).AssertNumberOfCalls(t, "GetByName", 2)
})
}

58
store/localcachelayer/scheme_layer.go Обычный файл
Просмотреть файл

@@ -0,0 +1,58 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package localcachelayer
import (
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
)
type LocalCacheSchemeStore struct {
store.SchemeStore
rootStore *LocalCacheStore
}
func (s *LocalCacheSchemeStore) handleClusterInvalidateScheme(msg *model.ClusterMessage) {
if msg.Data == CLEAR_CACHE_MESSAGE_DATA {
s.rootStore.schemeCache.Purge()
} else {
s.rootStore.schemeCache.Remove(msg.Data)
}
}
func (s LocalCacheSchemeStore) Save(scheme *model.Scheme) (*model.Scheme, *model.AppError) {
if len(scheme.Id) != 0 {
defer s.rootStore.doInvalidateCacheCluster(s.rootStore.schemeCache, scheme.Id)
}
return s.SchemeStore.Save(scheme)
}
func (s LocalCacheSchemeStore) Get(schemeId string) (*model.Scheme, *model.AppError) {
if scheme := s.rootStore.doStandardReadCache(s.rootStore.schemeCache, schemeId); scheme != nil {
return scheme.(*model.Scheme), nil
}
scheme, err := s.SchemeStore.Get(schemeId)
if err != nil {
return nil, err
}
s.rootStore.doStandardAddToCache(s.rootStore.schemeCache, schemeId, scheme)
return scheme, nil
}
func (s LocalCacheSchemeStore) Delete(schemeId string) (*model.Scheme, *model.AppError) {
defer s.rootStore.doInvalidateCacheCluster(s.rootStore.schemeCache, schemeId)
defer s.rootStore.doClearCacheCluster(s.rootStore.roleCache)
return s.SchemeStore.Delete(schemeId)
}
func (s LocalCacheSchemeStore) PermanentDeleteAll() *model.AppError {
defer s.rootStore.doClearCacheCluster(s.rootStore.schemeCache)
defer s.rootStore.doClearCacheCluster(s.rootStore.roleCache)
return s.SchemeStore.PermanentDeleteAll()
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше