Merge branch 'master' into mark-as-unread
Этот коммит содержится в:
8
.circleci/config.yml
Обычный файл
8
.circleci/config.yml
Обычный файл
@@ -0,0 +1,8 @@
|
||||
version: 2.1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/golang:1.12
|
||||
steps:
|
||||
- run: echo "skipping build. PR \#11978 in progress."
|
||||
11
Makefile
11
Makefile
@@ -1,4 +1,4 @@
|
||||
.PHONY: build package run stop run-client run-server stop-client stop-server restart restart-server restart-client start-docker clean-dist clean nuke check-style check-client-style check-server-style check-unit-tests test dist prepare-enteprise run-client-tests setup-run-client-tests cleanup-run-client-tests test-client build-linux build-osx build-windows internal-test-web-client vet run-server-for-web-client-tests
|
||||
.PHONY: build package run stop run-client run-server stop-client stop-server restart restart-server restart-client start-docker clean-dist clean nuke check-style check-client-style check-server-style check-unit-tests test dist prepare-enteprise run-client-tests setup-run-client-tests cleanup-run-client-tests test-client build-linux build-osx build-windows internal-test-web-client vet run-server-for-web-client-tests diff-config
|
||||
|
||||
ROOT := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||
|
||||
@@ -87,7 +87,7 @@ PLUGIN_PACKAGES += mattermost-plugin-github-v0.10.2
|
||||
PLUGIN_PACKAGES += mattermost-plugin-welcomebot-v1.1.0
|
||||
PLUGIN_PACKAGES += mattermost-plugin-aws-SNS-v1.0.2
|
||||
PLUGIN_PACKAGES += mattermost-plugin-antivirus-v0.1.1
|
||||
PLUGIN_PACKAGES += mattermost-plugin-jira-v2.1.0
|
||||
PLUGIN_PACKAGES += mattermost-plugin-jira-v2.1.1
|
||||
PLUGIN_PACKAGES += mattermost-plugin-gitlab-v1.0.0
|
||||
PLUGIN_PACKAGES += mattermost-plugin-jenkins-v1.0.0
|
||||
|
||||
@@ -361,11 +361,11 @@ ifeq ($(BUILDER_GOOS_GOARCH),"windows_amd64")
|
||||
wmic process where "Caption='go.exe' and CommandLine like '%go.exe run%'" call terminate
|
||||
wmic process where "Caption='mattermost.exe' and CommandLine like '%go-build%'" call terminate
|
||||
else
|
||||
@for PID in $$(ps -ef | grep "[g]o run" | awk '{ print $$2 }'); do \
|
||||
@for PID in $$(ps -ef | grep "[g]o run" | grep "disableconfigwatch" | awk '{ print $$2 }'); do \
|
||||
echo stopping go $$PID; \
|
||||
kill $$PID; \
|
||||
done
|
||||
@for PID in $$(ps -ef | grep "[g]o-build" | awk '{ print $$2 }'); do \
|
||||
@for PID in $$(ps -ef | grep "[g]o-build" | grep "disableconfigwatch" | awk '{ print $$2 }'); do \
|
||||
echo stopping mattermost $$PID; \
|
||||
kill $$PID; \
|
||||
done
|
||||
@@ -410,6 +410,9 @@ config-reset: ## Resets the config/config.json file to the default.
|
||||
rm -f config/config.json
|
||||
OUTPUT_CONFIG=$(PWD)/config/config.json go generate ./config
|
||||
|
||||
diff-config: ## Compares default configuration between two mattermost versions
|
||||
@./scripts/diff-config.sh
|
||||
|
||||
clean: stop-docker ## Clean up everything except persistant server data.
|
||||
@echo Cleaning
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ import (
|
||||
"github.com/mattermost/mattermost-server/web"
|
||||
"github.com/mattermost/mattermost-server/wsapi"
|
||||
|
||||
s3 "github.com/minio/minio-go"
|
||||
"github.com/minio/minio-go/pkg/credentials"
|
||||
s3 "github.com/minio/minio-go/v6"
|
||||
"github.com/minio/minio-go/v6/pkg/credentials"
|
||||
)
|
||||
|
||||
type TestHelper struct {
|
||||
|
||||
@@ -527,7 +527,13 @@ func getChannelStats(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
stats := model.ChannelStats{ChannelId: c.Params.ChannelId, MemberCount: memberCount, GuestCount: guestCount}
|
||||
pinnedPostCount, err := c.App.GetChannelPinnedPostCount(c.Params.ChannelId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
stats := model.ChannelStats{ChannelId: c.Params.ChannelId, MemberCount: memberCount, GuestCount: guestCount, PinnedPostCount: pinnedPostCount}
|
||||
w.Write([]byte(stats.ToJson()))
|
||||
}
|
||||
|
||||
|
||||
@@ -1849,6 +1849,16 @@ func TestGetChannelStats(t *testing.T) {
|
||||
t.Fatal("couldnt't get extra info")
|
||||
} else if stats.MemberCount != 1 {
|
||||
t.Fatal("got incorrect member count")
|
||||
} else if stats.PinnedPostCount != 0 {
|
||||
t.Fatal("got incorrect pinned post count")
|
||||
}
|
||||
|
||||
th.CreatePinnedPostWithClient(th.Client, channel)
|
||||
stats, resp = Client.GetChannelStats(channel.Id, "")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if stats.PinnedPostCount != 1 {
|
||||
t.Fatal("should have returned 1 pinned post count")
|
||||
}
|
||||
|
||||
_, resp = Client.GetChannelStats("junk", "")
|
||||
|
||||
@@ -27,6 +27,7 @@ func (api *API) InitSystem() {
|
||||
|
||||
api.BaseRoutes.ApiRoot.Handle("/audits", api.ApiSessionRequired(getAudits)).Methods("GET")
|
||||
api.BaseRoutes.ApiRoot.Handle("/email/test", api.ApiSessionRequired(testEmail)).Methods("POST")
|
||||
api.BaseRoutes.ApiRoot.Handle("/site_url/test", api.ApiSessionRequired(testSiteURL)).Methods("POST")
|
||||
api.BaseRoutes.ApiRoot.Handle("/file/s3_test", api.ApiSessionRequired(testS3)).Methods("POST")
|
||||
api.BaseRoutes.ApiRoot.Handle("/database/recycle", api.ApiSessionRequired(databaseRecycle)).Methods("POST")
|
||||
api.BaseRoutes.ApiRoot.Handle("/caches/invalidate", api.ApiSessionRequired(invalidateCaches)).Methods("POST")
|
||||
@@ -145,6 +146,32 @@ func testEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func testSiteURL(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
|
||||
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||
return
|
||||
}
|
||||
|
||||
if *c.App.Config().ExperimentalSettings.RestrictSystemAdmin {
|
||||
c.Err = model.NewAppError("testSiteURL", "api.restricted_system_admin", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
props := model.MapFromJson(r.Body)
|
||||
siteURL := props["site_url"]
|
||||
if siteURL == "" {
|
||||
c.SetInvalidParam("site_url")
|
||||
return
|
||||
}
|
||||
err := c.App.TestSiteURL(siteURL)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func getAudits(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
|
||||
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/mlog"
|
||||
@@ -149,6 +150,47 @@ func TestEmailTest(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestSiteURLTest(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
Client := th.Client
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasSuffix(r.URL.Path, "/valid/api/v4/system/ping") {
|
||||
w.WriteHeader(200)
|
||||
} else {
|
||||
w.WriteHeader(400)
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
validSiteURL := ts.URL + "/valid"
|
||||
invalidSiteURL := ts.URL + "/invalid"
|
||||
|
||||
t.Run("as system admin", func(t *testing.T) {
|
||||
_, resp := th.SystemAdminClient.TestSiteURL("")
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
_, resp = th.SystemAdminClient.TestSiteURL(invalidSiteURL)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
_, resp = th.SystemAdminClient.TestSiteURL(validSiteURL)
|
||||
CheckOKStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("as system user", func(t *testing.T) {
|
||||
_, resp := Client.TestSiteURL(validSiteURL)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("as restricted system admin", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
||||
|
||||
_, resp := Client.TestSiteURL(validSiteURL)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDatabaseRecycle(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
14
api4/team.go
14
api4/team.go
@@ -729,7 +729,7 @@ func updateTeamMemberSchemeRoles(c *Context, w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
|
||||
func getAllTeams(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
var teams []*model.Team
|
||||
teams := []*model.Team{}
|
||||
var err *model.AppError
|
||||
var teamsWithCount *model.TeamsWithCount
|
||||
|
||||
@@ -740,9 +740,17 @@ func getAllTeams(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
teams, err = c.App.GetAllTeamsPage(c.Params.Page*c.Params.PerPage, c.Params.PerPage)
|
||||
}
|
||||
} else if c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_LIST_PRIVATE_TEAMS) {
|
||||
teams, err = c.App.GetAllPrivateTeamsPage(c.Params.Page*c.Params.PerPage, c.Params.PerPage)
|
||||
if c.Params.IncludeTotalCount {
|
||||
teamsWithCount, err = c.App.GetAllPrivateTeamsPageWithCount(c.Params.Page*c.Params.PerPage, c.Params.PerPage)
|
||||
} else {
|
||||
teams, err = c.App.GetAllPrivateTeamsPage(c.Params.Page*c.Params.PerPage, c.Params.PerPage)
|
||||
}
|
||||
} else if c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_LIST_PUBLIC_TEAMS) {
|
||||
teams, err = c.App.GetAllPublicTeamsPage(c.Params.Page*c.Params.PerPage, c.Params.PerPage)
|
||||
if c.Params.IncludeTotalCount {
|
||||
teamsWithCount, err = c.App.GetAllPublicTeamsPageWithCount(c.Params.Page*c.Params.PerPage, c.Params.PerPage)
|
||||
} else {
|
||||
teams, err = c.App.GetAllPublicTeamsPage(c.Params.Page*c.Params.PerPage, c.Params.PerPage)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -621,6 +621,10 @@ func TestGetAllTeams(t *testing.T) {
|
||||
team3, resp = Client.CreateTeam(team3)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
team4 := &model.Team{DisplayName: "Name4", Name: GenerateTestTeamName(), Email: th.GenerateTestEmail(), Type: model.TEAM_OPEN, AllowOpenInvite: false}
|
||||
team4, resp = Client.CreateTeam(team4)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
Page int
|
||||
@@ -663,14 +667,14 @@ func TestGetAllTeams(t *testing.T) {
|
||||
Page: 0,
|
||||
PerPage: 10,
|
||||
Permissions: []string{model.PERMISSION_LIST_PRIVATE_TEAMS.Id},
|
||||
ExpectedTeams: []string{th.BasicTeam.Id, team3.Id},
|
||||
ExpectedTeams: []string{th.BasicTeam.Id, team3.Id, team4.Id},
|
||||
},
|
||||
{
|
||||
Name: "Get all teams",
|
||||
Page: 0,
|
||||
PerPage: 10,
|
||||
Permissions: []string{model.PERMISSION_LIST_PUBLIC_TEAMS.Id, model.PERMISSION_LIST_PRIVATE_TEAMS.Id},
|
||||
ExpectedTeams: []string{th.BasicTeam.Id, team1.Id, team2.Id, team3.Id},
|
||||
ExpectedTeams: []string{th.BasicTeam.Id, team1.Id, team2.Id, team3.Id, team4.Id},
|
||||
},
|
||||
{
|
||||
Name: "Get no teams because permissions",
|
||||
@@ -684,9 +688,27 @@ func TestGetAllTeams(t *testing.T) {
|
||||
Page: 0,
|
||||
PerPage: 10,
|
||||
Permissions: []string{model.PERMISSION_LIST_PUBLIC_TEAMS.Id, model.PERMISSION_LIST_PRIVATE_TEAMS.Id},
|
||||
ExpectedTeams: []string{th.BasicTeam.Id, team1.Id, team2.Id, team3.Id},
|
||||
ExpectedTeams: []string{th.BasicTeam.Id, team1.Id, team2.Id, team3.Id, team4.Id},
|
||||
WithCount: true,
|
||||
ExpectedCount: 4,
|
||||
ExpectedCount: 5,
|
||||
},
|
||||
{
|
||||
Name: "Get all public teams with count",
|
||||
Page: 0,
|
||||
PerPage: 10,
|
||||
Permissions: []string{model.PERMISSION_LIST_PUBLIC_TEAMS.Id},
|
||||
ExpectedTeams: []string{team1.Id, team2.Id},
|
||||
WithCount: true,
|
||||
ExpectedCount: 2,
|
||||
},
|
||||
{
|
||||
Name: "Get all private teams with count",
|
||||
Page: 0,
|
||||
PerPage: 10,
|
||||
Permissions: []string{model.PERMISSION_LIST_PRIVATE_TEAMS.Id},
|
||||
ExpectedTeams: []string{th.BasicTeam.Id, team3.Id, team4.Id},
|
||||
WithCount: true,
|
||||
ExpectedCount: 3,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2310,7 +2332,9 @@ func TestInviteGuestsToTeam(t *testing.T) {
|
||||
defer func() {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableEmailInvitations = &enableEmailInvitations })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.RestrictCreationToDomains = restrictCreationToDomains })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GuestAccountsSettings.RestrictCreationToDomains = guestRestrictCreationToDomains })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.GuestAccountsSettings.RestrictCreationToDomains = guestRestrictCreationToDomains
|
||||
})
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GuestAccountsSettings.Enable = &enableGuestAccounts })
|
||||
}()
|
||||
|
||||
|
||||
@@ -86,6 +86,8 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
user.SanitizeInput()
|
||||
|
||||
tokenId := r.URL.Query().Get("t")
|
||||
inviteId := r.URL.Query().Get("iid")
|
||||
|
||||
|
||||
@@ -83,6 +83,70 @@ func TestCreateUser(t *testing.T) {
|
||||
assert.Equal(t, http.StatusBadRequest, r.StatusCode)
|
||||
}
|
||||
|
||||
func TestCreateUserInputFilter(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("DomainRestriction", func(t *testing.T) {
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.TeamSettings.EnableOpenServer = true
|
||||
*cfg.TeamSettings.EnableUserCreation = true
|
||||
*cfg.TeamSettings.RestrictCreationToDomains = "mattermost.com"
|
||||
})
|
||||
|
||||
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.TeamSettings.RestrictCreationToDomains = ""
|
||||
})
|
||||
|
||||
t.Run("ValidUser", func(t *testing.T) {
|
||||
user := &model.User{Email: "foobar+testdomainrestriction@mattermost.com", Password: "Password1", Username: GenerateTestUsername()}
|
||||
_, resp := th.SystemAdminClient.CreateUser(user)
|
||||
CheckNoError(t, resp)
|
||||
})
|
||||
|
||||
t.Run("InvalidEmail", func(t *testing.T) {
|
||||
user := &model.User{Email: "foobar+testdomainrestriction@mattermost.org", Password: "Password1", Username: GenerateTestUsername()}
|
||||
_, resp := th.SystemAdminClient.CreateUser(user)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("AuthServiceFilter", func(t *testing.T) {
|
||||
user := &model.User{Email: "foobar+testdomainrestriction@mattermost.org", Password: "Password1", Username: GenerateTestUsername(), AuthService: "ldap"}
|
||||
_, resp := th.SystemAdminClient.CreateUser(user)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Roles", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.TeamSettings.EnableOpenServer = true
|
||||
*cfg.TeamSettings.EnableUserCreation = true
|
||||
*cfg.TeamSettings.RestrictCreationToDomains = ""
|
||||
})
|
||||
|
||||
t.Run("InvalidRole", func(t *testing.T) {
|
||||
user := &model.User{Email: "foobar+testinvalidrole@mattermost.com", Password: "Password1", Username: GenerateTestUsername(), Roles: "system_user system_admin"}
|
||||
_, resp := th.SystemAdminClient.CreateUser(user)
|
||||
CheckNoError(t, resp)
|
||||
ruser, err := th.App.GetUserByEmail("foobar+testinvalidrole@mattermost.com")
|
||||
assert.Nil(t, err)
|
||||
assert.NotEqual(t, ruser.Roles, "system_user system_admin")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("InvalidId", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.TeamSettings.EnableOpenServer = true
|
||||
*cfg.TeamSettings.EnableUserCreation = true
|
||||
})
|
||||
|
||||
user := &model.User{Id: "AAAAAAAAAAAAAAAAAAAAAAAAAA", Email: "foobar+testinvalidid@mattermost.com", Password: "Password1", Username: GenerateTestUsername(), Roles: "system_user system_admin"}
|
||||
_, resp := th.SystemAdminClient.CreateUser(user)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateUserWithToken(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
11
app/admin.go
11
app/admin.go
@@ -4,6 +4,7 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
@@ -174,6 +175,16 @@ func (a *App) RecycleDatabaseConnection() {
|
||||
mlog.Warn("Finished recycling the database connection.")
|
||||
}
|
||||
|
||||
func (a *App) TestSiteURL(siteURL string) *model.AppError {
|
||||
url := fmt.Sprintf("%s/api/v4/system/ping", siteURL)
|
||||
res, err := http.Get(url)
|
||||
if err != nil || res.StatusCode != 200 {
|
||||
return model.NewAppError("testSiteURL", "app.admin.test_site_url.failure", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) TestEmail(userId string, cfg *model.Config) *model.AppError {
|
||||
if len(*cfg.EmailSettings.SMTPServer) == 0 {
|
||||
return model.NewAppError("testEmail", "api.admin.test_email.missing_server", nil, utils.T("api.context.invalid_param.app_error", map[string]interface{}{"Name": "SMTPServer"}), http.StatusBadRequest)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
@@ -28,10 +29,8 @@ func TestCheckIfRolesGrantPermission(t *testing.T) {
|
||||
{[]string{model.TEAM_ADMIN_ROLE_ID, model.TEAM_USER_ROLE_ID}, model.PERMISSION_MANAGE_SLASH_COMMANDS.Id, true},
|
||||
}
|
||||
|
||||
for testnum, testcase := range cases {
|
||||
if th.App.RolesGrantPermission(testcase.roles, testcase.permissionId) != testcase.shouldGrant {
|
||||
t.Fatal("Failed test case ", testnum)
|
||||
}
|
||||
for _, testcase := range cases {
|
||||
assert.Equal(t, th.App.RolesGrantPermission(testcase.roles, testcase.permissionId), testcase.shouldGrant)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ func (a *App) CreateChannelWithUser(channel *model.Channel, userId string) (*mod
|
||||
}
|
||||
|
||||
// Get total number of channels on current team
|
||||
count, err := a.GetNumberOfChannelsOnTeam(channel.TeamId)
|
||||
count, err := a.GetNumberOfChannelsOnTeam(channel.TeamId, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1330,6 +1330,10 @@ func (a *App) GetChannelGuestCount(channelId string) (int64, *model.AppError) {
|
||||
return a.Srv.Store.Channel().GetGuestCount(channelId, true)
|
||||
}
|
||||
|
||||
func (a *App) GetChannelPinnedPostCount(channelId string) (int64, *model.AppError) {
|
||||
return a.Srv.Store.Channel().GetPinnedPostCount(channelId, true)
|
||||
}
|
||||
|
||||
func (a *App) GetChannelCounts(teamId string, userId string) (*model.ChannelCounts, *model.AppError) {
|
||||
return a.Srv.Store.Channel().GetChannelCounts(teamId, userId)
|
||||
}
|
||||
@@ -1720,12 +1724,23 @@ func (a *App) RemoveUserFromChannel(userIdToRemove string, removerUserId string,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) GetNumberOfChannelsOnTeam(teamId string) (int, *model.AppError) {
|
||||
func (a *App) GetNumberOfChannelsOnTeam(teamId string, includeDeleted bool) (int, *model.AppError) {
|
||||
// Get total number of channels on current team
|
||||
list, err := a.Srv.Store.Channel().GetTeamChannels(teamId)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if !includeDeleted {
|
||||
count := 0
|
||||
for _, channel := range *list {
|
||||
if channel.DeleteAt == 0 {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
return len(*list), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1042,6 +1042,21 @@ func TestSearchChannelsForUser(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetNumberOfChannelsOnTeam(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.DeleteChannel(th.BasicChannel, th.BasicUser.Id)
|
||||
|
||||
count, err := th.App.GetNumberOfChannelsOnTeam(th.BasicTeam.Id, true)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 3, count)
|
||||
|
||||
count, err = th.App.GetNumberOfChannelsOnTeam(th.BasicTeam.Id, false)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 2, count)
|
||||
}
|
||||
|
||||
func TestMarkChannelAsUnreadFromPost(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
@@ -187,7 +187,7 @@ func (a *App) ExecuteCommand(args *model.CommandArgs) (*model.CommandResponse, *
|
||||
return nil, model.NewAppError("command", "api.command.execute_command.not_found.app_error", map[string]interface{}{"Trigger": trigger}, "", http.StatusNotFound)
|
||||
}
|
||||
|
||||
// tryExecutePluginCommand attempts to run a built in command based on the given arguments. If no such command can be
|
||||
// tryExecuteBuiltInCommand attempts to run a built in command based on the given arguments. If no such command can be
|
||||
// found, returns nil for all arguments.
|
||||
func (a *App) tryExecuteBuiltInCommand(args *model.CommandArgs, trigger string, message string) (*model.Command, *model.CommandResponse) {
|
||||
provider := GetCommandProvider(trigger)
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -90,7 +89,7 @@ func (me *EchoProvider) DoCommand(a *App, args *model.CommandArgs, message strin
|
||||
time.Sleep(time.Duration(delay) * time.Second)
|
||||
|
||||
if _, err := a.CreatePostMissingChannel(post, true); err != nil {
|
||||
mlog.Error(fmt.Sprintf("Unable to create /echo post, err=%v", err))
|
||||
mlog.Error("Unable to create /echo post.", mlog.Err(err))
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -58,6 +58,15 @@ func (me *msgProvider) DoCommand(a *App, args *model.CommandArgs, message string
|
||||
return &model.CommandResponse{Text: args.T("api.command_msg.missing.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
|
||||
}
|
||||
|
||||
canSee, err := a.UserCanSeeOtherUser(args.UserId, userProfile.Id)
|
||||
if err != nil {
|
||||
mlog.Error(err.Error())
|
||||
return &model.CommandResponse{Text: args.T("api.command_msg.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
|
||||
}
|
||||
if !canSee {
|
||||
return &model.CommandResponse{Text: args.T("api.command_msg.missing.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
|
||||
}
|
||||
|
||||
// Find the channel based on this user
|
||||
channelName := model.GetDMNameFromIds(args.UserId, userProfile.Id)
|
||||
|
||||
|
||||
@@ -62,4 +62,44 @@ func TestMsgProvider(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "", resp.Text)
|
||||
assert.Equal(t, "http://test.url/"+team.Name+"/channels/"+channelName, resp.GotoLocation)
|
||||
|
||||
// Check that a guest user cannot message a user who is not in a channel/team with him
|
||||
guest := th.CreateGuest()
|
||||
user := th.CreateUser()
|
||||
|
||||
th.LinkUserToTeam(user, team)
|
||||
th.LinkUserToTeam(guest, th.BasicTeam)
|
||||
th.AddUserToChannel(guest, th.BasicChannel)
|
||||
|
||||
resp = cmd.DoCommand(th.App, &model.CommandArgs{
|
||||
T: i18n.IdentityTfunc(),
|
||||
SiteURL: "http://test.url",
|
||||
TeamId: th.BasicTeam.Id,
|
||||
UserId: guest.Id,
|
||||
Session: model.Session{
|
||||
Roles: model.SYSTEM_GUEST_ROLE_ID,
|
||||
},
|
||||
}, "@"+user.Username+" hello")
|
||||
|
||||
assert.Equal(t, "api.command_msg.missing.app_error", resp.Text)
|
||||
assert.Equal(t, "", resp.GotoLocation)
|
||||
|
||||
// Check that a guest user can message a user who is in a channel/team with him
|
||||
th.LinkUserToTeam(user, th.BasicTeam)
|
||||
th.AddUserToChannel(user, th.BasicChannel)
|
||||
|
||||
resp = cmd.DoCommand(th.App, &model.CommandArgs{
|
||||
T: i18n.IdentityTfunc(),
|
||||
SiteURL: "http://test.url",
|
||||
TeamId: th.BasicTeam.Id,
|
||||
UserId: guest.Id,
|
||||
Session: model.Session{
|
||||
Roles: model.SYSTEM_GUEST_ROLE_ID,
|
||||
},
|
||||
}, "@"+user.Username+" hello")
|
||||
|
||||
channelName = model.GetDMNameFromIds(guest.Id, user.Id)
|
||||
|
||||
assert.Equal(t, "", resp.Text)
|
||||
assert.Equal(t, "http://test.url/"+th.BasicTeam.Name+"/channels/"+channelName, resp.GotoLocation)
|
||||
}
|
||||
|
||||
@@ -586,13 +586,13 @@ func (a *App) trackConfig() {
|
||||
})
|
||||
|
||||
a.SendDiagnostic(TRACK_CONFIG_PLUGIN, map[string]interface{}{
|
||||
"enable_gitlab": pluginActivated(cfg.PluginSettings.PluginStates, "com.github.manland.mattermost-plugin-gitlab"),
|
||||
"enable_antivirus": pluginActivated(cfg.PluginSettings.PluginStates, "antivirus"),
|
||||
"enable_jenkins": pluginActivated(cfg.PluginSettings.PluginStates, "jenkins"),
|
||||
"enable_autolink": pluginActivated(cfg.PluginSettings.PluginStates, "mattermost-autolink"),
|
||||
"enable_aws_sns": pluginActivated(cfg.PluginSettings.PluginStates, "com.mattermost.aws-sns"),
|
||||
"enable_custom_user_attributes": pluginActivated(cfg.PluginSettings.PluginStates, "com.mattermost.custom-attributes"),
|
||||
"enable_github": pluginActivated(cfg.PluginSettings.PluginStates, "github"),
|
||||
"enable_gitlab": pluginActivated(cfg.PluginSettings.PluginStates, "com.github.manland.mattermost-plugin-gitlab"),
|
||||
"enable_jenkins": pluginActivated(cfg.PluginSettings.PluginStates, "jenkins"),
|
||||
"enable_jira": pluginActivated(cfg.PluginSettings.PluginStates, "jira"),
|
||||
"enable_nps": pluginActivated(cfg.PluginSettings.PluginStates, "com.mattermost.nps"),
|
||||
"enable_nps_survey": pluginSetting(&cfg.PluginSettings, "com.mattermost.nps", "enablesurvey", true),
|
||||
|
||||
@@ -296,6 +296,26 @@ func (me *TestHelper) CreatePost(channel *model.Channel) *model.Post {
|
||||
return post
|
||||
}
|
||||
|
||||
func (me *TestHelper) CreateMessagePost(channel *model.Channel, message string) *model.Post {
|
||||
post := &model.Post{
|
||||
UserId: me.BasicUser.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: message,
|
||||
CreateAt: model.GetMillis() - 10000,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
if post, err = me.App.CreatePost(post, channel, false); err != nil {
|
||||
mlog.Error(err.Error())
|
||||
|
||||
time.Sleep(time.Second)
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return post
|
||||
}
|
||||
|
||||
func (me *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
|
||||
@@ -593,7 +593,9 @@ func (a *App) getMentionKeywordsInChannel(profiles map[string]*model.User, lookF
|
||||
for _, k := range splitKeys {
|
||||
// note that these are made lower case so that we can do a case insensitive check for them
|
||||
key := strings.ToLower(k)
|
||||
keywords[key] = append(keywords[key], id)
|
||||
if key != "" {
|
||||
keywords[key] = append(keywords[key], id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -784,3 +786,16 @@ func (e *ExplicitMentions) processText(text string, keywords map[string][]string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) GetNotificationNameFormat(user *model.User) string {
|
||||
if !*a.Config().PrivacySettings.ShowFullName {
|
||||
return model.SHOW_USERNAME
|
||||
}
|
||||
|
||||
data, err := a.Srv.Store.Preference().Get(user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, model.PREFERENCE_NAME_NAME_FORMAT)
|
||||
if err != nil {
|
||||
return *a.Config().TeamSettings.TeammateNameDisplay
|
||||
}
|
||||
|
||||
return data.Value
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package app
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"html/template"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -73,12 +74,7 @@ func (a *App) sendNotificationEmail(notification *postNotification, user *model.
|
||||
useMilitaryTime = data.Value == "true"
|
||||
}
|
||||
|
||||
var nameFormat string
|
||||
if data, err := a.Srv.Store.Preference().Get(user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, model.PREFERENCE_NAME_NAME_FORMAT); err != nil {
|
||||
nameFormat = *a.Config().TeamSettings.TeammateNameDisplay
|
||||
} else {
|
||||
nameFormat = data.Value
|
||||
}
|
||||
nameFormat := a.GetNotificationNameFormat(user)
|
||||
|
||||
channelName := notification.GetChannelName(nameFormat, "")
|
||||
senderName := notification.GetSenderName(nameFormat, *a.Config().ServiceSettings.EnablePostUsernameOverride)
|
||||
@@ -171,7 +167,10 @@ func (a *App) getNotificationEmailBody(recipient *model.User, post *model.Post,
|
||||
var bodyPage *utils.HTMLTemplate
|
||||
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
|
||||
bodyPage = a.NewEmailTemplate("post_body_full", recipient.Locale)
|
||||
bodyPage.Props["PostMessage"] = a.GetMessageForNotification(post, translateFunc)
|
||||
postMessage := a.GetMessageForNotification(post, translateFunc)
|
||||
postMessage = html.EscapeString(postMessage)
|
||||
normalizedPostMessage := a.generateHyperlinkForChannels(postMessage, teamName, teamURL)
|
||||
bodyPage.Props["PostMessage"] = template.HTML(normalizedPostMessage)
|
||||
} else {
|
||||
bodyPage = a.NewEmailTemplate("post_body_generic", recipient.Locale)
|
||||
}
|
||||
@@ -283,6 +282,36 @@ func getFormattedPostTime(user *model.User, post *model.Post, useMilitaryTime bo
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) generateHyperlinkForChannels(postMessage, teamName, teamURL string) string {
|
||||
team, err := a.GetTeamByName(teamName)
|
||||
if err != nil {
|
||||
mlog.Error("Encountered error while looking up team by name", mlog.String("Team Name", teamName), mlog.Err(err))
|
||||
return postMessage
|
||||
}
|
||||
|
||||
channelNames := model.ChannelMentions(postMessage)
|
||||
if len(channelNames) == 0 {
|
||||
return postMessage
|
||||
}
|
||||
|
||||
channels, err := a.GetChannelsByNames(channelNames, team.Id)
|
||||
if err != nil {
|
||||
mlog.Error("Encountered error while getting channels", mlog.Err(err))
|
||||
return postMessage
|
||||
}
|
||||
|
||||
visited := make(map[string]bool)
|
||||
for _, ch := range channels {
|
||||
if !visited[ch.Id] && ch.Type == model.CHANNEL_OPEN {
|
||||
channelURL := teamURL + "/channels/" + ch.Name
|
||||
channelHyperLink := fmt.Sprintf("<a href='%s'>%s</a>", channelURL, "~"+ch.Name)
|
||||
postMessage = strings.Replace(postMessage, "~"+ch.Name, channelHyperLink, -1)
|
||||
visited[ch.Id] = true
|
||||
}
|
||||
}
|
||||
return postMessage
|
||||
}
|
||||
|
||||
func (a *App) GetMessageForNotification(post *model.Post, translateFunc i18n.TranslateFunc) string {
|
||||
if len(strings.TrimSpace(post.Message)) != 0 || len(post.FileIds) == 0 {
|
||||
return post.Message
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/services/timezones"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
@@ -25,7 +27,7 @@ func TestGetDirectMessageNotificationEmailSubject(t *testing.T) {
|
||||
CreateAt: 1501804801000,
|
||||
}
|
||||
translateFunc := utils.GetUserTranslations("en")
|
||||
subject := getDirectMessageNotificationEmailSubject(user, post, translateFunc, "http://localhost:8065", "sender", true)
|
||||
subject := getDirectMessageNotificationEmailSubject(user, post, translateFunc, "http://localhost:8065", "@sender", true)
|
||||
if !strings.HasPrefix(subject, expectedPrefix) {
|
||||
t.Fatal("Expected subject line prefix '" + expectedPrefix + "', got " + subject)
|
||||
}
|
||||
@@ -499,3 +501,177 @@ func TestGetNotificationEmailBodyGenericNotificationDirectChannel(t *testing.T)
|
||||
t.Fatal("Expected email text '" + teamURL + "'. Got " + body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNotificationEmailEscapingChars(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
ch := &model.Channel{
|
||||
DisplayName: "ChannelName",
|
||||
Type: model.CHANNEL_OPEN,
|
||||
}
|
||||
channelName := "ChannelName"
|
||||
recipient := &model.User{}
|
||||
message := "<b>Bold Test</b>"
|
||||
post := &model.Post{
|
||||
Message: message,
|
||||
}
|
||||
|
||||
senderName := "sender"
|
||||
teamName := "team"
|
||||
teamURL := "http://localhost:8065/" + teamName
|
||||
emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL
|
||||
translateFunc := utils.GetUserTranslations("en")
|
||||
|
||||
body := th.App.getNotificationEmailBody(recipient, post, ch,
|
||||
channelName, senderName, teamName, teamURL,
|
||||
emailNotificationContentsType, true, translateFunc)
|
||||
|
||||
fmt.Println(body)
|
||||
assert.NotContains(t, body, message)
|
||||
}
|
||||
|
||||
func TestGetNotificationEmailBodyPublicChannelMention(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
ch := th.BasicChannel
|
||||
recipient := th.BasicUser2
|
||||
post := &model.Post{
|
||||
Message: "This is the message ~" + ch.Name,
|
||||
}
|
||||
|
||||
senderName := th.BasicUser.Username
|
||||
teamName := th.BasicTeam.Name
|
||||
teamURL := "http://localhost:8065/" + teamName
|
||||
emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL
|
||||
translateFunc := utils.GetUserTranslations("en")
|
||||
|
||||
body := th.App.getNotificationEmailBody(recipient, post, ch,
|
||||
ch.Name, senderName, teamName, teamURL,
|
||||
emailNotificationContentsType, true, translateFunc)
|
||||
channelURL := teamURL + "/channels/" + ch.Name
|
||||
mention := "~" + ch.Name
|
||||
assert.Contains(t, body, "<a href='"+channelURL+"'>"+mention+"</a>")
|
||||
}
|
||||
|
||||
func TestGetNotificationEmailBodyMultiPublicChannelMention(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
ch := th.BasicChannel
|
||||
mention := "~" + ch.Name
|
||||
|
||||
ch2 := th.CreateChannel(th.BasicTeam)
|
||||
mention2 := "~" + ch2.Name
|
||||
|
||||
ch3 := th.CreateChannel(th.BasicTeam)
|
||||
mention3 := "~" + ch3.Name
|
||||
|
||||
message := fmt.Sprintf("This is the message Channel1: %s; Channel2: %s;"+
|
||||
" Channel3: %s", mention, mention2, mention3)
|
||||
recipient := th.BasicUser2
|
||||
post := &model.Post{
|
||||
Message: message,
|
||||
}
|
||||
|
||||
senderName := th.BasicUser.Username
|
||||
teamName := th.BasicTeam.Name
|
||||
teamURL := "http://localhost:8065/" + teamName
|
||||
emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL
|
||||
translateFunc := utils.GetUserTranslations("en")
|
||||
|
||||
body := th.App.getNotificationEmailBody(recipient, post, ch,
|
||||
ch.Name, senderName, teamName, teamURL,
|
||||
emailNotificationContentsType, true, translateFunc)
|
||||
channelURL := teamURL + "/channels/" + ch.Name
|
||||
channelURL2 := teamURL + "/channels/" + ch2.Name
|
||||
channelURL3 := teamURL + "/channels/" + ch3.Name
|
||||
expMessage := fmt.Sprintf("This is the message Channel1: <a href='%s'>%s</a>;"+
|
||||
" Channel2: <a href='%s'>%s</a>; Channel3: <a href='%s'>%s</a>",
|
||||
channelURL, mention, channelURL2, mention2, channelURL3, mention3)
|
||||
assert.Contains(t, body, expMessage)
|
||||
}
|
||||
|
||||
func TestGetNotificationEmailBodyPrivateChannelMention(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
ch := th.CreatePrivateChannel(th.BasicTeam)
|
||||
recipient := th.BasicUser2
|
||||
post := &model.Post{
|
||||
Message: "This is the message ~" + ch.Name,
|
||||
}
|
||||
|
||||
senderName := th.BasicUser.Username
|
||||
teamName := ch.Name
|
||||
teamURL := "http://localhost:8065/" + teamName
|
||||
emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL
|
||||
translateFunc := utils.GetUserTranslations("en")
|
||||
|
||||
body := th.App.getNotificationEmailBody(recipient, post, ch,
|
||||
ch.Name, senderName, teamName, teamURL,
|
||||
emailNotificationContentsType, true, translateFunc)
|
||||
channelURL := teamURL + "/channels/" + ch.Name
|
||||
mention := "~" + ch.Name
|
||||
assert.NotContains(t, body, "<a href='"+channelURL+"'>"+mention+"</a>")
|
||||
}
|
||||
|
||||
func TestGenerateHyperlinkForChannelsPublic(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
ch := th.BasicChannel
|
||||
message := "This is the message "
|
||||
mention := "~" + ch.Name
|
||||
|
||||
teamName := th.BasicTeam.Name
|
||||
teamURL := "http://localhost:8065/" + teamName
|
||||
|
||||
outMessage := th.App.generateHyperlinkForChannels(message+mention, teamName, teamURL)
|
||||
channelURL := teamURL + "/channels/" + ch.Name
|
||||
assert.Equal(t, message+"<a href='"+channelURL+"'>"+mention+"</a>", outMessage)
|
||||
}
|
||||
|
||||
func TestGenerateHyperlinkForChannelsMultiPublic(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
ch := th.BasicChannel
|
||||
mention := "~" + ch.Name
|
||||
|
||||
ch2 := th.CreateChannel(th.BasicTeam)
|
||||
mention2 := "~" + ch2.Name
|
||||
|
||||
ch3 := th.CreateChannel(th.BasicTeam)
|
||||
mention3 := "~" + ch3.Name
|
||||
|
||||
message := fmt.Sprintf("This is the message Channel1: %s; Channel2: %s;"+
|
||||
" Channel3: %s", mention, mention2, mention3)
|
||||
|
||||
teamName := th.BasicTeam.Name
|
||||
teamURL := "http://localhost:8065/" + teamName
|
||||
|
||||
outMessage := th.App.generateHyperlinkForChannels(message, teamName, teamURL)
|
||||
channelURL := teamURL + "/channels/" + ch.Name
|
||||
channelURL2 := teamURL + "/channels/" + ch2.Name
|
||||
channelURL3 := teamURL + "/channels/" + ch3.Name
|
||||
expMessage := fmt.Sprintf("This is the message Channel1: <a href='%s'>%s</a>;"+
|
||||
" Channel2: <a href='%s'>%s</a>; Channel3: <a href='%s'>%s</a>",
|
||||
channelURL, mention, channelURL2, mention2, channelURL3, mention3)
|
||||
assert.Equal(t, expMessage, outMessage)
|
||||
}
|
||||
|
||||
func TestGenerateHyperlinkForChannelsPrivate(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
ch := th.CreatePrivateChannel(th.BasicTeam)
|
||||
message := "This is the message ~" + ch.Name
|
||||
|
||||
teamName := th.BasicTeam.Name
|
||||
teamURL := "http://localhost:8065/" + teamName
|
||||
|
||||
outMessage := th.App.generateHyperlinkForChannels(message, teamName, teamURL)
|
||||
assert.Equal(t, message, outMessage)
|
||||
}
|
||||
|
||||
@@ -53,58 +53,16 @@ func (hub *PushNotificationsHub) GetGoChannelFromUserId(userId string) chan Push
|
||||
}
|
||||
|
||||
func (a *App) sendPushNotificationSync(post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string,
|
||||
explicitMention, channelWideMention bool, replyToThreadType string) *model.AppError {
|
||||
cfg := a.Config()
|
||||
explicitMention bool, channelWideMention bool, replyToThreadType string) *model.AppError {
|
||||
|
||||
sessions, err := a.getMobileAppSessions(user.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := model.PushNotification{
|
||||
Category: model.CATEGORY_CAN_REPLY,
|
||||
Version: model.PUSH_MESSAGE_V2,
|
||||
Type: model.PUSH_TYPE_MESSAGE,
|
||||
TeamId: channel.TeamId,
|
||||
ChannelId: channel.Id,
|
||||
PostId: post.Id,
|
||||
RootId: post.RootId,
|
||||
SenderId: post.UserId,
|
||||
}
|
||||
|
||||
if unreadCount, err := a.Srv.Store.User().GetUnreadCount(user.Id); err != nil {
|
||||
msg.Badge = 1
|
||||
mlog.Error(fmt.Sprint("We could not get the unread message count for the user", user.Id, err), mlog.String("user_id", user.Id))
|
||||
} else {
|
||||
msg.Badge = int(unreadCount)
|
||||
}
|
||||
|
||||
contentsConfig := *cfg.EmailSettings.PushNotificationContents
|
||||
if contentsConfig != model.GENERIC_NO_CHANNEL_NOTIFICATION || channel.Type == model.CHANNEL_DIRECT {
|
||||
msg.ChannelName = channelName
|
||||
}
|
||||
|
||||
msg.SenderName = senderName
|
||||
if ou, ok := post.Props["override_username"].(string); ok && *cfg.ServiceSettings.EnablePostUsernameOverride {
|
||||
msg.OverrideUsername = ou
|
||||
msg.SenderName = ou
|
||||
}
|
||||
|
||||
if oi, ok := post.Props["override_icon_url"].(string); ok && *cfg.ServiceSettings.EnablePostIconOverride {
|
||||
msg.OverrideIconUrl = oi
|
||||
}
|
||||
|
||||
if fw, ok := post.Props["from_webhook"].(string); ok {
|
||||
msg.FromWebhook = fw
|
||||
}
|
||||
|
||||
userLocale := utils.GetUserTranslations(user.Locale)
|
||||
hasFiles := post.FileIds != nil && len(post.FileIds) > 0
|
||||
|
||||
msg.Message = a.getPushNotificationMessage(post.Message, explicitMention, channelWideMention, hasFiles, msg.SenderName, channelName, channel.Type, replyToThreadType, userLocale)
|
||||
msg := a.BuildPushNotificationMessage(post, user, channel, channelName, senderName, explicitMention, channelWideMention, replyToThreadType)
|
||||
|
||||
for _, session := range sessions {
|
||||
|
||||
if session.IsExpired() {
|
||||
continue
|
||||
}
|
||||
@@ -151,12 +109,7 @@ func (a *App) sendPushNotification(notification *postNotification, user *model.U
|
||||
channel := notification.channel
|
||||
post := notification.post
|
||||
|
||||
var nameFormat string
|
||||
if data, err := a.Srv.Store.Preference().Get(user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, model.PREFERENCE_NAME_NAME_FORMAT); err != nil {
|
||||
nameFormat = *a.Config().TeamSettings.TeammateNameDisplay
|
||||
} else {
|
||||
nameFormat = data.Value
|
||||
}
|
||||
nameFormat := a.GetNotificationNameFormat(user)
|
||||
|
||||
channelName := notification.GetChannelName(nameFormat, user.Id)
|
||||
senderName := notification.GetSenderName(nameFormat, *cfg.ServiceSettings.EnablePostUsernameOverride)
|
||||
@@ -475,3 +428,61 @@ func DoesStatusAllowPushNotification(userNotifyProps model.StringMap, status *mo
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *App) BuildPushNotificationMessage(post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string,
|
||||
explicitMention bool, channelWideMention bool, replyToThreadType string) model.PushNotification {
|
||||
|
||||
msg := model.PushNotification{
|
||||
Category: model.CATEGORY_CAN_REPLY,
|
||||
Version: model.PUSH_MESSAGE_V2,
|
||||
Type: model.PUSH_TYPE_MESSAGE,
|
||||
TeamId: channel.TeamId,
|
||||
ChannelId: channel.Id,
|
||||
PostId: post.Id,
|
||||
RootId: post.RootId,
|
||||
SenderId: post.UserId,
|
||||
}
|
||||
|
||||
if user.NotifyProps["push"] == "all" {
|
||||
if unreadCount, err := a.Srv.Store.User().GetAnyUnreadPostCountForChannel(user.Id, channel.Id); err != nil {
|
||||
msg.Badge = 1
|
||||
mlog.Error(fmt.Sprint("We could not get the unread message count for the user", user.Id, err), mlog.String("user_id", user.Id))
|
||||
} else {
|
||||
msg.Badge = int(unreadCount)
|
||||
}
|
||||
} else {
|
||||
if unreadCount, err := a.Srv.Store.User().GetUnreadCount(user.Id); err != nil {
|
||||
msg.Badge = 1
|
||||
mlog.Error(fmt.Sprint("We could not get the unread message count for the user", user.Id, err), mlog.String("user_id", user.Id))
|
||||
} else {
|
||||
msg.Badge = int(unreadCount)
|
||||
}
|
||||
}
|
||||
|
||||
cfg := a.Config()
|
||||
contentsConfig := *cfg.EmailSettings.PushNotificationContents
|
||||
if contentsConfig != model.GENERIC_NO_CHANNEL_NOTIFICATION || channel.Type == model.CHANNEL_DIRECT {
|
||||
msg.ChannelName = channelName
|
||||
}
|
||||
|
||||
msg.SenderName = senderName
|
||||
if ou, ok := post.Props["override_username"].(string); ok && *cfg.ServiceSettings.EnablePostUsernameOverride {
|
||||
msg.OverrideUsername = ou
|
||||
msg.SenderName = ou
|
||||
}
|
||||
|
||||
if oi, ok := post.Props["override_icon_url"].(string); ok && *cfg.ServiceSettings.EnablePostIconOverride {
|
||||
msg.OverrideIconUrl = oi
|
||||
}
|
||||
|
||||
if fw, ok := post.Props["from_webhook"].(string); ok {
|
||||
msg.FromWebhook = fw
|
||||
}
|
||||
|
||||
userLocale := utils.GetUserTranslations(user.Locale)
|
||||
hasFiles := post.FileIds != nil && len(post.FileIds) > 0
|
||||
|
||||
msg.Message = a.getPushNotificationMessage(post.Message, explicitMention, channelWideMention, hasFiles, msg.SenderName, channelName, channel.Type, replyToThreadType, userLocale)
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
@@ -880,7 +881,7 @@ func TestGetPushNotificationMessage(t *testing.T) {
|
||||
*cfg.EmailSettings.PushNotificationContents = pushNotificationContents
|
||||
})
|
||||
|
||||
if actualMessage := th.App.getPushNotificationMessage(
|
||||
actualMessage := th.App.getPushNotificationMessage(
|
||||
tc.Message,
|
||||
tc.explicitMention,
|
||||
tc.channelWideMention,
|
||||
@@ -890,9 +891,59 @@ func TestGetPushNotificationMessage(t *testing.T) {
|
||||
tc.ChannelType,
|
||||
tc.replyToThreadType,
|
||||
utils.GetUserTranslations(locale),
|
||||
); actualMessage != tc.ExpectedMessage {
|
||||
t.Fatalf("Received incorrect push notification message `%v`, expected `%v`", actualMessage, tc.ExpectedMessage)
|
||||
}
|
||||
)
|
||||
|
||||
assert.Equal(t, tc.ExpectedMessage, actualMessage)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPushNotificationMessage(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
team := th.CreateTeam()
|
||||
sender := th.CreateUser()
|
||||
receiver := th.CreateUser()
|
||||
th.LinkUserToTeam(sender, team)
|
||||
th.LinkUserToTeam(receiver, team)
|
||||
channel := th.CreateChannel(team)
|
||||
th.AddUserToChannel(sender, channel)
|
||||
th.AddUserToChannel(receiver, channel)
|
||||
|
||||
// Create three mention posts and two non-mention posts
|
||||
th.CreateMessagePost(channel, "@channel Hello")
|
||||
th.CreateMessagePost(channel, "@all Hello")
|
||||
th.CreateMessagePost(channel, fmt.Sprintf("@%s Hello", receiver.Username))
|
||||
th.CreatePost(channel)
|
||||
post := th.CreatePost(channel)
|
||||
|
||||
for name, tc := range map[string]struct {
|
||||
explicitMention bool
|
||||
channelWideMention bool
|
||||
replyToThreadType string
|
||||
pushNotifyProps string
|
||||
expectedBadge int
|
||||
}{
|
||||
"only mentions included in badge count": {
|
||||
explicitMention: false,
|
||||
channelWideMention: true,
|
||||
replyToThreadType: "",
|
||||
pushNotifyProps: "mention",
|
||||
expectedBadge: 3,
|
||||
},
|
||||
"mentions and non-mentions included in badge count": {
|
||||
explicitMention: false,
|
||||
channelWideMention: true,
|
||||
replyToThreadType: "",
|
||||
pushNotifyProps: "all",
|
||||
expectedBadge: 5,
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
receiver.NotifyProps["push"] = tc.pushNotifyProps
|
||||
msg := th.App.BuildPushNotificationMessage(post, receiver, channel, channel.Name, sender.Username, tc.explicitMention, tc.channelWideMention, tc.replyToThreadType)
|
||||
assert.Equal(t, tc.expectedBadge, msg.Badge)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1150,6 +1150,29 @@ func TestGetMentionKeywords(t *testing.T) {
|
||||
} else if _, ok := mentions["@here"]; ok {
|
||||
t.Fatal("should not have mentioned any user with @here")
|
||||
}
|
||||
|
||||
// user with empty mention keys
|
||||
userNoMentionKeys := &model.User{
|
||||
Id: model.NewId(),
|
||||
FirstName: "First",
|
||||
Username: "User",
|
||||
NotifyProps: map[string]string{
|
||||
"mention_keys": ",",
|
||||
},
|
||||
}
|
||||
|
||||
channelMemberNotifyPropsMapEmptyOff := map[string]model.StringMap{
|
||||
userNoMentionKeys.Id: {
|
||||
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF,
|
||||
},
|
||||
}
|
||||
|
||||
profiles = map[string]*model.User{userNoMentionKeys.Id: userNoMentionKeys}
|
||||
mentions = th.App.getMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMapEmptyOff)
|
||||
assert.Equal(t, 1, len(mentions), "should've returned one metion keyword")
|
||||
ids, ok := mentions["@user"]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, userNoMentionKeys.Id, ids[0], "should've returned mention key of @user")
|
||||
}
|
||||
|
||||
func TestGetMentionsEnabledFields(t *testing.T) {
|
||||
@@ -1743,3 +1766,26 @@ func TestProcessText(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNotificationNameFormat(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("show full name on", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.PrivacySettings.ShowFullName = true
|
||||
*cfg.TeamSettings.TeammateNameDisplay = model.SHOW_FULLNAME
|
||||
})
|
||||
|
||||
assert.Equal(t, model.SHOW_FULLNAME, th.App.GetNotificationNameFormat(th.BasicUser))
|
||||
})
|
||||
|
||||
t.Run("show full name off", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.PrivacySettings.ShowFullName = false
|
||||
*cfg.TeamSettings.TeammateNameDisplay = model.SHOW_FULLNAME
|
||||
})
|
||||
|
||||
assert.Equal(t, model.SHOW_USERNAME, th.App.GetNotificationNameFormat(th.BasicUser))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -108,22 +109,17 @@ func TestMakeOpenGraphURLsAbsolute(t *testing.T) {
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
og := opengraph.NewOpenGraph()
|
||||
if err := og.ProcessHTML(strings.NewReader(tc.HTML)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err := og.ProcessHTML(strings.NewReader(tc.HTML))
|
||||
require.Nil(t, err)
|
||||
|
||||
makeOpenGraphURLsAbsolute(og, tc.RequestURL)
|
||||
|
||||
if og.URL != tc.URL {
|
||||
t.Fatalf("incorrect url, expected %v, got %v", tc.URL, og.URL)
|
||||
}
|
||||
assert.Equalf(t, og.URL, tc.URL, "incorrect url, expected %v, got %v", tc.URL, og.URL)
|
||||
|
||||
if len(og.Images) > 0 {
|
||||
if og.Images[0].URL != tc.ImageURL {
|
||||
t.Fatalf("incorrect image url, expected %v, got %v", tc.ImageURL, og.Images[0].URL)
|
||||
}
|
||||
} else if tc.ImageURL != "" {
|
||||
t.Fatalf("missing image url, expected %v, got nothing", tc.ImageURL)
|
||||
assert.Equalf(t, og.Images[0].URL, tc.ImageURL, "incorrect image url, expected %v, got %v", tc.ImageURL, og.Images[0].URL)
|
||||
} else {
|
||||
assert.Empty(t, tc.ImageURL, "missing image url, expected %v, got nothing", tc.ImageURL)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -81,6 +81,11 @@ func (api *PluginAPI) GetConfig() *model.Config {
|
||||
return api.app.GetSanitizedConfig()
|
||||
}
|
||||
|
||||
// GetUnsanitizedConfig gets the configuration for a system admin without removing secrets.
|
||||
func (api *PluginAPI) GetUnsanitizedConfig() *model.Config {
|
||||
return api.app.Config().Clone()
|
||||
}
|
||||
|
||||
func (api *PluginAPI) SaveConfig(config *model.Config) *model.AppError {
|
||||
return api.app.SaveConfig(config, true)
|
||||
}
|
||||
|
||||
@@ -1331,3 +1331,77 @@ func TestPluginCreatePostWithUploadedFile(t *testing.T) {
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, model.StringArray{fileInfo.Id}, actualPost.FileIds)
|
||||
}
|
||||
|
||||
func TestPluginAPIGetConfig(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
api := th.SetupPluginAPI()
|
||||
|
||||
config := api.GetConfig()
|
||||
if config.LdapSettings.BindPassword != nil && len(*config.LdapSettings.BindPassword) > 0 {
|
||||
assert.Equal(t, *config.LdapSettings.BindPassword, model.FAKE_SETTING)
|
||||
}
|
||||
|
||||
assert.Equal(t, *config.FileSettings.PublicLinkSalt, model.FAKE_SETTING)
|
||||
|
||||
if len(*config.FileSettings.AmazonS3SecretAccessKey) > 0 {
|
||||
assert.Equal(t, *config.FileSettings.AmazonS3SecretAccessKey, model.FAKE_SETTING)
|
||||
}
|
||||
|
||||
if config.EmailSettings.SMTPPassword != nil && len(*config.EmailSettings.SMTPPassword) > 0 {
|
||||
assert.Equal(t, *config.EmailSettings.SMTPPassword, model.FAKE_SETTING)
|
||||
}
|
||||
|
||||
if len(*config.GitLabSettings.Secret) > 0 {
|
||||
assert.Equal(t, *config.GitLabSettings.Secret, model.FAKE_SETTING)
|
||||
}
|
||||
|
||||
assert.Equal(t, *config.SqlSettings.DataSource, model.FAKE_SETTING)
|
||||
assert.Equal(t, *config.SqlSettings.AtRestEncryptKey, model.FAKE_SETTING)
|
||||
assert.Equal(t, *config.ElasticsearchSettings.Password, model.FAKE_SETTING)
|
||||
|
||||
for i := range config.SqlSettings.DataSourceReplicas {
|
||||
assert.Equal(t, config.SqlSettings.DataSourceReplicas[i], model.FAKE_SETTING)
|
||||
}
|
||||
|
||||
for i := range config.SqlSettings.DataSourceSearchReplicas {
|
||||
assert.Equal(t, config.SqlSettings.DataSourceSearchReplicas[i], model.FAKE_SETTING)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginAPIGetUnsanitizedConfig(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
api := th.SetupPluginAPI()
|
||||
|
||||
config := api.GetUnsanitizedConfig()
|
||||
if config.LdapSettings.BindPassword != nil && len(*config.LdapSettings.BindPassword) > 0 {
|
||||
assert.NotEqual(t, *config.LdapSettings.BindPassword, model.FAKE_SETTING)
|
||||
}
|
||||
|
||||
assert.NotEqual(t, *config.FileSettings.PublicLinkSalt, model.FAKE_SETTING)
|
||||
|
||||
if len(*config.FileSettings.AmazonS3SecretAccessKey) > 0 {
|
||||
assert.NotEqual(t, *config.FileSettings.AmazonS3SecretAccessKey, model.FAKE_SETTING)
|
||||
}
|
||||
|
||||
if config.EmailSettings.SMTPPassword != nil && len(*config.EmailSettings.SMTPPassword) > 0 {
|
||||
assert.NotEqual(t, *config.EmailSettings.SMTPPassword, model.FAKE_SETTING)
|
||||
}
|
||||
|
||||
if len(*config.GitLabSettings.Secret) > 0 {
|
||||
assert.NotEqual(t, *config.GitLabSettings.Secret, model.FAKE_SETTING)
|
||||
}
|
||||
|
||||
assert.NotEqual(t, *config.SqlSettings.DataSource, model.FAKE_SETTING)
|
||||
assert.NotEqual(t, *config.SqlSettings.AtRestEncryptKey, model.FAKE_SETTING)
|
||||
assert.NotEqual(t, *config.ElasticsearchSettings.Password, model.FAKE_SETTING)
|
||||
|
||||
for i := range config.SqlSettings.DataSourceReplicas {
|
||||
assert.NotEqual(t, config.SqlSettings.DataSourceReplicas[i], model.FAKE_SETTING)
|
||||
}
|
||||
|
||||
for i := range config.SqlSettings.DataSourceSearchReplicas {
|
||||
assert.NotEqual(t, config.SqlSettings.DataSourceSearchReplicas[i], model.FAKE_SETTING)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,20 +97,29 @@ func (a *App) tryExecutePluginCommand(args *model.CommandArgs) (*model.Command,
|
||||
trigger := parts[0][1:]
|
||||
trigger = strings.ToLower(trigger)
|
||||
|
||||
var matched *PluginCommand
|
||||
a.Srv.pluginCommandsLock.RLock()
|
||||
defer a.Srv.pluginCommandsLock.RUnlock()
|
||||
|
||||
for _, pc := range a.Srv.pluginCommands {
|
||||
if (pc.Command.TeamId == "" || pc.Command.TeamId == args.TeamId) && pc.Command.Trigger == trigger {
|
||||
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
|
||||
pluginHooks, err := pluginsEnvironment.HooksForPlugin(pc.PluginId)
|
||||
if err != nil {
|
||||
return pc.Command, nil, model.NewAppError("ExecutePluginCommand", "model.plugin_command.error.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
response, appErr := pluginHooks.ExecuteCommand(a.PluginContext(), args)
|
||||
return pc.Command, response, appErr
|
||||
}
|
||||
matched = pc
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil, nil, nil
|
||||
a.Srv.pluginCommandsLock.RUnlock()
|
||||
if matched == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
pluginsEnvironment := a.GetPluginsEnvironment()
|
||||
if pluginsEnvironment == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
pluginHooks, err := pluginsEnvironment.HooksForPlugin(matched.PluginId)
|
||||
if err != nil {
|
||||
return matched.Command, nil, model.NewAppError("ExecutePluginCommand", "model.plugin_command.error.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
response, appErr := pluginHooks.ExecuteCommand(a.PluginContext(), args)
|
||||
return matched.Command, response, appErr
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package app
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
@@ -46,7 +47,7 @@ func TestPluginCommand(t *testing.T) {
|
||||
}
|
||||
|
||||
type MyPlugin struct {
|
||||
plugin.MattermostPlugin
|
||||
plugin.MattermostPlugin
|
||||
|
||||
configuration configuration
|
||||
}
|
||||
@@ -110,6 +111,110 @@ func TestPluginCommand(t *testing.T) {
|
||||
th.App.RemovePlugin(pluginIds[0])
|
||||
})
|
||||
|
||||
t.Run("re-entrant command registration on config change", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.PluginSettings.Plugins["testloadpluginconfig"] = map[string]interface{}{
|
||||
"TeamId": args.TeamId,
|
||||
}
|
||||
})
|
||||
|
||||
tearDown, pluginIds, activationErrors := SetAppEnvironmentWithPlugins(t, []string{`
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost-server/plugin"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
)
|
||||
|
||||
type configuration struct {
|
||||
TeamId string
|
||||
}
|
||||
|
||||
type MyPlugin struct {
|
||||
plugin.MattermostPlugin
|
||||
|
||||
configuration configuration
|
||||
}
|
||||
|
||||
func (p *MyPlugin) OnConfigurationChange() error {
|
||||
p.API.LogInfo("OnConfigurationChange")
|
||||
err := p.API.LoadPluginConfiguration(&p.configuration);
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.API.LogInfo("About to register")
|
||||
err = p.API.RegisterCommand(&model.Command{
|
||||
TeamId: p.configuration.TeamId,
|
||||
Trigger: "plugin",
|
||||
DisplayName: "Plugin Command",
|
||||
AutoComplete: true,
|
||||
AutoCompleteDesc: "autocomplete",
|
||||
})
|
||||
if err != nil {
|
||||
p.API.LogInfo("Registered, with error", err, err.Error())
|
||||
return err
|
||||
}
|
||||
p.API.LogInfo("Registered, without error")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *MyPlugin) ExecuteCommand(c *plugin.Context, commandArgs *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
|
||||
p.API.LogInfo("ExecuteCommand")
|
||||
// Saving the plugin config eventually results in a call to
|
||||
// OnConfigurationChange. This used to deadlock on account of
|
||||
// effectively acquiring a RWLock reentrantly.
|
||||
err := p.API.SavePluginConfig(map[string]interface{}{
|
||||
"TeamId": p.configuration.TeamId,
|
||||
})
|
||||
if err != nil {
|
||||
p.API.LogError("Failed to save plugin config", err, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
p.API.LogInfo("ExecuteCommand, saved plugin config")
|
||||
|
||||
return &model.CommandResponse{
|
||||
ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL,
|
||||
Text: "text",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
plugin.ClientMain(&MyPlugin{})
|
||||
}
|
||||
`}, th.App, th.App.NewPluginAPI)
|
||||
defer tearDown()
|
||||
|
||||
require.Len(t, activationErrors, 1)
|
||||
require.Nil(t, nil, activationErrors[0])
|
||||
|
||||
wait := make(chan bool)
|
||||
killed := false
|
||||
go func() {
|
||||
defer close(wait)
|
||||
|
||||
resp, err := th.App.ExecuteCommand(args)
|
||||
|
||||
// Ignore if we kill below.
|
||||
if !killed {
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, model.COMMAND_RESPONSE_TYPE_EPHEMERAL, resp.ResponseType)
|
||||
require.Equal(t, "text", resp.Text)
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-wait:
|
||||
case <-time.After(10 * time.Second):
|
||||
killed = true
|
||||
}
|
||||
|
||||
th.App.RemovePlugin(pluginIds[0])
|
||||
if killed {
|
||||
t.Fatal("execute command appears to have deadlocked")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("error after plugin command unregistered", func(t *testing.T) {
|
||||
_, err := th.App.ExecuteCommand(args)
|
||||
require.NotNil(t, err)
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -47,6 +46,12 @@ func SetAppEnvironmentWithPlugins(t *testing.T, pluginCode []string, app *App, a
|
||||
_, _, activationErr := env.Activate(pluginId)
|
||||
pluginIds = append(pluginIds, pluginId)
|
||||
activationErrors = append(activationErrors, activationErr)
|
||||
|
||||
app.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.PluginSettings.PluginStates[pluginId] = &model.PluginState{
|
||||
Enable: true,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return func() {
|
||||
@@ -172,9 +177,8 @@ func TestHookMessageWillBePosted(t *testing.T) {
|
||||
CreateAt: model.GetMillis() - 10000,
|
||||
}
|
||||
post, err := th.App.CreatePost(post, th.BasicChannel, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Nil(t, err)
|
||||
|
||||
assert.Equal(t, "message", post.Message)
|
||||
retrievedPost, errSingle := th.App.Srv.Store.Post().GetSingle(post.Id)
|
||||
require.Nil(t, errSingle)
|
||||
@@ -217,15 +221,12 @@ func TestHookMessageWillBePosted(t *testing.T) {
|
||||
CreateAt: model.GetMillis() - 10000,
|
||||
}
|
||||
post, err := th.App.CreatePost(post, th.BasicChannel, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Nil(t, err)
|
||||
|
||||
assert.Equal(t, "message_fromplugin", post.Message)
|
||||
if retrievedPost, errSingle := th.App.Srv.Store.Post().GetSingle(post.Id); err != nil {
|
||||
t.Fatal(errSingle)
|
||||
} else {
|
||||
assert.Equal(t, "message_fromplugin", retrievedPost.Message)
|
||||
}
|
||||
retrievedPost, errSingle := th.App.Srv.Store.Post().GetSingle(post.Id)
|
||||
require.Nil(t, errSingle)
|
||||
assert.Equal(t, "message_fromplugin", retrievedPost.Message)
|
||||
})
|
||||
|
||||
t.Run("multiple updated", func(t *testing.T) {
|
||||
@@ -286,9 +287,7 @@ func TestHookMessageWillBePosted(t *testing.T) {
|
||||
CreateAt: model.GetMillis() - 10000,
|
||||
}
|
||||
post, err := th.App.CreatePost(post, th.BasicChannel, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, "prefix_message_suffix", post.Message)
|
||||
})
|
||||
}
|
||||
@@ -332,9 +331,7 @@ func TestHookMessageHasBeenPosted(t *testing.T) {
|
||||
CreateAt: model.GetMillis() - 10000,
|
||||
}
|
||||
_, err := th.App.CreatePost(post, th.BasicChannel, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestHookMessageWillBeUpdated(t *testing.T) {
|
||||
@@ -373,15 +370,11 @@ func TestHookMessageWillBeUpdated(t *testing.T) {
|
||||
CreateAt: model.GetMillis() - 10000,
|
||||
}
|
||||
post, err := th.App.CreatePost(post, th.BasicChannel, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, "message_", post.Message)
|
||||
post.Message = post.Message + "edited_"
|
||||
post, err = th.App.UpdatePost(post, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, "message_edited_fromplugin", post.Message)
|
||||
}
|
||||
|
||||
@@ -425,15 +418,11 @@ func TestHookMessageHasBeenUpdated(t *testing.T) {
|
||||
CreateAt: model.GetMillis() - 10000,
|
||||
}
|
||||
post, err := th.App.CreatePost(post, th.BasicChannel, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, "message_", post.Message)
|
||||
post.Message = post.Message + "edited"
|
||||
_, err = th.App.UpdatePost(post, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestHookFileWillBeUploaded(t *testing.T) {
|
||||
@@ -582,8 +571,8 @@ func TestHookFileWillBeUploaded(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, response)
|
||||
assert.Equal(t, 1, len(response.FileInfos))
|
||||
|
||||
fileId := response.FileInfos[0].Id
|
||||
|
||||
fileInfo, err := th.App.GetFileInfo(fileId)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, fileInfo)
|
||||
@@ -678,11 +667,7 @@ func TestUserWillLogIn_Blocked(t *testing.T) {
|
||||
defer th.TearDown()
|
||||
|
||||
err := th.App.UpdatePassword(th.BasicUser, "hunter2")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Error updating user password: %s", err)
|
||||
}
|
||||
|
||||
assert.Nil(t, err, "Error updating user password: %s", err)
|
||||
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
||||
[]string{
|
||||
`
|
||||
@@ -711,9 +696,7 @@ func TestUserWillLogIn_Blocked(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
_, err = th.App.DoLogin(w, r, th.BasicUser, "")
|
||||
|
||||
if !strings.HasPrefix(err.Id, "Login rejected by plugin") {
|
||||
t.Errorf("Expected Login rejected by plugin, got %s", err.Id)
|
||||
}
|
||||
assert.Contains(t, err.Id, "Login rejected by plugin", "Expected Login rejected by plugin, got %s", err.Id)
|
||||
}
|
||||
|
||||
func TestUserWillLogInIn_Passed(t *testing.T) {
|
||||
@@ -722,9 +705,7 @@ func TestUserWillLogInIn_Passed(t *testing.T) {
|
||||
|
||||
err := th.App.UpdatePassword(th.BasicUser, "hunter2")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Error updating user password: %s", err)
|
||||
}
|
||||
assert.Nil(t, err, "Error updating user password: %s", err)
|
||||
|
||||
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
||||
[]string{
|
||||
@@ -754,13 +735,8 @@ func TestUserWillLogInIn_Passed(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
session, err := th.App.DoLogin(w, r, th.BasicUser, "")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected nil, got %s", err)
|
||||
}
|
||||
|
||||
if session.UserId != th.BasicUser.Id {
|
||||
t.Errorf("Expected %s, got %s", th.BasicUser.Id, session.UserId)
|
||||
}
|
||||
assert.Nil(t, err, "Expected nil, got %s", err)
|
||||
assert.Equal(t, session.UserId, th.BasicUser.Id)
|
||||
}
|
||||
|
||||
func TestUserHasLoggedIn(t *testing.T) {
|
||||
@@ -769,9 +745,7 @@ func TestUserHasLoggedIn(t *testing.T) {
|
||||
|
||||
err := th.App.UpdatePassword(th.BasicUser, "hunter2")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Error updating user password: %s", err)
|
||||
}
|
||||
assert.Nil(t, err, "Error updating user password: %s", err)
|
||||
|
||||
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
||||
[]string{
|
||||
@@ -802,17 +776,13 @@ func TestUserHasLoggedIn(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
_, err = th.App.DoLogin(w, r, th.BasicUser, "")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected nil, got %s", err)
|
||||
}
|
||||
assert.Nil(t, err, "Expected nil, got %s", err)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
user, _ := th.App.GetUser(th.BasicUser.Id)
|
||||
|
||||
if user.FirstName != "plugin-callback-success" {
|
||||
t.Errorf("Expected firstname overwrite, got default")
|
||||
}
|
||||
assert.Equal(t, user.FirstName, "plugin-callback-success", "Expected firstname overwrite, got default")
|
||||
}
|
||||
|
||||
func TestUserHasBeenCreated(t *testing.T) {
|
||||
@@ -858,7 +828,6 @@ func TestUserHasBeenCreated(t *testing.T) {
|
||||
|
||||
user, err = th.App.GetUser(user.Id)
|
||||
require.Nil(t, err)
|
||||
|
||||
require.Equal(t, "plugin-callback-success", user.Nickname)
|
||||
}
|
||||
|
||||
@@ -988,7 +957,5 @@ func TestHookContext(t *testing.T) {
|
||||
CreateAt: model.GetMillis() - 10000,
|
||||
}
|
||||
_, err := th.App.CreatePost(post, th.BasicChannel, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
69
app/plugin_shutdown_test.go
Обычный файл
69
app/plugin_shutdown_test.go
Обычный файл
@@ -0,0 +1,69 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPluginShutdownTest(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test to verify forced shutdown of slow plugin")
|
||||
}
|
||||
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
||||
[]string{
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost-server/plugin"
|
||||
)
|
||||
|
||||
type MyPlugin struct {
|
||||
plugin.MattermostPlugin
|
||||
}
|
||||
|
||||
func main() {
|
||||
plugin.ClientMain(&MyPlugin{})
|
||||
}
|
||||
`,
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost-server/plugin"
|
||||
)
|
||||
|
||||
type MyPlugin struct {
|
||||
plugin.MattermostPlugin
|
||||
}
|
||||
|
||||
func (p *MyPlugin) OnDeactivate() error {
|
||||
c := make(chan bool)
|
||||
<-c
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
plugin.ClientMain(&MyPlugin{})
|
||||
}
|
||||
`,
|
||||
}, th.App, th.App.NewPluginAPI)
|
||||
defer tearDown()
|
||||
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
defer close(done)
|
||||
th.App.ShutDownPlugins()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(15 * time.Second):
|
||||
t.Fatal("failed to force plugin shutdown after 10 seconds")
|
||||
}
|
||||
}
|
||||
42
app/team.go
42
app/team.go
@@ -89,6 +89,9 @@ func (a *App) isTeamEmailAddressAllowed(email string, allowedDomains string) boo
|
||||
}
|
||||
|
||||
func (a *App) isTeamEmailAllowed(user *model.User, team *model.Team) bool {
|
||||
if user.IsBot {
|
||||
return true
|
||||
}
|
||||
email := strings.ToLower(user.Email)
|
||||
return a.isTeamEmailAddressAllowed(email, team.AllowedDomains)
|
||||
}
|
||||
@@ -482,7 +485,7 @@ func (a *App) AddUserToTeamByToken(userId string, tokenId string) (*model.Team,
|
||||
for _, channel := range channels {
|
||||
_, err := a.AddUserToChannel(user, channel)
|
||||
if err != nil {
|
||||
mlog.Error(err.Error())
|
||||
mlog.Error("error adding user to channel", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -614,7 +617,12 @@ func (a *App) JoinUserToTeam(team *model.Team, user *model.User, userRequestorId
|
||||
if !user.IsGuest() {
|
||||
// Soft error if there is an issue joining the default channels
|
||||
if err := a.JoinDefaultChannels(team.Id, user, shouldBeAdmin, userRequestorId); 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.Error(
|
||||
"Encountered an issue joining default channels.",
|
||||
mlog.String("user_id", user.Id),
|
||||
mlog.String("team_id", team.Id),
|
||||
mlog.Err(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -675,6 +683,18 @@ func (a *App) GetAllPrivateTeamsPage(offset int, limit int) ([]*model.Team, *mod
|
||||
return a.Srv.Store.Team().GetAllPrivateTeamPageListing(offset, limit)
|
||||
}
|
||||
|
||||
func (a *App) GetAllPrivateTeamsPageWithCount(offset int, limit int) (*model.TeamsWithCount, *model.AppError) {
|
||||
totalCount, err := a.Srv.Store.Team().AnalyticsPrivateTeamCount()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
teams, err := a.Srv.Store.Team().GetAllPrivateTeamPageListing(offset, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.TeamsWithCount{Teams: teams, TotalCount: totalCount}, nil
|
||||
}
|
||||
|
||||
func (a *App) GetAllPublicTeams() ([]*model.Team, *model.AppError) {
|
||||
return a.Srv.Store.Team().GetAllTeamListing()
|
||||
}
|
||||
@@ -683,6 +703,18 @@ func (a *App) GetAllPublicTeamsPage(offset int, limit int) ([]*model.Team, *mode
|
||||
return a.Srv.Store.Team().GetAllTeamPageListing(offset, limit)
|
||||
}
|
||||
|
||||
func (a *App) GetAllPublicTeamsPageWithCount(offset int, limit int) (*model.TeamsWithCount, *model.AppError) {
|
||||
totalCount, err := a.Srv.Store.Team().AnalyticsPublicTeamCount()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
teams, err := a.Srv.Store.Team().GetAllPublicTeamPageListing(offset, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.TeamsWithCount{Teams: teams, TotalCount: totalCount}, nil
|
||||
}
|
||||
|
||||
func (a *App) SearchAllTeams(term string) ([]*model.Team, *model.AppError) {
|
||||
return a.Srv.Store.Team().SearchAll(term)
|
||||
}
|
||||
@@ -940,11 +972,11 @@ func (a *App) LeaveTeam(team *model.Team, user *model.User, requestorId string)
|
||||
if *a.Config().ServiceSettings.ExperimentalEnableDefaultChannelLeaveJoinMessages {
|
||||
if requestorId == user.Id {
|
||||
if err = a.postLeaveTeamMessage(user, channel); err != nil {
|
||||
mlog.Error(fmt.Sprint("Failed to post join/leave message", err))
|
||||
mlog.Error("Failed to post join/leave message", mlog.Err(err))
|
||||
}
|
||||
} else {
|
||||
if err = a.postRemoveFromTeamMessage(user, channel); err != nil {
|
||||
mlog.Error(fmt.Sprint("Failed to post join/leave message", err))
|
||||
mlog.Error("Failed to post join/leave message", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1299,7 +1331,7 @@ func (a *App) GetTeamIdFromQuery(query url.Values) (string, *model.AppError) {
|
||||
return team.Id, nil
|
||||
}
|
||||
// soft fail, so we still create user but don't auto-join team
|
||||
mlog.Error(fmt.Sprintf("%v", err))
|
||||
mlog.Error("error getting team by inviteId.", mlog.String("invite_id", inviteId), mlog.Err(err))
|
||||
}
|
||||
|
||||
return "", nil
|
||||
|
||||
@@ -105,7 +105,7 @@ func TestAddUserToTeam(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("block user by domain", func(t *testing.T) {
|
||||
t.Run("block user by domain but allow bot", func(t *testing.T) {
|
||||
th.BasicTeam.AllowedDomains = "example.com"
|
||||
if _, err := th.App.UpdateTeam(th.BasicTeam); err != nil {
|
||||
t.Log(err)
|
||||
@@ -131,10 +131,20 @@ func TestAddUserToTeam(t *testing.T) {
|
||||
}
|
||||
defer th.App.PermanentDeleteUser(&user)
|
||||
|
||||
if _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, ""); err == nil || err.Where != "JoinUserToTeam" {
|
||||
if _, err = th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, ""); err == nil || err.Where != "JoinUserToTeam" {
|
||||
t.Log(err)
|
||||
t.Fatal("Should not add authservice user")
|
||||
}
|
||||
|
||||
bot, err := th.App.CreateBot(&model.Bot{
|
||||
Username: "somebot",
|
||||
Description: "a bot",
|
||||
OwnerId: th.BasicUser.Id,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = th.App.AddUserToTeam(th.BasicTeam.Id, bot.UserId, "")
|
||||
assert.Nil(t, err, "should be able to add bot to domain restricted team")
|
||||
})
|
||||
|
||||
t.Run("block user with subdomain", func(t *testing.T) {
|
||||
|
||||
@@ -2151,19 +2151,12 @@ func (a *App) GetViewUsersRestrictions(userId string) (*model.ViewUsersRestricti
|
||||
}
|
||||
|
||||
teamIdsWithPermission := []string{}
|
||||
teamIdsWithoutPermission := []string{}
|
||||
for _, teamId := range teamIds {
|
||||
if a.HasPermissionToTeam(userId, teamId, model.PERMISSION_VIEW_MEMBERS) {
|
||||
teamIdsWithPermission = append(teamIdsWithPermission, teamId)
|
||||
} else {
|
||||
teamIdsWithoutPermission = append(teamIdsWithoutPermission, teamId)
|
||||
}
|
||||
}
|
||||
|
||||
if len(teamIdsWithoutPermission) == 0 {
|
||||
return &model.ViewUsersRestrictions{Teams: teamIdsWithPermission}, nil
|
||||
}
|
||||
|
||||
userChannelMembers, err := a.Srv.Store.Channel().GetAllChannelMembersForUser(userId, true, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -899,7 +899,8 @@ func TestGetViewUsersRestrictions(t *testing.T) {
|
||||
|
||||
assert.NotNil(t, restrictions)
|
||||
assert.NotNil(t, restrictions.Teams)
|
||||
assert.Len(t, restrictions.Channels, 0)
|
||||
assert.NotNil(t, restrictions.Channels)
|
||||
assert.ElementsMatch(t, []string{team1townsquare.Id, team1offtopic.Id, team1channel1.Id, team1channel2.Id, team2townsquare.Id, team2offtopic.Id, team2channel1.Id}, restrictions.Channels)
|
||||
assert.ElementsMatch(t, []string{team1.Id, team2.Id}, restrictions.Teams)
|
||||
})
|
||||
|
||||
|
||||
@@ -289,6 +289,7 @@ func (a *App) InvalidateCacheForChannelPosts(channelId string) {
|
||||
|
||||
func (a *App) InvalidateCacheForChannelPostsSkipClusterSend(channelId string) {
|
||||
a.Srv.Store.Post().InvalidateLastPostTimeCache(channelId)
|
||||
a.Srv.Store.Channel().InvalidatePinnedPostCount(channelId)
|
||||
}
|
||||
|
||||
func (a *App) InvalidateCacheForUser(userId string) {
|
||||
|
||||
@@ -120,8 +120,6 @@ func TestCreateIncomingWebhookForChannel(t *testing.T) {
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = tc.EnableIncomingHooks })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.EnablePostUsernameOverride = tc.EnablePostUsernameOverride
|
||||
@@ -129,22 +127,22 @@ func TestCreateIncomingWebhookForChannel(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnablePostIconOverride = tc.EnablePostIconOverride })
|
||||
|
||||
createdHook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &tc.IncomingWebhook)
|
||||
if tc.ExpectedError && err == nil {
|
||||
t.Fatal("should have failed")
|
||||
} else if !tc.ExpectedError && err != nil {
|
||||
t.Fatalf("should not have failed: %v", err.Error())
|
||||
if tc.ExpectedError {
|
||||
require.NotNil(t, err, "should have failed")
|
||||
} else {
|
||||
require.Nil(t, err, "should not have failed")
|
||||
}
|
||||
if createdHook != nil {
|
||||
defer th.App.DeleteIncomingWebhook(createdHook.Id)
|
||||
}
|
||||
if tc.ExpectedIncomingWebhook == nil {
|
||||
assert.Nil(createdHook, "expected nil webhook")
|
||||
} else if assert.NotNil(createdHook, "expected non-nil webhook") {
|
||||
assert.Equal(tc.ExpectedIncomingWebhook.DisplayName, createdHook.DisplayName)
|
||||
assert.Equal(tc.ExpectedIncomingWebhook.Description, createdHook.Description)
|
||||
assert.Equal(tc.ExpectedIncomingWebhook.ChannelId, createdHook.ChannelId)
|
||||
assert.Equal(tc.ExpectedIncomingWebhook.Username, createdHook.Username)
|
||||
assert.Equal(tc.ExpectedIncomingWebhook.IconURL, createdHook.IconURL)
|
||||
assert.Nil(t, createdHook, "expected nil webhook")
|
||||
} else if assert.NotNil(t, createdHook, "expected non-nil webhook") {
|
||||
assert.Equal(t, tc.ExpectedIncomingWebhook.DisplayName, createdHook.DisplayName)
|
||||
assert.Equal(t, tc.ExpectedIncomingWebhook.Description, createdHook.Description)
|
||||
assert.Equal(t, tc.ExpectedIncomingWebhook.ChannelId, createdHook.ChannelId)
|
||||
assert.Equal(t, tc.ExpectedIncomingWebhook.Username, createdHook.Username)
|
||||
assert.Equal(t, tc.ExpectedIncomingWebhook.IconURL, createdHook.IconURL)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -251,16 +249,12 @@ func TestUpdateIncomingWebhook(t *testing.T) {
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = true })
|
||||
|
||||
hook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &model.IncomingWebhook{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
require.Nil(t, err)
|
||||
defer th.App.DeleteIncomingWebhook(hook.Id)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = tc.EnableIncomingHooks })
|
||||
@@ -270,19 +264,19 @@ func TestUpdateIncomingWebhook(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnablePostIconOverride = tc.EnablePostIconOverride })
|
||||
|
||||
updatedHook, err := th.App.UpdateIncomingWebhook(hook, &tc.IncomingWebhook)
|
||||
if tc.ExpectedError && err == nil {
|
||||
t.Fatal("should have failed")
|
||||
} else if !tc.ExpectedError && err != nil {
|
||||
t.Fatalf("should not have failed: %v", err.Error())
|
||||
if tc.ExpectedError {
|
||||
require.NotNil(t, err, "should have failed")
|
||||
} else {
|
||||
require.Nil(t, err, "should not have failed")
|
||||
}
|
||||
if tc.ExpectedIncomingWebhook == nil {
|
||||
assert.Nil(updatedHook, "expected nil webhook")
|
||||
} else if assert.NotNil(updatedHook, "expected non-nil webhook") {
|
||||
assert.Equal(tc.ExpectedIncomingWebhook.DisplayName, updatedHook.DisplayName)
|
||||
assert.Equal(tc.ExpectedIncomingWebhook.Description, updatedHook.Description)
|
||||
assert.Equal(tc.ExpectedIncomingWebhook.ChannelId, updatedHook.ChannelId)
|
||||
assert.Equal(tc.ExpectedIncomingWebhook.Username, updatedHook.Username)
|
||||
assert.Equal(tc.ExpectedIncomingWebhook.IconURL, updatedHook.IconURL)
|
||||
assert.Nil(t, updatedHook, "expected nil webhook")
|
||||
} else if assert.NotNil(t, updatedHook, "expected non-nil webhook") {
|
||||
assert.Equal(t, tc.ExpectedIncomingWebhook.DisplayName, updatedHook.DisplayName)
|
||||
assert.Equal(t, tc.ExpectedIncomingWebhook.Description, updatedHook.Description)
|
||||
assert.Equal(t, tc.ExpectedIncomingWebhook.ChannelId, updatedHook.ChannelId)
|
||||
assert.Equal(t, tc.ExpectedIncomingWebhook.Username, updatedHook.Username)
|
||||
assert.Equal(t, tc.ExpectedIncomingWebhook.IconURL, updatedHook.IconURL)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -295,9 +289,7 @@ func TestCreateWebhookPost(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = true })
|
||||
|
||||
hook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &model.IncomingWebhook{ChannelId: th.BasicChannel.Id})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
require.Nil(t, err)
|
||||
defer th.App.DeleteIncomingWebhook(hook.Id)
|
||||
|
||||
post, err := th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, "foo", "user", "http://iconurl", "", model.StringInterface{
|
||||
@@ -308,21 +300,14 @@ func TestCreateWebhookPost(t *testing.T) {
|
||||
},
|
||||
"webhook_display_name": hook.DisplayName,
|
||||
}, model.POST_SLACK_ATTACHMENT, "")
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
require.Nil(t, err)
|
||||
|
||||
for _, k := range []string{"from_webhook", "attachments", "webhook_display_name"} {
|
||||
if _, ok := post.Props[k]; !ok {
|
||||
t.Log("missing one props: " + k)
|
||||
t.Fatal(k)
|
||||
}
|
||||
}
|
||||
assert.Contains(t, post.Props, "from_webhook", "missing from_webhook prop")
|
||||
assert.Contains(t, post.Props, "attachments", "missing attachments prop")
|
||||
assert.Contains(t, post.Props, "webhook_display_name", "missing webhook_display_name prop")
|
||||
|
||||
_, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, "foo", "user", "http://iconurl", "", nil, model.POST_SYSTEM_GENERIC, "")
|
||||
if err == nil {
|
||||
t.Fatal("should have failed - bad post type")
|
||||
}
|
||||
require.NotNil(t, err, "Should have failed - bad post type")
|
||||
|
||||
expectedText := "`<>|<>|`"
|
||||
post, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", "", model.StringInterface{
|
||||
@@ -333,9 +318,7 @@ func TestCreateWebhookPost(t *testing.T) {
|
||||
},
|
||||
"webhook_display_name": hook.DisplayName,
|
||||
}, model.POST_SLACK_ATTACHMENT, "")
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, expectedText, post.Message)
|
||||
|
||||
expectedText = "< | \n|\n>"
|
||||
@@ -347,9 +330,7 @@ func TestCreateWebhookPost(t *testing.T) {
|
||||
},
|
||||
"webhook_display_name": hook.DisplayName,
|
||||
}, model.POST_SLACK_ATTACHMENT, "")
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, expectedText, post.Message)
|
||||
|
||||
expectedText = `commit bc95839e4a430ace453e8b209a3723c000c1729a
|
||||
@@ -377,9 +358,7 @@ Date: Thu Mar 1 19:46:48 2018 +0300
|
||||
},
|
||||
"webhook_display_name": hook.DisplayName,
|
||||
}, model.POST_SLACK_ATTACHMENT, "")
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, expectedText, post.Message)
|
||||
}
|
||||
|
||||
@@ -495,10 +474,7 @@ func TestCreateOutGoingWebhookWithUsernameAndIconURL(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOutgoingWebhooks = true })
|
||||
|
||||
createdHook, err := th.App.CreateOutgoingWebhook(&outgoingWebhook)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("should not have failed: %v", err.Error())
|
||||
}
|
||||
require.Nil(t, err)
|
||||
|
||||
assert.NotNil(t, createdHook, "should not be null")
|
||||
|
||||
|
||||
78
cmd/mattermost/commands/integrity.go
Обычный файл
78
cmd/mattermost/commands/integrity.go
Обычный файл
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var IntegrityCmd = &cobra.Command{
|
||||
Use: "integrity",
|
||||
Short: "Check database data integrity",
|
||||
RunE: integrityCmdF,
|
||||
}
|
||||
|
||||
func init() {
|
||||
IntegrityCmd.Flags().Bool("confirm", false, "Confirm you really want to run a complete integrity check that may temporarily harm system performance")
|
||||
IntegrityCmd.Flags().BoolP("verbose", "v", false, "Show detailed information on integrity check results")
|
||||
RootCmd.AddCommand(IntegrityCmd)
|
||||
}
|
||||
|
||||
func printRelationalIntegrityCheckResult(data store.RelationalIntegrityCheckData, verbose bool) {
|
||||
fmt.Println(fmt.Sprintf("Found %d records in relation %s orphans of relation %s",
|
||||
len(data.Records), data.ChildName, data.ParentName))
|
||||
if !verbose {
|
||||
return
|
||||
}
|
||||
for _, record := range data.Records {
|
||||
if record.ChildId != "" {
|
||||
fmt.Println(fmt.Sprintf(" Child %s (%s.%s) is missing Parent %s (%s.%s)", record.ChildId, data.ChildName, data.ChildIdAttr, record.ParentId, data.ChildName, data.ParentIdAttr))
|
||||
} else {
|
||||
fmt.Println(fmt.Sprintf(" Child is missing Parent %s (%s.%s)", record.ParentId, data.ChildName, data.ParentIdAttr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printIntegrityCheckResult(result store.IntegrityCheckResult, verbose bool) {
|
||||
switch data := result.Data.(type) {
|
||||
case store.RelationalIntegrityCheckData:
|
||||
printRelationalIntegrityCheckResult(data, verbose)
|
||||
}
|
||||
}
|
||||
|
||||
func integrityCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Shutdown()
|
||||
|
||||
confirmFlag, _ := command.Flags().GetBool("confirm")
|
||||
if !confirmFlag {
|
||||
var confirm string
|
||||
fmt.Fprintf(os.Stdout, "This check may harm performance on live systems. Are you sure you want to proceed? (y/N): ")
|
||||
fmt.Scanln(&confirm)
|
||||
if !strings.EqualFold(confirm, "y") && !strings.EqualFold(confirm, "yes") {
|
||||
fmt.Fprintf(os.Stderr, "Aborted.\n")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
verboseFlag, _ := command.Flags().GetBool("verbose")
|
||||
results := a.Srv.Store.CheckIntegrity()
|
||||
for result := range results {
|
||||
if result.Err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", result.Err.Error())
|
||||
break
|
||||
}
|
||||
printIntegrityCheckResult(result, verboseFlag)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -91,7 +91,7 @@ func runServer(configStore config.Store, disableConfigWatch bool, usedPlatform b
|
||||
|
||||
// wait for kill signal before attempting to gracefully shutdown
|
||||
// the running service
|
||||
signal.Notify(interruptChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
signal.Notify(interruptChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGPIPE)
|
||||
<-interruptChan
|
||||
|
||||
return nil
|
||||
|
||||
9
go.mod
9
go.mod
@@ -50,7 +50,7 @@ require (
|
||||
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.11.0
|
||||
github.com/miekg/dns v1.1.15 // indirect
|
||||
github.com/minio/minio-go v0.0.0-20190422205105-a8704b60278f
|
||||
github.com/minio/minio-go/v6 v6.0.34
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
||||
github.com/muesli/smartcrop v0.3.0 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.1 // indirect
|
||||
@@ -100,5 +100,8 @@ require (
|
||||
willnorris.com/go/imageproxy v0.9.0
|
||||
)
|
||||
|
||||
// Workaround for https://github.com/golang/go/issues/30831 and fallout.
|
||||
replace github.com/golang/lint => github.com/golang/lint v0.0.0-20190227174305-8f45f776aaf1
|
||||
replace (
|
||||
git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999
|
||||
// Workaround for https://github.com/golang/go/issues/30831 and fallout.
|
||||
github.com/golang/lint => github.com/golang/lint v0.0.0-20190227174305-8f45f776aaf1
|
||||
)
|
||||
|
||||
24
go.sum
24
go.sum
@@ -3,8 +3,8 @@ cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.37.1/go.mod h1:SAbnLi6YTSPKSI0dTUEOVLCkyPfKXK8n4ibqiMoj4ok=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.4.9/go.mod h1:ueLzZcP7LPhPulEBukGn4aLh7Mx9YJwpVJ9nL2FYltw=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/Azure/azure-sdk-for-go v26.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-autorest v11.5.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
@@ -20,6 +20,7 @@ github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMo
|
||||
github.com/PaulARoy/azurestoragecache v0.0.0-20170906084534-3c249a3ba788/go.mod h1:lY1dZd8HBzJ10eqKERHn3CU59tfhzcAVb2c0ZhIWSOk=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/a8m/mark v0.1.1-0.20170507133748-44f2db618845/go.mod h1:c8Mh99Cw82nrsAnPgxQSZHkswVOJF7/MqZb1ZdvriLM=
|
||||
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/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
@@ -65,6 +66,7 @@ github.com/die-net/lrucache v0.0.0-20181227122439-19a39ef22a11/go.mod h1:ew0MSjC
|
||||
github.com/disintegration/imaging v1.6.0 h1:nVPXRUUQ36Z7MNf0O77UzgnOb1mkMMor7lmJMJXc/mA=
|
||||
github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
|
||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8 h1:6muCmMJat6z7qptVrIf/+OWPxsjAfvhw5/6t+FwEkgg=
|
||||
github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8/go.mod h1:nYia/MIs9OyvXXYboPmNOj0gVWo97Wx0sde+ZuKkoM4=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
@@ -79,6 +81,7 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/gernest/wow v0.1.0/go.mod h1:dEPabJRi5BneI1Nev1VWo0ZlcTWibHWp43qxKms4elY=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-gorp/gorp v2.0.0+incompatible h1:dIQPsBtl6/H1MjVseWuWPXa7ET4p6Dve4j3Hg+UjqYw=
|
||||
@@ -127,7 +130,6 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
@@ -202,7 +204,6 @@ github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
|
||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
@@ -254,8 +255,9 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.15 h1:CSSIDtllwGLMoA6zjdKnaE6Tx6eVUxQ29LUgGetiDCI=
|
||||
github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/minio/minio-go v0.0.0-20190422205105-a8704b60278f h1:pIUObqY9ljwlPfUINTvfWhEuoJyy9dkCloS2f5Mf1Ks=
|
||||
github.com/minio/minio-go v0.0.0-20190422205105-a8704b60278f/go.mod h1:/haSOWG8hQNx2+JOfLJ9GKp61EAmgPwRVw/Sac0NzaM=
|
||||
github.com/minio/cli v1.20.0/go.mod h1:bYxnK0uS629N3Bq+AOZZ+6lwF77Sodk4+UL9vNuXhOY=
|
||||
github.com/minio/minio-go/v6 v6.0.34 h1:ESPDlIg8Pe2BRvsxPomd0xB72uLmsXrkDNoze36yb90=
|
||||
github.com/minio/minio-go/v6 v6.0.34/go.mod h1:vaNT59cWULS37E+E9zkuN/BVnKHyXtVGS+b04Boc66Y=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
@@ -355,11 +357,10 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8=
|
||||
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 h1:N8Bg45zpk/UcpNGnfJt2y/3lRWASHNTUET8owPYCgYI=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
@@ -413,9 +414,10 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190128193316-c7b33c32a30b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@@ -446,6 +448,7 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190322120337-addf6b3196f6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
|
||||
@@ -475,7 +478,7 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190116161447-11f53e031339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -545,7 +548,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.41.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.44.0 h1:YRJzTUp0kSYWUVFF5XAbDFfyiqwsl0Vb9R8TVP5eRi0=
|
||||
gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
|
||||
@@ -557,6 +560,7 @@ gopkg.in/olivere/elastic.v5 v5.0.81/go.mod h1:uhHoB4o3bvX5sorxBU29rPcmBQdV2Qfg0F
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
|
||||
18
i18n/en.json
18
i18n/en.json
@@ -2774,6 +2774,10 @@
|
||||
"id": "app.admin.test_email.failure",
|
||||
"translation": "Connection unsuccessful: {{.Error}}"
|
||||
},
|
||||
{
|
||||
"id": "app.admin.test_site_url.failure",
|
||||
"translation": "This is not a valid live URL"
|
||||
},
|
||||
{
|
||||
"id": "app.channel.create_channel.no_team_id.app_error",
|
||||
"translation": "Must specify the team ID to create a channel"
|
||||
@@ -3424,7 +3428,7 @@
|
||||
},
|
||||
{
|
||||
"id": "app.notification.subject.direct.full",
|
||||
"translation": "[{{.SiteName}}] New Direct Message from @{{.SenderDisplayName}} on {{.Month}} {{.Day}}, {{.Year}}"
|
||||
"translation": "[{{.SiteName}}] New Direct Message from {{.SenderDisplayName}} on {{.Month}} {{.Day}}, {{.Year}}"
|
||||
},
|
||||
{
|
||||
"id": "app.notification.subject.group_message.full",
|
||||
@@ -5662,6 +5666,10 @@
|
||||
"id": "store.sql_channel.get_more_channels.get.app_error",
|
||||
"translation": "Unable to get the channels"
|
||||
},
|
||||
{
|
||||
"id": "store.sql_channel.get_pinnedpost_count.app_error",
|
||||
"translation": "Unable to get the channel pinned post count"
|
||||
},
|
||||
{
|
||||
"id": "store.sql_channel.get_public_channels.get.app_error",
|
||||
"translation": "Unable to get public channels"
|
||||
@@ -6678,6 +6686,14 @@
|
||||
"id": "store.sql_team.analytics_get_team_count_for_scheme.app_error",
|
||||
"translation": "Unable to get the channel count for the scheme."
|
||||
},
|
||||
{
|
||||
"id": "store.sql_team.analytics_private_team_count.app_error",
|
||||
"translation": "Unable to count the private teams"
|
||||
},
|
||||
{
|
||||
"id": "store.sql_team.analytics_public_team_count.app_error",
|
||||
"translation": "Unable to count the public teams"
|
||||
},
|
||||
{
|
||||
"id": "store.sql_team.analytics_team_count.app_error",
|
||||
"translation": "Unable to count the teams"
|
||||
|
||||
@@ -103,8 +103,7 @@ func (schedulers *Schedulers) Start() *Schedulers {
|
||||
if scheduler != nil {
|
||||
if scheduler.Enabled(cfg) {
|
||||
if _, err := schedulers.scheduleJob(cfg, scheduler); err != nil {
|
||||
mlog.Warn(fmt.Sprintf("Failed to schedule job with scheduler: %v", scheduler.Name()))
|
||||
mlog.Error(fmt.Sprint(err))
|
||||
mlog.Error("Failed to schedule job", mlog.String("scheduler", scheduler.Name()), mlog.Err(err))
|
||||
} else {
|
||||
schedulers.setNextRunTime(cfg, idx, now, true)
|
||||
}
|
||||
@@ -148,7 +147,7 @@ func (schedulers *Schedulers) setNextRunTime(cfg *model.Config, idx int, now tim
|
||||
|
||||
if !pendingJobs {
|
||||
if pj, err := schedulers.jobs.CheckForPendingJobsByType(scheduler.JobType()); err != nil {
|
||||
mlog.Error("Failed to set next job run time: " + err.Error())
|
||||
mlog.Error("Failed to set next job run time", mlog.Err(err))
|
||||
schedulers.nextRunTimes[idx] = nil
|
||||
return
|
||||
} else {
|
||||
@@ -158,13 +157,13 @@ func (schedulers *Schedulers) setNextRunTime(cfg *model.Config, idx int, now tim
|
||||
|
||||
lastSuccessfulJob, err := schedulers.jobs.GetLastSuccessfulJobByType(scheduler.JobType())
|
||||
if err != nil {
|
||||
mlog.Error("Failed to set next job run time: " + err.Error())
|
||||
mlog.Error("Failed to set next job run time", mlog.Err(err))
|
||||
schedulers.nextRunTimes[idx] = nil
|
||||
return
|
||||
}
|
||||
|
||||
schedulers.nextRunTimes[idx] = scheduler.NextScheduleTime(cfg, now, pendingJobs, lastSuccessfulJob)
|
||||
mlog.Debug(fmt.Sprintf("Next run time for scheduler %v: %v", scheduler.Name(), schedulers.nextRunTimes[idx]))
|
||||
mlog.Debug("Next run time for scheduler", mlog.String("scheduler_name", scheduler.Name()), mlog.String("next_runtime", fmt.Sprintf("%v", schedulers.nextRunTimes[idx])))
|
||||
}
|
||||
|
||||
func (schedulers *Schedulers) scheduleJob(cfg *model.Config, scheduler model.Scheduler) (*model.Job, *model.AppError) {
|
||||
|
||||
@@ -9,9 +9,10 @@ import (
|
||||
)
|
||||
|
||||
type ChannelStats struct {
|
||||
ChannelId string `json:"channel_id"`
|
||||
MemberCount int64 `json:"member_count"`
|
||||
GuestCount int64 `json:"guest_count"`
|
||||
ChannelId string `json:"channel_id"`
|
||||
MemberCount int64 `json:"member_count"`
|
||||
GuestCount int64 `json:"guest_count"`
|
||||
PinnedPostCount int64 `json:"pinnedpost_count"`
|
||||
}
|
||||
|
||||
func (o *ChannelStats) ToJson() string {
|
||||
|
||||
@@ -268,6 +268,10 @@ func (c *Client4) GetTestEmailRoute() string {
|
||||
return fmt.Sprintf("/email/test")
|
||||
}
|
||||
|
||||
func (c *Client4) GetTestSiteURLRoute() string {
|
||||
return fmt.Sprintf("/site_url/test")
|
||||
}
|
||||
|
||||
func (c *Client4) GetTestS3Route() string {
|
||||
return fmt.Sprintf("/file/s3_test")
|
||||
}
|
||||
@@ -2922,6 +2926,18 @@ func (c *Client4) TestEmail(config *Config) (bool, *Response) {
|
||||
return CheckStatusOK(r), BuildResponse(r)
|
||||
}
|
||||
|
||||
// TestSiteURL will test the validity of a site URL.
|
||||
func (c *Client4) TestSiteURL(siteURL string) (bool, *Response) {
|
||||
requestBody := make(map[string]string)
|
||||
requestBody["site_url"] = siteURL
|
||||
r, err := c.DoApiPost(c.GetTestSiteURLRoute(), MapToJson(requestBody))
|
||||
if err != nil {
|
||||
return false, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
return CheckStatusOK(r), BuildResponse(r)
|
||||
}
|
||||
|
||||
// TestS3Connection will attempt to connect to the AWS S3.
|
||||
func (c *Client4) TestS3Connection(config *Config) (bool, *Response) {
|
||||
r, err := c.DoApiPost(c.GetTestS3Route(), config.ToJson())
|
||||
|
||||
@@ -138,7 +138,7 @@ const (
|
||||
SAML_SETTINGS_DEFAULT_LOCALE_ATTRIBUTE = ""
|
||||
SAML_SETTINGS_DEFAULT_POSITION_ATTRIBUTE = ""
|
||||
|
||||
NATIVEAPP_SETTINGS_DEFAULT_APP_DOWNLOAD_LINK = "https://about.mattermost.com/downloads/"
|
||||
NATIVEAPP_SETTINGS_DEFAULT_APP_DOWNLOAD_LINK = "https://mattermost.com/download/#mattermostApps"
|
||||
NATIVEAPP_SETTINGS_DEFAULT_ANDROID_APP_DOWNLOAD_LINK = "https://about.mattermost.com/mattermost-android-app/"
|
||||
NATIVEAPP_SETTINGS_DEFAULT_IOS_APP_DOWNLOAD_LINK = "https://about.mattermost.com/mattermost-ios-app/"
|
||||
|
||||
@@ -1231,7 +1231,7 @@ func (s *EmailSettings) SetDefaults(isUpdate bool) {
|
||||
}
|
||||
|
||||
if s.SMTPPort == nil || len(*s.SMTPPort) == 0 {
|
||||
s.SMTPPort = NewString("2500")
|
||||
s.SMTPPort = NewString("10025")
|
||||
}
|
||||
|
||||
if s.ConnectionSecurity == nil || *s.ConnectionSecurity == CONN_SECURITY_PLAIN {
|
||||
|
||||
@@ -494,6 +494,18 @@ func (u *User) Sanitize(options map[string]bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any input data from the user object that is not user controlled
|
||||
func (u *User) SanitizeInput() {
|
||||
u.AuthData = NewString("")
|
||||
u.AuthService = ""
|
||||
u.LastPasswordUpdate = 0
|
||||
u.LastPictureUpdate = 0
|
||||
u.FailedAttempts = 0
|
||||
u.EmailVerified = false
|
||||
u.MfaActive = false
|
||||
u.MfaSecret = ""
|
||||
}
|
||||
|
||||
func (u *User) ClearNonProfileFields() {
|
||||
u.Password = ""
|
||||
u.AuthData = NewString("")
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
// It should be maintained in chronological order with most current
|
||||
// release at the front of the list.
|
||||
var versions = []string{
|
||||
"5.15.0",
|
||||
"5.14.0",
|
||||
"5.13.0",
|
||||
"5.12.0",
|
||||
|
||||
@@ -31,6 +31,11 @@ type API interface {
|
||||
// GetConfig fetches the currently persisted config
|
||||
GetConfig() *model.Config
|
||||
|
||||
// GetUnsanitizedConfig fetches the currently persisted config without removing secrets.
|
||||
//
|
||||
// Minimum server version: 5.16
|
||||
GetUnsanitizedConfig() *model.Config
|
||||
|
||||
// SaveConfig sets the given config and persists the changes
|
||||
SaveConfig(config *model.Config) *model.AppError
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
const (
|
||||
INTERNAL_KEY_PREFIX = "mmi_"
|
||||
BOT_USER_KEY = INTERNAL_KEY_PREFIX + "botid"
|
||||
CHANNEL_KEY = INTERNAL_KEY_PREFIX + "channelid"
|
||||
)
|
||||
|
||||
// Starts the serving of a Mattermost plugin over net/rpc. gRPC is not yet supported.
|
||||
|
||||
@@ -588,6 +588,33 @@ func (s *apiRPCServer) GetConfig(args *Z_GetConfigArgs, returns *Z_GetConfigRetu
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_GetUnsanitizedConfigArgs struct {
|
||||
}
|
||||
|
||||
type Z_GetUnsanitizedConfigReturns struct {
|
||||
A *model.Config
|
||||
}
|
||||
|
||||
func (g *apiRPCClient) GetUnsanitizedConfig() *model.Config {
|
||||
_args := &Z_GetUnsanitizedConfigArgs{}
|
||||
_returns := &Z_GetUnsanitizedConfigReturns{}
|
||||
if err := g.client.Call("Plugin.GetUnsanitizedConfig", _args, _returns); err != nil {
|
||||
log.Printf("RPC call to GetUnsanitizedConfig API failed: %s", err.Error())
|
||||
}
|
||||
return _returns.A
|
||||
}
|
||||
|
||||
func (s *apiRPCServer) GetUnsanitizedConfig(args *Z_GetUnsanitizedConfigArgs, returns *Z_GetUnsanitizedConfigReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
GetUnsanitizedConfig() *model.Config
|
||||
}); ok {
|
||||
returns.A = hook.GetUnsanitizedConfig()
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("API GetUnsanitizedConfig called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_SaveConfigArgs struct {
|
||||
A *model.Config
|
||||
}
|
||||
|
||||
@@ -284,16 +284,46 @@ func (env *Environment) RestartPlugin(id string) error {
|
||||
|
||||
// Shutdown deactivates all plugins and gracefully shuts down the environment.
|
||||
func (env *Environment) Shutdown() {
|
||||
if env.pluginHealthCheckJob != nil {
|
||||
env.pluginHealthCheckJob.Cancel()
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
env.registeredPlugins.Range(func(key, value interface{}) bool {
|
||||
rp := value.(*registeredPlugin)
|
||||
|
||||
if rp.supervisor != nil {
|
||||
if rp.supervisor == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
defer close(done)
|
||||
if err := rp.supervisor.Hooks().OnDeactivate(); err != nil {
|
||||
env.logger.Error("Plugin OnDeactivate() error", mlog.String("plugin_id", rp.BundleInfo.Manifest.Id), mlog.Err(err))
|
||||
}
|
||||
rp.supervisor.Shutdown()
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
select {
|
||||
case <-time.After(10 * time.Second):
|
||||
env.logger.Warn("Plugin OnDeactivate() failed to complete in 10 seconds", mlog.String("plugin_id", rp.BundleInfo.Manifest.Id))
|
||||
case <-done:
|
||||
}
|
||||
|
||||
rp.supervisor.Shutdown()
|
||||
}()
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
wg.Wait()
|
||||
|
||||
env.registeredPlugins.Range(func(key, value interface{}) bool {
|
||||
env.registeredPlugins.Delete(key)
|
||||
|
||||
return true
|
||||
|
||||
@@ -5,6 +5,7 @@ package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/mlog"
|
||||
@@ -19,9 +20,10 @@ const (
|
||||
)
|
||||
|
||||
type PluginHealthCheckJob struct {
|
||||
cancel chan struct{}
|
||||
cancelled chan struct{}
|
||||
env *Environment
|
||||
cancel chan struct{}
|
||||
cancelled chan struct{}
|
||||
cancelOnce sync.Once
|
||||
env *Environment
|
||||
}
|
||||
|
||||
// InitPluginHealthCheckJob starts a new job if one is not running and is set to enabled, or kills an existing one if set to disabled.
|
||||
@@ -125,7 +127,9 @@ func newPluginHealthCheckJob(env *Environment) *PluginHealthCheckJob {
|
||||
}
|
||||
|
||||
func (job *PluginHealthCheckJob) Cancel() {
|
||||
close(job.cancel)
|
||||
job.cancelOnce.Do(func() {
|
||||
close(job.cancel)
|
||||
})
|
||||
<-job.cancelled
|
||||
}
|
||||
|
||||
|
||||
111
plugin/helpers_channels.go
Обычный файл
111
plugin/helpers_channels.go
Обычный файл
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (p *HelpersImpl) EnsureChannel(channel *model.Channel) (retChannelId string, retErr error) {
|
||||
// Must provide a channel with a name and teadId
|
||||
if channel == nil || len(channel.Name) < 1 || len(channel.TeamId) < 1 {
|
||||
return "", errors.New("passed a bad channel, nil or no name or no team id")
|
||||
}
|
||||
|
||||
// If we fail for any reason, this could be a race between creation of channel and
|
||||
// retrieval from another EnsureChannel. Just try the basic retrieve existing again.
|
||||
defer func() {
|
||||
if retChannelId == "" || retErr != nil {
|
||||
var err error
|
||||
var channelIdBytes []byte
|
||||
|
||||
err = utils.ProgressiveRetry(func() error {
|
||||
channelIdBytes, err = p.API.KVGet(CHANNEL_KEY)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err == nil && channelIdBytes != nil {
|
||||
retChannelId = string(channelIdBytes)
|
||||
retErr = nil
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Fetch channel ID from key value store
|
||||
channelIdBytes, kvGetErr := p.API.KVGet(CHANNEL_KEY)
|
||||
if kvGetErr != nil {
|
||||
// Failed to retrive the value of channel
|
||||
return "", errors.Wrap(kvGetErr, "failed to get channel ID")
|
||||
}
|
||||
|
||||
var existingChannel *model.Channel
|
||||
var channelGetErr *model.AppError
|
||||
|
||||
// If channel ID exists, get existing channel by ID else get it by Name
|
||||
if channelIdBytes != nil {
|
||||
existingChannel, channelGetErr = p.API.GetChannel(string(channelIdBytes))
|
||||
if channelGetErr != nil {
|
||||
return "", errors.Wrap(channelGetErr, "failed to get channel by ID")
|
||||
}
|
||||
} else {
|
||||
existingChannel, channelGetErr = p.API.GetChannelByName(channel.TeamId, channel.Name, false)
|
||||
if channelGetErr != nil {
|
||||
return "", errors.Wrap(channelGetErr, "failed to get channel by name")
|
||||
}
|
||||
}
|
||||
|
||||
// If channel exists, update the metadata
|
||||
if existingChannel != nil {
|
||||
return updateChannel(p, existingChannel, channel)
|
||||
}
|
||||
|
||||
// Create a new channel
|
||||
createdChannel, createChannelErr := p.API.CreateChannel(channel)
|
||||
if createChannelErr != nil {
|
||||
return "", errors.Wrap(createChannelErr, "failed to create channel")
|
||||
}
|
||||
|
||||
// Set the new channel id in key value store
|
||||
if kvSetErr := p.API.KVSet(CHANNEL_KEY, []byte(createdChannel.Id)); kvSetErr != nil {
|
||||
p.API.LogWarn("Failed to set created channel id.", "channelid", createdChannel.Id, "err", kvSetErr)
|
||||
}
|
||||
|
||||
return createdChannel.Id, nil
|
||||
}
|
||||
|
||||
func updateChannel(p *HelpersImpl, existing *model.Channel, new *model.Channel) (string, error) {
|
||||
// Update metadata of the channel
|
||||
if updateErr := updateChannelMeta(existing, new); updateErr != nil {
|
||||
return "", errors.Wrap(updateErr, "Failed to update the metadata of existing channel")
|
||||
}
|
||||
|
||||
// Send the updates to API
|
||||
updatedChannel, channelUpdateErr := p.API.UpdateChannel(existing)
|
||||
if channelUpdateErr != nil {
|
||||
return "", errors.Wrap(channelUpdateErr, "Failed to update the existing channel")
|
||||
}
|
||||
|
||||
// Channel exists!
|
||||
return updatedChannel.Id, nil
|
||||
}
|
||||
|
||||
func updateChannelMeta(existing *model.Channel, new *model.Channel) error {
|
||||
// Check if channels are of different types
|
||||
if existing.Type != new.Type {
|
||||
return errors.New("Channel type cannot be updated")
|
||||
}
|
||||
|
||||
// Update metadata of channel
|
||||
existing.Name = new.Name
|
||||
existing.DisplayName = new.DisplayName
|
||||
existing.Purpose = new.Purpose
|
||||
existing.Header = new.Header
|
||||
|
||||
return nil
|
||||
}
|
||||
297
plugin/helpers_channels_test.go
Обычный файл
297
plugin/helpers_channels_test.go
Обычный файл
@@ -0,0 +1,297 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package plugin_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/plugin"
|
||||
"github.com/mattermost/mattermost-server/plugin/plugintest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEnsureChannel(t *testing.T) {
|
||||
setupAPI := func() *plugintest.API {
|
||||
return &plugintest.API{}
|
||||
}
|
||||
|
||||
testChannel := &model.Channel{
|
||||
Id: model.NewId(),
|
||||
TeamId: model.NewId(),
|
||||
Type: "public",
|
||||
Name: "test_channel",
|
||||
DisplayName: "Test Channel",
|
||||
Purpose: "Testing EnsureChannel",
|
||||
Header: "Testing EnsureChannel",
|
||||
}
|
||||
|
||||
t.Run("bad parameters", func(t *testing.T) {
|
||||
t.Run("no channel", func(t *testing.T) {
|
||||
p := &plugin.HelpersImpl{}
|
||||
channelId, err := p.EnsureChannel(nil)
|
||||
assert.Equal(t, "", channelId)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
t.Run("empty name", func(t *testing.T) {
|
||||
p := &plugin.HelpersImpl{}
|
||||
channelId, err := p.EnsureChannel(&model.Channel{
|
||||
Name: "",
|
||||
})
|
||||
assert.Equal(t, "", channelId)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
t.Run("name without teamId", func(t *testing.T) {
|
||||
p := &plugin.HelpersImpl{}
|
||||
channelId, err := p.EnsureChannel(&model.Channel{
|
||||
Name: "test_channel",
|
||||
})
|
||||
assert.Equal(t, "", channelId)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
t.Run("teamId without name", func(t *testing.T) {
|
||||
p := &plugin.HelpersImpl{}
|
||||
channelId, err := p.EnsureChannel(&model.Channel{
|
||||
TeamId: model.NewId(),
|
||||
})
|
||||
assert.Equal(t, "", channelId)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
t.Run("teamId with empty name", func(t *testing.T) {
|
||||
p := &plugin.HelpersImpl{}
|
||||
channelId, err := p.EnsureChannel(&model.Channel{
|
||||
TeamId: model.NewId(),
|
||||
})
|
||||
assert.Equal(t, "", channelId)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("if channel already exists in Key Value store", func(t *testing.T) {
|
||||
t.Run("should return an error if unable to get channel id", func(t *testing.T) {
|
||||
api := setupAPI()
|
||||
api.On("KVGet", plugin.CHANNEL_KEY).Return(nil, &model.AppError{})
|
||||
defer api.AssertExpectations(t)
|
||||
|
||||
p := &plugin.HelpersImpl{API: api}
|
||||
|
||||
channelId, err := p.EnsureChannel(testChannel)
|
||||
|
||||
assert.Equal(t, "", channelId)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
t.Run("should return an error if unable to get channel", func(t *testing.T) {
|
||||
expectedChannelId := model.NewId()
|
||||
|
||||
api := setupAPI()
|
||||
api.On("KVGet", plugin.CHANNEL_KEY).Return([]byte(expectedChannelId), nil)
|
||||
api.On("GetChannel", expectedChannelId).Return(nil, &model.AppError{})
|
||||
|
||||
p := &plugin.HelpersImpl{API: api}
|
||||
|
||||
channelId, err := p.EnsureChannel(testChannel)
|
||||
|
||||
assert.Equal(t, "", channelId)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
t.Run("should return an error if unable to update channel", func(t *testing.T) {
|
||||
expectedChannelId := model.NewId()
|
||||
|
||||
api := setupAPI()
|
||||
api.On("KVGet", plugin.CHANNEL_KEY).Return([]byte(expectedChannelId), nil)
|
||||
api.On("GetChannel", expectedChannelId).Return(testChannel, nil)
|
||||
api.On("UpdateChannel", testChannel).Return(nil, &model.AppError{})
|
||||
defer api.AssertExpectations(t)
|
||||
|
||||
p := &plugin.HelpersImpl{API: api}
|
||||
|
||||
channelId, err := p.EnsureChannel(testChannel)
|
||||
|
||||
assert.Equal(t, "", channelId)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
t.Run("should return the Id of existing channel if metadata is same", func(t *testing.T) {
|
||||
api := setupAPI()
|
||||
api.On("KVGet", plugin.CHANNEL_KEY).Return([]byte(testChannel.Id), nil)
|
||||
api.On("GetChannel", testChannel.Id).Return(testChannel, nil)
|
||||
api.On("UpdateChannel", testChannel).Return(testChannel, nil)
|
||||
defer api.AssertExpectations(t)
|
||||
|
||||
p := &plugin.HelpersImpl{API: api}
|
||||
|
||||
channelId, err := p.EnsureChannel(testChannel)
|
||||
|
||||
assert.Equal(t, testChannel.Id, channelId)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
t.Run("should return error if channel type is different from existing one", func(t *testing.T) {
|
||||
privChannel := &model.Channel{
|
||||
Id: model.NewId(),
|
||||
Type: "private",
|
||||
TeamId: testChannel.TeamId,
|
||||
Name: testChannel.Name,
|
||||
}
|
||||
api := setupAPI()
|
||||
api.On("KVGet", plugin.CHANNEL_KEY).Return([]byte(testChannel.Id), nil)
|
||||
api.On("GetChannel", testChannel.Id).Return(privChannel, nil)
|
||||
defer api.AssertExpectations(t)
|
||||
|
||||
p := &plugin.HelpersImpl{API: api}
|
||||
|
||||
channelId, err := p.EnsureChannel(testChannel)
|
||||
|
||||
assert.Equal(t, "", channelId)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
t.Run("should return the Id of updated channel if metadata is different", func(t *testing.T) {
|
||||
updatedChannel := &model.Channel{
|
||||
Id: model.NewId(),
|
||||
TeamId: testChannel.TeamId,
|
||||
Name: testChannel.Name,
|
||||
}
|
||||
api := setupAPI()
|
||||
api.On("KVGet", plugin.CHANNEL_KEY).Return([]byte(testChannel.Id), nil)
|
||||
api.On("GetChannel", testChannel.Id).Return(testChannel, nil)
|
||||
api.On("UpdateChannel", testChannel).Return(updatedChannel, nil)
|
||||
defer api.AssertExpectations(t)
|
||||
|
||||
p := &plugin.HelpersImpl{API: api}
|
||||
|
||||
channelId, err := p.EnsureChannel(testChannel)
|
||||
|
||||
assert.Equal(t, updatedChannel.Id, channelId)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("if channel is not in Key Value store but already exists", func(t *testing.T) {
|
||||
t.Run("should return an error if unable to get channel", func(t *testing.T) {
|
||||
api := setupAPI()
|
||||
api.On("KVGet", plugin.CHANNEL_KEY).Return(nil, nil)
|
||||
api.On("GetChannelByName", testChannel.TeamId, testChannel.Name, false).Return(nil, &model.AppError{})
|
||||
defer api.AssertExpectations(t)
|
||||
|
||||
p := &plugin.HelpersImpl{API: api}
|
||||
|
||||
channelId, err := p.EnsureChannel(testChannel)
|
||||
|
||||
assert.Equal(t, "", channelId)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
t.Run("should return the Id of existing channel if metadata is same", func(t *testing.T) {
|
||||
api := setupAPI()
|
||||
api.On("KVGet", plugin.CHANNEL_KEY).Return(nil, nil)
|
||||
api.On("GetChannelByName", testChannel.TeamId, testChannel.Name, false).Return(testChannel, nil)
|
||||
api.On("UpdateChannel", testChannel).Return(testChannel, nil)
|
||||
defer api.AssertExpectations(t)
|
||||
|
||||
p := &plugin.HelpersImpl{API: api}
|
||||
|
||||
channelId, err := p.EnsureChannel(testChannel)
|
||||
|
||||
assert.Equal(t, testChannel.Id, channelId)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
t.Run("should return error if failed to update the channel", func(t *testing.T) {
|
||||
api := setupAPI()
|
||||
api.On("KVGet", plugin.CHANNEL_KEY).Return(nil, nil)
|
||||
api.On("GetChannelByName", testChannel.TeamId, testChannel.Name, false).Return(testChannel, nil)
|
||||
api.On("UpdateChannel", testChannel).Return(nil, &model.AppError{})
|
||||
defer api.AssertExpectations(t)
|
||||
|
||||
p := &plugin.HelpersImpl{API: api}
|
||||
|
||||
channelId, err := p.EnsureChannel(testChannel)
|
||||
|
||||
assert.Equal(t, "", channelId)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
t.Run("should return the Id of updated channel if metadata is different", func(t *testing.T) {
|
||||
updatedChannel := &model.Channel{
|
||||
Id: model.NewId(),
|
||||
TeamId: testChannel.TeamId,
|
||||
Name: testChannel.Name,
|
||||
}
|
||||
api := setupAPI()
|
||||
api.On("KVGet", plugin.CHANNEL_KEY).Return(nil, nil)
|
||||
api.On("GetChannelByName", testChannel.TeamId, testChannel.Name, false).Return(testChannel, nil)
|
||||
api.On("UpdateChannel", testChannel).Return(updatedChannel, nil)
|
||||
defer api.AssertExpectations(t)
|
||||
|
||||
p := &plugin.HelpersImpl{API: api}
|
||||
|
||||
channelId, err := p.EnsureChannel(testChannel)
|
||||
|
||||
assert.Equal(t, updatedChannel.Id, channelId)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
t.Run("should return error if channel type is different from existing one", func(t *testing.T) {
|
||||
privChannel := &model.Channel{
|
||||
Id: model.NewId(),
|
||||
Type: "private",
|
||||
TeamId: testChannel.TeamId,
|
||||
Name: testChannel.Name,
|
||||
}
|
||||
api := setupAPI()
|
||||
api.On("KVGet", plugin.CHANNEL_KEY).Return(nil, nil)
|
||||
api.On("GetChannelByName", testChannel.TeamId, testChannel.Name, false).Return(privChannel, nil)
|
||||
defer api.AssertExpectations(t)
|
||||
|
||||
p := &plugin.HelpersImpl{API: api}
|
||||
|
||||
channelId, err := p.EnsureChannel(testChannel)
|
||||
|
||||
assert.Equal(t, "", channelId)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("if channel does not exist", func(t *testing.T) {
|
||||
t.Run("should create new channel and return the Id", func(t *testing.T) {
|
||||
api := setupAPI()
|
||||
api.On("KVGet", plugin.CHANNEL_KEY).Return(nil, nil)
|
||||
api.On("GetChannelByName", testChannel.TeamId, testChannel.Name, false).Return(nil, nil)
|
||||
api.On("CreateChannel", testChannel).Return(testChannel, nil)
|
||||
api.On("KVSet", plugin.CHANNEL_KEY, []byte(testChannel.Id)).Return(nil)
|
||||
defer api.AssertExpectations(t)
|
||||
|
||||
p := &plugin.HelpersImpl{API: api}
|
||||
|
||||
channelId, err := p.EnsureChannel(testChannel)
|
||||
|
||||
assert.Equal(t, testChannel.Id, channelId)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
t.Run("should return error if unable to create new channel", func(t *testing.T) {
|
||||
api := setupAPI()
|
||||
api.On("KVGet", plugin.CHANNEL_KEY).Return(nil, nil)
|
||||
api.On("GetChannelByName", testChannel.TeamId, testChannel.Name, false).Return(nil, nil)
|
||||
api.On("CreateChannel", testChannel).Return(nil, &model.AppError{})
|
||||
defer api.AssertExpectations(t)
|
||||
|
||||
p := &plugin.HelpersImpl{API: api}
|
||||
|
||||
channelId, err := p.EnsureChannel(testChannel)
|
||||
|
||||
assert.Equal(t, "", channelId)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
t.Run("should log and return id if unable to write to Key Value store", func(t *testing.T) {
|
||||
api := setupAPI()
|
||||
api.On("KVGet", plugin.CHANNEL_KEY).Return(nil, nil)
|
||||
api.On("GetChannelByName", testChannel.TeamId, testChannel.Name, false).Return(nil, nil)
|
||||
api.On("CreateChannel", testChannel).Return(testChannel, nil)
|
||||
api.On("KVSet", plugin.CHANNEL_KEY, []byte(testChannel.Id)).Return(&model.AppError{})
|
||||
api.On("LogWarn", "Failed to set created channel id.", "channelid", testChannel.Id, "err", &model.AppError{})
|
||||
defer api.AssertExpectations(t)
|
||||
|
||||
p := &plugin.HelpersImpl{API: api}
|
||||
|
||||
channelId, err := p.EnsureChannel(testChannel)
|
||||
|
||||
assert.Equal(t, testChannel.Id, channelId)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -34,7 +34,12 @@ func (p *HelpersImpl) KVSetJSON(key string, value interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.API.KVSet(key, data)
|
||||
appErr := p.API.KVSet(key, data)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// KVCompareAndSetJSON is a wrapper around KVCompareAndSet to simplify atomically writing a JSON object to the key value store.
|
||||
@@ -56,7 +61,12 @@ func (p *HelpersImpl) KVCompareAndSetJSON(key string, oldValue interface{}, newV
|
||||
}
|
||||
}
|
||||
|
||||
return p.API.KVCompareAndSet(key, oldData, newData)
|
||||
set, appErr := p.API.KVCompareAndSet(key, oldData, newData)
|
||||
if appErr != nil {
|
||||
return set, appErr
|
||||
}
|
||||
|
||||
return set, nil
|
||||
}
|
||||
|
||||
// KVCompareAndDeleteJSON is a wrapper around KVCompareAndDelete to simplify atomically deleting a JSON object from the key value store.
|
||||
@@ -71,7 +81,12 @@ func (p *HelpersImpl) KVCompareAndDeleteJSON(key string, oldValue interface{}) (
|
||||
}
|
||||
}
|
||||
|
||||
return p.API.KVCompareAndDelete(key, oldData)
|
||||
deleted, appErr := p.API.KVCompareAndDelete(key, oldData)
|
||||
if appErr != nil {
|
||||
return deleted, appErr
|
||||
}
|
||||
|
||||
return deleted, nil
|
||||
}
|
||||
|
||||
// KVSetWithExpiryJSON is a wrapper around KVSetWithExpiry to simplify atomically writing a JSON object with expiry to the key value store.
|
||||
@@ -81,5 +96,10 @@ func (p *HelpersImpl) KVSetWithExpiryJSON(key string, value interface{}, expireI
|
||||
return err
|
||||
}
|
||||
|
||||
return p.API.KVSetWithExpiry(key, data, expireInSeconds)
|
||||
appErr := p.API.KVSetWithExpiry(key, data, expireInSeconds)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestKVGetJSON(t *testing.T) {
|
||||
ok, err := p.KVGetJSON("test-key", dat)
|
||||
api.AssertExpectations(t)
|
||||
assert.False(t, ok)
|
||||
assert.NotNil(t, err)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, dat)
|
||||
})
|
||||
|
||||
@@ -38,7 +38,7 @@ func TestKVGetJSON(t *testing.T) {
|
||||
ok, err := p.KVGetJSON("test-key", dat)
|
||||
api.AssertExpectations(t)
|
||||
assert.False(t, ok)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, dat)
|
||||
})
|
||||
|
||||
@@ -54,7 +54,7 @@ func TestKVGetJSON(t *testing.T) {
|
||||
ok, err := p.KVGetJSON("test-key", &dat)
|
||||
api.AssertExpectations(t)
|
||||
assert.False(t, ok)
|
||||
assert.NotNil(t, err)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, dat)
|
||||
})
|
||||
|
||||
@@ -70,7 +70,7 @@ func TestKVGetJSON(t *testing.T) {
|
||||
ok, err := p.KVGetJSON("test-key", &dat)
|
||||
assert.True(t, ok)
|
||||
api.AssertExpectations(t)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, map[string]interface{}{
|
||||
"val-a": float64(10),
|
||||
}, dat)
|
||||
@@ -86,7 +86,21 @@ func TestKVSetJSON(t *testing.T) {
|
||||
|
||||
err := p.KVSetJSON("test-key", func() { return })
|
||||
api.AssertExpectations(t)
|
||||
assert.NotNil(t, err)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("KVSet error", func(t *testing.T) {
|
||||
api := &plugintest.API{}
|
||||
api.On("KVSet", "test-key", []byte(`{"val-a":10}`)).Return(&model.AppError{})
|
||||
|
||||
p := &plugin.HelpersImpl{API: api}
|
||||
|
||||
err := p.KVSetJSON("test-key", map[string]interface{}{
|
||||
"val-a": float64(10),
|
||||
})
|
||||
|
||||
api.AssertExpectations(t)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("marshallable struct", func(t *testing.T) {
|
||||
@@ -100,7 +114,7 @@ func TestKVSetJSON(t *testing.T) {
|
||||
})
|
||||
|
||||
api.AssertExpectations(t)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -114,7 +128,7 @@ func TestKVCompareAndSetJSON(t *testing.T) {
|
||||
|
||||
api.AssertExpectations(t)
|
||||
assert.Equal(t, false, ok)
|
||||
assert.NotNil(t, err)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("new value JSON marshal error", func(t *testing.T) {
|
||||
@@ -127,7 +141,23 @@ func TestKVCompareAndSetJSON(t *testing.T) {
|
||||
|
||||
api.AssertExpectations(t)
|
||||
assert.False(t, ok)
|
||||
assert.NotNil(t, err)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("KVCompareAndSet error", func(t *testing.T) {
|
||||
api := &plugintest.API{}
|
||||
api.On("KVCompareAndSet", "test-key", []byte(`{"val-a":10}`), []byte(`{"val-b":20}`)).Return(false, &model.AppError{})
|
||||
p := &plugin.HelpersImpl{API: api}
|
||||
|
||||
ok, err := p.KVCompareAndSetJSON("test-key", map[string]interface{}{
|
||||
"val-a": 10,
|
||||
}, map[string]interface{}{
|
||||
"val-b": 20,
|
||||
})
|
||||
|
||||
api.AssertExpectations(t)
|
||||
assert.False(t, ok)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("old value nil", func(t *testing.T) {
|
||||
@@ -141,7 +171,7 @@ func TestKVCompareAndSetJSON(t *testing.T) {
|
||||
|
||||
api.AssertExpectations(t)
|
||||
assert.True(t, ok)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("old value non-nil", func(t *testing.T) {
|
||||
@@ -157,7 +187,7 @@ func TestKVCompareAndSetJSON(t *testing.T) {
|
||||
|
||||
api.AssertExpectations(t)
|
||||
assert.True(t, ok)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("new value nil", func(t *testing.T) {
|
||||
@@ -171,7 +201,7 @@ func TestKVCompareAndSetJSON(t *testing.T) {
|
||||
|
||||
api.AssertExpectations(t)
|
||||
assert.True(t, ok)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -185,7 +215,21 @@ func TestKVCompareAndDeleteJSON(t *testing.T) {
|
||||
|
||||
api.AssertExpectations(t)
|
||||
assert.Equal(t, false, ok)
|
||||
assert.NotNil(t, err)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("KVCompareAndDelete error", func(t *testing.T) {
|
||||
api := &plugintest.API{}
|
||||
api.On("KVCompareAndDelete", "test-key", []byte(`{"val-a":10}`)).Return(false, &model.AppError{})
|
||||
p := &plugin.HelpersImpl{API: api}
|
||||
|
||||
ok, err := p.KVCompareAndDeleteJSON("test-key", map[string]interface{}{
|
||||
"val-a": 10,
|
||||
})
|
||||
|
||||
api.AssertExpectations(t)
|
||||
assert.False(t, ok)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("old value nil", func(t *testing.T) {
|
||||
@@ -197,7 +241,7 @@ func TestKVCompareAndDeleteJSON(t *testing.T) {
|
||||
|
||||
api.AssertExpectations(t)
|
||||
assert.True(t, ok)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("old value non-nil", func(t *testing.T) {
|
||||
@@ -211,7 +255,7 @@ func TestKVCompareAndDeleteJSON(t *testing.T) {
|
||||
|
||||
api.AssertExpectations(t)
|
||||
assert.True(t, ok)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -225,7 +269,20 @@ func TestKVSetWithExpiryJSON(t *testing.T) {
|
||||
err := p.KVSetWithExpiryJSON("test-key", func() { return }, 100)
|
||||
|
||||
api.AssertExpectations(t)
|
||||
assert.NotNil(t, err)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("KVSetWithExpiry error", func(t *testing.T) {
|
||||
api := &plugintest.API{}
|
||||
api.On("KVSetWithExpiry", "test-key", []byte(`{"val-a":10}`), int64(100)).Return(&model.AppError{})
|
||||
p := &plugin.HelpersImpl{API: api}
|
||||
|
||||
err := p.KVSetWithExpiryJSON("test-key", map[string]interface{}{
|
||||
"val-a": float64(10),
|
||||
}, 100)
|
||||
|
||||
api.AssertExpectations(t)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("wellformed JSON", func(t *testing.T) {
|
||||
@@ -239,6 +296,6 @@ func TestKVSetWithExpiryJSON(t *testing.T) {
|
||||
}, 100)
|
||||
|
||||
api.AssertExpectations(t)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package plugintest
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// API is an autogenerated mock type for the API type
|
||||
type API struct {
|
||||
@@ -1636,6 +1638,22 @@ func (_m *API) GetTeamsUnreadForUser(userId string) ([]*model.TeamUnread, *model
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetUnsanitizedConfig provides a mock function with given fields:
|
||||
func (_m *API) GetUnsanitizedConfig() *model.Config {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 *model.Config
|
||||
if rf, ok := ret.Get(0).(func() *model.Config); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Config)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetUser provides a mock function with given fields: userId
|
||||
func (_m *API) GetUser(userId string) (*model.User, *model.AppError) {
|
||||
ret := _m.Called(userId)
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package plugintest
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// Helpers is an autogenerated mock type for the Helpers type
|
||||
type Helpers struct {
|
||||
|
||||
@@ -4,11 +4,16 @@
|
||||
|
||||
package plugintest
|
||||
|
||||
import http "net/http"
|
||||
import io "io"
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import plugin "github.com/mattermost/mattermost-server/plugin"
|
||||
import (
|
||||
io "io"
|
||||
http "net/http"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
|
||||
plugin "github.com/mattermost/mattermost-server/plugin"
|
||||
)
|
||||
|
||||
// Hooks is an autogenerated mock type for the Hooks type
|
||||
type Hooks struct {
|
||||
|
||||
53
scripts/diff-config.sh
Исполняемый файл
53
scripts/diff-config.sh
Исполняемый файл
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
jq_cmd=jq
|
||||
[[ $(type -P "$jq_cmd") ]] || {
|
||||
echo "'$jq_cmd' command line JSON processor not found";
|
||||
echo "Please install on linux with 'sudo apt-get install jq'"
|
||||
echo "Please install on mac with 'brew install jq'"
|
||||
exit 1;
|
||||
}
|
||||
|
||||
if [ -z "$FROM" ]
|
||||
then
|
||||
echo "Missing FROM version. Usage: make diff-config FROM=1.1.1 TO=2.2.2"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$TO" ]
|
||||
then
|
||||
echo "Missing TO version. Usage: make diff-config FROM=1.1.1 TO=2.2.2"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Returns the config file for a specific release
|
||||
fetch_config() {
|
||||
local url="https://releases.mattermost.com/$1/mattermost-$1-linux-amd64.tar.gz"
|
||||
curl -sf "$url" | tar -xzOf - mattermost/config/config.json | jq -S .
|
||||
}
|
||||
|
||||
echo Fetching config files
|
||||
from_config="$(fetch_config "$FROM")"
|
||||
if [ -z "$from_config" ]
|
||||
then
|
||||
echo Invalid version "$FROM"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
to_config=$(fetch_config "$TO")
|
||||
if [ -z "$to_config" ]
|
||||
then
|
||||
echo Invalid version "$TO"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo Comparing config files
|
||||
diff -y <(echo "$from_config") <(echo "$to_config")
|
||||
|
||||
# We ignore exits with 1 since it just means there's a difference, which is fine for us.
|
||||
diff_exit=$?
|
||||
if [ $diff_exit -eq 1 ]; then
|
||||
exit 0
|
||||
else
|
||||
exit $diff_exit
|
||||
fi
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
s3 "github.com/minio/minio-go"
|
||||
"github.com/minio/minio-go/pkg/credentials"
|
||||
"github.com/minio/minio-go/pkg/encrypt"
|
||||
s3 "github.com/minio/minio-go/v6"
|
||||
"github.com/minio/minio-go/v6/pkg/credentials"
|
||||
"github.com/minio/minio-go/v6/pkg/encrypt"
|
||||
|
||||
"github.com/mattermost/mattermost-server/mlog"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
|
||||
@@ -6,7 +6,6 @@ package mailservice
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net"
|
||||
@@ -139,14 +138,14 @@ func ConnectToSMTPServer(config *model.Config) (net.Conn, *model.AppError) {
|
||||
func NewSMTPClientAdvanced(conn net.Conn, hostname string, connectionInfo *SmtpConnectionInfo) (*smtp.Client, *model.AppError) {
|
||||
c, err := smtp.NewClient(conn, connectionInfo.SmtpServerName+":"+connectionInfo.SmtpPort)
|
||||
if err != nil {
|
||||
mlog.Error(fmt.Sprintf("Failed to open a connection to SMTP server %v", err))
|
||||
mlog.Error("Failed to open a connection to SMTP server", mlog.Err(err))
|
||||
return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.open_tls.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
if hostname != "" {
|
||||
err = c.Hello(hostname)
|
||||
if err != nil {
|
||||
mlog.Error(fmt.Sprintf("Failed to to set the HELO to SMTP server %v", err))
|
||||
mlog.Error("Failed to to set the HELO to SMTP server", mlog.Err(err))
|
||||
return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.helo.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
@@ -191,14 +190,14 @@ func TestConnection(config *model.Config) {
|
||||
|
||||
conn, err1 := ConnectToSMTPServer(config)
|
||||
if err1 != nil {
|
||||
mlog.Error(fmt.Sprintf("SMTP server settings do not appear to be configured properly err=%v details=%v", utils.T(err1.Message), err1.DetailedError))
|
||||
mlog.Error("SMTP server settings do not appear to be configured properly", mlog.Err(err1))
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c, err2 := NewSMTPClient(conn, config)
|
||||
if err2 != nil {
|
||||
mlog.Error(fmt.Sprintf("SMTP server settings do not appear to be configured properly err=%v details=%v", utils.T(err2.Message), err2.DetailedError))
|
||||
mlog.Error("SMTP server settings do not appear to be configured properly", mlog.Err(err2))
|
||||
return
|
||||
}
|
||||
defer c.Quit()
|
||||
@@ -240,13 +239,13 @@ func SendMailUsingConfigAdvanced(mimeTo, smtpTo string, from, replyTo mail.Addre
|
||||
}
|
||||
|
||||
func SendMail(c smtpClient, mimeTo, smtpTo string, from, replyTo mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, fileBackend filesstore.FileBackend, date time.Time) *model.AppError {
|
||||
mlog.Debug(fmt.Sprintf("sending mail to %v with subject of '%v'", smtpTo, subject))
|
||||
mlog.Debug("sending mail", mlog.String("to", smtpTo), mlog.String("subject", subject))
|
||||
|
||||
htmlMessage := "\r\n<html><body>" + htmlBody + "</body></html>"
|
||||
|
||||
txtBody, err := html2text.FromString(htmlBody)
|
||||
if err != nil {
|
||||
mlog.Warn(fmt.Sprint(err))
|
||||
mlog.Warn("Unable to convert html body to text", mlog.Err(err))
|
||||
txtBody = ""
|
||||
}
|
||||
|
||||
|
||||
@@ -217,6 +217,10 @@ func (s *LayeredStore) TotalSearchDbConnections() int {
|
||||
return s.DatabaseLayer.TotalSearchDbConnections()
|
||||
}
|
||||
|
||||
func (s *LayeredStore) CheckIntegrity() <-chan IntegrityCheckResult {
|
||||
return s.DatabaseLayer.CheckIntegrity()
|
||||
}
|
||||
|
||||
type LayeredRoleStore struct {
|
||||
*LayeredStore
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ const (
|
||||
CHANNEL_GUESTS_COUNTS_CACHE_SIZE = model.CHANNEL_CACHE_SIZE
|
||||
CHANNEL_GUESTS_COUNTS_CACHE_SEC = 1800 // 30 mins
|
||||
|
||||
CHANNEL_PINNEDPOSTS_COUNTS_CACHE_SIZE = model.CHANNEL_CACHE_SIZE
|
||||
CHANNEL_PINNEDPOSTS_COUNTS_CACHE_SEC = 1800 // 30 mins
|
||||
|
||||
CHANNEL_CACHE_SEC = 900 // 15 mins
|
||||
)
|
||||
|
||||
@@ -281,6 +284,7 @@ type publicChannel struct {
|
||||
}
|
||||
|
||||
var channelMemberCountsCache = utils.NewLru(CHANNEL_MEMBERS_COUNTS_CACHE_SIZE)
|
||||
var channelPinnedPostCountsCache = utils.NewLru(CHANNEL_PINNEDPOSTS_COUNTS_CACHE_SIZE)
|
||||
var channelGuestCountsCache = utils.NewLru(CHANNEL_GUESTS_COUNTS_CACHE_SIZE)
|
||||
var allChannelMembersForUserCache = utils.NewLru(ALL_CHANNEL_MEMBERS_FOR_USER_CACHE_SIZE)
|
||||
var allChannelMembersNotifyPropsForChannelCache = utils.NewLru(ALL_CHANNEL_MEMBERS_NOTIFY_PROPS_FOR_CHANNEL_CACHE_SIZE)
|
||||
@@ -289,6 +293,7 @@ var channelByNameCache = utils.NewLru(model.CHANNEL_CACHE_SIZE)
|
||||
|
||||
func (s SqlChannelStore) ClearCaches() {
|
||||
channelMemberCountsCache.Purge()
|
||||
channelPinnedPostCountsCache.Purge()
|
||||
channelGuestCountsCache.Purge()
|
||||
allChannelMembersForUserCache.Purge()
|
||||
allChannelMembersNotifyPropsForChannelCache.Purge()
|
||||
@@ -297,6 +302,7 @@ func (s SqlChannelStore) ClearCaches() {
|
||||
|
||||
if s.metrics != nil {
|
||||
s.metrics.IncrementMemCacheInvalidationCounter("Channel Member Counts - Purge")
|
||||
s.metrics.IncrementMemCacheInvalidationCounter("Channel Pinned Post Counts - Purge")
|
||||
s.metrics.IncrementMemCacheInvalidationCounter("All Channel Members for User - Purge")
|
||||
s.metrics.IncrementMemCacheInvalidationCounter("All Channel Members Notify Props for Channel - Purge")
|
||||
s.metrics.IncrementMemCacheInvalidationCounter("Channel - Purge")
|
||||
@@ -1626,6 +1632,66 @@ func (s SqlChannelStore) GetMemberCount(channelId string, allowFromCache bool) (
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) InvalidatePinnedPostCount(channelId string) {
|
||||
channelPinnedPostCountsCache.Remove(channelId)
|
||||
if s.metrics != nil {
|
||||
s.metrics.IncrementMemCacheInvalidationCounter("Channel Pinned Post Counts - Remove by ChannelId")
|
||||
}
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) GetPinnedPostCountFromCache(channelId string) int64 {
|
||||
if cacheItem, ok := channelPinnedPostCountsCache.Get(channelId); ok {
|
||||
if s.metrics != nil {
|
||||
s.metrics.IncrementMemCacheHitCounter("Channel Pinned Post Counts")
|
||||
}
|
||||
return cacheItem.(int64)
|
||||
}
|
||||
|
||||
if s.metrics != nil {
|
||||
s.metrics.IncrementMemCacheMissCounter("Channel Pinned Post Counts")
|
||||
}
|
||||
|
||||
count, err := s.GetPinnedPostCount(channelId, true)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) GetPinnedPostCount(channelId string, allowFromCache bool) (int64, *model.AppError) {
|
||||
if allowFromCache {
|
||||
if cacheItem, ok := channelPinnedPostCountsCache.Get(channelId); ok {
|
||||
if s.metrics != nil {
|
||||
s.metrics.IncrementMemCacheHitCounter("Channel Pinned Post Counts")
|
||||
}
|
||||
return cacheItem.(int64), nil
|
||||
}
|
||||
}
|
||||
|
||||
if s.metrics != nil {
|
||||
s.metrics.IncrementMemCacheMissCounter("Channel Pinned Post Counts")
|
||||
}
|
||||
|
||||
count, err := s.GetReplica().SelectInt(`
|
||||
SELECT count(*)
|
||||
FROM Posts
|
||||
WHERE
|
||||
IsPinned = true
|
||||
AND ChannelId = :ChannelId
|
||||
AND DeleteAt = 0`, map[string]interface{}{"ChannelId": channelId})
|
||||
|
||||
if err != nil {
|
||||
return 0, model.NewAppError("SqlChannelStore.GetPinnedPostCount", "store.sql_channel.get_pinnedpost_count.app_error", nil, "channel_id="+channelId+", "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
if allowFromCache {
|
||||
channelPinnedPostCountsCache.AddWithExpiresInSecs(channelId, count, CHANNEL_PINNEDPOSTS_COUNTS_CACHE_SEC)
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) InvalidateGuestCount(channelId string) {
|
||||
channelGuestCountsCache.Remove(channelId)
|
||||
if s.metrics != nil {
|
||||
|
||||
514
store/sqlstore/integrity.go
Обычный файл
514
store/sqlstore/integrity.go
Обычный файл
@@ -0,0 +1,514 @@
|
||||
// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost-server/mlog"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
)
|
||||
|
||||
type relationalCheckConfig struct {
|
||||
parentName string
|
||||
parentIdAttr string
|
||||
childName string
|
||||
childIdAttr string
|
||||
canParentIdBeEmpty bool
|
||||
sortRecords bool
|
||||
}
|
||||
|
||||
func getOrphanedRecords(ss *SqlSupplier, cfg relationalCheckConfig) ([]store.OrphanedRecord, error) {
|
||||
var records []store.OrphanedRecord
|
||||
|
||||
sub := ss.getQueryBuilder().
|
||||
Select("TRUE").
|
||||
From(cfg.parentName).
|
||||
Prefix("NOT EXISTS (").
|
||||
Suffix(")").
|
||||
Where(sq.Eq{"id": cfg.childName + "." + cfg.parentIdAttr})
|
||||
|
||||
main := ss.getQueryBuilder().
|
||||
Select().
|
||||
Column(cfg.parentIdAttr + " AS ParentId").
|
||||
From(cfg.childName).
|
||||
Where(sub)
|
||||
|
||||
if cfg.childIdAttr != "" {
|
||||
main = main.Column(cfg.childIdAttr + " AS ChildId")
|
||||
}
|
||||
|
||||
if cfg.canParentIdBeEmpty {
|
||||
main = main.Where(sq.NotEq{cfg.parentIdAttr: ""})
|
||||
}
|
||||
|
||||
if cfg.sortRecords {
|
||||
main = main.OrderBy(cfg.parentIdAttr)
|
||||
}
|
||||
|
||||
query, args, _ := main.ToSql()
|
||||
_, err := ss.GetMaster().Select(&records, query, args...)
|
||||
|
||||
return records, err
|
||||
}
|
||||
|
||||
func checkParentChildIntegrity(ss *SqlSupplier, config relationalCheckConfig) store.IntegrityCheckResult {
|
||||
var result store.IntegrityCheckResult
|
||||
var data store.RelationalIntegrityCheckData
|
||||
|
||||
config.sortRecords = true
|
||||
data.Records, result.Err = getOrphanedRecords(ss, config)
|
||||
if result.Err != nil {
|
||||
mlog.Error(result.Err.Error())
|
||||
return result
|
||||
}
|
||||
data.ParentName = config.parentName
|
||||
data.ChildName = config.childName
|
||||
data.ParentIdAttr = config.parentIdAttr
|
||||
data.ChildIdAttr = config.childIdAttr
|
||||
result.Data = data
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func checkChannelsCommandWebhooksIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Channels",
|
||||
parentIdAttr: "ChannelId",
|
||||
childName: "CommandWebhooks",
|
||||
childIdAttr: "Id",
|
||||
})
|
||||
}
|
||||
|
||||
func checkChannelsChannelMemberHistoryIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Channels",
|
||||
parentIdAttr: "ChannelId",
|
||||
childName: "ChannelMemberHistory",
|
||||
childIdAttr: "",
|
||||
})
|
||||
}
|
||||
|
||||
func checkChannelsChannelMembersIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Channels",
|
||||
parentIdAttr: "ChannelId",
|
||||
childName: "ChannelMembers",
|
||||
childIdAttr: "",
|
||||
})
|
||||
}
|
||||
|
||||
func checkChannelsIncomingWebhooksIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Channels",
|
||||
parentIdAttr: "ChannelId",
|
||||
childName: "IncomingWebhooks",
|
||||
childIdAttr: "Id",
|
||||
})
|
||||
}
|
||||
|
||||
func checkChannelsOutgoingWebhooksIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Channels",
|
||||
parentIdAttr: "ChannelId",
|
||||
childName: "OutgoingWebhooks",
|
||||
childIdAttr: "Id",
|
||||
})
|
||||
}
|
||||
|
||||
func checkChannelsPostsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Channels",
|
||||
parentIdAttr: "ChannelId",
|
||||
childName: "Posts",
|
||||
childIdAttr: "Id",
|
||||
})
|
||||
}
|
||||
|
||||
func checkCommandsCommandWebhooksIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Commands",
|
||||
parentIdAttr: "CommandId",
|
||||
childName: "CommandWebhooks",
|
||||
childIdAttr: "Id",
|
||||
})
|
||||
}
|
||||
|
||||
func checkPostsFileInfoIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Posts",
|
||||
parentIdAttr: "PostId",
|
||||
childName: "FileInfo",
|
||||
childIdAttr: "",
|
||||
})
|
||||
}
|
||||
|
||||
func checkPostsPostsParentIdIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Posts",
|
||||
parentIdAttr: "ParentId",
|
||||
childName: "Posts",
|
||||
childIdAttr: "Id",
|
||||
canParentIdBeEmpty: true,
|
||||
})
|
||||
}
|
||||
|
||||
func checkPostsPostsRootIdIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Posts",
|
||||
parentIdAttr: "RootId",
|
||||
childName: "Posts",
|
||||
childIdAttr: "Id",
|
||||
canParentIdBeEmpty: true,
|
||||
})
|
||||
}
|
||||
|
||||
func checkPostsReactionsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Posts",
|
||||
parentIdAttr: "PostId",
|
||||
childName: "Reactions",
|
||||
childIdAttr: "",
|
||||
})
|
||||
}
|
||||
|
||||
func checkSchemesChannelsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Schemes",
|
||||
parentIdAttr: "SchemeId",
|
||||
childName: "Channels",
|
||||
childIdAttr: "Id",
|
||||
canParentIdBeEmpty: true,
|
||||
})
|
||||
}
|
||||
|
||||
func checkSchemesTeamsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Schemes",
|
||||
parentIdAttr: "SchemeId",
|
||||
childName: "Teams",
|
||||
childIdAttr: "Id",
|
||||
canParentIdBeEmpty: true,
|
||||
})
|
||||
}
|
||||
|
||||
func checkSessionsAuditsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Sessions",
|
||||
parentIdAttr: "SessionId",
|
||||
childName: "Audits",
|
||||
childIdAttr: "Id",
|
||||
canParentIdBeEmpty: true,
|
||||
})
|
||||
}
|
||||
|
||||
func checkTeamsChannelsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Teams",
|
||||
parentIdAttr: "TeamId",
|
||||
childName: "Channels",
|
||||
childIdAttr: "Id",
|
||||
})
|
||||
}
|
||||
|
||||
func checkTeamsCommandsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Teams",
|
||||
parentIdAttr: "TeamId",
|
||||
childName: "Commands",
|
||||
childIdAttr: "Id",
|
||||
})
|
||||
}
|
||||
|
||||
func checkTeamsIncomingWebhooksIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Teams",
|
||||
parentIdAttr: "TeamId",
|
||||
childName: "IncomingWebhooks",
|
||||
childIdAttr: "Id",
|
||||
})
|
||||
}
|
||||
|
||||
func checkTeamsOutgoingWebhooksIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Teams",
|
||||
parentIdAttr: "TeamId",
|
||||
childName: "OutgoingWebhooks",
|
||||
childIdAttr: "Id",
|
||||
})
|
||||
}
|
||||
|
||||
func checkTeamsTeamMembersIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Teams",
|
||||
parentIdAttr: "TeamId",
|
||||
childName: "TeamMembers",
|
||||
childIdAttr: "",
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersAuditsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "UserId",
|
||||
childName: "Audits",
|
||||
childIdAttr: "Id",
|
||||
canParentIdBeEmpty: true,
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersCommandWebhooksIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "UserId",
|
||||
childName: "CommandWebhooks",
|
||||
childIdAttr: "Id",
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersChannelMemberHistoryIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "UserId",
|
||||
childName: "ChannelMemberHistory",
|
||||
childIdAttr: "",
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersChannelMembersIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "UserId",
|
||||
childName: "ChannelMembers",
|
||||
childIdAttr: "",
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersChannelsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "CreatorId",
|
||||
childName: "Channels",
|
||||
childIdAttr: "Id",
|
||||
canParentIdBeEmpty: true,
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersCommandsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "CreatorId",
|
||||
childName: "Commands",
|
||||
childIdAttr: "Id",
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersCompliancesIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "UserId",
|
||||
childName: "Compliances",
|
||||
childIdAttr: "Id",
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersEmojiIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "CreatorId",
|
||||
childName: "Emoji",
|
||||
childIdAttr: "Id",
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersFileInfoIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Posts",
|
||||
parentIdAttr: "CreatorId",
|
||||
childName: "FileInfo",
|
||||
childIdAttr: "",
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersIncomingWebhooksIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "UserId",
|
||||
childName: "IncomingWebhooks",
|
||||
childIdAttr: "Id",
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersOAuthAccessDataIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "UserId",
|
||||
childName: "OAuthAccessData",
|
||||
childIdAttr: "Token",
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersOAuthAppsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "CreatorId",
|
||||
childName: "OAuthApps",
|
||||
childIdAttr: "Id",
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersOAuthAuthDataIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "UserId",
|
||||
childName: "OAuthAuthData",
|
||||
childIdAttr: "Code",
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersOutgoingWebhooksIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "CreatorId",
|
||||
childName: "OutgoingWebhooks",
|
||||
childIdAttr: "Id",
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersPostsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "UserId",
|
||||
childName: "Posts",
|
||||
childIdAttr: "Id",
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersPreferencesIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "UserId",
|
||||
childName: "Preferences",
|
||||
childIdAttr: "",
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersReactionsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "UserId",
|
||||
childName: "Reactions",
|
||||
childIdAttr: "",
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersSessionsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "UserId",
|
||||
childName: "Sessions",
|
||||
childIdAttr: "Id",
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersStatusIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "UserId",
|
||||
childName: "Status",
|
||||
childIdAttr: "",
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersTeamMembersIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "UserId",
|
||||
childName: "TeamMembers",
|
||||
childIdAttr: "",
|
||||
})
|
||||
}
|
||||
|
||||
func checkUsersUserAccessTokensIntegrity(ss *SqlSupplier) store.IntegrityCheckResult {
|
||||
return checkParentChildIntegrity(ss, relationalCheckConfig{
|
||||
parentName: "Users",
|
||||
parentIdAttr: "UserId",
|
||||
childName: "UserAccessTokens",
|
||||
childIdAttr: "Id",
|
||||
})
|
||||
}
|
||||
|
||||
func checkChannelsIntegrity(ss *SqlSupplier, results chan<- store.IntegrityCheckResult) {
|
||||
results <- checkChannelsCommandWebhooksIntegrity(ss)
|
||||
results <- checkChannelsChannelMemberHistoryIntegrity(ss)
|
||||
results <- checkChannelsChannelMembersIntegrity(ss)
|
||||
results <- checkChannelsIncomingWebhooksIntegrity(ss)
|
||||
results <- checkChannelsOutgoingWebhooksIntegrity(ss)
|
||||
results <- checkChannelsPostsIntegrity(ss)
|
||||
}
|
||||
|
||||
func checkCommandsIntegrity(ss *SqlSupplier, results chan<- store.IntegrityCheckResult) {
|
||||
results <- checkCommandsCommandWebhooksIntegrity(ss)
|
||||
}
|
||||
|
||||
func checkPostsIntegrity(ss *SqlSupplier, results chan<- store.IntegrityCheckResult) {
|
||||
results <- checkPostsFileInfoIntegrity(ss)
|
||||
results <- checkPostsPostsParentIdIntegrity(ss)
|
||||
results <- checkPostsPostsRootIdIntegrity(ss)
|
||||
results <- checkPostsReactionsIntegrity(ss)
|
||||
}
|
||||
|
||||
func checkSchemesIntegrity(ss *SqlSupplier, results chan<- store.IntegrityCheckResult) {
|
||||
results <- checkSchemesChannelsIntegrity(ss)
|
||||
results <- checkSchemesTeamsIntegrity(ss)
|
||||
}
|
||||
|
||||
func checkSessionsIntegrity(ss *SqlSupplier, results chan<- store.IntegrityCheckResult) {
|
||||
results <- checkSessionsAuditsIntegrity(ss)
|
||||
}
|
||||
|
||||
func checkTeamsIntegrity(ss *SqlSupplier, results chan<- store.IntegrityCheckResult) {
|
||||
results <- checkTeamsChannelsIntegrity(ss)
|
||||
results <- checkTeamsCommandsIntegrity(ss)
|
||||
results <- checkTeamsIncomingWebhooksIntegrity(ss)
|
||||
results <- checkTeamsOutgoingWebhooksIntegrity(ss)
|
||||
results <- checkTeamsTeamMembersIntegrity(ss)
|
||||
}
|
||||
|
||||
func checkUsersIntegrity(ss *SqlSupplier, results chan<- store.IntegrityCheckResult) {
|
||||
results <- checkUsersAuditsIntegrity(ss)
|
||||
results <- checkUsersCommandWebhooksIntegrity(ss)
|
||||
results <- checkUsersChannelMemberHistoryIntegrity(ss)
|
||||
results <- checkUsersChannelMembersIntegrity(ss)
|
||||
results <- checkUsersChannelsIntegrity(ss)
|
||||
results <- checkUsersCommandsIntegrity(ss)
|
||||
results <- checkUsersCompliancesIntegrity(ss)
|
||||
results <- checkUsersEmojiIntegrity(ss)
|
||||
results <- checkUsersFileInfoIntegrity(ss)
|
||||
results <- checkUsersIncomingWebhooksIntegrity(ss)
|
||||
results <- checkUsersOAuthAccessDataIntegrity(ss)
|
||||
results <- checkUsersOAuthAppsIntegrity(ss)
|
||||
results <- checkUsersOAuthAuthDataIntegrity(ss)
|
||||
results <- checkUsersOutgoingWebhooksIntegrity(ss)
|
||||
results <- checkUsersPostsIntegrity(ss)
|
||||
results <- checkUsersPreferencesIntegrity(ss)
|
||||
results <- checkUsersReactionsIntegrity(ss)
|
||||
results <- checkUsersSessionsIntegrity(ss)
|
||||
results <- checkUsersStatusIntegrity(ss)
|
||||
results <- checkUsersTeamMembersIntegrity(ss)
|
||||
results <- checkUsersUserAccessTokensIntegrity(ss)
|
||||
}
|
||||
|
||||
func CheckRelationalIntegrity(ss *SqlSupplier, results chan<- store.IntegrityCheckResult) {
|
||||
mlog.Info("Starting relational integrity checks...")
|
||||
checkChannelsIntegrity(ss, results)
|
||||
checkCommandsIntegrity(ss, results)
|
||||
checkPostsIntegrity(ss, results)
|
||||
checkSchemesIntegrity(ss, results)
|
||||
checkSessionsIntegrity(ss, results)
|
||||
checkTeamsIntegrity(ss, results)
|
||||
checkUsersIntegrity(ss, results)
|
||||
mlog.Info("Done with relational integrity checks")
|
||||
close(results)
|
||||
}
|
||||
1539
store/sqlstore/integrity_test.go
Обычный файл
1539
store/sqlstore/integrity_test.go
Обычный файл
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
@@ -4,7 +4,6 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
@@ -251,13 +250,13 @@ func (me SqlSessionStore) Cleanup(expiryTime int64, batchSize int64) {
|
||||
|
||||
for rowsAffected > 0 {
|
||||
if sqlResult, err := me.GetMaster().Exec(query, map[string]interface{}{"ExpiresAt": expiryTime, "Limit": batchSize}); err != nil {
|
||||
mlog.Error(fmt.Sprintf("Unable to cleanup session store. err=%v", err.Error()))
|
||||
mlog.Error("Unable to cleanup session store.", mlog.Err(err))
|
||||
return
|
||||
} else {
|
||||
var rowErr error
|
||||
rowsAffected, rowErr = sqlResult.RowsAffected()
|
||||
if rowErr != nil {
|
||||
mlog.Error(fmt.Sprintf("Unable to cleanup session store. err=%v", err.Error()))
|
||||
mlog.Error("Unable to cleanup session store.", mlog.Err(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,14 +159,14 @@ func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInter
|
||||
|
||||
err := supplier.GetMaster().CreateTablesIfNotExists()
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Error creating database tables: %v", err))
|
||||
mlog.Critical("Error creating database tables.", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_CREATE_TABLE)
|
||||
}
|
||||
|
||||
err = UpgradeDatabase(supplier, model.CurrentVersion)
|
||||
if err != nil {
|
||||
mlog.Critical("Failed to upgrade database", mlog.Err(err))
|
||||
mlog.Critical("Failed to upgrade database.", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_GENERIC_FAILURE)
|
||||
}
|
||||
@@ -214,13 +214,13 @@ func (s *SqlSupplier) Next() store.LayeredStoreSupplier {
|
||||
func setupConnection(con_type string, dataSource string, settings *model.SqlSettings) *gorp.DbMap {
|
||||
db, err := dbsql.Open(*settings.DriverName, dataSource)
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to open SQL connection to err:%v", err.Error()))
|
||||
mlog.Critical("Failed to open SQL connection to err.", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_DB_OPEN)
|
||||
}
|
||||
|
||||
for i := 0; i < DB_PING_ATTEMPTS; i++ {
|
||||
mlog.Info(fmt.Sprintf("Pinging SQL %v database", con_type))
|
||||
mlog.Info("Pinging SQL", mlog.String("database", con_type))
|
||||
ctx, cancel := context.WithTimeout(context.Background(), DB_PING_TIMEOUT_SECS*time.Second)
|
||||
defer cancel()
|
||||
err = db.PingContext(ctx)
|
||||
@@ -228,11 +228,11 @@ func setupConnection(con_type string, dataSource string, settings *model.SqlSett
|
||||
break
|
||||
} else {
|
||||
if i == DB_PING_ATTEMPTS-1 {
|
||||
mlog.Critical(fmt.Sprintf("Failed to ping DB, server will exit err=%v", err))
|
||||
mlog.Critical("Failed to ping DB, server will exit.", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_PING)
|
||||
} else {
|
||||
mlog.Error(fmt.Sprintf("Failed to ping DB retrying in %v seconds err=%v", DB_PING_TIMEOUT_SECS, err))
|
||||
mlog.Error("Failed to ping DB", mlog.Err(err), mlog.Int("retrying in seconds", DB_PING_TIMEOUT_SECS))
|
||||
time.Sleep(DB_PING_TIMEOUT_SECS * time.Second)
|
||||
}
|
||||
}
|
||||
@@ -365,7 +365,7 @@ func (ss *SqlSupplier) DoesTableExist(tableName string) bool {
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to check if table exists %v", err))
|
||||
mlog.Critical("Failed to check if table exists", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_TABLE_EXISTS)
|
||||
}
|
||||
@@ -387,7 +387,7 @@ func (ss *SqlSupplier) DoesTableExist(tableName string) bool {
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to check if table exists %v", err))
|
||||
mlog.Critical("Failed to check if table exists", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_TABLE_EXISTS_MYSQL)
|
||||
}
|
||||
@@ -401,7 +401,7 @@ func (ss *SqlSupplier) DoesTableExist(tableName string) bool {
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to check if table exists %v", err))
|
||||
mlog.Critical("Failed to check if table exists", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_TABLE_EXISTS_SQLITE)
|
||||
}
|
||||
@@ -433,7 +433,7 @@ func (ss *SqlSupplier) DoesColumnExist(tableName string, columnName string) bool
|
||||
return false
|
||||
}
|
||||
|
||||
mlog.Critical(fmt.Sprintf("Failed to check if column exists %v", err))
|
||||
mlog.Critical("Failed to check if column exists", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_DOES_COLUMN_EXISTS_POSTGRES)
|
||||
}
|
||||
@@ -456,7 +456,7 @@ func (ss *SqlSupplier) DoesColumnExist(tableName string, columnName string) bool
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to check if column exists %v", err))
|
||||
mlog.Critical("Failed to check if column exists", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_DOES_COLUMN_EXISTS_MYSQL)
|
||||
}
|
||||
@@ -471,7 +471,7 @@ func (ss *SqlSupplier) DoesColumnExist(tableName string, columnName string) bool
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to check if column exists %v", err))
|
||||
mlog.Critical("Failed to check if column exists", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_DOES_COLUMN_EXISTS_SQLITE)
|
||||
}
|
||||
@@ -498,7 +498,7 @@ func (ss *SqlSupplier) DoesTriggerExist(triggerName string) bool {
|
||||
`, triggerName)
|
||||
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to check if trigger exists %v", err))
|
||||
mlog.Critical("Failed to check if trigger exists", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_GENERIC_FAILURE)
|
||||
}
|
||||
@@ -517,7 +517,7 @@ func (ss *SqlSupplier) DoesTriggerExist(triggerName string) bool {
|
||||
`, triggerName)
|
||||
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to check if trigger exists %v", err))
|
||||
mlog.Critical("Failed to check if trigger exists", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_GENERIC_FAILURE)
|
||||
}
|
||||
@@ -541,7 +541,7 @@ func (ss *SqlSupplier) CreateColumnIfNotExists(tableName string, columnName stri
|
||||
if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
|
||||
_, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + postgresColType + " DEFAULT '" + defaultValue + "'")
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to create column %v", err))
|
||||
mlog.Critical("Failed to create column", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_CREATE_COLUMN_POSTGRES)
|
||||
}
|
||||
@@ -551,7 +551,7 @@ func (ss *SqlSupplier) CreateColumnIfNotExists(tableName string, columnName stri
|
||||
} else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
|
||||
_, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + mySqlColType + " DEFAULT '" + defaultValue + "'")
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to create column %v", err))
|
||||
mlog.Critical("Failed to create column", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_CREATE_COLUMN_MYSQL)
|
||||
}
|
||||
@@ -575,7 +575,7 @@ func (ss *SqlSupplier) CreateColumnIfNotExistsNoDefault(tableName string, column
|
||||
if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
|
||||
_, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + postgresColType)
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to create column %v", err))
|
||||
mlog.Critical("Failed to create column", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_CREATE_COLUMN_POSTGRES)
|
||||
}
|
||||
@@ -585,7 +585,7 @@ func (ss *SqlSupplier) CreateColumnIfNotExistsNoDefault(tableName string, column
|
||||
} else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
|
||||
_, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + mySqlColType)
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to create column %v", err))
|
||||
mlog.Critical("Failed to create column", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_CREATE_COLUMN_MYSQL)
|
||||
}
|
||||
@@ -608,7 +608,7 @@ func (ss *SqlSupplier) RemoveColumnIfExists(tableName string, columnName string)
|
||||
|
||||
_, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " DROP COLUMN " + columnName)
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to drop column %v", err))
|
||||
mlog.Critical("Failed to drop column", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_REMOVE_COLUMN)
|
||||
}
|
||||
@@ -623,7 +623,7 @@ func (ss *SqlSupplier) RemoveTableIfExists(tableName string) bool {
|
||||
|
||||
_, err := ss.GetMaster().ExecNoTimeout("DROP TABLE " + tableName)
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to drop table %v", err))
|
||||
mlog.Critical("Failed to drop table", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_REMOVE_TABLE)
|
||||
}
|
||||
@@ -644,7 +644,7 @@ func (ss *SqlSupplier) RenameColumnIfExists(tableName string, oldColumnName stri
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to rename column %v", err))
|
||||
mlog.Critical("Failed to rename column", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_RENAME_COLUMN)
|
||||
}
|
||||
@@ -666,7 +666,7 @@ func (ss *SqlSupplier) GetMaxLengthOfColumnIfExists(tableName string, columnName
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to get max length of column %v", err))
|
||||
mlog.Critical("Failed to get max length of column", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_MAX_COLUMN)
|
||||
}
|
||||
@@ -687,7 +687,7 @@ func (ss *SqlSupplier) AlterColumnTypeIfExists(tableName string, columnName stri
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to alter column type %v", err))
|
||||
mlog.Critical("Failed to alter column type", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_ALTER_COLUMN)
|
||||
}
|
||||
@@ -736,7 +736,7 @@ func (ss *SqlSupplier) AlterColumnDefaultIfExists(tableName string, columnName s
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to alter column %s.%s default %s: %v", tableName, columnName, defaultValue, err))
|
||||
mlog.Critical("Failed to alter column", mlog.String("table", tableName), mlog.String("column", columnName), mlog.String("default value", defaultValue), mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_GENERIC_FAILURE)
|
||||
return false
|
||||
@@ -790,7 +790,7 @@ func (ss *SqlSupplier) createIndexIfNotExists(indexName string, tableName string
|
||||
|
||||
_, err := ss.GetMaster().ExecNoTimeout(query)
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to create index %v, %v", errExists, err))
|
||||
mlog.Critical("Failed to create index", mlog.Err(errExists), mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_CREATE_INDEX_POSTGRES)
|
||||
}
|
||||
@@ -798,7 +798,7 @@ func (ss *SqlSupplier) createIndexIfNotExists(indexName string, tableName string
|
||||
|
||||
count, err := ss.GetMaster().SelectInt("SELECT COUNT(0) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = ? AND index_name = ?", tableName, indexName)
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to check index %v", err))
|
||||
mlog.Critical("Failed to check index", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_CREATE_INDEX_MYSQL)
|
||||
}
|
||||
@@ -814,14 +814,14 @@ func (ss *SqlSupplier) createIndexIfNotExists(indexName string, tableName string
|
||||
|
||||
_, err = ss.GetMaster().ExecNoTimeout("CREATE " + uniqueStr + fullTextIndex + " INDEX " + indexName + " ON " + tableName + " (" + strings.Join(columnNames, ", ") + ")")
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to create index %v", err))
|
||||
mlog.Critical("Failed to create index", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_CREATE_INDEX_FULL_MYSQL)
|
||||
}
|
||||
} else if ss.DriverName() == model.DATABASE_DRIVER_SQLITE {
|
||||
_, err := ss.GetMaster().ExecNoTimeout("CREATE INDEX IF NOT EXISTS " + indexName + " ON " + tableName + " (" + strings.Join(columnNames, ", ") + ")")
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to create index %v", err))
|
||||
mlog.Critical("Failed to create index", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_CREATE_INDEX_SQLITE)
|
||||
}
|
||||
@@ -845,7 +845,7 @@ func (ss *SqlSupplier) RemoveIndexIfExists(indexName string, tableName string) b
|
||||
|
||||
_, err = ss.GetMaster().ExecNoTimeout("DROP INDEX " + indexName)
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to remove index %v", err))
|
||||
mlog.Critical("Failed to remove index", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_REMOVE_INDEX_POSTGRES)
|
||||
}
|
||||
@@ -855,7 +855,7 @@ func (ss *SqlSupplier) RemoveIndexIfExists(indexName string, tableName string) b
|
||||
|
||||
count, err := ss.GetMaster().SelectInt("SELECT COUNT(0) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = ? AND index_name = ?", tableName, indexName)
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to check index %v", err))
|
||||
mlog.Critical("Failed to check index", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_REMOVE_INDEX_MYSQL)
|
||||
}
|
||||
@@ -866,14 +866,14 @@ func (ss *SqlSupplier) RemoveIndexIfExists(indexName string, tableName string) b
|
||||
|
||||
_, err = ss.GetMaster().ExecNoTimeout("DROP INDEX " + indexName + " ON " + tableName)
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to remove index %v", err))
|
||||
mlog.Critical("Failed to remove index", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_REMOVE_INDEX_MYSQL)
|
||||
}
|
||||
} else if ss.DriverName() == model.DATABASE_DRIVER_SQLITE {
|
||||
_, err := ss.GetMaster().ExecNoTimeout("DROP INDEX IF EXISTS " + indexName)
|
||||
if err != nil {
|
||||
mlog.Critical(fmt.Sprintf("Failed to remove index %v", err))
|
||||
mlog.Critical("Failed to remove index", mlog.Err(err))
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(EXIT_REMOVE_INDEX_SQLITE)
|
||||
}
|
||||
@@ -1066,6 +1066,12 @@ func (ss *SqlSupplier) getQueryBuilder() sq.StatementBuilderType {
|
||||
return builder
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) CheckIntegrity() <-chan store.IntegrityCheckResult {
|
||||
results := make(chan store.IntegrityCheckResult)
|
||||
go CheckRelationalIntegrity(ss, results)
|
||||
return results
|
||||
}
|
||||
|
||||
type mattermConverter struct{}
|
||||
|
||||
func (me mattermConverter) ToDb(val interface{}) (interface{}, error) {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/gorp"
|
||||
@@ -141,7 +140,7 @@ func (s *SqlReactionStore) DeleteAllWithEmojiName(emojiName string) *model.AppEr
|
||||
for _, reaction := range reactions {
|
||||
if _, err := s.GetMaster().Exec(UPDATE_POST_HAS_REACTIONS_ON_DELETE_QUERY,
|
||||
map[string]interface{}{"PostId": reaction.PostId, "UpdateAt": model.GetMillis()}); err != nil {
|
||||
mlog.Warn(fmt.Sprintf("Unable to update Post.HasReactions while removing reactions post_id=%v, error=%v", reaction.PostId, err.Error()))
|
||||
mlog.Warn("Unable to update Post.HasReactions while removing reactions", mlog.String("post_id", reaction.PostId), mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -381,6 +381,21 @@ func (s SqlTeamStore) GetAllPrivateTeamListing() ([]*model.Team, *model.AppError
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) GetAllPublicTeamPageListing(offset int, limit int) ([]*model.Team, *model.AppError) {
|
||||
query := "SELECT * FROM Teams WHERE AllowOpenInvite = 1 ORDER BY DisplayName LIMIT :Limit OFFSET :Offset"
|
||||
|
||||
if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
|
||||
query = "SELECT * FROM Teams WHERE AllowOpenInvite = true ORDER BY DisplayName LIMIT :Limit OFFSET :Offset"
|
||||
}
|
||||
|
||||
var data []*model.Team
|
||||
if _, err := s.GetReplica().Select(&data, query, map[string]interface{}{"Offset": offset, "Limit": limit}); err != nil {
|
||||
return nil, model.NewAppError("SqlTeamStore.GetAllPrivateTeamListing", "store.sql_team.get_all_private_team_listing.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) GetAllPrivateTeamPageListing(offset int, limit int) ([]*model.Team, *model.AppError) {
|
||||
query := "SELECT * FROM Teams WHERE AllowOpenInvite = 0 ORDER BY DisplayName LIMIT :Limit OFFSET :Offset"
|
||||
|
||||
@@ -433,6 +448,35 @@ func (s SqlTeamStore) PermanentDelete(teamId string) *model.AppError {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) AnalyticsPublicTeamCount() (int64, *model.AppError) {
|
||||
|
||||
c, err := s.GetReplica().SelectInt("SELECT COUNT(*) FROM Teams WHERE DeleteAt = 0 AND AllowOpenInvite = 1", map[string]interface{}{})
|
||||
|
||||
if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
|
||||
c, err = s.GetReplica().SelectInt("SELECT COUNT(*) FROM Teams WHERE DeleteAt = 0 AND AllowOpenInvite = true", map[string]interface{}{})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return int64(0), model.NewAppError("SqlTeamStore.AnalyticsPublicTeamCount", "store.sql_team.analytics_public_team_count.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) AnalyticsPrivateTeamCount() (int64, *model.AppError) {
|
||||
c, err := s.GetReplica().SelectInt("SELECT COUNT(*) FROM Teams WHERE DeleteAt = 0 AND AllowOpenInvite = 0", map[string]interface{}{})
|
||||
|
||||
if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
|
||||
c, err = s.GetReplica().SelectInt("SELECT COUNT(*) FROM Teams WHERE DeleteAt = 0 AND AllowOpenInvite = false", map[string]interface{}{})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return int64(0), model.NewAppError("SqlTeamStore.AnalyticsPrivateTeamCount", "store.sql_team.analytics_private_team_count.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (s SqlTeamStore) AnalyticsTeamCount() (int64, *model.AppError) {
|
||||
c, err := s.GetReplica().SelectInt("SELECT COUNT(*) FROM Teams WHERE DeleteAt = 0", map[string]interface{}{})
|
||||
|
||||
|
||||
@@ -729,11 +729,9 @@ func UpgradeDatabaseToVersion514(sqlStore SqlStore) {
|
||||
}
|
||||
|
||||
func UpgradeDatabaseToVersion515(sqlStore SqlStore) {
|
||||
// TODO: Uncomment following condition when version 5.15.0 is released
|
||||
// if shouldPerformUpgrade(sqlStore, VERSION_5_14_0, VERSION_5_15_0) {
|
||||
|
||||
// saveSchemaVersion(sqlStore, VERSION_5_15_0)
|
||||
// }
|
||||
if shouldPerformUpgrade(sqlStore, VERSION_5_14_0, VERSION_5_15_0) {
|
||||
saveSchemaVersion(sqlStore, VERSION_5_15_0)
|
||||
}
|
||||
}
|
||||
|
||||
func UpgradeDatabaseToVersion516(sqlStore SqlStore) {
|
||||
|
||||
@@ -55,6 +55,7 @@ type Store interface {
|
||||
TotalMasterDbConnections() int
|
||||
TotalReadDbConnections() int
|
||||
TotalSearchDbConnections() int
|
||||
CheckIntegrity() <-chan IntegrityCheckResult
|
||||
}
|
||||
|
||||
type TeamStore interface {
|
||||
@@ -69,12 +70,15 @@ type TeamStore interface {
|
||||
GetAllPage(offset int, limit int) ([]*model.Team, *model.AppError)
|
||||
GetAllPrivateTeamListing() ([]*model.Team, *model.AppError)
|
||||
GetAllPrivateTeamPageListing(offset int, limit int) ([]*model.Team, *model.AppError)
|
||||
GetAllPublicTeamPageListing(offset int, limit int) ([]*model.Team, *model.AppError)
|
||||
GetAllTeamListing() ([]*model.Team, *model.AppError)
|
||||
GetAllTeamPageListing(offset int, limit int) ([]*model.Team, *model.AppError)
|
||||
GetTeamsByUserId(userId string) ([]*model.Team, *model.AppError)
|
||||
GetByInviteId(inviteId string) (*model.Team, *model.AppError)
|
||||
PermanentDelete(teamId string) *model.AppError
|
||||
AnalyticsTeamCount() (int64, *model.AppError)
|
||||
AnalyticsPublicTeamCount() (int64, *model.AppError)
|
||||
AnalyticsPrivateTeamCount() (int64, *model.AppError)
|
||||
SaveMember(member *model.TeamMember, maxUsersPerTeam int) (*model.TeamMember, *model.AppError)
|
||||
UpdateMember(member *model.TeamMember) (*model.TeamMember, *model.AppError)
|
||||
GetMember(teamId string, userId string) (*model.TeamMember, *model.AppError)
|
||||
@@ -147,6 +151,9 @@ type ChannelStore interface {
|
||||
InvalidateMemberCount(channelId string)
|
||||
GetMemberCountFromCache(channelId string) int64
|
||||
GetMemberCount(channelId string, allowFromCache bool) (int64, *model.AppError)
|
||||
InvalidatePinnedPostCount(channelId string)
|
||||
GetPinnedPostCountFromCache(channelId string) int64
|
||||
GetPinnedPostCount(channelId string, allowFromCache bool) (int64, *model.AppError)
|
||||
InvalidateGuestCount(channelId string)
|
||||
GetGuestCountFromCache(channelId string) int64
|
||||
GetGuestCount(channelId string, allowFromCache bool) (int64, *model.AppError)
|
||||
@@ -628,3 +635,21 @@ type UserGetByIdsOpts struct {
|
||||
// Since filters the users based on their UpdateAt timestamp.
|
||||
Since int64
|
||||
}
|
||||
|
||||
type OrphanedRecord struct {
|
||||
ParentId string
|
||||
ChildId string
|
||||
}
|
||||
|
||||
type RelationalIntegrityCheckData struct {
|
||||
ParentName string
|
||||
ChildName string
|
||||
ParentIdAttr string
|
||||
ChildIdAttr string
|
||||
Records []OrphanedRecord
|
||||
}
|
||||
|
||||
type IntegrityCheckResult struct {
|
||||
Data interface{}
|
||||
Err error
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlSupplier) {
|
||||
t.Run("SearchGroupChannels", func(t *testing.T) { testChannelStoreSearchGroupChannels(t, ss) })
|
||||
t.Run("AnalyticsDeletedTypeCount", func(t *testing.T) { testChannelStoreAnalyticsDeletedTypeCount(t, ss) })
|
||||
t.Run("GetPinnedPosts", func(t *testing.T) { testChannelStoreGetPinnedPosts(t, ss) })
|
||||
t.Run("GetPinnedPostCount", func(t *testing.T) { testChannelStoreGetPinnedPostCount(t, ss) })
|
||||
t.Run("MaxChannelsPerTeam", func(t *testing.T) { testChannelStoreMaxChannelsPerTeam(t, ss) })
|
||||
t.Run("GetChannelsByScheme", func(t *testing.T) { testChannelStoreGetChannelsByScheme(t, ss) })
|
||||
t.Run("MigrateChannelMembers", func(t *testing.T) { testChannelStoreMigrateChannelMembers(t, ss) })
|
||||
@@ -3379,6 +3380,78 @@ func testChannelStoreGetPinnedPosts(t *testing.T, ss store.Store) {
|
||||
}
|
||||
}
|
||||
|
||||
func testChannelStoreGetPinnedPostCount(t *testing.T, ss store.Store) {
|
||||
ch1 := &model.Channel{
|
||||
TeamId: model.NewId(),
|
||||
DisplayName: "Name",
|
||||
Name: "zz" + model.NewId() + "b",
|
||||
Type: model.CHANNEL_OPEN,
|
||||
}
|
||||
|
||||
o1, err := ss.Channel().Save(ch1, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = ss.Post().Save(&model.Post{
|
||||
UserId: model.NewId(),
|
||||
ChannelId: o1.Id,
|
||||
Message: "test",
|
||||
IsPinned: true,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = ss.Post().Save(&model.Post{
|
||||
UserId: model.NewId(),
|
||||
ChannelId: o1.Id,
|
||||
Message: "test",
|
||||
IsPinned: true,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
if count, errGet := ss.Channel().GetPinnedPostCount(o1.Id, true); errGet != nil {
|
||||
t.Fatal(errGet)
|
||||
} else if count != 2 {
|
||||
t.Fatal("didn't return right count")
|
||||
}
|
||||
|
||||
if ss.Channel().GetPinnedPostCountFromCache(o1.Id) != 2 {
|
||||
t.Fatal("should have saved 2 pinned post count ")
|
||||
}
|
||||
|
||||
ch2 := &model.Channel{
|
||||
TeamId: model.NewId(),
|
||||
DisplayName: "Name",
|
||||
Name: "zz" + model.NewId() + "b",
|
||||
Type: model.CHANNEL_OPEN,
|
||||
}
|
||||
|
||||
o2, err := ss.Channel().Save(ch2, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = ss.Post().Save(&model.Post{
|
||||
UserId: model.NewId(),
|
||||
ChannelId: o2.Id,
|
||||
Message: "test",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = ss.Post().Save(&model.Post{
|
||||
UserId: model.NewId(),
|
||||
ChannelId: o2.Id,
|
||||
Message: "test",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
if count, errGet := ss.Channel().GetPinnedPostCount(o2.Id, true); errGet != nil {
|
||||
t.Fatal(errGet)
|
||||
} else if count != 0 {
|
||||
t.Fatal("should return 0")
|
||||
}
|
||||
|
||||
if ss.Channel().GetPinnedPostCountFromCache(o2.Id) != 0 {
|
||||
t.Fatal("should have saved 0 pinned post count ")
|
||||
}
|
||||
}
|
||||
|
||||
func testChannelStoreMaxChannelsPerTeam(t *testing.T, ss store.Store) {
|
||||
channel := &model.Channel{
|
||||
TeamId: model.NewId(),
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// AuditStore is an autogenerated mock type for the AuditStore type
|
||||
type AuditStore struct {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// BotStore is an autogenerated mock type for the BotStore type
|
||||
type BotStore struct {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// ChannelMemberHistoryStore is an autogenerated mock type for the ChannelMemberHistoryStore type
|
||||
type ChannelMemberHistoryStore struct {
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import store "github.com/mattermost/mattermost-server/store"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
store "github.com/mattermost/mattermost-server/store"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// ChannelStore is an autogenerated mock type for the ChannelStore type
|
||||
type ChannelStore struct {
|
||||
@@ -993,6 +995,43 @@ func (_m *ChannelStore) GetMoreChannels(teamId string, userId string, offset int
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetPinnedPostCount provides a mock function with given fields: channelId, allowFromCache
|
||||
func (_m *ChannelStore) GetPinnedPostCount(channelId string, allowFromCache bool) (int64, *model.AppError) {
|
||||
ret := _m.Called(channelId, allowFromCache)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(string, bool) int64); ok {
|
||||
r0 = rf(channelId, allowFromCache)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(1).(func(string, bool) *model.AppError); ok {
|
||||
r1 = rf(channelId, allowFromCache)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetPinnedPostCountFromCache provides a mock function with given fields: channelId
|
||||
func (_m *ChannelStore) GetPinnedPostCountFromCache(channelId string) int64 {
|
||||
ret := _m.Called(channelId)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(string) int64); ok {
|
||||
r0 = rf(channelId)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetPinnedPosts provides a mock function with given fields: channelId
|
||||
func (_m *ChannelStore) GetPinnedPosts(channelId string) (*model.PostList, *model.AppError) {
|
||||
ret := _m.Called(channelId)
|
||||
@@ -1139,6 +1178,11 @@ func (_m *ChannelStore) InvalidateMemberCount(channelId string) {
|
||||
_m.Called(channelId)
|
||||
}
|
||||
|
||||
// InvalidatePinnedPostCount provides a mock function with given fields: channelId
|
||||
func (_m *ChannelStore) InvalidatePinnedPostCount(channelId string) {
|
||||
_m.Called(channelId)
|
||||
}
|
||||
|
||||
// IsUserInChannelUseCache provides a mock function with given fields: userId, channelId
|
||||
func (_m *ChannelStore) IsUserInChannelUseCache(userId string, channelId string) bool {
|
||||
ret := _m.Called(userId, channelId)
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// ClusterDiscoveryStore is an autogenerated mock type for the ClusterDiscoveryStore type
|
||||
type ClusterDiscoveryStore struct {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// CommandStore is an autogenerated mock type for the CommandStore type
|
||||
type CommandStore struct {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// CommandWebhookStore is an autogenerated mock type for the CommandWebhookStore type
|
||||
type CommandWebhookStore struct {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// ComplianceStore is an autogenerated mock type for the ComplianceStore type
|
||||
type ComplianceStore struct {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// EmojiStore is an autogenerated mock type for the EmojiStore type
|
||||
type EmojiStore struct {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// FileInfoStore is an autogenerated mock type for the FileInfoStore type
|
||||
type FileInfoStore struct {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// GroupStore is an autogenerated mock type for the GroupStore type
|
||||
type GroupStore struct {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// JobStore is an autogenerated mock type for the JobStore type
|
||||
type JobStore struct {
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import context "context"
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import store "github.com/mattermost/mattermost-server/store"
|
||||
import (
|
||||
context "context"
|
||||
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
store "github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
// LayeredStoreDatabaseLayer is an autogenerated mock type for the LayeredStoreDatabaseLayer type
|
||||
type LayeredStoreDatabaseLayer struct {
|
||||
@@ -78,6 +82,22 @@ func (_m *LayeredStoreDatabaseLayer) ChannelMemberHistory() store.ChannelMemberH
|
||||
return r0
|
||||
}
|
||||
|
||||
// CheckIntegrity provides a mock function with given fields:
|
||||
func (_m *LayeredStoreDatabaseLayer) CheckIntegrity() <-chan store.IntegrityCheckResult {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 <-chan store.IntegrityCheckResult
|
||||
if rf, ok := ret.Get(0).(func() <-chan store.IntegrityCheckResult); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(<-chan store.IntegrityCheckResult)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Close provides a mock function with given fields:
|
||||
func (_m *LayeredStoreDatabaseLayer) Close() {
|
||||
_m.Called()
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import context "context"
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import store "github.com/mattermost/mattermost-server/store"
|
||||
import (
|
||||
context "context"
|
||||
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
store "github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
// LayeredStoreSupplier is an autogenerated mock type for the LayeredStoreSupplier type
|
||||
type LayeredStoreSupplier struct {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// LicenseStore is an autogenerated mock type for the LicenseStore type
|
||||
type LicenseStore struct {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// LinkMetadataStore is an autogenerated mock type for the LinkMetadataStore type
|
||||
type LinkMetadataStore struct {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// OAuthStore is an autogenerated mock type for the OAuthStore type
|
||||
type OAuthStore struct {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// PluginStore is an autogenerated mock type for the PluginStore type
|
||||
type PluginStore struct {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// PostStore is an autogenerated mock type for the PostStore type
|
||||
type PostStore struct {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// PreferenceStore is an autogenerated mock type for the PreferenceStore type
|
||||
type PreferenceStore struct {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// ReactionStore is an autogenerated mock type for the ReactionStore type
|
||||
type ReactionStore struct {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// RoleStore is an autogenerated mock type for the RoleStore type
|
||||
type RoleStore struct {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import model "github.com/mattermost/mattermost-server/model"
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// SchemeStore is an autogenerated mock type for the SchemeStore type
|
||||
type SchemeStore struct {
|
||||
|
||||
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Ссылка в новой задаче
Block a user