Merge branch 'master' into advanced-permissions-phase-1

Этот коммит содержится в:
George Goldberg
2018-03-13 13:36:23 +00:00
родитель b66e4bc932 f2d26801b9
Коммит fadcdd271a
167 изменённых файлов: 5008 добавлений и 3043 удалений

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

@@ -56,7 +56,7 @@ GO_LINKER_FLAGS ?= -ldflags \
# GOOS/GOARCH of the build host, used to determine whether we're cross-compiling or not # GOOS/GOARCH of the build host, used to determine whether we're cross-compiling or not
BUILDER_GOOS_GOARCH="$(shell $(GO) env GOOS)_$(shell $(GO) env GOARCH)" BUILDER_GOOS_GOARCH="$(shell $(GO) env GOOS)_$(shell $(GO) env GOARCH)"
PLATFORM_FILES=$(shell ls -1 ./cmd/platform/*.go | grep -v _test.go) PLATFORM_FILES="./main.go"
# Output paths # Output paths
DIST_ROOT=dist DIST_ROOT=dist
@@ -118,7 +118,7 @@ start-docker: ## Starts the docker containers for local development.
@if [ $(shell docker ps -a | grep -ci mattermost-inbucket) -eq 0 ]; then \ @if [ $(shell docker ps -a | grep -ci mattermost-inbucket) -eq 0 ]; then \
echo starting mattermost-inbucket; \ echo starting mattermost-inbucket; \
docker run --name mattermost-inbucket -p 9000:10080 -p 2500:10025 -d jhillyerd/inbucket:latest > /dev/null; \ docker run --name mattermost-inbucket -p 9000:10080 -p 2500:10025 -d jhillyerd/inbucket:release-1.2.0 > /dev/null; \
elif [ $(shell docker ps | grep -ci mattermost-inbucket) -eq 0 ]; then \ elif [ $(shell docker ps | grep -ci mattermost-inbucket) -eq 0 ]; then \
echo restarting mattermost-inbucket; \ echo restarting mattermost-inbucket; \
docker start mattermost-inbucket > /dev/null; \ docker start mattermost-inbucket > /dev/null; \

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

@@ -7,10 +7,10 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"github.com/avct/uasurfer"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mssola/user_agent"
) )
func (api *API) InitAdmin() { func (api *API) InitAdmin() {
@@ -201,12 +201,11 @@ func downloadComplianceReport(c *Context, w http.ResponseWriter, r *http.Request
w.Header().Del("Content-Type") // Content-Type will be set automatically by the http writer w.Header().Del("Content-Type") // Content-Type will be set automatically by the http writer
// attach extra headers to trigger a download on IE, Edge, and Safari // attach extra headers to trigger a download on IE, Edge, and Safari
ua := user_agent.New(r.UserAgent()) ua := uasurfer.Parse(r.UserAgent())
bname, _ := ua.Browser()
w.Header().Set("Content-Disposition", "attachment;filename=\""+job.JobName()+".zip\"") w.Header().Set("Content-Disposition", "attachment;filename=\""+job.JobName()+".zip\"")
if bname == "Edge" || bname == "Internet Explorer" || bname == "Safari" { if ua.Browser.Name == uasurfer.BrowserIE || ua.Browser.Name == uasurfer.BrowserSafari {
// trim off anything before the final / so we just get the file's name // trim off anything before the final / so we just get the file's name
w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Type", "application/octet-stream")
} }

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

@@ -11,7 +11,6 @@ import (
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/store"
"github.com/mattermost/mattermost-server/store/sqlstore"
) )
func TestCreateChannel(t *testing.T) { func TestCreateChannel(t *testing.T) {
@@ -345,7 +344,7 @@ func TestUpdateChannel(t *testing.T) {
th.MakeUserChannelUser(th.BasicUser, channel2) th.MakeUserChannelUser(th.BasicUser, channel2)
th.MakeUserChannelUser(th.BasicUser, channel3) th.MakeUserChannelUser(th.BasicUser, channel3)
sqlstore.ClearChannelCaches() th.App.Srv.Store.Channel().ClearCaches()
if _, err := Client.UpdateChannel(channel2); err == nil { if _, err := Client.UpdateChannel(channel2); err == nil {
t.Fatal("should have errored not channel admin") t.Fatal("should have errored not channel admin")
@@ -356,7 +355,7 @@ func TestUpdateChannel(t *testing.T) {
th.MakeUserChannelAdmin(th.BasicUser, channel2) th.MakeUserChannelAdmin(th.BasicUser, channel2)
th.MakeUserChannelAdmin(th.BasicUser, channel3) th.MakeUserChannelAdmin(th.BasicUser, channel3)
sqlstore.ClearChannelCaches() th.App.Srv.Store.Channel().ClearCaches()
if _, err := Client.UpdateChannel(channel2); err != nil { if _, err := Client.UpdateChannel(channel2); err != nil {
t.Fatal(err) t.Fatal(err)
@@ -508,7 +507,7 @@ func TestUpdateChannelHeader(t *testing.T) {
th.MakeUserChannelUser(th.BasicUser, channel2) th.MakeUserChannelUser(th.BasicUser, channel2)
th.MakeUserChannelUser(th.BasicUser, channel3) th.MakeUserChannelUser(th.BasicUser, channel3)
sqlstore.ClearChannelCaches() th.App.Srv.Store.Channel().ClearCaches()
if _, err := Client.UpdateChannelHeader(data2); err == nil { if _, err := Client.UpdateChannelHeader(data2); err == nil {
t.Fatal("should have errored not channel admin") t.Fatal("should have errored not channel admin")
@@ -519,7 +518,7 @@ func TestUpdateChannelHeader(t *testing.T) {
th.MakeUserChannelAdmin(th.BasicUser, channel2) th.MakeUserChannelAdmin(th.BasicUser, channel2)
th.MakeUserChannelAdmin(th.BasicUser, channel3) th.MakeUserChannelAdmin(th.BasicUser, channel3)
sqlstore.ClearChannelCaches() th.App.Srv.Store.Channel().ClearCaches()
if _, err := Client.UpdateChannelHeader(data2); err != nil { if _, err := Client.UpdateChannelHeader(data2); err != nil {
t.Fatal(err) t.Fatal(err)
@@ -629,7 +628,7 @@ func TestUpdateChannelPurpose(t *testing.T) {
th.MakeUserChannelUser(th.BasicUser, channel2) th.MakeUserChannelUser(th.BasicUser, channel2)
th.MakeUserChannelUser(th.BasicUser, channel3) th.MakeUserChannelUser(th.BasicUser, channel3)
sqlstore.ClearChannelCaches() th.App.Srv.Store.Channel().ClearCaches()
if _, err := Client.UpdateChannelPurpose(data2); err == nil { if _, err := Client.UpdateChannelPurpose(data2); err == nil {
t.Fatal("should have errored not channel admin") t.Fatal("should have errored not channel admin")
@@ -640,7 +639,7 @@ func TestUpdateChannelPurpose(t *testing.T) {
th.MakeUserChannelAdmin(th.BasicUser, channel2) th.MakeUserChannelAdmin(th.BasicUser, channel2)
th.MakeUserChannelAdmin(th.BasicUser, channel3) th.MakeUserChannelAdmin(th.BasicUser, channel3)
sqlstore.ClearChannelCaches() th.App.Srv.Store.Channel().ClearCaches()
if _, err := Client.UpdateChannelPurpose(data2); err != nil { if _, err := Client.UpdateChannelPurpose(data2); err != nil {
t.Fatal(err) t.Fatal(err)
@@ -1154,7 +1153,7 @@ func TestDeleteChannel(t *testing.T) {
th.MakeUserChannelAdmin(th.BasicUser, channel2) th.MakeUserChannelAdmin(th.BasicUser, channel2)
th.MakeUserChannelAdmin(th.BasicUser, channel3) th.MakeUserChannelAdmin(th.BasicUser, channel3)
sqlstore.ClearChannelCaches() th.App.Srv.Store.Channel().ClearCaches()
if _, err := Client.DeleteChannel(channel2.Id); err != nil { if _, err := Client.DeleteChannel(channel2.Id); err != nil {
t.Fatal(err) t.Fatal(err)

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

@@ -364,10 +364,6 @@ func NewInvalidParamError(where string, name string) *model.AppError {
return err return err
} }
func (c *Context) SetUnknownError(where string, details string) {
c.Err = model.NewAppError(where, "api.context.unknown.app_error", nil, details, http.StatusInternalServerError)
}
func (c *Context) SetPermissionError(permission *model.Permission) { func (c *Context) SetPermissionError(permission *model.Permission) {
c.Err = model.NewAppError("Permissions", "api.context.permissions.app_error", nil, "userId="+c.Session.UserId+", "+"permission="+permission.Id, http.StatusForbidden) c.Err = model.NewAppError("Permissions", "api.context.permissions.app_error", nil, "userId="+c.Session.UserId+", "+"permission="+permission.Id, http.StatusForbidden)
} }
@@ -387,11 +383,6 @@ func (c *Context) SetSiteURLHeader(url string) {
c.siteURLHeader = strings.TrimRight(url, "/") c.siteURLHeader = strings.TrimRight(url, "/")
} }
// TODO see where these are used
func (c *Context) GetTeamURLFromTeam(team *model.Team) string {
return c.GetSiteURLHeader() + "/" + team.Name
}
func (c *Context) GetTeamURL() string { func (c *Context) GetTeamURL() string {
if !c.teamURLValid { if !c.teamURLValid {
c.SetTeamURLFromSession() c.SetTeamURLFromSession()
@@ -406,10 +397,6 @@ func (c *Context) GetSiteURLHeader() string {
return c.siteURLHeader return c.siteURLHeader
} }
func (c *Context) GetCurrentTeamMember() *model.TeamMember {
return c.Session.GetTeamByTeamId(c.TeamId)
}
func (c *Context) HandleEtag(etag string, routeName string, w http.ResponseWriter, r *http.Request) bool { func (c *Context) HandleEtag(etag string, routeName string, w http.ResponseWriter, r *http.Request) bool {
metrics := c.App.Metrics metrics := c.App.Metrics
if et := r.Header.Get(model.HEADER_ETAG_CLIENT); len(etag) > 0 { if et := r.Header.Get(model.HEADER_ETAG_CLIENT); len(etag) > 0 {

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

@@ -136,10 +136,7 @@ func saveIsPinnedPost(c *Context, w http.ResponseWriter, r *http.Request, isPinn
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", rpost.ChannelId, "", nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", rpost.ChannelId, "", nil)
message.Add("post", c.App.PostWithProxyAddedToImageURLs(rpost).ToJson()) message.Add("post", c.App.PostWithProxyAddedToImageURLs(rpost).ToJson())
c.App.Publish(message)
c.App.Go(func() {
c.App.Publish(message)
})
c.App.InvalidateCacheForChannelPosts(rpost.ChannelId) c.App.InvalidateCacheForChannelPosts(rpost.ChannelId)

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

@@ -227,7 +227,7 @@ func TestWebSocketEvent(t *testing.T) {
} }
evt2 := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_TYPING, "", "somerandomid", "", nil) evt2 := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_TYPING, "", "somerandomid", "", nil)
go th.App.Publish(evt2) th.App.Publish(evt2)
time.Sleep(300 * time.Millisecond) time.Sleep(300 * time.Millisecond)
eventHit = false eventHit = false

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

@@ -468,6 +468,22 @@ func (me *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) {
utils.EnableDebugLogForTest() utils.EnableDebugLogForTest()
} }
func (me *TestHelper) AddUserToChannel(user *model.User, channel *model.Channel) *model.ChannelMember {
utils.DisableDebugLogForTest()
member, err := me.App.AddUserToChannel(user, channel)
if err != nil {
l4g.Error(err.Error())
l4g.Close()
time.Sleep(time.Second)
panic(err)
}
utils.EnableDebugLogForTest()
return member
}
func (me *TestHelper) GenerateTestEmail() string { func (me *TestHelper) GenerateTestEmail() string {
if me.App.Config().EmailSettings.SMTPServer != "dockerhost" && os.Getenv("CI_INBUCKET_PORT") == "" { if me.App.Config().EmailSettings.SMTPServer != "dockerhost" && os.Getenv("CI_INBUCKET_PORT") == "" {
return strings.ToLower("success+" + model.NewId() + "@simulator.amazonses.com") return strings.ToLower("success+" + model.NewId() + "@simulator.amazonses.com")
@@ -511,18 +527,6 @@ func CheckUserSanitization(t *testing.T, user *model.User) {
} }
} }
func CheckTeamSanitization(t *testing.T, team *model.Team) {
t.Helper()
if team.Email != "" {
t.Fatal("email wasn't blank")
}
if team.AllowedDomains != "" {
t.Fatal("'allowed domains' wasn't blank")
}
}
func CheckEtag(t *testing.T, data interface{}, resp *model.Response) { func CheckEtag(t *testing.T, data interface{}, resp *model.Response) {
t.Helper() t.Helper()
@@ -670,21 +674,6 @@ func CheckInternalErrorStatus(t *testing.T, resp *model.Response) {
} }
} }
func CheckPayLoadTooLargeStatus(t *testing.T, resp *model.Response) {
t.Helper()
if resp.Error == nil {
t.Fatal("should have errored with status:" + strconv.Itoa(http.StatusRequestEntityTooLarge))
return
}
if resp.StatusCode != http.StatusRequestEntityTooLarge {
t.Log("actual: " + strconv.Itoa(resp.StatusCode))
t.Log("expected: " + strconv.Itoa(http.StatusRequestEntityTooLarge))
t.Fatal("wrong status code")
}
}
func readTestFile(name string) ([]byte, error) { func readTestFile(name string) ([]byte, error) {
path, _ := utils.FindDir("tests") path, _ := utils.FindDir("tests")
file, err := os.Open(path + "/" + name) file, err := os.Open(path + "/" + name)

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

@@ -13,7 +13,6 @@ import (
"testing" "testing"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store/sqlstore"
) )
func TestCreateChannel(t *testing.T) { func TestCreateChannel(t *testing.T) {
@@ -874,7 +873,7 @@ func TestDeleteChannel(t *testing.T) {
// successful delete by channel admin // successful delete by channel admin
th.MakeUserChannelAdmin(user, publicChannel6) th.MakeUserChannelAdmin(user, publicChannel6)
th.MakeUserChannelAdmin(user, privateChannel7) th.MakeUserChannelAdmin(user, privateChannel7)
sqlstore.ClearChannelCaches() th.App.Srv.Store.Channel().ClearCaches()
_, resp = Client.DeleteChannel(publicChannel6.Id) _, resp = Client.DeleteChannel(publicChannel6.Id)
CheckNoError(t, resp) CheckNoError(t, resp)

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

@@ -7,8 +7,8 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"github.com/avct/uasurfer"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mssola/user_agent"
) )
func (api *API) InitCompliance() { func (api *API) InitCompliance() {
@@ -108,12 +108,11 @@ func downloadComplianceReport(c *Context, w http.ResponseWriter, r *http.Request
w.Header().Del("Content-Type") // Content-Type will be set automatically by the http writer w.Header().Del("Content-Type") // Content-Type will be set automatically by the http writer
// attach extra headers to trigger a download on IE, Edge, and Safari // attach extra headers to trigger a download on IE, Edge, and Safari
ua := user_agent.New(r.UserAgent()) ua := uasurfer.Parse(r.UserAgent())
bname, _ := ua.Browser()
w.Header().Set("Content-Disposition", "attachment;filename=\""+job.JobName()+".zip\"") w.Header().Set("Content-Disposition", "attachment;filename=\""+job.JobName()+".zip\"")
if bname == "Edge" || bname == "Internet Explorer" || bname == "Safari" { if ua.Browser.Name == uasurfer.BrowserIE || ua.Browser.Name == uasurfer.BrowserSafari {
// trim off anything before the final / so we just get the file's name // trim off anything before the final / so we just get the file's name
w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Type", "application/octet-stream")
} }

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

@@ -8,7 +8,6 @@ import (
"io" "io"
"net/http" "net/http"
"runtime" "runtime"
"strconv"
l4g "github.com/alecthomas/log4go" l4g "github.com/alecthomas/log4go"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
@@ -247,14 +246,7 @@ func getClientConfig(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
respCfg := map[string]string{} w.Write([]byte(model.MapToJson(c.App.ClientConfigWithNoAccounts())))
for k, v := range c.App.ClientConfig() {
respCfg[k] = v
}
respCfg["NoAccounts"] = strconv.FormatBool(c.App.IsFirstUserAccount())
w.Write([]byte(model.MapToJson(respCfg)))
} }
func getClientLicense(c *Context, w http.ResponseWriter, r *http.Request) { func getClientLicense(c *Context, w http.ResponseWriter, r *http.Request) {

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

@@ -290,16 +290,21 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if sort != "" && sort != "last_activity_at" && sort != "create_at" { if sort != "" && sort != "last_activity_at" && sort != "create_at" && sort != "status" {
c.SetInvalidUrlParam("sort") c.SetInvalidUrlParam("sort")
return return
} }
// Currently only supports sorting on a team // Currently only supports sorting on a team
// or sort="status" on inChannelId
if (sort == "last_activity_at" || sort == "create_at") && (inTeamId == "" || notInTeamId != "" || inChannelId != "" || notInChannelId != "" || withoutTeam != "") { if (sort == "last_activity_at" || sort == "create_at") && (inTeamId == "" || notInTeamId != "" || inChannelId != "" || notInChannelId != "" || withoutTeam != "") {
c.SetInvalidUrlParam("sort") c.SetInvalidUrlParam("sort")
return return
} }
if sort == "status" && inChannelId == "" {
c.SetInvalidUrlParam("sort")
return
}
var profiles []*model.User var profiles []*model.User
var err *model.AppError var err *model.AppError
@@ -355,8 +360,11 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
c.SetPermissionError(model.PERMISSION_READ_CHANNEL) c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
return return
} }
if sort == "status" {
profiles, err = c.App.GetUsersInChannelPage(inChannelId, c.Params.Page, c.Params.PerPage, c.IsSystemAdmin()) profiles, err = c.App.GetUsersInChannelPageByStatus(inChannelId, c.Params.Page, c.Params.PerPage, c.IsSystemAdmin())
} else {
profiles, err = c.App.GetUsersInChannelPage(inChannelId, c.Params.Page, c.Params.PerPage, c.IsSystemAdmin())
}
} else { } else {
// No permission check required // No permission check required

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

@@ -2650,3 +2650,146 @@ func TestUserAccessTokenDisableConfig(t *testing.T) {
_, resp = Client.GetMe("") _, resp = Client.GetMe("")
CheckNoError(t, resp) CheckNoError(t, resp)
} }
func TestGetUsersByStatus(t *testing.T) {
th := Setup()
defer th.TearDown()
team, err := th.App.CreateTeam(&model.Team{
DisplayName: "dn_" + model.NewId(),
Name: GenerateTestTeamName(),
Email: th.GenerateTestEmail(),
Type: model.TEAM_OPEN,
})
if err != nil {
t.Fatalf("failed to create team: %v", err)
}
channel, err := th.App.CreateChannel(&model.Channel{
DisplayName: "dn_" + model.NewId(),
Name: "name_" + model.NewId(),
Type: model.CHANNEL_OPEN,
TeamId: team.Id,
CreatorId: model.NewId(),
}, false)
if err != nil {
t.Fatalf("failed to create channel: %v", err)
}
createUserWithStatus := func(username string, status string) *model.User {
id := model.NewId()
user, err := th.App.CreateUser(&model.User{
Email: "success+" + id + "@simulator.amazonses.com",
Username: "un_" + username + "_" + id,
Nickname: "nn_" + id,
Password: "Password1",
})
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
th.LinkUserToTeam(user, team)
th.AddUserToChannel(user, channel)
th.App.SaveAndBroadcastStatus(&model.Status{
UserId: user.Id,
Status: status,
Manual: true,
})
return user
}
// Creating these out of order in case that affects results
offlineUser1 := createUserWithStatus("offline1", model.STATUS_OFFLINE)
offlineUser2 := createUserWithStatus("offline2", model.STATUS_OFFLINE)
awayUser1 := createUserWithStatus("away1", model.STATUS_AWAY)
awayUser2 := createUserWithStatus("away2", model.STATUS_AWAY)
onlineUser1 := createUserWithStatus("online1", model.STATUS_ONLINE)
onlineUser2 := createUserWithStatus("online2", model.STATUS_ONLINE)
dndUser1 := createUserWithStatus("dnd1", model.STATUS_DND)
dndUser2 := createUserWithStatus("dnd2", model.STATUS_DND)
client := th.CreateClient()
if _, resp := client.Login(onlineUser2.Username, "Password1"); resp.Error != nil {
t.Fatal(resp.Error)
}
t.Run("sorting by status then alphabetical", func(t *testing.T) {
usersByStatus, resp := client.GetUsersInChannelByStatus(channel.Id, 0, 8, "")
if resp.Error != nil {
t.Fatal(resp.Error)
}
expectedUsersByStatus := []*model.User{
onlineUser1,
onlineUser2,
awayUser1,
awayUser2,
dndUser1,
dndUser2,
offlineUser1,
offlineUser2,
}
if len(usersByStatus) != len(expectedUsersByStatus) {
t.Fatalf("received only %v users, expected %v", len(usersByStatus), len(expectedUsersByStatus))
}
for i := range usersByStatus {
if usersByStatus[i].Id != expectedUsersByStatus[i].Id {
t.Fatalf("received user %v at index %v, expected %v", usersByStatus[i].Username, i, expectedUsersByStatus[i].Username)
}
}
})
t.Run("paging", func(t *testing.T) {
usersByStatus, resp := client.GetUsersInChannelByStatus(channel.Id, 0, 3, "")
if resp.Error != nil {
t.Fatal(resp.Error)
}
if len(usersByStatus) != 3 {
t.Fatal("received too many users")
}
if usersByStatus[0].Id != onlineUser1.Id && usersByStatus[1].Id != onlineUser2.Id {
t.Fatal("expected to receive online users first")
}
if usersByStatus[2].Id != awayUser1.Id {
t.Fatal("expected to receive away users second")
}
usersByStatus, resp = client.GetUsersInChannelByStatus(channel.Id, 1, 3, "")
if resp.Error != nil {
t.Fatal(resp.Error)
}
if usersByStatus[0].Id != awayUser2.Id {
t.Fatal("expected to receive away users second")
}
if usersByStatus[1].Id != dndUser1.Id && usersByStatus[2].Id != dndUser2.Id {
t.Fatal("expected to receive dnd users third")
}
usersByStatus, resp = client.GetUsersInChannelByStatus(channel.Id, 1, 4, "")
if resp.Error != nil {
t.Fatal(resp.Error)
}
if len(usersByStatus) != 4 {
t.Fatal("received too many users")
}
if usersByStatus[0].Id != dndUser1.Id && usersByStatus[1].Id != dndUser2.Id {
t.Fatal("expected to receive dnd users third")
}
if usersByStatus[2].Id != offlineUser1.Id && usersByStatus[3].Id != offlineUser2.Id {
t.Fatal("expected to receive offline users last")
}
})
}

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

@@ -522,7 +522,6 @@ func commandWebhook(c *Context, w http.ResponseWriter, r *http.Request) {
} }
func decodePayload(payload io.Reader) (*model.IncomingWebhookRequest, *model.AppError) { func decodePayload(payload io.Reader) (*model.IncomingWebhookRequest, *model.AppError) {
decodeError := &model.AppError{}
incomingWebhookPayload, decodeError := model.IncomingWebhookRequestFromJson(payload) incomingWebhookPayload, decodeError := model.IncomingWebhookRequestFromJson(payload)
if decodeError != nil { if decodeError != nil {

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

@@ -15,7 +15,6 @@ import (
l4g "github.com/alecthomas/log4go" l4g "github.com/alecthomas/log4go"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store/sqlstore"
"github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/utils"
) )
@@ -141,10 +140,11 @@ func (a *App) InvalidateAllCachesSkipSend() {
l4g.Info(utils.T("api.context.invalidate_all_caches")) l4g.Info(utils.T("api.context.invalidate_all_caches"))
a.sessionCache.Purge() a.sessionCache.Purge()
ClearStatusCache() ClearStatusCache()
sqlstore.ClearChannelCaches() a.Srv.Store.Channel().ClearCaches()
sqlstore.ClearUserCaches() a.Srv.Store.User().ClearCaches()
sqlstore.ClearPostCaches() a.Srv.Store.Post().ClearCaches()
sqlstore.ClearWebhookCaches() a.Srv.Store.FileInfo().ClearCaches()
a.Srv.Store.Webhook().ClearCaches()
a.LoadLicense() a.LoadLicense()
} }

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

@@ -133,8 +133,24 @@ func New(options ...Option) (outApp *App, outErr error) {
app.configListenerId = app.AddConfigListener(func(_, _ *model.Config) { app.configListenerId = app.AddConfigListener(func(_, _ *model.Config) {
app.configOrLicenseListener() app.configOrLicenseListener()
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CONFIG_CHANGED, "", "", "", nil)
message.Add("config", app.ClientConfigWithNoAccounts())
app.Go(func() {
app.Publish(message)
})
})
app.licenseListenerId = app.AddLicenseListener(func() {
app.configOrLicenseListener()
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_LICENSE_CHANGED, "", "", "", nil)
message.Add("license", app.GetSanitizedClientLicense())
app.Go(func() {
app.Publish(message)
})
}) })
app.licenseListenerId = app.AddLicenseListener(app.configOrLicenseListener)
app.regenerateClientConfig() app.regenerateClientConfig()
l4g.Info(utils.T("api.server.new_server.init.info")) l4g.Info(utils.T("api.server.new_server.init.info"))

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

@@ -143,10 +143,6 @@ func (me *TestHelper) InitBasic() *TestHelper {
return me return me
} }
func (me *TestHelper) MakeUsername() string {
return "un_" + model.NewId()
}
func (me *TestHelper) MakeEmail() string { func (me *TestHelper) MakeEmail() string {
return "success_" + model.NewId() + "@simulator.amazonses.com" return "success_" + model.NewId() + "@simulator.amazonses.com"
} }
@@ -199,10 +195,6 @@ func (me *TestHelper) CreateChannel(team *model.Team) *model.Channel {
return me.createChannel(team, model.CHANNEL_OPEN) return me.createChannel(team, model.CHANNEL_OPEN)
} }
func (me *TestHelper) CreatePrivateChannel(team *model.Team) *model.Channel {
return me.createChannel(team, model.CHANNEL_PRIVATE)
}
func (me *TestHelper) createChannel(team *model.Team, channelType string) *model.Channel { func (me *TestHelper) createChannel(team *model.Team, channelType string) *model.Channel {
id := model.NewId() id := model.NewId()
@@ -262,6 +254,22 @@ func (me *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) {
utils.EnableDebugLogForTest() utils.EnableDebugLogForTest()
} }
func (me *TestHelper) AddUserToChannel(user *model.User, channel *model.Channel) *model.ChannelMember {
utils.DisableDebugLogForTest()
member, err := me.App.AddUserToChannel(user, channel)
if err != nil {
l4g.Error(err.Error())
l4g.Close()
time.Sleep(time.Second)
panic(err)
}
utils.EnableDebugLogForTest()
return member
}
func (me *TestHelper) TearDown() { func (me *TestHelper) TearDown() {
me.App.Shutdown() me.App.Shutdown()
os.Remove(me.tempConfigPath) os.Remove(me.tempConfigPath)

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

@@ -9,16 +9,15 @@ import (
) )
const ( const (
USER_PASSWORD = "passwd" USER_PASSWORD = "passwd"
CHANNEL_TYPE = model.CHANNEL_OPEN CHANNEL_TYPE = model.CHANNEL_OPEN
FUZZ_USER_EMAIL_PREFIX_LEN = 10 BTEST_TEAM_DISPLAY_NAME = "TestTeam"
BTEST_TEAM_DISPLAY_NAME = "TestTeam" BTEST_TEAM_NAME = "z-z-testdomaina"
BTEST_TEAM_NAME = "z-z-testdomaina" BTEST_TEAM_EMAIL = "test@nowhere.com"
BTEST_TEAM_EMAIL = "test@nowhere.com" BTEST_TEAM_TYPE = model.TEAM_OPEN
BTEST_TEAM_TYPE = model.TEAM_OPEN BTEST_USER_NAME = "Mr. Testing Tester"
BTEST_USER_NAME = "Mr. Testing Tester" BTEST_USER_EMAIL = "success+ttester@simulator.amazonses.com"
BTEST_USER_EMAIL = "success+ttester@simulator.amazonses.com" BTEST_USER_PASSWORD = "passwd"
BTEST_USER_PASSWORD = "passwd"
) )
var ( var (
@@ -29,8 +28,5 @@ var (
USER_EMAIL_LEN = utils.Range{Begin: 15, End: 30} USER_EMAIL_LEN = utils.Range{Begin: 15, End: 30}
CHANNEL_DISPLAY_NAME_LEN = utils.Range{Begin: 10, End: 20} CHANNEL_DISPLAY_NAME_LEN = utils.Range{Begin: 10, End: 20}
CHANNEL_NAME_LEN = utils.Range{Begin: 5, End: 20} CHANNEL_NAME_LEN = utils.Range{Begin: 5, End: 20}
POST_MESSAGE_LEN = utils.Range{Begin: 100, End: 400}
POST_HASHTAGS_NUM = utils.Range{Begin: 5, End: 10}
POST_MENTIONS_NUM = utils.Range{Begin: 0, End: 3}
TEST_IMAGE_FILENAMES = []string{"test.png", "testjpg.jpg", "testgif.gif"} TEST_IMAGE_FILENAMES = []string{"test.png", "testjpg.jpg", "testgif.gif"}
) )

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

@@ -90,18 +90,3 @@ func (cfg *AutoPostCreator) CreateRandomPost() (*model.Post, bool) {
} }
return result.Data.(*model.Post), true return result.Data.(*model.Post), true
} }
func (cfg *AutoPostCreator) CreateTestPosts(rangePosts utils.Range) ([]*model.Post, bool) {
numPosts := utils.RandIntFromRange(rangePosts)
posts := make([]*model.Post, numPosts)
for i := 0; i < numPosts; i++ {
var err bool
posts[i], err = cfg.CreateRandomPost()
if !err {
return posts, false
}
}
return posts, true
}

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

@@ -225,6 +225,14 @@ func (a *App) createDirectChannel(userId string, otherUserId string) (*model.Cha
} }
} else { } else {
channel := result.Data.(*model.Channel) channel := result.Data.(*model.Channel)
if result := <-a.Srv.Store.ChannelMemberHistory().LogJoinEvent(userId, channel.Id, model.GetMillis()); result.Err != nil {
l4g.Warn("Failed to update ChannelMemberHistory table %v", result.Err)
}
if result := <-a.Srv.Store.ChannelMemberHistory().LogJoinEvent(otherUserId, channel.Id, model.GetMillis()); result.Err != nil {
l4g.Warn("Failed to update ChannelMemberHistory table %v", result.Err)
}
return channel, nil return channel, nil
} }
} }
@@ -369,7 +377,7 @@ func (a *App) postChannelPrivacyMessage(user *model.User, channel *model.Channel
})[channel.Type] })[channel.Type]
post := &model.Post{ post := &model.Post{
ChannelId: channel.Id, ChannelId: channel.Id,
Message: fmt.Sprintf(utils.T("api.channel.change_channel_privacy." + privacy)), Message: utils.T("api.channel.change_channel_privacy." + privacy),
Type: model.POST_CHANGE_CHANNEL_PRIVACY, Type: model.POST_CHANGE_CHANNEL_PRIVACY,
UserId: user.Id, UserId: user.Id,
Props: model.StringInterface{ Props: model.StringInterface{
@@ -549,7 +557,6 @@ func (a *App) DeleteChannel(channel *model.Channel, userId string) *model.AppErr
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_DELETED, channel.TeamId, "", "", nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_DELETED, channel.TeamId, "", "", nil)
message.Add("channel_id", channel.Id) message.Add("channel_id", channel.Id)
a.Publish(message) a.Publish(message)
} }
@@ -1059,7 +1066,7 @@ func (a *App) LeaveChannel(channelId string, userId string) *model.AppError {
return err return err
} }
if channel.Name == model.DEFAULT_CHANNEL && *a.Config().ServiceSettings.ExperimentalEnableDefaultChannelLeaveJoinMessages == false { if channel.Name == model.DEFAULT_CHANNEL && !*a.Config().ServiceSettings.ExperimentalEnableDefaultChannelLeaveJoinMessages {
return nil return nil
} }
@@ -1097,7 +1104,9 @@ func (a *App) PostAddToChannelMessage(user *model.User, addedUser *model.User, c
UserId: user.Id, UserId: user.Id,
RootId: postRootId, RootId: postRootId,
Props: model.StringInterface{ Props: model.StringInterface{
"userId": user.Id,
"username": user.Username, "username": user.Username,
"addedUserId": addedUser.Id,
"addedUsername": addedUser.Username, "addedUsername": addedUser.Username,
}, },
} }
@@ -1117,7 +1126,9 @@ func (a *App) postAddToTeamMessage(user *model.User, addedUser *model.User, chan
UserId: user.Id, UserId: user.Id,
RootId: postRootId, RootId: postRootId,
Props: model.StringInterface{ Props: model.StringInterface{
"userId": user.Id,
"username": user.Username, "username": user.Username,
"addedUserId": addedUser.Id,
"addedUsername": addedUser.Username, "addedUsername": addedUser.Username,
}, },
} }
@@ -1136,6 +1147,7 @@ func (a *App) postRemoveFromChannelMessage(removerUserId string, removedUser *mo
Type: model.POST_REMOVE_FROM_CHANNEL, Type: model.POST_REMOVE_FROM_CHANNEL,
UserId: removerUserId, UserId: removerUserId,
Props: model.StringInterface{ Props: model.StringInterface{
"removedUserId": removedUser.Id,
"removedUsername": removedUser.Username, "removedUsername": removedUser.Username,
}, },
} }
@@ -1170,17 +1182,13 @@ func (a *App) removeUserFromChannel(userIdToRemove string, removerUserId string,
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_REMOVED, "", channel.Id, "", nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_REMOVED, "", channel.Id, "", nil)
message.Add("user_id", userIdToRemove) message.Add("user_id", userIdToRemove)
message.Add("remover_id", removerUserId) message.Add("remover_id", removerUserId)
a.Go(func() { a.Publish(message)
a.Publish(message)
})
// because the removed user no longer belongs to the channel we need to send a separate websocket event // because the removed user no longer belongs to the channel we need to send a separate websocket event
userMsg := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_REMOVED, "", "", userIdToRemove, nil) userMsg := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_REMOVED, "", "", userIdToRemove, nil)
userMsg.Add("channel_id", channel.Id) userMsg.Add("channel_id", channel.Id)
userMsg.Add("remover_id", removerUserId) userMsg.Add("remover_id", removerUserId)
a.Go(func() { a.Publish(userMsg)
a.Publish(userMsg)
})
return nil return nil
} }
@@ -1250,9 +1258,7 @@ func (a *App) UpdateChannelLastViewedAt(channelIds []string, userId string) *mod
for _, channelId := range channelIds { for _, channelId := range channelIds {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, "", "", userId, nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, "", "", userId, nil)
message.Add("channel_id", channelId) message.Add("channel_id", channelId)
a.Go(func() { a.Publish(message)
a.Publish(message)
})
} }
} }
@@ -1329,9 +1335,7 @@ func (a *App) ViewChannel(view *model.ChannelView, userId string, clearPushNotif
if *a.Config().ServiceSettings.EnableChannelViewedMessages && model.IsValidId(view.ChannelId) { if *a.Config().ServiceSettings.EnableChannelViewedMessages && model.IsValidId(view.ChannelId) {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, "", "", userId, nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, "", "", userId, nil)
message.Add("channel_id", view.ChannelId) message.Add("channel_id", view.ChannelId)
a.Go(func() { a.Publish(message)
a.Publish(message)
})
} }
return times, nil return times, nil
@@ -1434,7 +1438,16 @@ func (a *App) GetDirectChannel(userId1, userId2 string) (*model.Channel, *model.
} }
a.InvalidateCacheForUser(userId1) a.InvalidateCacheForUser(userId1)
a.InvalidateCacheForUser(userId2) a.InvalidateCacheForUser(userId2)
return result.Data.(*model.Channel), nil
channel := result.Data.(*model.Channel)
if result := <-a.Srv.Store.ChannelMemberHistory().LogJoinEvent(userId1, channel.Id, model.GetMillis()); result.Err != nil {
l4g.Warn("Failed to update ChannelMemberHistory table %v", result.Err)
}
if result := <-a.Srv.Store.ChannelMemberHistory().LogJoinEvent(userId2, channel.Id, model.GetMillis()); result.Err != nil {
l4g.Warn("Failed to update ChannelMemberHistory table %v", result.Err)
}
return channel, nil
} else if result.Err != nil { } else if result.Err != nil {
return nil, model.NewAppError("GetOrCreateDMChannel", "web.incoming_webhook.channel.app_error", nil, "err="+result.Err.Message, result.Err.StatusCode) return nil, model.NewAppError("GetOrCreateDMChannel", "web.incoming_webhook.channel.app_error", nil, "err="+result.Err.Message, result.Err.StatusCode)
} }

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

@@ -110,7 +110,7 @@ func TestMoveChannel(t *testing.T) {
} }
} }
func TestJoinDefaultChannelsTownSquare(t *testing.T) { func TestJoinDefaultChannelsCreatesChannelMemberHistoryRecordTownSquare(t *testing.T) {
th := Setup().InitBasic() th := Setup().InitBasic()
defer th.TearDown() defer th.TearDown()
@@ -136,7 +136,7 @@ func TestJoinDefaultChannelsTownSquare(t *testing.T) {
assert.True(t, found) assert.True(t, found)
} }
func TestJoinDefaultChannelsOffTopic(t *testing.T) { func TestJoinDefaultChannelsCreatesChannelMemberHistoryRecordOffTopic(t *testing.T) {
th := Setup().InitBasic() th := Setup().InitBasic()
defer th.TearDown() defer th.TearDown()
@@ -162,7 +162,7 @@ func TestJoinDefaultChannelsOffTopic(t *testing.T) {
assert.True(t, found) assert.True(t, found)
} }
func TestCreateChannelPublic(t *testing.T) { func TestCreateChannelPublicCreatesChannelMemberHistoryRecord(t *testing.T) {
th := Setup().InitBasic() th := Setup().InitBasic()
defer th.TearDown() defer th.TearDown()
@@ -176,7 +176,7 @@ func TestCreateChannelPublic(t *testing.T) {
assert.Equal(t, publicChannel.Id, histories[0].ChannelId) assert.Equal(t, publicChannel.Id, histories[0].ChannelId)
} }
func TestCreateChannelPrivate(t *testing.T) { func TestCreateChannelPrivateCreatesChannelMemberHistoryRecord(t *testing.T) {
th := Setup().InitBasic() th := Setup().InitBasic()
defer th.TearDown() defer th.TearDown()
@@ -205,7 +205,7 @@ func TestUpdateChannelPrivacy(t *testing.T) {
} }
} }
func TestCreateGroupChannel(t *testing.T) { func TestCreateGroupChannelCreatesChannelMemberHistoryRecord(t *testing.T) {
th := Setup().InitBasic() th := Setup().InitBasic()
defer th.TearDown() defer th.TearDown()
@@ -233,7 +233,62 @@ func TestCreateGroupChannel(t *testing.T) {
} }
} }
func TestAddUserToChannel(t *testing.T) { func TestCreateDirectChannelCreatesChannelMemberHistoryRecord(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
user1 := th.CreateUser()
user2 := th.CreateUser()
if channel, err := th.App.CreateDirectChannel(user1.Id, user2.Id); err != nil {
t.Fatal("Failed to create direct channel. Error: " + err.Message)
} else {
// there should be a ChannelMemberHistory record for both users
histories := store.Must(th.App.Srv.Store.ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, channel.Id)).([]*model.ChannelMemberHistoryResult)
assert.Len(t, histories, 2)
historyId0 := histories[0].UserId
historyId1 := histories[1].UserId
switch historyId0 {
case user1.Id:
assert.Equal(t, user2.Id, historyId1)
case user2.Id:
assert.Equal(t, user1.Id, historyId1)
default:
t.Fatal("Unexpected user id " + historyId0 + " in ChannelMemberHistory table")
}
}
}
func TestGetDirectChannelCreatesChannelMemberHistoryRecord(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
user1 := th.CreateUser()
user2 := th.CreateUser()
// this function call implicitly creates a direct channel between the two users if one doesn't already exist
if channel, err := th.App.GetDirectChannel(user1.Id, user2.Id); err != nil {
t.Fatal("Failed to create direct channel. Error: " + err.Message)
} else {
// there should be a ChannelMemberHistory record for both users
histories := store.Must(th.App.Srv.Store.ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, channel.Id)).([]*model.ChannelMemberHistoryResult)
assert.Len(t, histories, 2)
historyId0 := histories[0].UserId
historyId1 := histories[1].UserId
switch historyId0 {
case user1.Id:
assert.Equal(t, user2.Id, historyId1)
case user2.Id:
assert.Equal(t, user1.Id, historyId1)
default:
t.Fatal("Unexpected user id " + historyId0 + " in ChannelMemberHistory table")
}
}
}
func TestAddUserToChannelCreatesChannelMemberHistoryRecord(t *testing.T) {
th := Setup().InitBasic() th := Setup().InitBasic()
defer th.TearDown() defer th.TearDown()
@@ -263,7 +318,7 @@ func TestAddUserToChannel(t *testing.T) {
assert.Equal(t, groupUserIds, channelMemberHistoryUserIds) assert.Equal(t, groupUserIds, channelMemberHistoryUserIds)
} }
func TestRemoveUserFromChannel(t *testing.T) { func TestRemoveUserFromChannelUpdatesChannelMemberHistoryRecord(t *testing.T) {
th := Setup().InitBasic() th := Setup().InitBasic()
defer th.TearDown() defer th.TearDown()

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

@@ -74,9 +74,7 @@ func (a *App) setCollapsePreference(args *model.CommandArgs, isCollapse bool) *m
socketMessage := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PREFERENCE_CHANGED, "", "", args.UserId, nil) socketMessage := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PREFERENCE_CHANGED, "", "", args.UserId, nil)
socketMessage.Add("preference", pref.ToJson()) socketMessage.Add("preference", pref.ToJson())
a.Go(func() { a.Publish(socketMessage)
a.Publish(socketMessage)
})
var rmsg string var rmsg string

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

@@ -14,6 +14,7 @@ import (
"fmt" "fmt"
"net/url" "net/url"
"runtime/debug" "runtime/debug"
"strconv"
"strings" "strings"
l4g "github.com/alecthomas/log4go" l4g "github.com/alecthomas/log4go"
@@ -34,6 +35,7 @@ func (a *App) UpdateConfig(f func(*model.Config)) {
updated := old.Clone() updated := old.Clone()
f(updated) f(updated)
a.config.Store(updated) a.config.Store(updated)
a.InvokeConfigListeners(old, updated) a.InvokeConfigListeners(old, updated)
} }
@@ -269,3 +271,16 @@ func (a *App) GetCookieDomain() string {
func (a *App) GetSiteURL() string { func (a *App) GetSiteURL() string {
return a.siteURL return a.siteURL
} }
// ClientConfigWithNoAccounts gets the configuration in a format suitable for sending to the client.
func (a *App) ClientConfigWithNoAccounts() map[string]string {
respCfg := map[string]string{}
for k, v := range a.ClientConfig() {
respCfg[k] = v
}
// NoAccounts is not actually part of the configuration, but is expected by the client.
respCfg["NoAccounts"] = strconv.FormatBool(a.IsFirstUserAccount())
return respCfg
}

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

@@ -63,3 +63,13 @@ func TestAsymmetricSigningKey(t *testing.T) {
assert.NotNil(t, th.App.AsymmetricSigningKey()) assert.NotNil(t, th.App.AsymmetricSigningKey())
assert.NotEmpty(t, th.App.ClientConfig()["AsymmetricSigningPublicKey"]) assert.NotEmpty(t, th.App.ClientConfig()["AsymmetricSigningPublicKey"])
} }
func TestClientConfigWithNoAccounts(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
config := th.App.ClientConfigWithNoAccounts()
if _, ok := config["NoAccounts"]; !ok {
t.Fatal("expected NoAccounts in returned config")
}
}

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

@@ -502,11 +502,15 @@ func (a *App) trackConfig() {
}) })
a.SendDiagnostic(TRACK_CONFIG_MESSAGE_EXPORT, map[string]interface{}{ a.SendDiagnostic(TRACK_CONFIG_MESSAGE_EXPORT, map[string]interface{}{
"enable_message_export": *cfg.MessageExportSettings.EnableExport, "enable_message_export": *cfg.MessageExportSettings.EnableExport,
"export_format": *cfg.MessageExportSettings.ExportFormat, "export_format": *cfg.MessageExportSettings.ExportFormat,
"daily_run_time": *cfg.MessageExportSettings.DailyRunTime, "daily_run_time": *cfg.MessageExportSettings.DailyRunTime,
"default_export_from_timestamp": *cfg.MessageExportSettings.ExportFromTimestamp, "default_export_from_timestamp": *cfg.MessageExportSettings.ExportFromTimestamp,
"batch_size": *cfg.MessageExportSettings.BatchSize, "batch_size": *cfg.MessageExportSettings.BatchSize,
"global_relay_customer_type": *cfg.MessageExportSettings.GlobalRelaySettings.CustomerType,
"is_default_global_relay_smtp_username": isDefault(*cfg.MessageExportSettings.GlobalRelaySettings.SmtpUsername, ""),
"is_default_global_relay_smtp_password": isDefault(*cfg.MessageExportSettings.GlobalRelaySettings.SmtpPassword, ""),
"is_default_global_relay_email_address": isDefault(*cfg.MessageExportSettings.GlobalRelaySettings.EmailAddress, ""),
}) })
} }

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

@@ -60,7 +60,6 @@ func (a *App) CreateEmoji(sessionUserId string, emoji *model.Emoji, multiPartIma
} else { } else {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_EMOJI_ADDED, "", "", "", nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_EMOJI_ADDED, "", "", "", nil)
message.Add("emoji", emoji.ToJson()) message.Add("emoji", emoji.ToJson())
a.Publish(message) a.Publish(message)
return result.Data.(*model.Emoji), nil return result.Data.(*model.Emoji), nil
} }

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

@@ -9,8 +9,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/avct/uasurfer"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mssola/user_agent"
) )
func (a *App) AuthenticateUserForLogin(id, loginId, password, mfaToken, deviceId string, ldapOnly bool) (*model.User, *model.AppError) { func (a *App) AuthenticateUserForLogin(id, loginId, password, mfaToken, deviceId string, ldapOnly bool) (*model.User, *model.AppError) {
@@ -71,19 +71,19 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User,
session.SetExpireInDays(*a.Config().ServiceSettings.SessionLengthWebInDays) session.SetExpireInDays(*a.Config().ServiceSettings.SessionLengthWebInDays)
} }
ua := user_agent.New(r.UserAgent()) ua := uasurfer.Parse(r.UserAgent())
plat := ua.Platform() plat := ua.OS.Platform.String()
if plat == "" { if plat == "" {
plat = "unknown" plat = "unknown"
} }
os := ua.OS() os := ua.OS.Name.String()
if os == "" { if os == "" {
os = "unknown" os = "unknown"
} }
bname, bversion := ua.Browser() bname := ua.Browser.Name.String()
if bname == "" { if bname == "" {
bname = "unknown" bname = "unknown"
} }
@@ -92,9 +92,7 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User,
bname = "Desktop App" bname = "Desktop App"
} }
if bversion == "" { bversion := ua.Browser.Version
bversion = "0.0"
}
session.AddProp(model.SESSION_PROP_PLATFORM, plat) session.AddProp(model.SESSION_PROP_PLATFORM, plat)
session.AddProp(model.SESSION_PROP_OS, os) session.AddProp(model.SESSION_PROP_OS, os)

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

@@ -84,9 +84,7 @@ func (a *App) CreatePostAsUser(post *model.Post) (*model.Post, *model.AppError)
if *a.Config().ServiceSettings.EnableChannelViewedMessages { if *a.Config().ServiceSettings.EnableChannelViewedMessages {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, "", "", post.UserId, nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, "", "", post.UserId, nil)
message.Add("channel_id", post.ChannelId) message.Add("channel_id", post.ChannelId)
a.Go(func() { a.Publish(message)
a.Publish(message)
})
} }
} }
@@ -314,10 +312,7 @@ func (a *App) SendEphemeralPost(userId string, post *model.Post) *model.Post {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, "", post.ChannelId, userId, nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, "", post.ChannelId, userId, nil)
message.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson()) message.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson())
a.Publish(message)
a.Go(func() {
a.Publish(message)
})
return post return post
} }
@@ -417,10 +412,7 @@ func (a *App) PatchPost(postId string, patch *model.PostPatch) (*model.Post, *mo
func (a *App) sendUpdatedPostEvent(post *model.Post) { func (a *App) sendUpdatedPostEvent(post *model.Post) {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", post.ChannelId, "", nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", post.ChannelId, "", nil)
message.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson()) message.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson())
a.Publish(message)
a.Go(func() {
a.Publish(message)
})
} }
func (a *App) GetPostsPage(channelId string, page int, perPage int) (*model.PostList, *model.AppError) { func (a *App) GetPostsPage(channelId string, page int, perPage int) (*model.PostList, *model.AppError) {
@@ -560,10 +552,8 @@ func (a *App) DeletePost(postId string) (*model.Post, *model.AppError) {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", post.ChannelId, "", nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", post.ChannelId, "", nil)
message.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson()) message.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson())
a.Publish(message)
a.Go(func() {
a.Publish(message)
})
a.Go(func() { a.Go(func() {
a.DeletePostFiles(post) a.DeletePostFiles(post)
}) })

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

@@ -55,9 +55,7 @@ func (a *App) UpdatePreferences(userId string, preferences model.Preferences) *m
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PREFERENCES_CHANGED, "", "", userId, nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PREFERENCES_CHANGED, "", "", userId, nil)
message.Add("preferences", preferences.ToJson()) message.Add("preferences", preferences.ToJson())
a.Go(func() { a.Publish(message)
a.Publish(message)
})
return nil return nil
} }
@@ -80,9 +78,7 @@ func (a *App) DeletePreferences(userId string, preferences model.Preferences) *m
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PREFERENCES_DELETED, "", "", userId, nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PREFERENCES_DELETED, "", "", userId, nil)
message.Add("preferences", preferences.ToJson()) message.Add("preferences", preferences.ToJson())
a.Go(func() { a.Publish(message)
a.Publish(message)
})
return nil return nil
} }

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

@@ -84,28 +84,6 @@ func (cw *CorsWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
const TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN = time.Second const TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN = time.Second
type VaryBy struct {
useIP bool
useAuth bool
}
func (m *VaryBy) Key(r *http.Request) string {
key := ""
if m.useAuth {
token, tokenLocation := ParseAuthTokenFromRequest(r)
if tokenLocation != TokenLocationNotFound {
key += token
} else if m.useIP { // If we don't find an authentication token and IP based is enabled, fall back to IP
key += utils.GetIpAddress(r)
}
} else if m.useIP { // Only if Auth based is not enabed do we use a plain IP based
key = utils.GetIpAddress(r)
}
return key
}
func redirectHTTPToHTTPS(w http.ResponseWriter, r *http.Request) { func redirectHTTPToHTTPS(w http.ResponseWriter, r *http.Request) {
if r.Host == "" { if r.Host == "" {
http.Error(w, "Not Found", http.StatusNotFound) http.Error(w, "Not Found", http.StatusNotFound)
@@ -223,31 +201,6 @@ func (a *App) StartServer() error {
return nil return nil
} }
type tcpKeepAliveListener struct {
*net.TCPListener
}
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute)
return tc, nil
}
func (a *App) Listen(addr string) (net.Listener, error) {
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
return tcpKeepAliveListener{ln.(*net.TCPListener)}, nil
}
func (a *App) StopServer() { func (a *App) StopServer() {
if a.Srv.Server != nil { if a.Srv.Server != nil {
ctx, cancel := context.WithTimeout(context.Background(), TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN) ctx, cancel := context.WithTimeout(context.Background(), TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN)

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

@@ -26,7 +26,7 @@ func TestStartServerRateLimiterCriticalError(t *testing.T) {
// Attempt to use Rate Limiter with an invalid config // Attempt to use Rate Limiter with an invalid config
a.UpdateConfig(func(cfg *model.Config) { a.UpdateConfig(func(cfg *model.Config) {
*cfg.RateLimitSettings.Enable = true *cfg.RateLimitSettings.Enable = true
*cfg.RateLimitSettings.MaxBurst = -100 *cfg.RateLimitSettings.MaxBurst = -100
}) })

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

@@ -138,6 +138,9 @@ func (a *App) ClearSessionCacheForUserSkipClusterSend(userId string) {
session := ts.(*model.Session) session := ts.(*model.Session)
if session.UserId == userId { if session.UserId == userId {
a.sessionCache.Remove(key) a.sessionCache.Remove(key)
if a.Metrics != nil {
a.Metrics.IncrementMemCacheInvalidationCounterSession()
}
} }
} }
} }

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

@@ -109,13 +109,11 @@ func SlackParseUsers(data io.Reader) ([]SlackUser, error) {
decoder := json.NewDecoder(data) decoder := json.NewDecoder(data)
var users []SlackUser var users []SlackUser
if err := decoder.Decode(&users); err != nil { err := decoder.Decode(&users)
// This actually returns errors that are ignored. // This actually returns errors that are ignored.
// In this case it is erroring because of a null that Slack // In this case it is erroring because of a null that Slack
// introduced. So we just return the users here. // introduced. So we just return the users here.
return users, err return users, err
}
return users, nil
} }
func SlackParsePosts(data io.Reader) ([]SlackPost, error) { func SlackParsePosts(data io.Reader) ([]SlackPost, error) {

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

@@ -221,9 +221,7 @@ func (a *App) BroadcastStatus(status *model.Status) {
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil) event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
event.Add("status", status.Status) event.Add("status", status.Status)
event.Add("user_id", status.UserId) event.Add("user_id", status.UserId)
a.Go(func() { a.Publish(event)
a.Publish(event)
})
} }
func (a *App) SetStatusOffline(userId string, manual bool) { func (a *App) SetStatusOffline(userId string, manual bool) {
@@ -238,18 +236,7 @@ func (a *App) SetStatusOffline(userId string, manual bool) {
status = &model.Status{UserId: userId, Status: model.STATUS_OFFLINE, Manual: manual, LastActivityAt: model.GetMillis(), ActiveChannel: ""} status = &model.Status{UserId: userId, Status: model.STATUS_OFFLINE, Manual: manual, LastActivityAt: model.GetMillis(), ActiveChannel: ""}
a.AddStatusCache(status) a.SaveAndBroadcastStatus(status)
if result := <-a.Srv.Store.Status().SaveOrUpdate(status); result.Err != nil {
l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err)
}
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
event.Add("status", model.STATUS_OFFLINE)
event.Add("user_id", status.UserId)
a.Go(func() {
a.Publish(event)
})
} }
func (a *App) SetStatusAwayIfNeeded(userId string, manual bool) { func (a *App) SetStatusAwayIfNeeded(userId string, manual bool) {
@@ -281,18 +268,7 @@ func (a *App) SetStatusAwayIfNeeded(userId string, manual bool) {
status.Manual = manual status.Manual = manual
status.ActiveChannel = "" status.ActiveChannel = ""
a.AddStatusCache(status) a.SaveAndBroadcastStatus(status)
if result := <-a.Srv.Store.Status().SaveOrUpdate(status); result.Err != nil {
l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err)
}
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
event.Add("status", model.STATUS_AWAY)
event.Add("user_id", status.UserId)
a.Go(func() {
a.Publish(event)
})
} }
func (a *App) SetStatusDoNotDisturb(userId string) { func (a *App) SetStatusDoNotDisturb(userId string) {
@@ -309,18 +285,22 @@ func (a *App) SetStatusDoNotDisturb(userId string) {
status.Status = model.STATUS_DND status.Status = model.STATUS_DND
status.Manual = true status.Manual = true
a.SaveAndBroadcastStatus(status)
}
func (a *App) SaveAndBroadcastStatus(status *model.Status) *model.AppError {
a.AddStatusCache(status) a.AddStatusCache(status)
if result := <-a.Srv.Store.Status().SaveOrUpdate(status); result.Err != nil { if result := <-a.Srv.Store.Status().SaveOrUpdate(status); result.Err != nil {
l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err) l4g.Error(utils.T("api.status.save_status.error"), status.UserId, result.Err)
} }
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil) event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
event.Add("status", model.STATUS_DND) event.Add("status", status.Status)
event.Add("user_id", status.UserId) event.Add("user_id", status.UserId)
a.Go(func() { a.Publish(event)
a.Publish(event)
}) return nil
} }
func GetStatusFromCache(userId string) *model.Status { func GetStatusFromCache(userId string) *model.Status {

40
app/status_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,40 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"testing"
"github.com/mattermost/mattermost-server/model"
)
func TestSaveStatus(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
user := th.BasicUser
for _, statusString := range []string{
model.STATUS_ONLINE,
model.STATUS_AWAY,
model.STATUS_DND,
model.STATUS_OFFLINE,
} {
t.Run(statusString, func(t *testing.T) {
status := &model.Status{
UserId: user.Id,
Status: statusString,
}
th.App.SaveAndBroadcastStatus(status)
after, err := th.App.GetStatus(user.Id)
if err != nil {
t.Fatalf("failed to get status after save: %v", err)
} else if after.Status != statusString {
t.Fatalf("failed to save status, got %v, expected %v", after.Status, statusString)
}
})
}
}

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

@@ -139,9 +139,7 @@ func (a *App) sendTeamEvent(team *model.Team, event string) {
message := model.NewWebSocketEvent(event, "", "", "", nil) message := model.NewWebSocketEvent(event, "", "", "", nil)
message.Add("team", sanitizedTeam.ToJson()) message.Add("team", sanitizedTeam.ToJson())
a.Go(func() { a.Publish(message)
a.Publish(message)
})
} }
func (a *App) UpdateTeamMemberRoles(teamId string, userId string, newRoles string) (*model.TeamMember, *model.AppError) { func (a *App) UpdateTeamMemberRoles(teamId string, userId string, newRoles string) (*model.TeamMember, *model.AppError) {
@@ -182,10 +180,7 @@ func (a *App) UpdateTeamMemberRoles(teamId string, userId string, newRoles strin
func (a *App) sendUpdatedMemberRoleEvent(userId string, member *model.TeamMember) { func (a *App) sendUpdatedMemberRoleEvent(userId string, member *model.TeamMember) {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_MEMBERROLE_UPDATED, "", "", userId, nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_MEMBERROLE_UPDATED, "", "", userId, nil)
message.Add("member", member.ToJson()) message.Add("member", member.ToJson())
a.Publish(message)
a.Go(func() {
a.Publish(message)
})
} }
func (a *App) AddUserToTeam(teamId string, userId string, userRequestorId string) (*model.Team, *model.AppError) { func (a *App) AddUserToTeam(teamId string, userId string, userRequestorId string) (*model.Team, *model.AppError) {

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

@@ -34,7 +34,6 @@ const (
TOKEN_TYPE_PASSWORD_RECOVERY = "password_recovery" TOKEN_TYPE_PASSWORD_RECOVERY = "password_recovery"
TOKEN_TYPE_VERIFY_EMAIL = "verify_email" TOKEN_TYPE_VERIFY_EMAIL = "verify_email"
PASSWORD_RECOVER_EXPIRY_TIME = 1000 * 60 * 60 // 1 hour PASSWORD_RECOVER_EXPIRY_TIME = 1000 * 60 * 60 // 1 hour
VERIFY_EMAIL_EXPIRY_TIME = 1000 * 60 * 60 // 1 hour
IMAGE_PROFILE_PIXEL_DIMENSION = 128 IMAGE_PROFILE_PIXEL_DIMENSION = 128
) )
@@ -202,9 +201,7 @@ func (a *App) CreateUser(user *model.User) (*model.User, *model.AppError) {
// This message goes to everyone, so the teamId, channelId and userId are irrelevant // This message goes to everyone, so the teamId, channelId and userId are irrelevant
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_NEW_USER, "", "", "", nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_NEW_USER, "", "", "", nil)
message.Add("user_id", ruser.Id) message.Add("user_id", ruser.Id)
a.Go(func() { a.Publish(message)
a.Publish(message)
})
return ruser, nil return ruser, nil
} }
@@ -508,6 +505,14 @@ func (a *App) GetUsersInChannel(channelId string, offset int, limit int) ([]*mod
} }
} }
func (a *App) GetUsersInChannelByStatus(channelId string, offset int, limit int) ([]*model.User, *model.AppError) {
if result := <-a.Srv.Store.User().GetProfilesInChannelByStatus(channelId, offset, limit); result.Err != nil {
return nil, result.Err
} else {
return result.Data.([]*model.User), nil
}
}
func (a *App) GetUsersInChannelMap(channelId string, offset int, limit int, asAdmin bool) (map[string]*model.User, *model.AppError) { func (a *App) GetUsersInChannelMap(channelId string, offset int, limit int, asAdmin bool) (map[string]*model.User, *model.AppError) {
users, err := a.GetUsersInChannel(channelId, offset, limit) users, err := a.GetUsersInChannel(channelId, offset, limit)
if err != nil { if err != nil {
@@ -533,6 +538,15 @@ func (a *App) GetUsersInChannelPage(channelId string, page int, perPage int, asA
return a.sanitizeProfiles(users, asAdmin), nil return a.sanitizeProfiles(users, asAdmin), nil
} }
func (a *App) GetUsersInChannelPageByStatus(channelId string, page int, perPage int, asAdmin bool) ([]*model.User, *model.AppError) {
users, err := a.GetUsersInChannelByStatus(channelId, page*perPage, perPage)
if err != nil {
return nil, err
}
return a.sanitizeProfiles(users, asAdmin), nil
}
func (a *App) GetUsersNotInChannel(teamId string, channelId string, offset int, limit int) ([]*model.User, *model.AppError) { func (a *App) GetUsersNotInChannel(teamId string, channelId string, offset int, limit int) ([]*model.User, *model.AppError) {
if result := <-a.Srv.Store.User().GetProfilesNotInChannel(teamId, channelId, offset, limit); result.Err != nil { if result := <-a.Srv.Store.User().GetProfilesNotInChannel(teamId, channelId, offset, limit); result.Err != nil {
return nil, result.Err return nil, result.Err
@@ -832,7 +846,6 @@ func (a *App) SetProfileImageFromFile(userId string, file multipart.File) *model
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil)
message.Add("user", user) message.Add("user", user)
a.Publish(message) a.Publish(message)
} }
@@ -901,10 +914,6 @@ func (a *App) UpdateActive(user *model.User, active bool) (*model.User, *model.A
} }
} }
if extra := <-a.Srv.Store.Channel().ExtraUpdateByUser(user.Id, model.GetMillis()); extra.Err != nil {
return nil, extra.Err
}
ruser := result.Data.([2]*model.User)[0] ruser := result.Data.([2]*model.User)[0]
options := a.Config().GetSanitizeOptions() options := a.Config().GetSanitizeOptions()
options["passwordupdate"] = false options["passwordupdate"] = false
@@ -1002,9 +1011,7 @@ func (a *App) sendUpdatedUserEvent(user model.User, asAdmin bool) {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil)
message.Add("user", user) message.Add("user", user)
a.Go(func() { a.Publish(message)
a.Publish(message)
})
} }
func (a *App) UpdateUser(user *model.User, sendNotifications bool) (*model.User, *model.AppError) { func (a *App) UpdateUser(user *model.User, sendNotifications bool) (*model.User, *model.AppError) {

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

@@ -299,3 +299,132 @@ func createGitlabUser(t *testing.T, a *App, email string, username string) (*mod
return user, gitlabUserObj return user, gitlabUserObj
} }
func TestGetUsersByStatus(t *testing.T) {
th := Setup()
defer th.TearDown()
team := th.CreateTeam()
channel, err := th.App.CreateChannel(&model.Channel{
DisplayName: "dn_" + model.NewId(),
Name: "name_" + model.NewId(),
Type: model.CHANNEL_OPEN,
TeamId: team.Id,
CreatorId: model.NewId(),
}, false)
if err != nil {
t.Fatalf("failed to create channel: %v", err)
}
createUserWithStatus := func(username string, status string) *model.User {
id := model.NewId()
user, err := th.App.CreateUser(&model.User{
Email: "success+" + id + "@simulator.amazonses.com",
Username: "un_" + username + "_" + id,
Nickname: "nn_" + id,
Password: "Password1",
})
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
th.LinkUserToTeam(user, team)
th.AddUserToChannel(user, channel)
th.App.SaveAndBroadcastStatus(&model.Status{
UserId: user.Id,
Status: status,
Manual: true,
})
return user
}
// Creating these out of order in case that affects results
awayUser1 := createUserWithStatus("away1", model.STATUS_AWAY)
awayUser2 := createUserWithStatus("away2", model.STATUS_AWAY)
dndUser1 := createUserWithStatus("dnd1", model.STATUS_DND)
dndUser2 := createUserWithStatus("dnd2", model.STATUS_DND)
offlineUser1 := createUserWithStatus("offline1", model.STATUS_OFFLINE)
offlineUser2 := createUserWithStatus("offline2", model.STATUS_OFFLINE)
onlineUser1 := createUserWithStatus("online1", model.STATUS_ONLINE)
onlineUser2 := createUserWithStatus("online2", model.STATUS_ONLINE)
t.Run("sorting by status then alphabetical", func(t *testing.T) {
usersByStatus, err := th.App.GetUsersInChannelPageByStatus(channel.Id, 0, 8, true)
if err != nil {
t.Fatal(err)
}
expectedUsersByStatus := []*model.User{
onlineUser1,
onlineUser2,
awayUser1,
awayUser2,
dndUser1,
dndUser2,
offlineUser1,
offlineUser2,
}
if len(usersByStatus) != len(expectedUsersByStatus) {
t.Fatalf("received only %v users, expected %v", len(usersByStatus), len(expectedUsersByStatus))
}
for i := range usersByStatus {
if usersByStatus[i].Id != expectedUsersByStatus[i].Id {
t.Fatalf("received user %v at index %v, expected %v", usersByStatus[i].Username, i, expectedUsersByStatus[i].Username)
}
}
})
t.Run("paging", func(t *testing.T) {
usersByStatus, err := th.App.GetUsersInChannelPageByStatus(channel.Id, 0, 3, true)
if err != nil {
t.Fatal(err)
}
if len(usersByStatus) != 3 {
t.Fatal("received too many users")
}
if usersByStatus[0].Id != onlineUser1.Id && usersByStatus[1].Id != onlineUser2.Id {
t.Fatal("expected to receive online users first")
}
if usersByStatus[2].Id != awayUser1.Id {
t.Fatal("expected to receive away users second")
}
usersByStatus, err = th.App.GetUsersInChannelPageByStatus(channel.Id, 1, 3, true)
if err != nil {
t.Fatal(err)
}
if usersByStatus[0].Id != awayUser2.Id {
t.Fatal("expected to receive away users second")
}
if usersByStatus[1].Id != dndUser1.Id && usersByStatus[2].Id != dndUser2.Id {
t.Fatal("expected to receive dnd users third")
}
usersByStatus, err = th.App.GetUsersInChannelPageByStatus(channel.Id, 1, 4, true)
if err != nil {
t.Fatal(err)
}
if len(usersByStatus) != 4 {
t.Fatal("received too many users")
}
if usersByStatus[0].Id != dndUser1.Id && usersByStatus[1].Id != dndUser2.Id {
t.Fatal("expected to receive dnd users third")
}
if usersByStatus[2].Id != offlineUser1.Id && usersByStatus[3].Id != offlineUser2.Id {
t.Fatal("expected to receive offline users last")
}
})
}

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

@@ -225,7 +225,7 @@ func SplitWebhookPost(post *model.Post) ([]*model.Post, *model.AppError) {
func (a *App) CreateWebhookPost(userId string, channel *model.Channel, text, overrideUsername, overrideIconUrl string, props model.StringInterface, postType string, postRootId string) (*model.Post, *model.AppError) { func (a *App) CreateWebhookPost(userId string, channel *model.Channel, text, overrideUsername, overrideIconUrl string, props model.StringInterface, postType string, postRootId string) (*model.Post, *model.AppError) {
// parse links into Markdown format // parse links into Markdown format
linkWithTextRegex := regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`) linkWithTextRegex := regexp.MustCompile(`<([^\n<\|>]+)\|([^\n>]+)>`)
text = linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})") text = linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})")
post := &model.Post{UserId: userId, ChannelId: channel.Id, Message: text, Type: postType, RootId: postRootId} post := &model.Post{UserId: userId, ChannelId: channel.Id, Message: text, Type: postType, RootId: postRootId}

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

@@ -317,6 +317,64 @@ func TestCreateWebhookPost(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("should have failed - bad post type") t.Fatal("should have failed - bad post type")
} }
expectedText := "`<>|<>|`"
post, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", model.StringInterface{
"attachments": []*model.SlackAttachment{
{
Text: "text",
},
},
"webhook_display_name": hook.DisplayName,
}, model.POST_SLACK_ATTACHMENT, "")
if err != nil {
t.Fatal(err.Error())
}
assert.Equal(t, expectedText, post.Message)
expectedText = "< | \n|\n>"
post, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", model.StringInterface{
"attachments": []*model.SlackAttachment{
{
Text: "text",
},
},
"webhook_display_name": hook.DisplayName,
}, model.POST_SLACK_ATTACHMENT, "")
if err != nil {
t.Fatal(err.Error())
}
assert.Equal(t, expectedText, post.Message)
expectedText = `commit bc95839e4a430ace453e8b209a3723c000c1729a
Author: foo <foo@example.org>
Date: Thu Mar 1 19:46:54 2018 +0300
commit message 2
test | 1 +
1 file changed, 1 insertion(+)
commit 5df78b7139b543997838071cd912e375d8bd69b2
Author: foo <foo@example.org>
Date: Thu Mar 1 19:46:48 2018 +0300
commit message 1
test | 3 +++
1 file changed, 3 insertions(+)`
post, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", model.StringInterface{
"attachments": []*model.SlackAttachment{
{
Text: "text",
},
},
"webhook_display_name": hook.DisplayName,
}, model.POST_SLACK_ATTACHMENT, "")
if err != nil {
t.Fatal(err.Error())
}
assert.Equal(t, expectedText, post.Message)
} }
func TestSplitWebhookPost(t *testing.T) { func TestSplitWebhookPost(t *testing.T) {

2
build/Jenkinsfile поставляемый
Просмотреть файл

@@ -31,7 +31,7 @@ podTemplate(label: 'jenkins-slave',
), ),
containerTemplate( containerTemplate(
name: 'mattermost-inbucket', name: 'mattermost-inbucket',
image: 'jhillyerd/inbucket:latest', image: 'jhillyerd/inbucket:release-1.2.0',
resourceRequestCpu: '250m', resourceRequestCpu: '250m',
resourceLimitCpu: '250m', resourceLimitCpu: '250m',
resourceRequestMemory: '256Mi', resourceRequestMemory: '256Mi',

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

@@ -3,15 +3,27 @@ dist: | check-style test package
build-linux: build-linux:
@echo Build Linux amd64 @echo Build Linux amd64
env GOOS=linux GOARCH=amd64 $(GO) install $(GOFLAGS) $(GO_LINKER_FLAGS) ./cmd/platform ifeq ($(BUILDER_GOOS_GOARCH),"linux_amd64")
env GOOS=linux GOARCH=amd64 $(GO) build -i -o $(GOPATH)/bin/platform $(GOFLAGS) $(GO_LINKER_FLAGS) ./
else
env GOOS=linux GOARCH=amd64 $(GO) build -i -o $(GOPATH)/bin/linux_amd64/platform $(GOFLAGS) $(GO_LINKER_FLAGS) ./
endif
build-osx: build-osx:
@echo Build OSX amd64 @echo Build OSX amd64
env GOOS=darwin GOARCH=amd64 $(GO) install $(GOFLAGS) $(GO_LINKER_FLAGS) ./cmd/platform ifeq ($(BUILDER_GOOS_GOARCH),"darwin_amd64")
env GOOS=darwin GOARCH=amd64 $(GO) build -i -o $(GOPATH)/bin/platform $(GOFLAGS) $(GO_LINKER_FLAGS) ./
else
env GOOS=darwin GOARCH=amd64 $(GO) build -i -o $(GOPATH)/bin/darwin_amd64/platform $(GOFLAGS) $(GO_LINKER_FLAGS) ./
endif
build-windows: build-windows:
@echo Build Windows amd64 @echo Build Windows amd64
env GOOS=windows GOARCH=amd64 $(GO) install $(GOFLAGS) $(GO_LINKER_FLAGS) ./cmd/platform ifeq ($(BUILDER_GOOS_GOARCH),"windows_amd64")
env GOOS=windows GOARCH=amd64 $(GO) build -i -o $(GOPATH)/bin/platform.exe $(GOFLAGS) $(GO_LINKER_FLAGS) ./
else
env GOOS=windows GOARCH=amd64 $(GO) build -i -o $(GOPATH)/bin/windows_amd64/platform.exe $(GOFLAGS) $(GO_LINKER_FLAGS) ./
endif
build: build-linux build-windows build-osx build: build-linux build-windows build-osx

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

@@ -0,0 +1,26 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package cmd
import (
"github.com/spf13/cobra"
)
type Command = cobra.Command
func Run(args []string) error {
RootCmd.SetArgs(args)
return RootCmd.Execute()
}
var RootCmd = &cobra.Command{
Use: "platform",
Short: "Open source, self-hosted Slack-alternative",
Long: `Mattermost offers workplace messaging across web, PC and phones with archiving, search and integration with your existing systems. Documentation available at https://docs.mattermost.com`,
}
func init() {
RootCmd.PersistentFlags().StringP("config", "c", "config.json", "Configuration file to use.")
RootCmd.PersistentFlags().Bool("disableconfigwatch", false, "When set config.json will not be loaded from disk when the file is changed.")
}

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

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main package cmd
import ( import (
"flag" "flag"
@@ -30,7 +30,7 @@ func execArgs(t *testing.T, args []string) []string {
return append(append(ret, "--", "--disableconfigwatch"), args...) return append(append(ret, "--", "--disableconfigwatch"), args...)
} }
func checkCommand(t *testing.T, args ...string) string { func CheckCommand(t *testing.T, args ...string) string {
path, err := os.Executable() path, err := os.Executable()
require.NoError(t, err) require.NoError(t, err)
output, err := exec.Command(path, execArgs(t, args)...).CombinedOutput() output, err := exec.Command(path, execArgs(t, args)...).CombinedOutput()
@@ -38,16 +38,8 @@ func checkCommand(t *testing.T, args ...string) string {
return strings.TrimSpace(strings.TrimSuffix(strings.TrimSpace(string(output)), "PASS")) return strings.TrimSpace(strings.TrimSuffix(strings.TrimSpace(string(output)), "PASS"))
} }
func runCommand(t *testing.T, args ...string) error { func RunCommand(t *testing.T, args ...string) error {
path, err := os.Executable() path, err := os.Executable()
require.NoError(t, err) require.NoError(t, err)
return exec.Command(path, execArgs(t, args)...).Run() return exec.Command(path, execArgs(t, args)...).Run()
} }
func TestExecCommand(t *testing.T) {
if filter := flag.Lookup("test.run").Value.String(); filter != "ExecCommand" {
t.Skip("use -run ExecCommand to execute a command via the test executable")
}
rootCmd.SetArgs(flag.Args())
require.NoError(t, rootCmd.Execute())
}

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

@@ -1,22 +1,24 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main
package commands
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var channelCmd = &cobra.Command{ var ChannelCmd = &cobra.Command{
Use: "channel", Use: "channel",
Short: "Management of channels", Short: "Management of channels",
} }
var channelCreateCmd = &cobra.Command{ var ChannelCreateCmd = &cobra.Command{
Use: "create", Use: "create",
Short: "Create a channel", Short: "Create a channel",
Long: `Create a channel.`, Long: `Create a channel.`,
@@ -25,7 +27,7 @@ var channelCreateCmd = &cobra.Command{
RunE: createChannelCmdF, RunE: createChannelCmdF,
} }
var removeChannelUsersCmd = &cobra.Command{ var RemoveChannelUsersCmd = &cobra.Command{
Use: "remove [channel] [users]", Use: "remove [channel] [users]",
Short: "Remove users from channel", Short: "Remove users from channel",
Long: "Remove some users from channel", Long: "Remove some users from channel",
@@ -33,7 +35,7 @@ var removeChannelUsersCmd = &cobra.Command{
RunE: removeChannelUsersCmdF, RunE: removeChannelUsersCmdF,
} }
var addChannelUsersCmd = &cobra.Command{ var AddChannelUsersCmd = &cobra.Command{
Use: "add [channel] [users]", Use: "add [channel] [users]",
Short: "Add users to channel", Short: "Add users to channel",
Long: "Add some users to channel", Long: "Add some users to channel",
@@ -41,7 +43,7 @@ var addChannelUsersCmd = &cobra.Command{
RunE: addChannelUsersCmdF, RunE: addChannelUsersCmdF,
} }
var archiveChannelsCmd = &cobra.Command{ var ArchiveChannelsCmd = &cobra.Command{
Use: "archive [channels]", Use: "archive [channels]",
Short: "Archive channels", Short: "Archive channels",
Long: `Archive some channels. Long: `Archive some channels.
@@ -51,7 +53,7 @@ Channels can be specified by [team]:[channel]. ie. myteam:mychannel or by channe
RunE: archiveChannelsCmdF, RunE: archiveChannelsCmdF,
} }
var deleteChannelsCmd = &cobra.Command{ var DeleteChannelsCmd = &cobra.Command{
Use: "delete [channels]", Use: "delete [channels]",
Short: "Delete channels", Short: "Delete channels",
Long: `Permanently delete some channels. Long: `Permanently delete some channels.
@@ -61,7 +63,7 @@ Channels can be specified by [team]:[channel]. ie. myteam:mychannel or by channe
RunE: deleteChannelsCmdF, RunE: deleteChannelsCmdF,
} }
var listChannelsCmd = &cobra.Command{ var ListChannelsCmd = &cobra.Command{
Use: "list [teams]", Use: "list [teams]",
Short: "List all channels on specified teams.", Short: "List all channels on specified teams.",
Long: `List all channels on specified teams. Long: `List all channels on specified teams.
@@ -70,7 +72,7 @@ Archived channels are appended with ' (archived)'.`,
RunE: listChannelsCmdF, RunE: listChannelsCmdF,
} }
var moveChannelsCmd = &cobra.Command{ var MoveChannelsCmd = &cobra.Command{
Use: "move [team] [channels]", Use: "move [team] [channels]",
Short: "Moves channels to the specified team", Short: "Moves channels to the specified team",
Long: `Moves the provided channels to the specified team. Long: `Moves the provided channels to the specified team.
@@ -80,7 +82,7 @@ Channels can be specified by [team]:[channel]. ie. myteam:mychannel or by channe
RunE: moveChannelsCmdF, RunE: moveChannelsCmdF,
} }
var restoreChannelsCmd = &cobra.Command{ var RestoreChannelsCmd = &cobra.Command{
Use: "restore [channels]", Use: "restore [channels]",
Short: "Restore some channels", Short: "Restore some channels",
Long: `Restore a previously deleted channel Long: `Restore a previously deleted channel
@@ -89,7 +91,7 @@ Channels can be specified by [team]:[channel]. ie. myteam:mychannel or by channe
RunE: restoreChannelsCmdF, RunE: restoreChannelsCmdF,
} }
var modifyChannelCmd = &cobra.Command{ var ModifyChannelCmd = &cobra.Command{
Use: "modify [channel]", Use: "modify [channel]",
Short: "Modify a channel's public/private type", Short: "Modify a channel's public/private type",
Long: `Change the public/private type of a channel. Long: `Change the public/private type of a channel.
@@ -99,55 +101,57 @@ Channel can be specified by [team]:[channel]. ie. myteam:mychannel or by channel
} }
func init() { func init() {
channelCreateCmd.Flags().String("name", "", "Channel Name") ChannelCreateCmd.Flags().String("name", "", "Channel Name")
channelCreateCmd.Flags().String("display_name", "", "Channel Display Name") ChannelCreateCmd.Flags().String("display_name", "", "Channel Display Name")
channelCreateCmd.Flags().String("team", "", "Team name or ID") ChannelCreateCmd.Flags().String("team", "", "Team name or ID")
channelCreateCmd.Flags().String("header", "", "Channel header") ChannelCreateCmd.Flags().String("header", "", "Channel header")
channelCreateCmd.Flags().String("purpose", "", "Channel purpose") ChannelCreateCmd.Flags().String("purpose", "", "Channel purpose")
channelCreateCmd.Flags().Bool("private", false, "Create a private channel.") ChannelCreateCmd.Flags().Bool("private", false, "Create a private channel.")
moveChannelsCmd.Flags().String("username", "", "Required. Username who is moving the channel.") MoveChannelsCmd.Flags().String("username", "", "Required. Username who is moving the channel.")
deleteChannelsCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the channels.") DeleteChannelsCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the channels.")
modifyChannelCmd.Flags().Bool("private", false, "Convert the channel to a private channel") ModifyChannelCmd.Flags().Bool("private", false, "Convert the channel to a private channel")
modifyChannelCmd.Flags().Bool("public", false, "Convert the channel to a public channel") ModifyChannelCmd.Flags().Bool("public", false, "Convert the channel to a public channel")
modifyChannelCmd.Flags().String("username", "", "Required. Username who changes the channel privacy.") ModifyChannelCmd.Flags().String("username", "", "Required. Username who changes the channel privacy.")
channelCmd.AddCommand( ChannelCmd.AddCommand(
channelCreateCmd, ChannelCreateCmd,
removeChannelUsersCmd, RemoveChannelUsersCmd,
addChannelUsersCmd, AddChannelUsersCmd,
archiveChannelsCmd, ArchiveChannelsCmd,
deleteChannelsCmd, DeleteChannelsCmd,
listChannelsCmd, ListChannelsCmd,
moveChannelsCmd, MoveChannelsCmd,
restoreChannelsCmd, RestoreChannelsCmd,
modifyChannelCmd, ModifyChannelCmd,
) )
cmd.RootCmd.AddCommand(ChannelCmd)
} }
func createChannelCmdF(cmd *cobra.Command, args []string) error { func createChannelCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
name, errn := cmd.Flags().GetString("name") name, errn := command.Flags().GetString("name")
if errn != nil || name == "" { if errn != nil || name == "" {
return errors.New("Name is required") return errors.New("Name is required")
} }
displayname, errdn := cmd.Flags().GetString("display_name") displayname, errdn := command.Flags().GetString("display_name")
if errdn != nil || displayname == "" { if errdn != nil || displayname == "" {
return errors.New("Display Name is required") return errors.New("Display Name is required")
} }
teamArg, errteam := cmd.Flags().GetString("team") teamArg, errteam := command.Flags().GetString("team")
if errteam != nil || teamArg == "" { if errteam != nil || teamArg == "" {
return errors.New("Team is required") return errors.New("Team is required")
} }
header, _ := cmd.Flags().GetString("header") header, _ := command.Flags().GetString("header")
purpose, _ := cmd.Flags().GetString("purpose") purpose, _ := command.Flags().GetString("purpose")
useprivate, _ := cmd.Flags().GetBool("private") useprivate, _ := command.Flags().GetBool("private")
channelType := model.CHANNEL_OPEN channelType := model.CHANNEL_OPEN
if useprivate { if useprivate {
@@ -176,8 +180,8 @@ func createChannelCmdF(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func removeChannelUsersCmdF(cmd *cobra.Command, args []string) error { func removeChannelUsersCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -201,16 +205,16 @@ func removeChannelUsersCmdF(cmd *cobra.Command, args []string) error {
func removeUserFromChannel(a *app.App, channel *model.Channel, user *model.User, userArg string) { func removeUserFromChannel(a *app.App, channel *model.Channel, user *model.User, userArg string) {
if user == nil { if user == nil {
CommandPrintErrorln("Can't find user '" + userArg + "'") cmd.CommandPrintErrorln("Can't find user '" + userArg + "'")
return return
} }
if err := a.RemoveUserFromChannel(user.Id, "", channel); err != nil { if err := a.RemoveUserFromChannel(user.Id, "", channel); err != nil {
CommandPrintErrorln("Unable to remove '" + userArg + "' from " + channel.Name + ". Error: " + err.Error()) cmd.CommandPrintErrorln("Unable to remove '" + userArg + "' from " + channel.Name + ". Error: " + err.Error())
} }
} }
func addChannelUsersCmdF(cmd *cobra.Command, args []string) error { func addChannelUsersCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -234,16 +238,16 @@ func addChannelUsersCmdF(cmd *cobra.Command, args []string) error {
func addUserToChannel(a *app.App, channel *model.Channel, user *model.User, userArg string) { func addUserToChannel(a *app.App, channel *model.Channel, user *model.User, userArg string) {
if user == nil { if user == nil {
CommandPrintErrorln("Can't find user '" + userArg + "'") cmd.CommandPrintErrorln("Can't find user '" + userArg + "'")
return return
} }
if _, err := a.AddUserToChannel(user, channel); err != nil { if _, err := a.AddUserToChannel(user, channel); err != nil {
CommandPrintErrorln("Unable to add '" + userArg + "' from " + channel.Name + ". Error: " + err.Error()) cmd.CommandPrintErrorln("Unable to add '" + userArg + "' from " + channel.Name + ". Error: " + err.Error())
} }
} }
func archiveChannelsCmdF(cmd *cobra.Command, args []string) error { func archiveChannelsCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -255,19 +259,19 @@ func archiveChannelsCmdF(cmd *cobra.Command, args []string) error {
channels := getChannelsFromChannelArgs(a, args) channels := getChannelsFromChannelArgs(a, args)
for i, channel := range channels { for i, channel := range channels {
if channel == nil { if channel == nil {
CommandPrintErrorln("Unable to find channel '" + args[i] + "'") cmd.CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
continue continue
} }
if result := <-a.Srv.Store.Channel().Delete(channel.Id, model.GetMillis()); result.Err != nil { if result := <-a.Srv.Store.Channel().Delete(channel.Id, model.GetMillis()); result.Err != nil {
CommandPrintErrorln("Unable to archive channel '" + channel.Name + "' error: " + result.Err.Error()) cmd.CommandPrintErrorln("Unable to archive channel '" + channel.Name + "' error: " + result.Err.Error())
} }
} }
return nil return nil
} }
func deleteChannelsCmdF(cmd *cobra.Command, args []string) error { func deleteChannelsCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -276,10 +280,10 @@ func deleteChannelsCmdF(cmd *cobra.Command, args []string) error {
return errors.New("Enter at least one channel to delete.") return errors.New("Enter at least one channel to delete.")
} }
confirmFlag, _ := cmd.Flags().GetBool("confirm") confirmFlag, _ := command.Flags().GetBool("confirm")
if !confirmFlag { if !confirmFlag {
var confirm string var confirm string
CommandPrettyPrintln("Are you sure you want to delete the channels specified? All data will be permanently deleted? (YES/NO): ") cmd.CommandPrettyPrintln("Are you sure you want to delete the channels specified? All data will be permanently deleted? (YES/NO): ")
fmt.Scanln(&confirm) fmt.Scanln(&confirm)
if confirm != "YES" { if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.") return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
@@ -289,13 +293,13 @@ func deleteChannelsCmdF(cmd *cobra.Command, args []string) error {
channels := getChannelsFromChannelArgs(a, args) channels := getChannelsFromChannelArgs(a, args)
for i, channel := range channels { for i, channel := range channels {
if channel == nil { if channel == nil {
CommandPrintErrorln("Unable to find channel '" + args[i] + "'") cmd.CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
continue continue
} }
if err := deleteChannel(a, channel); err != nil { if err := deleteChannel(a, channel); err != nil {
CommandPrintErrorln("Unable to delete channel '" + channel.Name + "' error: " + err.Error()) cmd.CommandPrintErrorln("Unable to delete channel '" + channel.Name + "' error: " + err.Error())
} else { } else {
CommandPrettyPrintln("Deleted channel '" + channel.Name + "'") cmd.CommandPrettyPrintln("Deleted channel '" + channel.Name + "'")
} }
} }
@@ -306,8 +310,8 @@ func deleteChannel(a *app.App, channel *model.Channel) *model.AppError {
return a.PermanentDeleteChannel(channel) return a.PermanentDeleteChannel(channel)
} }
func moveChannelsCmdF(cmd *cobra.Command, args []string) error { func moveChannelsCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -321,7 +325,7 @@ func moveChannelsCmdF(cmd *cobra.Command, args []string) error {
return errors.New("Unable to find destination team '" + args[0] + "'") return errors.New("Unable to find destination team '" + args[0] + "'")
} }
username, erru := cmd.Flags().GetString("username") username, erru := command.Flags().GetString("username")
if erru != nil || username == "" { if erru != nil || username == "" {
return errors.New("Username is required") return errors.New("Username is required")
} }
@@ -330,14 +334,14 @@ func moveChannelsCmdF(cmd *cobra.Command, args []string) error {
channels := getChannelsFromChannelArgs(a, args[1:]) channels := getChannelsFromChannelArgs(a, args[1:])
for i, channel := range channels { for i, channel := range channels {
if channel == nil { if channel == nil {
CommandPrintErrorln("Unable to find channel '" + args[i] + "'") cmd.CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
continue continue
} }
originTeamID := channel.TeamId originTeamID := channel.TeamId
if err := moveChannel(a, team, channel, user); err != nil { if err := moveChannel(a, team, channel, user); err != nil {
CommandPrintErrorln("Unable to move channel '" + channel.Name + "' error: " + err.Error()) cmd.CommandPrintErrorln("Unable to move channel '" + channel.Name + "' error: " + err.Error())
} else { } else {
CommandPrettyPrintln("Moved channel '" + channel.Name + "' to " + team.Name + "(" + team.Id + ") from " + originTeamID + ".") cmd.CommandPrettyPrintln("Moved channel '" + channel.Name + "' to " + team.Name + "(" + team.Id + ") from " + originTeamID + ".")
} }
} }
@@ -358,7 +362,7 @@ func moveChannel(a *app.App, team *model.Team, channel *model.Channel, user *mod
if webhook.ChannelId == channel.Id { if webhook.ChannelId == channel.Id {
webhook.TeamId = team.Id webhook.TeamId = team.Id
if result := <-a.Srv.Store.Webhook().UpdateIncoming(webhook); result.Err != nil { if result := <-a.Srv.Store.Webhook().UpdateIncoming(webhook); result.Err != nil {
CommandPrintErrorln("Failed to move incoming webhook '" + webhook.Id + "' to new team.") cmd.CommandPrintErrorln("Failed to move incoming webhook '" + webhook.Id + "' to new team.")
} }
} }
} }
@@ -371,7 +375,7 @@ func moveChannel(a *app.App, team *model.Team, channel *model.Channel, user *mod
if webhook.ChannelId == channel.Id { if webhook.ChannelId == channel.Id {
webhook.TeamId = team.Id webhook.TeamId = team.Id
if result := <-a.Srv.Store.Webhook().UpdateOutgoing(webhook); result.Err != nil { if result := <-a.Srv.Store.Webhook().UpdateOutgoing(webhook); result.Err != nil {
CommandPrintErrorln("Failed to move outgoing webhook '" + webhook.Id + "' to new team.") cmd.CommandPrintErrorln("Failed to move outgoing webhook '" + webhook.Id + "' to new team.")
} }
} }
} }
@@ -380,8 +384,8 @@ func moveChannel(a *app.App, team *model.Team, channel *model.Channel, user *mod
return nil return nil
} }
func listChannelsCmdF(cmd *cobra.Command, args []string) error { func listChannelsCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -393,19 +397,19 @@ func listChannelsCmdF(cmd *cobra.Command, args []string) error {
teams := getTeamsFromTeamArgs(a, args) teams := getTeamsFromTeamArgs(a, args)
for i, team := range teams { for i, team := range teams {
if team == nil { if team == nil {
CommandPrintErrorln("Unable to find team '" + args[i] + "'") cmd.CommandPrintErrorln("Unable to find team '" + args[i] + "'")
continue continue
} }
if result := <-a.Srv.Store.Channel().GetAll(team.Id); result.Err != nil { if result := <-a.Srv.Store.Channel().GetAll(team.Id); result.Err != nil {
CommandPrintErrorln("Unable to list channels for '" + args[i] + "'") cmd.CommandPrintErrorln("Unable to list channels for '" + args[i] + "'")
} else { } else {
channels := result.Data.([]*model.Channel) channels := result.Data.([]*model.Channel)
for _, channel := range channels { for _, channel := range channels {
if channel.DeleteAt > 0 { if channel.DeleteAt > 0 {
CommandPrettyPrintln(channel.Name + " (archived)") cmd.CommandPrettyPrintln(channel.Name + " (archived)")
} else { } else {
CommandPrettyPrintln(channel.Name) cmd.CommandPrettyPrintln(channel.Name)
} }
} }
} }
@@ -414,8 +418,8 @@ func listChannelsCmdF(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func restoreChannelsCmdF(cmd *cobra.Command, args []string) error { func restoreChannelsCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -427,19 +431,19 @@ func restoreChannelsCmdF(cmd *cobra.Command, args []string) error {
channels := getChannelsFromChannelArgs(a, args) channels := getChannelsFromChannelArgs(a, args)
for i, channel := range channels { for i, channel := range channels {
if channel == nil { if channel == nil {
CommandPrintErrorln("Unable to find channel '" + args[i] + "'") cmd.CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
continue continue
} }
if result := <-a.Srv.Store.Channel().SetDeleteAt(channel.Id, 0, model.GetMillis()); result.Err != nil { if result := <-a.Srv.Store.Channel().SetDeleteAt(channel.Id, 0, model.GetMillis()); result.Err != nil {
CommandPrintErrorln("Unable to restore channel '" + args[i] + "'") cmd.CommandPrintErrorln("Unable to restore channel '" + args[i] + "'")
} }
} }
return nil return nil
} }
func modifyChannelCmdF(cmd *cobra.Command, args []string) error { func modifyChannelCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -448,13 +452,13 @@ func modifyChannelCmdF(cmd *cobra.Command, args []string) error {
return errors.New("Enter at one channel to modify.") return errors.New("Enter at one channel to modify.")
} }
username, erru := cmd.Flags().GetString("username") username, erru := command.Flags().GetString("username")
if erru != nil || username == "" { if erru != nil || username == "" {
return errors.New("Username is required") return errors.New("Username is required")
} }
public, _ := cmd.Flags().GetBool("public") public, _ := command.Flags().GetBool("public")
private, _ := cmd.Flags().GetBool("private") private, _ := command.Flags().GetBool("private")
if public == private { if public == private {
return errors.New("You must specify only one of --public or --private") return errors.New("You must specify only one of --public or --private")

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

@@ -1,13 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main package commands
import ( import (
"strings" "strings"
"testing" "testing"
"github.com/mattermost/mattermost-server/api" "github.com/mattermost/mattermost-server/api"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -18,13 +19,13 @@ func TestJoinChannel(t *testing.T) {
channel := th.CreateChannel(th.BasicClient, th.BasicTeam) channel := th.CreateChannel(th.BasicClient, th.BasicTeam)
checkCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email) cmd.CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
// Joining twice should succeed // Joining twice should succeed
checkCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email) cmd.CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
// should fail because channel does not exist // should fail because channel does not exist
require.Error(t, runCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name+"asdf", th.BasicUser2.Email)) require.Error(t, cmd.RunCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name+"asdf", th.BasicUser2.Email))
} }
func TestRemoveChannel(t *testing.T) { func TestRemoveChannel(t *testing.T) {
@@ -33,15 +34,15 @@ func TestRemoveChannel(t *testing.T) {
channel := th.CreateChannel(th.BasicClient, th.BasicTeam) channel := th.CreateChannel(th.BasicClient, th.BasicTeam)
checkCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email) cmd.CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
// should fail because channel does not exist // should fail because channel does not exist
require.Error(t, runCommand(t, "channel", "remove", th.BasicTeam.Name+":doesnotexist", th.BasicUser2.Email)) require.Error(t, cmd.RunCommand(t, "channel", "remove", th.BasicTeam.Name+":doesnotexist", th.BasicUser2.Email))
checkCommand(t, "channel", "remove", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email) cmd.CheckCommand(t, "channel", "remove", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
// Leaving twice should succeed // Leaving twice should succeed
checkCommand(t, "channel", "remove", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email) cmd.CheckCommand(t, "channel", "remove", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
} }
func TestMoveChannel(t *testing.T) { func TestMoveChannel(t *testing.T) {
@@ -63,12 +64,12 @@ func TestMoveChannel(t *testing.T) {
origin := team1.Name + ":" + channel.Name origin := team1.Name + ":" + channel.Name
dest := team2.Name dest := team2.Name
checkCommand(t, "channel", "add", origin, adminEmail) cmd.CheckCommand(t, "channel", "add", origin, adminEmail)
// should fail with nill because errors are logged instead of returned when a channel does not exist // should fail with nill because errors are logged instead of returned when a channel does not exist
require.Nil(t, runCommand(t, "channel", "move", dest, team1.Name+":doesnotexist", "--username", adminUsername)) require.Nil(t, cmd.RunCommand(t, "channel", "move", dest, team1.Name+":doesnotexist", "--username", adminUsername))
checkCommand(t, "channel", "move", dest, origin, "--username", adminUsername) cmd.CheckCommand(t, "channel", "move", dest, origin, "--username", adminUsername)
} }
func TestListChannels(t *testing.T) { func TestListChannels(t *testing.T) {
@@ -78,7 +79,7 @@ func TestListChannels(t *testing.T) {
channel := th.CreateChannel(th.BasicClient, th.BasicTeam) channel := th.CreateChannel(th.BasicClient, th.BasicTeam)
th.BasicClient.Must(th.BasicClient.DeleteChannel(channel.Id)) th.BasicClient.Must(th.BasicClient.DeleteChannel(channel.Id))
output := checkCommand(t, "channel", "list", th.BasicTeam.Name) output := cmd.CheckCommand(t, "channel", "list", th.BasicTeam.Name)
if !strings.Contains(string(output), "town-square") { if !strings.Contains(string(output), "town-square") {
t.Fatal("should have channels") t.Fatal("should have channels")
@@ -96,10 +97,10 @@ func TestRestoreChannel(t *testing.T) {
channel := th.CreateChannel(th.BasicClient, th.BasicTeam) channel := th.CreateChannel(th.BasicClient, th.BasicTeam)
th.BasicClient.Must(th.BasicClient.DeleteChannel(channel.Id)) th.BasicClient.Must(th.BasicClient.DeleteChannel(channel.Id))
checkCommand(t, "channel", "restore", th.BasicTeam.Name+":"+channel.Name) cmd.CheckCommand(t, "channel", "restore", th.BasicTeam.Name+":"+channel.Name)
// restoring twice should succeed // restoring twice should succeed
checkCommand(t, "channel", "restore", th.BasicTeam.Name+":"+channel.Name) cmd.CheckCommand(t, "channel", "restore", th.BasicTeam.Name+":"+channel.Name)
} }
func TestCreateChannel(t *testing.T) { func TestCreateChannel(t *testing.T) {
@@ -109,8 +110,8 @@ func TestCreateChannel(t *testing.T) {
id := model.NewId() id := model.NewId()
name := "name" + id name := "name" + id
checkCommand(t, "channel", "create", "--display_name", name, "--team", th.BasicTeam.Name, "--name", name) cmd.CheckCommand(t, "channel", "create", "--display_name", name, "--team", th.BasicTeam.Name, "--name", name)
name = name + "-private" name = name + "-private"
checkCommand(t, "channel", "create", "--display_name", name, "--team", th.BasicTeam.Name, "--private", "--name", name) cmd.CheckCommand(t, "channel", "create", "--display_name", name, "--team", th.BasicTeam.Name, "--private", "--name", name)
} }

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

@@ -1,6 +1,7 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main
package commands
import ( import (
"fmt" "fmt"

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

@@ -1,20 +1,22 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main
package commands
import ( import (
"errors" "errors"
"github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var commandCmd = &cobra.Command{ var CommandCmd = &cobra.Command{
Use: "command", Use: "command",
Short: "Management of slash commands", Short: "Management of slash commands",
} }
var commandMoveCmd = &cobra.Command{ var CommandMoveCmd = &cobra.Command{
Use: "move", Use: "move",
Short: "Move a slash command to a different team", Short: "Move a slash command to a different team",
Long: `Move a slash command to a different team. Commands can be specified by [team]:[command-trigger-word]. ie. myteam:trigger or by command ID.`, Long: `Move a slash command to a different team. Commands can be specified by [team]:[command-trigger-word]. ie. myteam:trigger or by command ID.`,
@@ -23,13 +25,14 @@ var commandMoveCmd = &cobra.Command{
} }
func init() { func init() {
commandCmd.AddCommand( CommandCmd.AddCommand(
commandMoveCmd, CommandMoveCmd,
) )
cmd.RootCmd.AddCommand(CommandCmd)
} }
func moveCommandCmdF(cmd *cobra.Command, args []string) error { func moveCommandCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -44,16 +47,16 @@ func moveCommandCmdF(cmd *cobra.Command, args []string) error {
} }
commands := getCommandsFromCommandArgs(a, args[1:]) commands := getCommandsFromCommandArgs(a, args[1:])
CommandPrintErrorln(commands) cmd.CommandPrintErrorln(commands)
for i, command := range commands { for i, command := range commands {
if command == nil { if command == nil {
CommandPrintErrorln("Unable to find command '" + args[i+1] + "'") cmd.CommandPrintErrorln("Unable to find command '" + args[i+1] + "'")
continue continue
} }
if err := moveCommand(a, team, command); err != nil { if err := moveCommand(a, team, command); err != nil {
CommandPrintErrorln("Unable to move command '" + command.Trigger + "' error: " + err.Error()) cmd.CommandPrintErrorln("Unable to move command '" + command.Trigger + "' error: " + err.Error())
} else { } else {
CommandPrettyPrintln("Moved command '" + command.Trigger + "'") cmd.CommandPrettyPrintln("Moved command '" + command.Trigger + "'")
} }
} }

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

@@ -1,6 +1,7 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main
package commands
import ( import (
"fmt" "fmt"

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

@@ -1,23 +1,25 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main
package commands
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"os" "os"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/utils"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var configCmd = &cobra.Command{ var ConfigCmd = &cobra.Command{
Use: "config", Use: "config",
Short: "Configuration", Short: "Configuration",
} }
var validateConfigCmd = &cobra.Command{ var ValidateConfigCmd = &cobra.Command{
Use: "validate", Use: "validate",
Short: "Validate config file", Short: "Validate config file",
Long: "If the config file is valid, this command will output a success message and have a zero exit code. If it is invalid, this command will output an error and have a non-zero exit code.", Long: "If the config file is valid, this command will output a success message and have a zero exit code. If it is invalid, this command will output an error and have a non-zero exit code.",
@@ -25,15 +27,16 @@ var validateConfigCmd = &cobra.Command{
} }
func init() { func init() {
configCmd.AddCommand( ConfigCmd.AddCommand(
validateConfigCmd, ValidateConfigCmd,
) )
cmd.RootCmd.AddCommand(ConfigCmd)
} }
func configValidateCmdF(cmd *cobra.Command, args []string) error { func configValidateCmdF(command *cobra.Command, args []string) error {
utils.TranslationsPreInit() utils.TranslationsPreInit()
model.AppErrorInit(utils.T) model.AppErrorInit(utils.T)
filePath, err := cmd.Flags().GetString("config") filePath, err := command.Flags().GetString("config")
if err != nil { if err != nil {
return err return err
} }
@@ -60,6 +63,6 @@ func configValidateCmdF(cmd *cobra.Command, args []string) error {
return errors.New(utils.T(err.Id)) return errors.New(utils.T(err.Id))
} }
CommandPrettyPrintln("The document is valid") cmd.CommandPrettyPrintln("The document is valid")
return nil return nil
} }

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

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main package commands
import ( import (
"io/ioutil" "io/ioutil"
@@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/utils"
) )
@@ -34,8 +35,8 @@ func TestConfigFlag(t *testing.T) {
defer os.Chdir(prevDir) defer os.Chdir(prevDir)
os.Chdir(dir) os.Chdir(dir)
require.Error(t, runCommand(t, "version")) require.Error(t, cmd.RunCommand(t, "version"))
checkCommand(t, "--config", "foo.json", "version") cmd.CheckCommand(t, "--config", "foo.json", "version")
checkCommand(t, "--config", "./foo.json", "version") cmd.CheckCommand(t, "--config", "./foo.json", "version")
checkCommand(t, "--config", configPath, "version") cmd.CheckCommand(t, "--config", configPath, "version")
} }

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

@@ -1,7 +1,7 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main package commands
import ( import (
"io/ioutil" "io/ioutil"
@@ -9,6 +9,7 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/mattermost/mattermost-server/cmd"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -25,6 +26,6 @@ func TestConfigValidate(t *testing.T) {
config.SetDefaults() config.SetDefaults()
require.NoError(t, ioutil.WriteFile(path, []byte(config.ToJson()), 0600)) require.NoError(t, ioutil.WriteFile(path, []byte(config.ToJson()), 0600))
assert.Error(t, runCommand(t, "--config", "foo.json", "config", "validate")) assert.Error(t, cmd.RunCommand(t, "--config", "foo.json", "config", "validate"))
assert.NoError(t, runCommand(t, "--config", path, "config", "validate")) assert.NoError(t, cmd.RunCommand(t, "--config", path, "config", "validate"))
} }

21
cmd/commands/exec_command_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,21 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"flag"
"testing"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/cmd"
)
func TestExecCommand(t *testing.T) {
if filter := flag.Lookup("test.run").Value.String(); filter != "ExecCommand" {
t.Skip("use -run ExecCommand to execute a command via the test executable")
}
cmd.RootCmd.SetArgs(flag.Args())
require.NoError(t, cmd.RootCmd.Execute())
}

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

@@ -1,6 +1,7 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main
package commands
import ( import (
"errors" "errors"
@@ -8,15 +9,16 @@ import (
"fmt" "fmt"
"github.com/mattermost/mattermost-server/cmd"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var importCmd = &cobra.Command{ var ImportCmd = &cobra.Command{
Use: "import", Use: "import",
Short: "Import data.", Short: "Import data.",
} }
var slackImportCmd = &cobra.Command{ var SlackImportCmd = &cobra.Command{
Use: "slack [team] [file]", Use: "slack [team] [file]",
Short: "Import a team from Slack.", Short: "Import a team from Slack.",
Long: "Import a team from a Slack export zip file.", Long: "Import a team from a Slack export zip file.",
@@ -24,7 +26,7 @@ var slackImportCmd = &cobra.Command{
RunE: slackImportCmdF, RunE: slackImportCmdF,
} }
var bulkImportCmd = &cobra.Command{ var BulkImportCmd = &cobra.Command{
Use: "bulk [file]", Use: "bulk [file]",
Short: "Import bulk data.", Short: "Import bulk data.",
Long: "Import data from a Mattermost Bulk Import File.", Long: "Import data from a Mattermost Bulk Import File.",
@@ -33,18 +35,19 @@ var bulkImportCmd = &cobra.Command{
} }
func init() { func init() {
bulkImportCmd.Flags().Bool("apply", false, "Save the import data to the database. Use with caution - this cannot be reverted.") BulkImportCmd.Flags().Bool("apply", false, "Save the import data to the database. Use with caution - this cannot be reverted.")
bulkImportCmd.Flags().Bool("validate", false, "Validate the import data without making any changes to the system.") BulkImportCmd.Flags().Bool("validate", false, "Validate the import data without making any changes to the system.")
bulkImportCmd.Flags().Int("workers", 2, "How many workers to run whilst doing the import.") BulkImportCmd.Flags().Int("workers", 2, "How many workers to run whilst doing the import.")
importCmd.AddCommand( ImportCmd.AddCommand(
bulkImportCmd, BulkImportCmd,
slackImportCmd, SlackImportCmd,
) )
cmd.RootCmd.AddCommand(ImportCmd)
} }
func slackImportCmdF(cmd *cobra.Command, args []string) error { func slackImportCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -69,32 +72,32 @@ func slackImportCmdF(cmd *cobra.Command, args []string) error {
return err return err
} }
CommandPrettyPrintln("Running Slack Import. This may take a long time for large teams or teams with many messages.") cmd.CommandPrettyPrintln("Running Slack Import. This may take a long time for large teams or teams with many messages.")
a.SlackImport(fileReader, fileInfo.Size(), team.Id) a.SlackImport(fileReader, fileInfo.Size(), team.Id)
CommandPrettyPrintln("Finished Slack Import.") cmd.CommandPrettyPrintln("Finished Slack Import.")
return nil return nil
} }
func bulkImportCmdF(cmd *cobra.Command, args []string) error { func bulkImportCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
apply, err := cmd.Flags().GetBool("apply") apply, err := command.Flags().GetBool("apply")
if err != nil { if err != nil {
return errors.New("Apply flag error") return errors.New("Apply flag error")
} }
validate, err := cmd.Flags().GetBool("validate") validate, err := command.Flags().GetBool("validate")
if err != nil { if err != nil {
return errors.New("Validate flag error") return errors.New("Validate flag error")
} }
workers, err := cmd.Flags().GetInt("workers") workers, err := command.Flags().GetInt("workers")
if err != nil { if err != nil {
return errors.New("Workers flag error") return errors.New("Workers flag error")
} }
@@ -110,28 +113,28 @@ func bulkImportCmdF(cmd *cobra.Command, args []string) error {
defer fileReader.Close() defer fileReader.Close()
if apply && validate { if apply && validate {
CommandPrettyPrintln("Use only one of --apply or --validate.") cmd.CommandPrettyPrintln("Use only one of --apply or --validate.")
return nil return nil
} else if apply && !validate { } else if apply && !validate {
CommandPrettyPrintln("Running Bulk Import. This may take a long time.") cmd.CommandPrettyPrintln("Running Bulk Import. This may take a long time.")
} else { } else {
CommandPrettyPrintln("Running Bulk Import Data Validation.") cmd.CommandPrettyPrintln("Running Bulk Import Data Validation.")
CommandPrettyPrintln("** This checks the validity of the entities in the data file, but does not persist any changes **") cmd.CommandPrettyPrintln("** This checks the validity of the entities in the data file, but does not persist any changes **")
CommandPrettyPrintln("Use the --apply flag to perform the actual data import.") cmd.CommandPrettyPrintln("Use the --apply flag to perform the actual data import.")
} }
CommandPrettyPrintln("") cmd.CommandPrettyPrintln("")
if err, lineNumber := a.BulkImport(fileReader, !apply, workers); err != nil { if err, lineNumber := a.BulkImport(fileReader, !apply, workers); err != nil {
CommandPrettyPrintln(err.Error()) cmd.CommandPrettyPrintln(err.Error())
if lineNumber != 0 { if lineNumber != 0 {
CommandPrettyPrintln(fmt.Sprintf("Error occurred on data file line %v", lineNumber)) cmd.CommandPrettyPrintln(fmt.Sprintf("Error occurred on data file line %v", lineNumber))
} }
} else { } else {
if apply { if apply {
CommandPrettyPrintln("Finished Bulk Import.") cmd.CommandPrettyPrintln("Finished Bulk Import.")
} else { } else {
CommandPrettyPrintln("Validation complete. You can now perform the import by rerunning this command with the --apply flag.") cmd.CommandPrettyPrintln("Validation complete. You can now perform the import by rerunning this command with the --apply flag.")
} }
} }

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

@@ -1,6 +1,7 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main
package commands
import ( import (
"os" "os"
@@ -8,27 +9,30 @@ import (
"syscall" "syscall"
l4g "github.com/alecthomas/log4go" l4g "github.com/alecthomas/log4go"
"github.com/mattermost/mattermost-server/cmd"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var jobserverCmd = &cobra.Command{ var JobserverCmd = &cobra.Command{
Use: "jobserver", Use: "jobserver",
Short: "Start the Mattermost job server", Short: "Start the Mattermost job server",
Run: jobserverCmdF, Run: jobserverCmdF,
} }
func init() { func init() {
jobserverCmd.Flags().Bool("nojobs", false, "Do not run jobs on this jobserver.") JobserverCmd.Flags().Bool("nojobs", false, "Do not run jobs on this jobserver.")
jobserverCmd.Flags().Bool("noschedule", false, "Do not schedule jobs from this jobserver.") JobserverCmd.Flags().Bool("noschedule", false, "Do not schedule jobs from this jobserver.")
cmd.RootCmd.AddCommand(JobserverCmd)
} }
func jobserverCmdF(cmd *cobra.Command, args []string) { func jobserverCmdF(command *cobra.Command, args []string) {
// Options // Options
noJobs, _ := cmd.Flags().GetBool("nojobs") noJobs, _ := command.Flags().GetBool("nojobs")
noSchedule, _ := cmd.Flags().GetBool("noschedule") noSchedule, _ := command.Flags().GetBool("noschedule")
// Initialize // Initialize
a, err := initDBCommandContext("config.json") a, err := cmd.InitDBCommandContext("config.json")
if err != nil { if err != nil {
panic(err.Error()) panic(err.Error())
} }

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

@@ -1,18 +1,20 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main
package commands
import ( import (
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var ldapCmd = &cobra.Command{ var LdapCmd = &cobra.Command{
Use: "ldap", Use: "ldap",
Short: "LDAP related utilities", Short: "LDAP related utilities",
} }
var ldapSyncCmd = &cobra.Command{ var LdapSyncCmd = &cobra.Command{
Use: "sync", Use: "sync",
Short: "Synchronize now", Short: "Synchronize now",
Long: "Synchronize all LDAP users now.", Long: "Synchronize all LDAP users now.",
@@ -21,13 +23,14 @@ var ldapSyncCmd = &cobra.Command{
} }
func init() { func init() {
ldapCmd.AddCommand( LdapCmd.AddCommand(
ldapSyncCmd, LdapSyncCmd,
) )
cmd.RootCmd.AddCommand(LdapCmd)
} }
func ldapSyncCmdF(cmd *cobra.Command, args []string) error { func ldapSyncCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -35,9 +38,9 @@ func ldapSyncCmdF(cmd *cobra.Command, args []string) error {
if ldapI := a.Ldap; ldapI != nil { if ldapI := a.Ldap; ldapI != nil {
job, err := ldapI.StartSynchronizeJob(true) job, err := ldapI.StartSynchronizeJob(true)
if err != nil || job.Status == model.JOB_STATUS_ERROR || job.Status == model.JOB_STATUS_CANCELED { if err != nil || job.Status == model.JOB_STATUS_ERROR || job.Status == model.JOB_STATUS_CANCELED {
CommandPrintErrorln("ERROR: AD/LDAP Synchronization please check the server logs") cmd.CommandPrintErrorln("ERROR: AD/LDAP Synchronization please check the server logs")
} else { } else {
CommandPrettyPrintln("SUCCESS: AD/LDAP Synchronization Complete") cmd.CommandPrettyPrintln("SUCCESS: AD/LDAP Synchronization Complete")
} }
} }

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

@@ -1,20 +1,22 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main
package commands
import ( import (
"errors" "errors"
"io/ioutil" "io/ioutil"
"github.com/mattermost/mattermost-server/cmd"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var licenseCmd = &cobra.Command{ var LicenseCmd = &cobra.Command{
Use: "license", Use: "license",
Short: "Licensing commands", Short: "Licensing commands",
} }
var uploadLicenseCmd = &cobra.Command{ var UploadLicenseCmd = &cobra.Command{
Use: "upload [license]", Use: "upload [license]",
Short: "Upload a license.", Short: "Upload a license.",
Long: "Upload a license. Replaces current license.", Long: "Upload a license. Replaces current license.",
@@ -23,11 +25,12 @@ var uploadLicenseCmd = &cobra.Command{
} }
func init() { func init() {
licenseCmd.AddCommand(uploadLicenseCmd) LicenseCmd.AddCommand(UploadLicenseCmd)
cmd.RootCmd.AddCommand(LicenseCmd)
} }
func uploadLicenseCmdF(cmd *cobra.Command, args []string) error { func uploadLicenseCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -45,7 +48,7 @@ func uploadLicenseCmdF(cmd *cobra.Command, args []string) error {
return err return err
} }
CommandPrettyPrintln("Uploaded license file") cmd.CommandPrettyPrintln("Uploaded license file")
return nil return nil
} }

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

@@ -1,7 +1,7 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main package commands
import ( import (
"errors" "errors"
@@ -10,11 +10,12 @@ import (
"time" "time"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var messageExportCmd = &cobra.Command{ var MessageExportCmd = &cobra.Command{
Use: "export", Use: "export",
Short: "Export data from Mattermost", Short: "Export data from Mattermost",
Long: "Export data from Mattermost in a format suitable for import into a third-party application", Long: "Export data from Mattermost in a format suitable for import into a third-party application",
@@ -23,13 +24,14 @@ var messageExportCmd = &cobra.Command{
} }
func init() { func init() {
messageExportCmd.Flags().String("format", "actiance", "The format to export data in") MessageExportCmd.Flags().String("format", "actiance", "The format to export data in")
messageExportCmd.Flags().Int64("exportFrom", -1, "The timestamp of the earliest post to export, expressed in seconds since the unix epoch.") MessageExportCmd.Flags().Int64("exportFrom", -1, "The timestamp of the earliest post to export, expressed in seconds since the unix epoch.")
messageExportCmd.Flags().Int("timeoutSeconds", -1, "The maximum number of seconds to wait for the job to complete before timing out.") MessageExportCmd.Flags().Int("timeoutSeconds", -1, "The maximum number of seconds to wait for the job to complete before timing out.")
cmd.RootCmd.AddCommand(MessageExportCmd)
} }
func messageExportCmdF(cmd *cobra.Command, args []string) error { func messageExportCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -39,20 +41,20 @@ func messageExportCmdF(cmd *cobra.Command, args []string) error {
} }
// for now, format is hard-coded to actiance. In time, we'll have to support other formats and inject them into job data // for now, format is hard-coded to actiance. In time, we'll have to support other formats and inject them into job data
if format, err := cmd.Flags().GetString("format"); err != nil { if format, err := command.Flags().GetString("format"); err != nil {
return errors.New("format flag error") return errors.New("format flag error")
} else if format != "actiance" { } else if format != "actiance" {
return errors.New("unsupported export format") return errors.New("unsupported export format")
} }
startTime, err := cmd.Flags().GetInt64("exportFrom") startTime, err := command.Flags().GetInt64("exportFrom")
if err != nil { if err != nil {
return errors.New("exportFrom flag error") return errors.New("exportFrom flag error")
} else if startTime < 0 { } else if startTime < 0 {
return errors.New("exportFrom must be a positive integer") return errors.New("exportFrom must be a positive integer")
} }
timeoutSeconds, err := cmd.Flags().GetInt("timeoutSeconds") timeoutSeconds, err := command.Flags().GetInt("timeoutSeconds")
if err != nil { if err != nil {
return errors.New("timeoutSeconds error") return errors.New("timeoutSeconds error")
} else if timeoutSeconds < 0 { } else if timeoutSeconds < 0 {
@@ -69,9 +71,9 @@ func messageExportCmdF(cmd *cobra.Command, args []string) error {
job, err := messageExportI.StartSynchronizeJob(ctx, startTime) job, err := messageExportI.StartSynchronizeJob(ctx, startTime)
if err != nil || job.Status == model.JOB_STATUS_ERROR || job.Status == model.JOB_STATUS_CANCELED { if err != nil || job.Status == model.JOB_STATUS_ERROR || job.Status == model.JOB_STATUS_CANCELED {
CommandPrintErrorln("ERROR: Message export job failed. Please check the server logs") cmd.CommandPrintErrorln("ERROR: Message export job failed. Please check the server logs")
} else { } else {
CommandPrettyPrintln("SUCCESS: Message export job complete") cmd.CommandPrettyPrintln("SUCCESS: Message export job complete")
} }
} }

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

@@ -1,7 +1,7 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main package commands
import ( import (
"io/ioutil" "io/ioutil"
@@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/utils"
) )
@@ -24,7 +25,7 @@ func TestMessageExportNotEnabled(t *testing.T) {
defer os.RemoveAll(filepath.Dir(configPath)) defer os.RemoveAll(filepath.Dir(configPath))
// should fail fast because the feature isn't enabled // should fail fast because the feature isn't enabled
require.Error(t, runCommand(t, "--config", configPath, "export")) require.Error(t, cmd.RunCommand(t, "--config", configPath, "export"))
} }
func TestMessageExportInvalidFormat(t *testing.T) { func TestMessageExportInvalidFormat(t *testing.T) {
@@ -32,7 +33,7 @@ func TestMessageExportInvalidFormat(t *testing.T) {
defer os.RemoveAll(filepath.Dir(configPath)) defer os.RemoveAll(filepath.Dir(configPath))
// should fail fast because format isn't supported // should fail fast because format isn't supported
require.Error(t, runCommand(t, "--config", configPath, "--format", "not_actiance", "export")) require.Error(t, cmd.RunCommand(t, "--config", configPath, "--format", "not_actiance", "export"))
} }
func TestMessageExportNegativeExportFrom(t *testing.T) { func TestMessageExportNegativeExportFrom(t *testing.T) {
@@ -40,7 +41,7 @@ func TestMessageExportNegativeExportFrom(t *testing.T) {
defer os.RemoveAll(filepath.Dir(configPath)) defer os.RemoveAll(filepath.Dir(configPath))
// should fail fast because export from must be a valid timestamp // should fail fast because export from must be a valid timestamp
require.Error(t, runCommand(t, "--config", configPath, "--format", "actiance", "--exportFrom", "-1", "export")) require.Error(t, cmd.RunCommand(t, "--config", configPath, "--format", "actiance", "--exportFrom", "-1", "export"))
} }
func TestMessageExportNegativeTimeoutSeconds(t *testing.T) { func TestMessageExportNegativeTimeoutSeconds(t *testing.T) {
@@ -48,7 +49,7 @@ func TestMessageExportNegativeTimeoutSeconds(t *testing.T) {
defer os.RemoveAll(filepath.Dir(configPath)) defer os.RemoveAll(filepath.Dir(configPath))
// should fail fast because timeout seconds must be a positive int // should fail fast because timeout seconds must be a positive int
require.Error(t, runCommand(t, "--config", configPath, "--format", "actiance", "--exportFrom", "0", "--timeoutSeconds", "-1", "export")) require.Error(t, cmd.RunCommand(t, "--config", configPath, "--format", "actiance", "--exportFrom", "0", "--timeoutSeconds", "-1", "export"))
} }
func writeTempConfig(t *testing.T, isMessageExportEnabled bool) string { func writeTempConfig(t *testing.T, isMessageExportEnabled bool) string {

53
cmd/commands/reset.go Обычный файл
Просмотреть файл

@@ -0,0 +1,53 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"errors"
"fmt"
"github.com/mattermost/mattermost-server/cmd"
"github.com/spf13/cobra"
)
var ResetCmd = &cobra.Command{
Use: "reset",
Short: "Reset the database to initial state",
Long: "Completely erases the database causing the loss of all data. This will reset Mattermost to its initial state.",
RunE: resetCmdF,
}
func init() {
ResetCmd.Flags().Bool("confirm", false, "Confirm you really want to delete everything and a DB backup has been performed.")
cmd.RootCmd.AddCommand(ResetCmd)
}
func resetCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
confirmFlag, _ := command.Flags().GetBool("confirm")
if !confirmFlag {
var confirm string
cmd.CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ")
fmt.Scanln(&confirm)
if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
}
cmd.CommandPrettyPrintln("Are you sure you want to delete everything? All data will be permanently deleted? (YES/NO): ")
fmt.Scanln(&confirm)
if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
}
}
a.Srv.Store.DropAllTables()
cmd.CommandPrettyPrintln("Database sucessfully reset")
return nil
}

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

@@ -1,19 +1,21 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main
package commands
import ( import (
"errors" "errors"
"github.com/mattermost/mattermost-server/cmd"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var rolesCmd = &cobra.Command{ var RolesCmd = &cobra.Command{
Use: "roles", Use: "roles",
Short: "Management of user roles", Short: "Management of user roles",
} }
var makeSystemAdminCmd = &cobra.Command{ var MakeSystemAdminCmd = &cobra.Command{
Use: "system_admin [users]", Use: "system_admin [users]",
Short: "Set a user as system admin", Short: "Set a user as system admin",
Long: "Make some users system admins", Long: "Make some users system admins",
@@ -21,7 +23,7 @@ var makeSystemAdminCmd = &cobra.Command{
RunE: makeSystemAdminCmdF, RunE: makeSystemAdminCmdF,
} }
var makeMemberCmd = &cobra.Command{ var MakeMemberCmd = &cobra.Command{
Use: "member [users]", Use: "member [users]",
Short: "Remove system admin privileges", Short: "Remove system admin privileges",
Long: "Remove system admin privileges from some users.", Long: "Remove system admin privileges from some users.",
@@ -30,14 +32,15 @@ var makeMemberCmd = &cobra.Command{
} }
func init() { func init() {
rolesCmd.AddCommand( RolesCmd.AddCommand(
makeSystemAdminCmd, MakeSystemAdminCmd,
makeMemberCmd, MakeMemberCmd,
) )
cmd.RootCmd.AddCommand(RolesCmd)
} }
func makeSystemAdminCmdF(cmd *cobra.Command, args []string) error { func makeSystemAdminCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -60,8 +63,8 @@ func makeSystemAdminCmdF(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func makeMemberCmdF(cmd *cobra.Command, args []string) error { func makeMemberCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }

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

@@ -1,12 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main package commands
import ( import (
"testing" "testing"
"github.com/mattermost/mattermost-server/api" "github.com/mattermost/mattermost-server/api"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
) )
@@ -14,7 +15,7 @@ func TestAssignRole(t *testing.T) {
th := api.Setup().InitBasic() th := api.Setup().InitBasic()
defer th.TearDown() defer th.TearDown()
checkCommand(t, "roles", "system_admin", th.BasicUser.Email) cmd.CheckCommand(t, "roles", "system_admin", th.BasicUser.Email)
if result := <-th.App.Srv.Store.User().GetByEmail(th.BasicUser.Email); result.Err != nil { if result := <-th.App.Srv.Store.User().GetByEmail(th.BasicUser.Email); result.Err != nil {
t.Fatal() t.Fatal()

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

@@ -1,6 +1,7 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main
package commands
import ( import (
"encoding/json" "encoding/json"
@@ -16,15 +17,34 @@ import (
"github.com/icrowley/fake" "github.com/icrowley/fake"
"github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/cmd"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var sampleDataCmd = &cobra.Command{ var SampleDataCmd = &cobra.Command{
Use: "sampledata", Use: "sampledata",
Short: "Generate sample data", Short: "Generate sample data",
RunE: sampleDataCmdF, RunE: sampleDataCmdF,
} }
func init() {
SampleDataCmd.Flags().Int64P("seed", "s", 1, "Seed used for generating the random data (Different seeds generate different data).")
SampleDataCmd.Flags().IntP("teams", "t", 2, "The number of sample teams.")
SampleDataCmd.Flags().Int("channels-per-team", 10, "The number of sample channels per team.")
SampleDataCmd.Flags().IntP("users", "u", 15, "The number of sample users.")
SampleDataCmd.Flags().Int("team-memberships", 2, "The number of sample team memberships per user.")
SampleDataCmd.Flags().Int("channel-memberships", 5, "The number of sample channel memberships per user in a team.")
SampleDataCmd.Flags().Int("posts-per-channel", 100, "The number of sample post per channel.")
SampleDataCmd.Flags().Int("direct-channels", 30, "The number of sample direct message channels.")
SampleDataCmd.Flags().Int("posts-per-direct-channel", 15, "The number of sample posts per direct message channel.")
SampleDataCmd.Flags().Int("group-channels", 15, "The number of sample group message channels.")
SampleDataCmd.Flags().Int("posts-per-group-channel", 30, "The number of sample posts per group message channel.")
SampleDataCmd.Flags().IntP("workers", "w", 2, "How many workers to run during the import.")
SampleDataCmd.Flags().String("profile-images", "", "Optional. Path to folder with images to randomly pick as user profile image.")
SampleDataCmd.Flags().StringP("bulk", "b", "", "Optional. Path to write a JSONL bulk file instead of loading into the database.")
cmd.RootCmd.AddCommand(SampleDataCmd)
}
func sliceIncludes(vs []string, t string) bool { func sliceIncludes(vs []string, t string) bool {
for _, v := range vs { for _, v := range vs {
if v == t { if v == t {
@@ -109,81 +129,64 @@ func randomMessage(users []string) string {
return message return message
} }
func init() { func sampleDataCmdF(command *cobra.Command, args []string) error {
sampleDataCmd.Flags().Int64P("seed", "s", 1, "Seed used for generating the random data (Different seeds generate different data).") a, err := cmd.InitDBCommandContextCobra(command)
sampleDataCmd.Flags().IntP("teams", "t", 2, "The number of sample teams.")
sampleDataCmd.Flags().Int("channels-per-team", 10, "The number of sample channels per team.")
sampleDataCmd.Flags().IntP("users", "u", 15, "The number of sample users.")
sampleDataCmd.Flags().Int("team-memberships", 2, "The number of sample team memberships per user.")
sampleDataCmd.Flags().Int("channel-memberships", 5, "The number of sample channel memberships per user in a team.")
sampleDataCmd.Flags().Int("posts-per-channel", 100, "The number of sample post per channel.")
sampleDataCmd.Flags().Int("direct-channels", 30, "The number of sample direct message channels.")
sampleDataCmd.Flags().Int("posts-per-direct-channel", 15, "The number of sample posts per direct message channel.")
sampleDataCmd.Flags().Int("group-channels", 15, "The number of sample group message channels.")
sampleDataCmd.Flags().Int("posts-per-group-channel", 30, "The number of sample posts per group message channel.")
sampleDataCmd.Flags().IntP("workers", "w", 2, "How many workers to run during the import.")
sampleDataCmd.Flags().String("profile-images", "", "Optional. Path to folder with images to randomly pick as user profile image.")
sampleDataCmd.Flags().StringP("bulk", "b", "", "Optional. Path to write a JSONL bulk file instead of loading into the database.")
}
func sampleDataCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil { if err != nil {
return err return err
} }
seed, err := cmd.Flags().GetInt64("seed") seed, err := command.Flags().GetInt64("seed")
if err != nil { if err != nil {
return errors.New("Invalid seed parameter") return errors.New("Invalid seed parameter")
} }
bulk, err := cmd.Flags().GetString("bulk") bulk, err := command.Flags().GetString("bulk")
if err != nil { if err != nil {
return errors.New("Invalid bulk parameter") return errors.New("Invalid bulk parameter")
} }
teams, err := cmd.Flags().GetInt("teams") teams, err := command.Flags().GetInt("teams")
if err != nil || teams < 0 { if err != nil || teams < 0 {
return errors.New("Invalid teams parameter") return errors.New("Invalid teams parameter")
} }
channelsPerTeam, err := cmd.Flags().GetInt("channels-per-team") channelsPerTeam, err := command.Flags().GetInt("channels-per-team")
if err != nil || channelsPerTeam < 0 { if err != nil || channelsPerTeam < 0 {
return errors.New("Invalid channels-per-team parameter") return errors.New("Invalid channels-per-team parameter")
} }
users, err := cmd.Flags().GetInt("users") users, err := command.Flags().GetInt("users")
if err != nil || users < 0 { if err != nil || users < 0 {
return errors.New("Invalid users parameter") return errors.New("Invalid users parameter")
} }
teamMemberships, err := cmd.Flags().GetInt("team-memberships") teamMemberships, err := command.Flags().GetInt("team-memberships")
if err != nil || teamMemberships < 0 { if err != nil || teamMemberships < 0 {
return errors.New("Invalid team-memberships parameter") return errors.New("Invalid team-memberships parameter")
} }
channelMemberships, err := cmd.Flags().GetInt("channel-memberships") channelMemberships, err := command.Flags().GetInt("channel-memberships")
if err != nil || channelMemberships < 0 { if err != nil || channelMemberships < 0 {
return errors.New("Invalid channel-memberships parameter") return errors.New("Invalid channel-memberships parameter")
} }
postsPerChannel, err := cmd.Flags().GetInt("posts-per-channel") postsPerChannel, err := command.Flags().GetInt("posts-per-channel")
if err != nil || postsPerChannel < 0 { if err != nil || postsPerChannel < 0 {
return errors.New("Invalid posts-per-channel parameter") return errors.New("Invalid posts-per-channel parameter")
} }
directChannels, err := cmd.Flags().GetInt("direct-channels") directChannels, err := command.Flags().GetInt("direct-channels")
if err != nil || directChannels < 0 { if err != nil || directChannels < 0 {
return errors.New("Invalid direct-channels parameter") return errors.New("Invalid direct-channels parameter")
} }
postsPerDirectChannel, err := cmd.Flags().GetInt("posts-per-direct-channel") postsPerDirectChannel, err := command.Flags().GetInt("posts-per-direct-channel")
if err != nil || postsPerDirectChannel < 0 { if err != nil || postsPerDirectChannel < 0 {
return errors.New("Invalid posts-per-direct-channel parameter") return errors.New("Invalid posts-per-direct-channel parameter")
} }
groupChannels, err := cmd.Flags().GetInt("group-channels") groupChannels, err := command.Flags().GetInt("group-channels")
if err != nil || groupChannels < 0 { if err != nil || groupChannels < 0 {
return errors.New("Invalid group-channels parameter") return errors.New("Invalid group-channels parameter")
} }
postsPerGroupChannel, err := cmd.Flags().GetInt("posts-per-group-channel") postsPerGroupChannel, err := command.Flags().GetInt("posts-per-group-channel")
if err != nil || postsPerGroupChannel < 0 { if err != nil || postsPerGroupChannel < 0 {
return errors.New("Invalid posts-per-group-channel parameter") return errors.New("Invalid posts-per-group-channel parameter")
} }
workers, err := cmd.Flags().GetInt("workers") workers, err := command.Flags().GetInt("workers")
if err != nil { if err != nil {
return errors.New("Invalid workers parameter") return errors.New("Invalid workers parameter")
} }
profileImagesPath, err := cmd.Flags().GetString("profile-images") profileImagesPath, err := command.Flags().GetString("profile-images")
if err != nil { if err != nil {
return errors.New("Invalid profile-images parameter") return errors.New("Invalid profile-images parameter")
} }
@@ -312,7 +315,7 @@ func sampleDataCmdF(cmd *cobra.Command, args []string) error {
} }
importErr, lineNumber := a.BulkImport(bulkFile, false, workers) importErr, lineNumber := a.BulkImport(bulkFile, false, workers)
if importErr != nil { if importErr != nil {
return errors.New(fmt.Sprintf("%s: %s, %s (line: %d)", importErr.Where, importErr.Message, importErr.DetailedError, lineNumber)) return fmt.Errorf("%s: %s, %s (line: %d)", importErr.Where, importErr.Message, importErr.DetailedError, lineNumber)
} }
} else if bulk != "-" { } else if bulk != "-" {
err := bulkFile.Close() err := bulkFile.Close()
@@ -395,7 +398,7 @@ func createUser(idx int, teamMemberships int, channelMemberships int, teamsAndCh
position := rand.Intn(len(possibleTeams)) position := rand.Intn(len(possibleTeams))
team := possibleTeams[position] team := possibleTeams[position]
possibleTeams = append(possibleTeams[:position], possibleTeams[position+1:]...) possibleTeams = append(possibleTeams[:position], possibleTeams[position+1:]...)
if teamChannels, err := teamsAndChannels[team]; err == true { if teamChannels, err := teamsAndChannels[team]; err {
teams = append(teams, createTeamMembership(channelMemberships, teamChannels, &team)) teams = append(teams, createTeamMembership(channelMemberships, teamChannels, &team))
} }
} }
@@ -429,10 +432,7 @@ func createTeamMembership(numOfchannels int, teamChannels []string, teamName *st
roles = "team_user team_admin" roles = "team_user team_admin"
} }
channels := []app.UserChannelImportData{} channels := []app.UserChannelImportData{}
teamChannelsCopy := []string{} teamChannelsCopy := append([]string(nil), teamChannels...)
for _, value := range teamChannels {
teamChannelsCopy = append(teamChannelsCopy, value)
}
for x := 0; x < numOfchannels; x++ { for x := 0; x < numOfchannels; x++ {
if len(teamChannelsCopy) == 0 { if len(teamChannelsCopy) == 0 {
break break

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

@@ -1,12 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main package commands
import ( import (
"testing" "testing"
"github.com/mattermost/mattermost-server/api" "github.com/mattermost/mattermost-server/api"
"github.com/mattermost/mattermost-server/cmd"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -15,11 +16,11 @@ func TestSampledataBadParameters(t *testing.T) {
defer th.TearDown() defer th.TearDown()
// should fail because you need at least 1 worker // should fail because you need at least 1 worker
require.Error(t, runCommand(t, "sampledata", "--workers", "0")) require.Error(t, cmd.RunCommand(t, "sampledata", "--workers", "0"))
// should fail because you have more team memberships than teams // should fail because you have more team memberships than teams
require.Error(t, runCommand(t, "sampledata", "--teams", "10", "--teams-memberships", "11")) require.Error(t, cmd.RunCommand(t, "sampledata", "--teams", "10", "--teams-memberships", "11"))
// should fail because you have more channel memberships than channels per team // should fail because you have more channel memberships than channels per team
require.Error(t, runCommand(t, "sampledata", "--channels-per-team", "10", "--channel-memberships", "11")) require.Error(t, cmd.RunCommand(t, "sampledata", "--channels-per-team", "10", "--channel-memberships", "11"))
} }

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

@@ -1,7 +1,7 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main package commands
import ( import (
"net" "net"
@@ -14,6 +14,7 @@ import (
"github.com/mattermost/mattermost-server/api" "github.com/mattermost/mattermost-server/api"
"github.com/mattermost/mattermost-server/api4" "github.com/mattermost/mattermost-server/api4"
"github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/manualtesting" "github.com/mattermost/mattermost-server/manualtesting"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/utils"
@@ -31,17 +32,22 @@ var MaxNotificationsPerChannelDefault int64 = 1000000
var serverCmd = &cobra.Command{ var serverCmd = &cobra.Command{
Use: "server", Use: "server",
Short: "Run the Mattermost server", Short: "Run the Mattermost server",
RunE: runServerCmd, RunE: serverCmdF,
SilenceUsage: true, SilenceUsage: true,
} }
func runServerCmd(cmd *cobra.Command, args []string) error { func init() {
config, err := cmd.Flags().GetString("config") cmd.RootCmd.AddCommand(serverCmd)
cmd.RootCmd.RunE = serverCmdF
}
func serverCmdF(command *cobra.Command, args []string) error {
config, err := command.Flags().GetString("config")
if err != nil { if err != nil {
return err return err
} }
disableConfigWatch, _ := cmd.Flags().GetBool("disableconfigwatch") disableConfigWatch, _ := command.Flags().GetBool("disableconfigwatch")
interruptChan := make(chan os.Signal, 1) interruptChan := make(chan os.Signal, 1)
return runServer(config, disableConfigWatch, interruptChan) return runServer(config, disableConfigWatch, interruptChan)

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

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main package commands
import ( import (
"io/ioutil" "io/ioutil"
@@ -110,7 +110,7 @@ func TestRunServerSystemdNotification(t *testing.T) {
panic(err) panic(err)
} }
data := buffer[0:count] data := buffer[0:count]
ch<- string(data) ch <- string(data)
}(socketReader) }(socketReader)
// Start and stop the server // Start and stop the server

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

@@ -1,22 +1,24 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main
package commands
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var teamCmd = &cobra.Command{ var TeamCmd = &cobra.Command{
Use: "team", Use: "team",
Short: "Management of teams", Short: "Management of teams",
} }
var teamCreateCmd = &cobra.Command{ var TeamCreateCmd = &cobra.Command{
Use: "create", Use: "create",
Short: "Create a team", Short: "Create a team",
Long: `Create a team.`, Long: `Create a team.`,
@@ -25,7 +27,7 @@ var teamCreateCmd = &cobra.Command{
RunE: createTeamCmdF, RunE: createTeamCmdF,
} }
var removeUsersCmd = &cobra.Command{ var RemoveUsersCmd = &cobra.Command{
Use: "remove [team] [users]", Use: "remove [team] [users]",
Short: "Remove users from team", Short: "Remove users from team",
Long: "Remove some users from team", Long: "Remove some users from team",
@@ -33,7 +35,7 @@ var removeUsersCmd = &cobra.Command{
RunE: removeUsersCmdF, RunE: removeUsersCmdF,
} }
var addUsersCmd = &cobra.Command{ var AddUsersCmd = &cobra.Command{
Use: "add [team] [users]", Use: "add [team] [users]",
Short: "Add users to team", Short: "Add users to team",
Long: "Add some users to team", Long: "Add some users to team",
@@ -41,7 +43,7 @@ var addUsersCmd = &cobra.Command{
RunE: addUsersCmdF, RunE: addUsersCmdF,
} }
var deleteTeamsCmd = &cobra.Command{ var DeleteTeamsCmd = &cobra.Command{
Use: "delete [teams]", Use: "delete [teams]",
Short: "Delete teams", Short: "Delete teams",
Long: `Permanently delete some teams. Long: `Permanently delete some teams.
@@ -51,37 +53,38 @@ Permanently deletes a team along with all related information including posts fr
} }
func init() { func init() {
teamCreateCmd.Flags().String("name", "", "Team Name") TeamCreateCmd.Flags().String("name", "", "Team Name")
teamCreateCmd.Flags().String("display_name", "", "Team Display Name") TeamCreateCmd.Flags().String("display_name", "", "Team Display Name")
teamCreateCmd.Flags().Bool("private", false, "Create a private team.") TeamCreateCmd.Flags().Bool("private", false, "Create a private team.")
teamCreateCmd.Flags().String("email", "", "Administrator Email (anyone with this email is automatically a team admin)") TeamCreateCmd.Flags().String("email", "", "Administrator Email (anyone with this email is automatically a team admin)")
deleteTeamsCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the team and a DB backup has been performed.") DeleteTeamsCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the team and a DB backup has been performed.")
teamCmd.AddCommand( TeamCmd.AddCommand(
teamCreateCmd, TeamCreateCmd,
removeUsersCmd, RemoveUsersCmd,
addUsersCmd, AddUsersCmd,
deleteTeamsCmd, DeleteTeamsCmd,
) )
cmd.RootCmd.AddCommand(TeamCmd)
} }
func createTeamCmdF(cmd *cobra.Command, args []string) error { func createTeamCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
name, errn := cmd.Flags().GetString("name") name, errn := command.Flags().GetString("name")
if errn != nil || name == "" { if errn != nil || name == "" {
return errors.New("Name is required") return errors.New("Name is required")
} }
displayname, errdn := cmd.Flags().GetString("display_name") displayname, errdn := command.Flags().GetString("display_name")
if errdn != nil || displayname == "" { if errdn != nil || displayname == "" {
return errors.New("Display Name is required") return errors.New("Display Name is required")
} }
email, _ := cmd.Flags().GetString("email") email, _ := command.Flags().GetString("email")
useprivate, _ := cmd.Flags().GetBool("private") useprivate, _ := command.Flags().GetBool("private")
teamType := model.TEAM_OPEN teamType := model.TEAM_OPEN
if useprivate { if useprivate {
@@ -102,8 +105,8 @@ func createTeamCmdF(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func removeUsersCmdF(cmd *cobra.Command, args []string) error { func removeUsersCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -127,16 +130,16 @@ func removeUsersCmdF(cmd *cobra.Command, args []string) error {
func removeUserFromTeam(a *app.App, team *model.Team, user *model.User, userArg string) { func removeUserFromTeam(a *app.App, team *model.Team, user *model.User, userArg string) {
if user == nil { if user == nil {
CommandPrintErrorln("Can't find user '" + userArg + "'") cmd.CommandPrintErrorln("Can't find user '" + userArg + "'")
return return
} }
if err := a.LeaveTeam(team, user, ""); err != nil { if err := a.LeaveTeam(team, user, ""); err != nil {
CommandPrintErrorln("Unable to remove '" + userArg + "' from " + team.Name + ". Error: " + err.Error()) cmd.CommandPrintErrorln("Unable to remove '" + userArg + "' from " + team.Name + ". Error: " + err.Error())
} }
} }
func addUsersCmdF(cmd *cobra.Command, args []string) error { func addUsersCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -160,16 +163,16 @@ func addUsersCmdF(cmd *cobra.Command, args []string) error {
func addUserToTeam(a *app.App, team *model.Team, user *model.User, userArg string) { func addUserToTeam(a *app.App, team *model.Team, user *model.User, userArg string) {
if user == nil { if user == nil {
CommandPrintErrorln("Can't find user '" + userArg + "'") cmd.CommandPrintErrorln("Can't find user '" + userArg + "'")
return return
} }
if err := a.JoinUserToTeam(team, user, ""); err != nil { if err := a.JoinUserToTeam(team, user, ""); err != nil {
CommandPrintErrorln("Unable to add '" + userArg + "' to " + team.Name) cmd.CommandPrintErrorln("Unable to add '" + userArg + "' to " + team.Name)
} }
} }
func deleteTeamsCmdF(cmd *cobra.Command, args []string) error { func deleteTeamsCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -178,16 +181,16 @@ func deleteTeamsCmdF(cmd *cobra.Command, args []string) error {
return errors.New("Not enough arguments.") return errors.New("Not enough arguments.")
} }
confirmFlag, _ := cmd.Flags().GetBool("confirm") confirmFlag, _ := command.Flags().GetBool("confirm")
if !confirmFlag { if !confirmFlag {
var confirm string var confirm string
CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ") cmd.CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ")
fmt.Scanln(&confirm) fmt.Scanln(&confirm)
if confirm != "YES" { if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.") return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
} }
CommandPrettyPrintln("Are you sure you want to delete the teams specified? All data will be permanently deleted? (YES/NO): ") cmd.CommandPrettyPrintln("Are you sure you want to delete the teams specified? All data will be permanently deleted? (YES/NO): ")
fmt.Scanln(&confirm) fmt.Scanln(&confirm)
if confirm != "YES" { if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.") return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
@@ -197,13 +200,13 @@ func deleteTeamsCmdF(cmd *cobra.Command, args []string) error {
teams := getTeamsFromTeamArgs(a, args) teams := getTeamsFromTeamArgs(a, args)
for i, team := range teams { for i, team := range teams {
if team == nil { if team == nil {
CommandPrintErrorln("Unable to find team '" + args[i] + "'") cmd.CommandPrintErrorln("Unable to find team '" + args[i] + "'")
continue continue
} }
if err := deleteTeam(a, team); err != nil { if err := deleteTeam(a, team); err != nil {
CommandPrintErrorln("Unable to delete team '" + team.Name + "' error: " + err.Error()) cmd.CommandPrintErrorln("Unable to delete team '" + team.Name + "' error: " + err.Error())
} else { } else {
CommandPrettyPrintln("Deleted team '" + team.Name + "'") cmd.CommandPrettyPrintln("Deleted team '" + team.Name + "'")
} }
} }

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

@@ -1,12 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main package commands
import ( import (
"testing" "testing"
"github.com/mattermost/mattermost-server/api" "github.com/mattermost/mattermost-server/api"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
) )
@@ -18,7 +19,7 @@ func TestCreateTeam(t *testing.T) {
name := "name" + id name := "name" + id
displayName := "Name " + id displayName := "Name " + id
checkCommand(t, "team", "create", "--name", name, "--display_name", displayName) cmd.CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName)
found := th.SystemAdminClient.Must(th.SystemAdminClient.FindTeamByName(name)).Data.(bool) found := th.SystemAdminClient.Must(th.SystemAdminClient.FindTeamByName(name)).Data.(bool)
@@ -31,7 +32,7 @@ func TestJoinTeam(t *testing.T) {
th := api.Setup().InitSystemAdmin().InitBasic() th := api.Setup().InitSystemAdmin().InitBasic()
defer th.TearDown() defer th.TearDown()
checkCommand(t, "team", "add", th.SystemAdminTeam.Name, th.BasicUser.Email) cmd.CheckCommand(t, "team", "add", th.SystemAdminTeam.Name, th.BasicUser.Email)
profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfilesInTeam(th.SystemAdminTeam.Id, 0, 1000, "")).Data.(map[string]*model.User) profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfilesInTeam(th.SystemAdminTeam.Id, 0, 1000, "")).Data.(map[string]*model.User)
@@ -53,7 +54,7 @@ func TestLeaveTeam(t *testing.T) {
th := api.Setup().InitBasic() th := api.Setup().InitBasic()
defer th.TearDown() defer th.TearDown()
checkCommand(t, "team", "remove", th.BasicTeam.Name, th.BasicUser.Email) cmd.CheckCommand(t, "team", "remove", th.BasicTeam.Name, th.BasicUser.Email)
profiles := th.BasicClient.Must(th.BasicClient.GetProfilesInTeam(th.BasicTeam.Id, 0, 1000, "")).Data.(map[string]*model.User) profiles := th.BasicClient.Must(th.BasicClient.GetProfilesInTeam(th.BasicTeam.Id, 0, 1000, "")).Data.(map[string]*model.User)

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

@@ -1,6 +1,7 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main
package commands
import ( import (
"github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/app"

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

@@ -1,7 +1,7 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main package commands
import ( import (
"bufio" "bufio"
@@ -14,39 +14,41 @@ import (
"github.com/mattermost/mattermost-server/api" "github.com/mattermost/mattermost-server/api"
"github.com/mattermost/mattermost-server/api4" "github.com/mattermost/mattermost-server/api4"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/utils"
"github.com/mattermost/mattermost-server/wsapi" "github.com/mattermost/mattermost-server/wsapi"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var testCmd = &cobra.Command{ var TestCmd = &cobra.Command{
Use: "test", Use: "test",
Short: "Testing Commands", Short: "Testing Commands",
Hidden: true, Hidden: true,
} }
var runWebClientTestsCmd = &cobra.Command{ var RunWebClientTestsCmd = &cobra.Command{
Use: "web_client_tests", Use: "web_client_tests",
Short: "Run the web client tests", Short: "Run the web client tests",
RunE: webClientTestsCmdF, RunE: webClientTestsCmdF,
} }
var runServerForWebClientTestsCmd = &cobra.Command{ var RunServerForWebClientTestsCmd = &cobra.Command{
Use: "web_client_tests_server", Use: "web_client_tests_server",
Short: "Run the server configured for running the web client tests against it", Short: "Run the server configured for running the web client tests against it",
RunE: serverForWebClientTestsCmdF, RunE: serverForWebClientTestsCmdF,
} }
func init() { func init() {
testCmd.AddCommand( TestCmd.AddCommand(
runWebClientTestsCmd, RunWebClientTestsCmd,
runServerForWebClientTestsCmd, RunServerForWebClientTestsCmd,
) )
cmd.RootCmd.AddCommand(TestCmd)
} }
func webClientTestsCmdF(cmd *cobra.Command, args []string) error { func webClientTestsCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -67,8 +69,8 @@ func webClientTestsCmdF(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func serverForWebClientTestsCmdF(cmd *cobra.Command, args []string) error { func serverForWebClientTestsCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -101,17 +103,17 @@ func setupClientTests(cfg *model.Config) {
cfg.ServiceSettings.EnableOutgoingWebhooks = false cfg.ServiceSettings.EnableOutgoingWebhooks = false
} }
func executeTestCommand(cmd *exec.Cmd) { func executeTestCommand(command *exec.Cmd) {
cmdOutPipe, err := cmd.StdoutPipe() cmdOutPipe, err := command.StdoutPipe()
if err != nil { if err != nil {
CommandPrintErrorln("Failed to run tests") cmd.CommandPrintErrorln("Failed to run tests")
os.Exit(1) os.Exit(1)
return return
} }
cmdErrOutPipe, err := cmd.StderrPipe() cmdErrOutPipe, err := command.StderrPipe()
if err != nil { if err != nil {
CommandPrintErrorln("Failed to run tests") cmd.CommandPrintErrorln("Failed to run tests")
os.Exit(1) os.Exit(1)
return return
} }
@@ -130,8 +132,8 @@ func executeTestCommand(cmd *exec.Cmd) {
} }
}() }()
if err := cmd.Run(); err != nil { if err := command.Run(); err != nil {
CommandPrintErrorln("Client Tests failed") cmd.CommandPrintErrorln("Client Tests failed")
os.Exit(1) os.Exit(1)
return return
} }

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

@@ -1,6 +1,7 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main
package commands
import ( import (
"encoding/json" "encoding/json"
@@ -10,16 +11,17 @@ import (
l4g "github.com/alecthomas/log4go" l4g "github.com/alecthomas/log4go"
"github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var userCmd = &cobra.Command{ var UserCmd = &cobra.Command{
Use: "user", Use: "user",
Short: "Management of users", Short: "Management of users",
} }
var userActivateCmd = &cobra.Command{ var UserActivateCmd = &cobra.Command{
Use: "activate [emails, usernames, userIds]", Use: "activate [emails, usernames, userIds]",
Short: "Activate users", Short: "Activate users",
Long: "Activate users that have been deactivated.", Long: "Activate users that have been deactivated.",
@@ -28,7 +30,7 @@ var userActivateCmd = &cobra.Command{
RunE: userActivateCmdF, RunE: userActivateCmdF,
} }
var userDeactivateCmd = &cobra.Command{ var UserDeactivateCmd = &cobra.Command{
Use: "deactivate [emails, usernames, userIds]", Use: "deactivate [emails, usernames, userIds]",
Short: "Deactivate users", Short: "Deactivate users",
Long: "Deactivate users. Deactivated users are immediately logged out of all sessions and are unable to log back in.", Long: "Deactivate users. Deactivated users are immediately logged out of all sessions and are unable to log back in.",
@@ -37,7 +39,7 @@ var userDeactivateCmd = &cobra.Command{
RunE: userDeactivateCmdF, RunE: userDeactivateCmdF,
} }
var userCreateCmd = &cobra.Command{ var UserCreateCmd = &cobra.Command{
Use: "create", Use: "create",
Short: "Create a user", Short: "Create a user",
Long: "Create a user", Long: "Create a user",
@@ -45,7 +47,7 @@ var userCreateCmd = &cobra.Command{
RunE: userCreateCmdF, RunE: userCreateCmdF,
} }
var userInviteCmd = &cobra.Command{ var UserInviteCmd = &cobra.Command{
Use: "invite [email] [teams]", Use: "invite [email] [teams]",
Short: "Send user an email invite to a team.", Short: "Send user an email invite to a team.",
Long: `Send user an email invite to a team. Long: `Send user an email invite to a team.
@@ -56,7 +58,7 @@ You can specify teams by name or ID.`,
RunE: userInviteCmdF, RunE: userInviteCmdF,
} }
var resetUserPasswordCmd = &cobra.Command{ var ResetUserPasswordCmd = &cobra.Command{
Use: "password [user] [password]", Use: "password [user] [password]",
Short: "Set a user's password", Short: "Set a user's password",
Long: "Set a user's password", Long: "Set a user's password",
@@ -64,7 +66,16 @@ var resetUserPasswordCmd = &cobra.Command{
RunE: resetUserPasswordCmdF, RunE: resetUserPasswordCmdF,
} }
var resetUserMfaCmd = &cobra.Command{ var updateUserEmailCmd = &cobra.Command{
Use: "email [user] [new email]",
Short: "Change email of the user",
Long: "Change email of the user.",
Example: ` user email test user@example.com
user activate username`,
RunE: updateUserEmailCmdF,
}
var ResetUserMfaCmd = &cobra.Command{
Use: "resetmfa [users]", Use: "resetmfa [users]",
Short: "Turn off MFA", Short: "Turn off MFA",
Long: `Turn off multi-factor authentication for a user. Long: `Turn off multi-factor authentication for a user.
@@ -73,7 +84,7 @@ If MFA enforcement is enabled, the user will be forced to re-enable MFA as soon
RunE: resetUserMfaCmdF, RunE: resetUserMfaCmdF,
} }
var deleteUserCmd = &cobra.Command{ var DeleteUserCmd = &cobra.Command{
Use: "delete [users]", Use: "delete [users]",
Short: "Delete users and all posts", Short: "Delete users and all posts",
Long: "Permanently delete user and all related information including posts.", Long: "Permanently delete user and all related information including posts.",
@@ -81,7 +92,7 @@ var deleteUserCmd = &cobra.Command{
RunE: deleteUserCmdF, RunE: deleteUserCmdF,
} }
var deleteAllUsersCmd = &cobra.Command{ var DeleteAllUsersCmd = &cobra.Command{
Use: "deleteall", Use: "deleteall",
Short: "Delete all users and all posts", Short: "Delete all users and all posts",
Long: "Permanently delete all users and all related information including posts.", Long: "Permanently delete all users and all related information including posts.",
@@ -89,7 +100,7 @@ var deleteAllUsersCmd = &cobra.Command{
RunE: deleteAllUsersCommandF, RunE: deleteAllUsersCommandF,
} }
var migrateAuthCmd = &cobra.Command{ var MigrateAuthCmd = &cobra.Command{
Use: "migrate_auth [from_auth] [to_auth] [migration-options]", Use: "migrate_auth [from_auth] [to_auth] [migration-options]",
Short: "Mass migrate user accounts authentication type", Short: "Mass migrate user accounts authentication type",
Long: `Migrates accounts from one authentication provider to another. For example, you can upgrade your authentication provider from email to ldap.`, Long: `Migrates accounts from one authentication provider to another. For example, you can upgrade your authentication provider from email to ldap.`,
@@ -127,7 +138,7 @@ var migrateAuthCmd = &cobra.Command{
RunE: migrateAuthCmdF, RunE: migrateAuthCmdF,
} }
var verifyUserCmd = &cobra.Command{ var VerifyUserCmd = &cobra.Command{
Use: "verify [users]", Use: "verify [users]",
Short: "Verify email of users", Short: "Verify email of users",
Long: "Verify the emails of some users.", Long: "Verify the emails of some users.",
@@ -135,7 +146,7 @@ var verifyUserCmd = &cobra.Command{
RunE: verifyUserCmdF, RunE: verifyUserCmdF,
} }
var searchUserCmd = &cobra.Command{ var SearchUserCmd = &cobra.Command{
Use: "search [users]", Use: "search [users]",
Short: "Search for users", Short: "Search for users",
Long: "Search for users based on username, email, or user ID.", Long: "Search for users based on username, email, or user ID.",
@@ -144,23 +155,23 @@ var searchUserCmd = &cobra.Command{
} }
func init() { func init() {
userCreateCmd.Flags().String("username", "", "Required. Username for the new user account.") UserCreateCmd.Flags().String("username", "", "Required. Username for the new user account.")
userCreateCmd.Flags().String("email", "", "Required. The email address for the new user account.") UserCreateCmd.Flags().String("email", "", "Required. The email address for the new user account.")
userCreateCmd.Flags().String("password", "", "Required. The password for the new user account.") UserCreateCmd.Flags().String("password", "", "Required. The password for the new user account.")
userCreateCmd.Flags().String("nickname", "", "Optional. The nickname for the new user account.") UserCreateCmd.Flags().String("nickname", "", "Optional. The nickname for the new user account.")
userCreateCmd.Flags().String("firstname", "", "Optional. The first name for the new user account.") UserCreateCmd.Flags().String("firstname", "", "Optional. The first name for the new user account.")
userCreateCmd.Flags().String("lastname", "", "Optional. The last name for the new user account.") UserCreateCmd.Flags().String("lastname", "", "Optional. The last name for the new user account.")
userCreateCmd.Flags().String("locale", "", "Optional. The locale (ex: en, fr) for the new user account.") UserCreateCmd.Flags().String("locale", "", "Optional. The locale (ex: en, fr) for the new user account.")
userCreateCmd.Flags().Bool("system_admin", false, "Optional. If supplied, the new user will be a system administrator. Defaults to false.") UserCreateCmd.Flags().Bool("system_admin", false, "Optional. If supplied, the new user will be a system administrator. Defaults to false.")
deleteUserCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the user and a DB backup has been performed.") DeleteUserCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the user and a DB backup has been performed.")
deleteAllUsersCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the user and a DB backup has been performed.") DeleteAllUsersCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the user and a DB backup has been performed.")
migrateAuthCmd.Flags().Bool("force", false, "Force the migration to occur even if there are duplicates on the LDAP server. Duplicates will not be migrated. (ldap only)") MigrateAuthCmd.Flags().Bool("force", false, "Force the migration to occur even if there are duplicates on the LDAP server. Duplicates will not be migrated. (ldap only)")
migrateAuthCmd.Flags().Bool("auto", false, "Automatically migrate all users. Assumes the usernames and emails are identical between Mattermost and SAML services. (saml only)") MigrateAuthCmd.Flags().Bool("auto", false, "Automatically migrate all users. Assumes the usernames and emails are identical between Mattermost and SAML services. (saml only)")
migrateAuthCmd.Flags().Bool("dryRun", false, "Run a simulation of the migration process without changing the database.") MigrateAuthCmd.Flags().Bool("dryRun", false, "Run a simulation of the migration process without changing the database.")
migrateAuthCmd.SetUsageTemplate(`Usage: MigrateAuthCmd.SetUsageTemplate(`Usage:
platform user migrate_auth [from_auth] [to_auth] [migration-options] [flags] platform user migrate_auth [from_auth] [to_auth] [migration-options] [flags]
Examples: Examples:
@@ -184,7 +195,7 @@ Flags:
Global Flags: Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}} {{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}
`) `)
migrateAuthCmd.SetHelpTemplate(`Usage: MigrateAuthCmd.SetHelpTemplate(`Usage:
platform user migrate_auth [from_auth] [to_auth] [migration-options] [flags] platform user migrate_auth [from_auth] [to_auth] [migration-options] [flags]
Examples: Examples:
@@ -221,23 +232,25 @@ Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}} {{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}
`) `)
userCmd.AddCommand( UserCmd.AddCommand(
userActivateCmd, UserActivateCmd,
userDeactivateCmd, UserDeactivateCmd,
userCreateCmd, UserCreateCmd,
userInviteCmd, UserInviteCmd,
resetUserPasswordCmd, ResetUserPasswordCmd,
resetUserMfaCmd, updateUserEmailCmd,
deleteUserCmd, ResetUserMfaCmd,
deleteAllUsersCmd, DeleteUserCmd,
migrateAuthCmd, DeleteAllUsersCmd,
verifyUserCmd, MigrateAuthCmd,
searchUserCmd, VerifyUserCmd,
SearchUserCmd,
) )
cmd.RootCmd.AddCommand(UserCmd)
} }
func userActivateCmdF(cmd *cobra.Command, args []string) error { func userActivateCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -256,7 +269,7 @@ func changeUsersActiveStatus(a *app.App, userArgs []string, active bool) {
err := changeUserActiveStatus(a, user, userArgs[i], active) err := changeUserActiveStatus(a, user, userArgs[i], active)
if err != nil { if err != nil {
CommandPrintErrorln(err.Error()) cmd.CommandPrintErrorln(err.Error())
} }
} }
} }
@@ -275,8 +288,8 @@ func changeUserActiveStatus(a *app.App, user *model.User, userArg string, activa
return nil return nil
} }
func userDeactivateCmdF(cmd *cobra.Command, args []string) error { func userDeactivateCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -289,29 +302,29 @@ func userDeactivateCmdF(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func userCreateCmdF(cmd *cobra.Command, args []string) error { func userCreateCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
username, erru := cmd.Flags().GetString("username") username, erru := command.Flags().GetString("username")
if erru != nil || username == "" { if erru != nil || username == "" {
return errors.New("Username is required") return errors.New("Username is required")
} }
email, erre := cmd.Flags().GetString("email") email, erre := command.Flags().GetString("email")
if erre != nil || email == "" { if erre != nil || email == "" {
return errors.New("Email is required") return errors.New("Email is required")
} }
password, errp := cmd.Flags().GetString("password") password, errp := command.Flags().GetString("password")
if errp != nil || password == "" { if errp != nil || password == "" {
return errors.New("Password is required") return errors.New("Password is required")
} }
nickname, _ := cmd.Flags().GetString("nickname") nickname, _ := command.Flags().GetString("nickname")
firstname, _ := cmd.Flags().GetString("firstname") firstname, _ := command.Flags().GetString("firstname")
lastname, _ := cmd.Flags().GetString("lastname") lastname, _ := command.Flags().GetString("lastname")
locale, _ := cmd.Flags().GetString("locale") locale, _ := command.Flags().GetString("locale")
systemAdmin, _ := cmd.Flags().GetBool("system_admin") systemAdmin, _ := command.Flags().GetBool("system_admin")
user := &model.User{ user := &model.User{
Username: username, Username: username,
@@ -329,13 +342,13 @@ func userCreateCmdF(cmd *cobra.Command, args []string) error {
a.UpdateUserRoles(ruser.Id, "system_user system_admin", false) a.UpdateUserRoles(ruser.Id, "system_user system_admin", false)
} }
CommandPrettyPrintln("Created User") cmd.CommandPrettyPrintln("Created User")
return nil return nil
} }
func userInviteCmdF(cmd *cobra.Command, args []string) error { func userInviteCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -354,7 +367,7 @@ func userInviteCmdF(cmd *cobra.Command, args []string) error {
err := inviteUser(a, email, team, args[i+1]) err := inviteUser(a, email, team, args[i+1])
if err != nil { if err != nil {
CommandPrintErrorln(err.Error()) cmd.CommandPrintErrorln(err.Error())
} }
} }
@@ -368,13 +381,13 @@ func inviteUser(a *app.App, email string, team *model.Team, teamArg string) erro
} }
a.SendInviteEmails(team, "Administrator", invites, *a.Config().ServiceSettings.SiteURL) a.SendInviteEmails(team, "Administrator", invites, *a.Config().ServiceSettings.SiteURL)
CommandPrettyPrintln("Invites may or may not have been sent.") cmd.CommandPrettyPrintln("Invites may or may not have been sent.")
return nil return nil
} }
func resetUserPasswordCmdF(cmd *cobra.Command, args []string) error { func resetUserPasswordCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -396,8 +409,38 @@ func resetUserPasswordCmdF(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func resetUserMfaCmdF(cmd *cobra.Command, args []string) error { func updateUserEmailCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
newEmail := args[1]
if !model.IsValidEmail(newEmail) {
return errors.New("Invalid email: '" + newEmail + "'")
}
if len(args) != 2 {
return errors.New("Expected two arguments. See help text for details.")
}
user := getUserFromUserArg(a, args[0])
if user == nil {
return errors.New("Unable to find user '" + args[0] + "'")
}
user.Email = newEmail
_, errUpdate := a.UpdateUser(user, true)
if err != nil {
return errUpdate
}
return nil
}
func resetUserMfaCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -421,8 +464,8 @@ func resetUserMfaCmdF(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func deleteUserCmdF(cmd *cobra.Command, args []string) error { func deleteUserCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -431,16 +474,16 @@ func deleteUserCmdF(cmd *cobra.Command, args []string) error {
return errors.New("Expected at least one argument. See help text for details.") return errors.New("Expected at least one argument. See help text for details.")
} }
confirmFlag, _ := cmd.Flags().GetBool("confirm") confirmFlag, _ := command.Flags().GetBool("confirm")
if !confirmFlag { if !confirmFlag {
var confirm string var confirm string
CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ") cmd.CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ")
fmt.Scanln(&confirm) fmt.Scanln(&confirm)
if confirm != "YES" { if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.") return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
} }
CommandPrettyPrintln("Are you sure you want to permanently delete the specified users? (YES/NO): ") cmd.CommandPrettyPrintln("Are you sure you want to permanently delete the specified users? (YES/NO): ")
fmt.Scanln(&confirm) fmt.Scanln(&confirm)
if confirm != "YES" { if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.") return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
@@ -462,8 +505,8 @@ func deleteUserCmdF(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func deleteAllUsersCommandF(cmd *cobra.Command, args []string) error { func deleteAllUsersCommandF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -472,16 +515,16 @@ func deleteAllUsersCommandF(cmd *cobra.Command, args []string) error {
return errors.New("Expected zero arguments.") return errors.New("Expected zero arguments.")
} }
confirmFlag, _ := cmd.Flags().GetBool("confirm") confirmFlag, _ := command.Flags().GetBool("confirm")
if !confirmFlag { if !confirmFlag {
var confirm string var confirm string
CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ") cmd.CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ")
fmt.Scanln(&confirm) fmt.Scanln(&confirm)
if confirm != "YES" { if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.") return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
} }
CommandPrettyPrintln("Are you sure you want to permanently delete all user accounts? (YES/NO): ") cmd.CommandPrettyPrintln("Are you sure you want to permanently delete all user accounts? (YES/NO): ")
fmt.Scanln(&confirm) fmt.Scanln(&confirm)
if confirm != "YES" { if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.") return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
@@ -492,19 +535,19 @@ func deleteAllUsersCommandF(cmd *cobra.Command, args []string) error {
return err return err
} }
CommandPrettyPrintln("All user accounts successfully deleted.") cmd.CommandPrettyPrintln("All user accounts successfully deleted.")
return nil return nil
} }
func migrateAuthCmdF(cmd *cobra.Command, args []string) error { func migrateAuthCmdF(command *cobra.Command, args []string) error {
if args[1] == "saml" { if args[1] == "saml" {
return migrateAuthToSamlCmdF(cmd, args) return migrateAuthToSamlCmdF(command, args)
} }
return migrateAuthToLdapCmdF(cmd, args) return migrateAuthToLdapCmdF(command, args)
} }
func migrateAuthToLdapCmdF(cmd *cobra.Command, args []string) error { func migrateAuthToLdapCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -525,28 +568,28 @@ func migrateAuthToLdapCmdF(cmd *cobra.Command, args []string) error {
return errors.New("Invalid match_field argument") return errors.New("Invalid match_field argument")
} }
forceFlag, _ := cmd.Flags().GetBool("force") forceFlag, _ := command.Flags().GetBool("force")
dryRunFlag, _ := cmd.Flags().GetBool("dryRun") dryRunFlag, _ := command.Flags().GetBool("dryRun")
if migrate := a.AccountMigration; migrate != nil { if migrate := a.AccountMigration; migrate != nil {
if err := migrate.MigrateToLdap(fromAuth, matchField, forceFlag, dryRunFlag); err != nil { if err := migrate.MigrateToLdap(fromAuth, matchField, forceFlag, dryRunFlag); err != nil {
return errors.New("Error while migrating users: " + err.Error()) return errors.New("Error while migrating users: " + err.Error())
} }
CommandPrettyPrintln("Sucessfully migrated accounts.") cmd.CommandPrettyPrintln("Sucessfully migrated accounts.")
} }
return nil return nil
} }
func migrateAuthToSamlCmdF(cmd *cobra.Command, args []string) error { func migrateAuthToSamlCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
dryRunFlag, _ := cmd.Flags().GetBool("dryRun") dryRunFlag, _ := command.Flags().GetBool("dryRun")
autoFlag, _ := cmd.Flags().GetBool("auto") autoFlag, _ := command.Flags().GetBool("auto")
matchesFile := "" matchesFile := ""
matches := map[string]string{} matches := map[string]string{}
@@ -570,7 +613,7 @@ func migrateAuthToSamlCmdF(cmd *cobra.Command, args []string) error {
if autoFlag && !dryRunFlag { if autoFlag && !dryRunFlag {
var confirm string var confirm string
CommandPrettyPrintln("You are about to perform an automatic \"" + fromAuth + " to saml\" migration. This must only be done if your current Mattermost users with " + fromAuth + " auth have the same username and email in your SAML service. Otherwise, provide the usernames and emails from your SAML Service using the \"users file\" without the \"--auto\" option.\n\nDo you want to proceed with automatic migration anyway? (YES/NO):") cmd.CommandPrettyPrintln("You are about to perform an automatic \"" + fromAuth + " to saml\" migration. This must only be done if your current Mattermost users with " + fromAuth + " auth have the same username and email in your SAML service. Otherwise, provide the usernames and emails from your SAML Service using the \"users file\" without the \"--auto\" option.\n\nDo you want to proceed with automatic migration anyway? (YES/NO):")
fmt.Scanln(&confirm) fmt.Scanln(&confirm)
if confirm != "YES" { if confirm != "YES" {
@@ -588,14 +631,14 @@ func migrateAuthToSamlCmdF(cmd *cobra.Command, args []string) error {
return errors.New("Error while migrating users: " + err.Error()) return errors.New("Error while migrating users: " + err.Error())
} }
l4g.Close() l4g.Close()
CommandPrettyPrintln("Sucessfully migrated accounts.") cmd.CommandPrettyPrintln("Sucessfully migrated accounts.")
} }
return nil return nil
} }
func verifyUserCmdF(cmd *cobra.Command, args []string) error { func verifyUserCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -608,19 +651,19 @@ func verifyUserCmdF(cmd *cobra.Command, args []string) error {
for i, user := range users { for i, user := range users {
if user == nil { if user == nil {
CommandPrintErrorln("Unable to find user '" + args[i] + "'") cmd.CommandPrintErrorln("Unable to find user '" + args[i] + "'")
continue continue
} }
if cresult := <-a.Srv.Store.User().VerifyEmail(user.Id); cresult.Err != nil { if cresult := <-a.Srv.Store.User().VerifyEmail(user.Id); cresult.Err != nil {
CommandPrintErrorln("Unable to verify '" + args[i] + "' email. Error: " + cresult.Err.Error()) cmd.CommandPrintErrorln("Unable to verify '" + args[i] + "' email. Error: " + cresult.Err.Error())
} }
} }
return nil return nil
} }
func searchUserCmdF(cmd *cobra.Command, args []string) error { func searchUserCmdF(command *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd) a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -633,21 +676,21 @@ func searchUserCmdF(cmd *cobra.Command, args []string) error {
for i, user := range users { for i, user := range users {
if i > 0 { if i > 0 {
CommandPrettyPrintln("------------------------------") cmd.CommandPrettyPrintln("------------------------------")
} }
if user == nil { if user == nil {
CommandPrintErrorln("Unable to find user '" + args[i] + "'") cmd.CommandPrintErrorln("Unable to find user '" + args[i] + "'")
continue continue
} }
CommandPrettyPrintln("id: " + user.Id) cmd.CommandPrettyPrintln("id: " + user.Id)
CommandPrettyPrintln("username: " + user.Username) cmd.CommandPrettyPrintln("username: " + user.Username)
CommandPrettyPrintln("nickname: " + user.Nickname) cmd.CommandPrettyPrintln("nickname: " + user.Nickname)
CommandPrettyPrintln("position: " + user.Position) cmd.CommandPrettyPrintln("position: " + user.Position)
CommandPrettyPrintln("first_name: " + user.FirstName) cmd.CommandPrettyPrintln("first_name: " + user.FirstName)
CommandPrettyPrintln("last_name: " + user.LastName) cmd.CommandPrettyPrintln("last_name: " + user.LastName)
CommandPrettyPrintln("email: " + user.Email) cmd.CommandPrettyPrintln("email: " + user.Email)
CommandPrettyPrintln("auth_service: " + user.AuthService) cmd.CommandPrettyPrintln("auth_service: " + user.AuthService)
} }
return nil return nil

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

@@ -1,13 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main package commands
import ( import (
"testing" "testing"
"github.com/mattermost/mattermost-server/api" "github.com/mattermost/mattermost-server/api"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/require"
) )
func TestCreateUserWithTeam(t *testing.T) { func TestCreateUserWithTeam(t *testing.T) {
@@ -18,9 +20,9 @@ func TestCreateUserWithTeam(t *testing.T) {
email := "success+" + id + "@simulator.amazonses.com" email := "success+" + id + "@simulator.amazonses.com"
username := "name" + id username := "name" + id
checkCommand(t, "user", "create", "--email", email, "--password", "mypassword1", "--username", username) cmd.CheckCommand(t, "user", "create", "--email", email, "--password", "mypassword1", "--username", username)
checkCommand(t, "team", "add", th.SystemAdminTeam.Id, email) cmd.CheckCommand(t, "team", "add", th.SystemAdminTeam.Id, email)
profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfilesInTeam(th.SystemAdminTeam.Id, 0, 1000, "")).Data.(map[string]*model.User) profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfilesInTeam(th.SystemAdminTeam.Id, 0, 1000, "")).Data.(map[string]*model.User)
@@ -46,7 +48,7 @@ func TestCreateUserWithoutTeam(t *testing.T) {
email := "success+" + id + "@simulator.amazonses.com" email := "success+" + id + "@simulator.amazonses.com"
username := "name" + id username := "name" + id
checkCommand(t, "user", "create", "--email", email, "--password", "mypassword1", "--username", username) cmd.CheckCommand(t, "user", "create", "--email", email, "--password", "mypassword1", "--username", username)
if result := <-th.App.Srv.Store.User().GetByEmail(email); result.Err != nil { if result := <-th.App.Srv.Store.User().GetByEmail(email); result.Err != nil {
t.Fatal() t.Fatal()
@@ -62,7 +64,7 @@ func TestResetPassword(t *testing.T) {
th := api.Setup().InitBasic() th := api.Setup().InitBasic()
defer th.TearDown() defer th.TearDown()
checkCommand(t, "user", "password", th.BasicUser.Email, "password2") cmd.CheckCommand(t, "user", "password", th.BasicUser.Email, "password2")
th.BasicClient.Logout() th.BasicClient.Logout()
th.BasicUser.Password = "password2" th.BasicUser.Password = "password2"
@@ -74,8 +76,35 @@ func TestMakeUserActiveAndInactive(t *testing.T) {
defer th.TearDown() defer th.TearDown()
// first inactivate the user // first inactivate the user
checkCommand(t, "user", "deactivate", th.BasicUser.Email) cmd.CheckCommand(t, "user", "deactivate", th.BasicUser.Email)
// activate the inactive user // activate the inactive user
checkCommand(t, "user", "activate", th.BasicUser.Email) cmd.CheckCommand(t, "user", "activate", th.BasicUser.Email)
}
func TestChangeUserEmail(t *testing.T) {
th := api.Setup().InitBasic()
defer th.TearDown()
newEmail := model.NewId() + "@mattermost-test.com"
cmd.CheckCommand(t, "user", "email", th.BasicUser.Username, newEmail)
if result := <-th.App.Srv.Store.User().GetByEmail(th.BasicUser.Email); result.Err == nil {
t.Fatal("should've updated to the new email")
}
if result := <-th.App.Srv.Store.User().GetByEmail(newEmail); result.Err != nil {
t.Fatal()
} else {
user := result.Data.(*model.User)
if user.Email != newEmail {
t.Fatal("should've updated to the new email")
}
}
// should fail because using an invalid email
require.Error(t, cmd.RunCommand(t, "user", "email", th.BasicUser.Username, "wrong$email.com"))
// should fail because user not found
require.Error(t, cmd.RunCommand(t, "user", "email", "invalidUser", newEmail))
} }

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

@@ -1,6 +1,7 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main
package commands
import ( import (
"github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/app"

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

@@ -1,23 +1,29 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main
package commands
import ( import (
"github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/store"
"github.com/mattermost/mattermost-server/store/sqlstore" "github.com/mattermost/mattermost-server/store/sqlstore"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var versionCmd = &cobra.Command{ var VersionCmd = &cobra.Command{
Use: "version", Use: "version",
Short: "Display version information", Short: "Display version information",
RunE: versionCmdF, RunE: versionCmdF,
} }
func versionCmdF(cmd *cobra.Command, args []string) error { func init() {
a, err := initDBCommandContextCobra(cmd) cmd.RootCmd.AddCommand(VersionCmd)
}
func versionCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil { if err != nil {
return err return err
} }
@@ -28,12 +34,12 @@ func versionCmdF(cmd *cobra.Command, args []string) error {
} }
func printVersion(a *app.App) { func printVersion(a *app.App) {
CommandPrintln("Version: " + model.CurrentVersion) cmd.CommandPrintln("Version: " + model.CurrentVersion)
CommandPrintln("Build Number: " + model.BuildNumber) cmd.CommandPrintln("Build Number: " + model.BuildNumber)
CommandPrintln("Build Date: " + model.BuildDate) cmd.CommandPrintln("Build Date: " + model.BuildDate)
CommandPrintln("Build Hash: " + model.BuildHash) cmd.CommandPrintln("Build Hash: " + model.BuildHash)
CommandPrintln("Build Enterprise Ready: " + model.BuildEnterpriseReady) cmd.CommandPrintln("Build Enterprise Ready: " + model.BuildEnterpriseReady)
if supplier, ok := a.Srv.Store.(*store.LayeredStore).DatabaseLayer.(*sqlstore.SqlSupplier); ok { if supplier, ok := a.Srv.Store.(*store.LayeredStore).DatabaseLayer.(*sqlstore.SqlSupplier); ok {
CommandPrintln("DB Version: " + supplier.GetCurrentSchemaVersion()) cmd.CommandPrintln("DB Version: " + supplier.GetCurrentSchemaVersion())
} }
} }

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

@@ -1,12 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main package commands
import ( import (
"testing" "testing"
"github.com/mattermost/mattermost-server/cmd"
) )
func TestVersion(t *testing.T) { func TestVersion(t *testing.T) {
checkCommand(t, "version") cmd.CheckCommand(t, "version")
} }

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

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main package cmd
import ( import (
"github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/app"
@@ -10,13 +10,13 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func initDBCommandContextCobra(cmd *cobra.Command) (*app.App, error) { func InitDBCommandContextCobra(cmd *cobra.Command) (*app.App, error) {
config, err := cmd.Flags().GetString("config") config, err := cmd.Flags().GetString("config")
if err != nil { if err != nil {
return nil, err return nil, err
} }
a, err := initDBCommandContext(config) a, err := InitDBCommandContext(config)
if err != nil { if err != nil {
// Returning an error just prints the usage message, so actually panic // Returning an error just prints the usage message, so actually panic
panic(err) panic(err)
@@ -25,7 +25,7 @@ func initDBCommandContextCobra(cmd *cobra.Command) (*app.App, error) {
return a, nil return a, nil
} }
func initDBCommandContext(configFileLocation string) (*app.App, error) { func InitDBCommandContext(configFileLocation string) (*app.App, error) {
if err := utils.TranslationsPreInit(); err != nil { if err := utils.TranslationsPreInit(); err != nil {
return nil, err return nil, err
} }

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

@@ -1,6 +1,7 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
package main
package cmd
import ( import (
"fmt" "fmt"

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

@@ -1,88 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"errors"
"fmt"
"os"
"github.com/spf13/cobra"
// Plugins
_ "github.com/mattermost/mattermost-server/model/gitlab"
// Enterprise Imports
_ "github.com/mattermost/mattermost-server/imports"
// Enterprise Deps
_ "github.com/dgryski/dgoogauth"
_ "github.com/go-ldap/ldap"
_ "github.com/hashicorp/memberlist"
_ "github.com/mattermost/rsc/qr"
_ "github.com/prometheus/client_golang/prometheus"
_ "github.com/prometheus/client_golang/prometheus/promhttp"
_ "github.com/tylerb/graceful"
_ "gopkg.in/olivere/elastic.v5"
// Temp imports for new dependencies
_ "github.com/gorilla/schema"
)
func main() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
func init() {
rootCmd.PersistentFlags().StringP("config", "c", "config.json", "Configuration file to use.")
rootCmd.PersistentFlags().Bool("disableconfigwatch", false, "When set config.json will not be loaded from disk when the file is changed.")
resetCmd.Flags().Bool("confirm", false, "Confirm you really want to delete everything and a DB backup has been performed.")
rootCmd.AddCommand(serverCmd, versionCmd, userCmd, teamCmd, licenseCmd, importCmd, resetCmd, channelCmd, rolesCmd, testCmd, ldapCmd, configCmd, jobserverCmd, commandCmd, messageExportCmd, sampleDataCmd)
}
var rootCmd = &cobra.Command{
Use: "platform",
Short: "Open source, self-hosted Slack-alternative",
Long: `Mattermost offers workplace messaging across web, PC and phones with archiving, search and integration with your existing systems. Documentation available at https://docs.mattermost.com`,
RunE: runServerCmd,
}
var resetCmd = &cobra.Command{
Use: "reset",
Short: "Reset the database to initial state",
Long: "Completely erases the database causing the loss of all data. This will reset Mattermost to its initial state.",
RunE: resetCmdF,
}
func resetCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
confirmFlag, _ := cmd.Flags().GetBool("confirm")
if !confirmFlag {
var confirm string
CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ")
fmt.Scanln(&confirm)
if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
}
CommandPrettyPrintln("Are you sure you want to delete everything? All data will be permanently deleted? (YES/NO): ")
fmt.Scanln(&confirm)
if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
}
}
a.Srv.Store.DropAllTables()
CommandPrettyPrintln("Database sucessfully reset")
return nil
}

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

@@ -358,7 +358,13 @@
"DailyRunTime": "01:00", "DailyRunTime": "01:00",
"ExportFromTimestamp": 0, "ExportFromTimestamp": 0,
"FileLocation": "export", "FileLocation": "export",
"BatchSize": 10000 "BatchSize": 10000,
"GlobalRelaySettings": {
"CustomerType": "A9",
"SmtpUsername": "",
"SmtpPassword": "",
"EmailAddress": ""
}
}, },
"JobSettings": { "JobSettings": {
"RunJobs": true, "RunJobs": true,

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

@@ -29,8 +29,10 @@ type MetricsInterface interface {
IncrementMemCacheHitCounter(cacheName string) IncrementMemCacheHitCounter(cacheName string)
IncrementMemCacheMissCounter(cacheName string) IncrementMemCacheMissCounter(cacheName string)
IncrementMemCacheInvalidationCounter(cacheName string)
IncrementMemCacheMissCounterSession() IncrementMemCacheMissCounterSession()
IncrementMemCacheHitCounterSession() IncrementMemCacheHitCounterSession()
IncrementMemCacheInvalidationCounterSession()
IncrementWebsocketEvent(eventType string) IncrementWebsocketEvent(eventType string)
IncrementWebSocketBroadcast(eventType string) IncrementWebSocketBroadcast(eventType string)

8
glide.lock сгенерированный
Просмотреть файл

@@ -1,11 +1,13 @@
hash: 6779beaa11fdb9c520471fb87c0a1a6ecc34a4c82610d942c44fba2f27a29936 hash: 822849f55f8ab4b5c7545597b209edb6114bcf1009a552a9ee2503ff8d3fda09
updated: 2018-02-15T18:28:32.209282461-08:00 updated: 2018-03-07T13:01:49.575101746+01:00
imports: imports:
- name: github.com/alecthomas/log4go - name: github.com/alecthomas/log4go
version: 3fbce08846379ec7f4f6bc7fce6dd01ce28fae4c version: 3fbce08846379ec7f4f6bc7fce6dd01ce28fae4c
repo: https://github.com/mattermost/log4go.git repo: https://github.com/mattermost/log4go.git
- name: github.com/armon/go-metrics - name: github.com/armon/go-metrics
version: 7aa49fde808223f8dadfdbfd3a20ff6c19e5f9ec version: 7aa49fde808223f8dadfdbfd3a20ff6c19e5f9ec
- name: github.com/avct/uasurfer
version: c4be5581ec9617d04f5c5e02b893903ead0b1eed
- name: github.com/beorn7/perks - name: github.com/beorn7/perks
version: 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9 version: 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
subpackages: subpackages:
@@ -138,8 +140,6 @@ imports:
version: b8bc1bf767474819792c23f32d8286a45736f1c6 version: b8bc1bf767474819792c23f32d8286a45736f1c6
- name: github.com/mitchellh/mapstructure - name: github.com/mitchellh/mapstructure
version: a4e142e9c047c904fa2f1e144d9a84e6133024bc version: a4e142e9c047c904fa2f1e144d9a84e6133024bc
- name: github.com/mssola/user_agent
version: 5243daae23628aeae9b6268541406bd5e95d5964
- name: github.com/nicksnyder/go-i18n - name: github.com/nicksnyder/go-i18n
version: 0dc1626d56435e9d605a29875701721c54bc9bbd version: 0dc1626d56435e9d605a29875701721c54bc9bbd
subpackages: subpackages:

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

@@ -77,3 +77,4 @@ import:
subpackages: subpackages:
- store/memstore - store/memstore
- package: gopkg.in/yaml.v2 - package: gopkg.in/yaml.v2
- package: github.com/avct/uasurfer

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error", "id": "api.file.upload_file.bad_parse.app_error",
"translation": "Dateiupload nicht möglich. Header können nicht geparst werden." "translation": "Dateiupload nicht möglich. Header können nicht geparst werden."
}, },
{
"id": "api.file.upload_file.incorrect_number_of_files.app_error",
"translation": "Unable to upload files. Incorrect number of files specified."
},
{ {
"id": "api.file.upload_file.large_image.app_error", "id": "api.file.upload_file.large_image.app_error",
"translation": "Datei über den maximalen Dimensionen konnte nicht hochgeladen werden: {{.Filename}}" "translation": "Datei über den maximalen Dimensionen konnte nicht hochgeladen werden: {{.Filename}}"
@@ -1814,6 +1818,10 @@
"id": "api.post.send_notifications_and_forget.push_image_only_dm", "id": "api.post.send_notifications_and_forget.push_image_only_dm",
"translation": " Eine oder mehrere Dateien in einer Direktnachricht hochgeladen" "translation": " Eine oder mehrere Dateien in einer Direktnachricht hochgeladen"
}, },
{
"id": "api.post.send_notifications_and_forget.push_image_only_no_channel",
"translation": " Eine oder mehrere Dateien hochgeladen in "
},
{ {
"id": "api.post.send_notifications_and_forget.push_in", "id": "api.post.send_notifications_and_forget.push_in",
"translation": " in " "translation": " in "
@@ -3648,7 +3656,7 @@
}, },
{ {
"id": "app.plugin.disabled.app_error", "id": "app.plugin.disabled.app_error",
"translation": "Plugins wurden deaktiviert." "translation": "Plugins have been disabled. Please check your logs for details."
}, },
{ {
"id": "app.plugin.extract.app_error", "id": "app.plugin.extract.app_error",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found", "id": "ent.migration.migratetoldap.user_not_found",
"translation": "Kann Benutzer nicht auf AD/LDAP-Server finden: " "translation": "Kann Benutzer nicht auf AD/LDAP-Server finden: "
}, },
{
"id": "ent.migration.migratetosaml.email_already_used_by_other_user",
"translation": "Email already used by another SAML user."
},
{
"id": "ent.migration.migratetosaml.user_not_found_in_users_mapping_file",
"translation": "User not found in the users file."
},
{
"id": "ent.migration.migratetosaml.username_already_used_by_other_user",
"translation": "Username already used by another Mattermost user."
},
{ {
"id": "ent.saml.attribute.app_error", "id": "ent.saml.attribute.app_error",
"translation": "SAML Login war nicht erfolgreich da Verschlüsselung nicht aktiviert ist. Bitte kontaktieren Sie Ihren Systemadministrator." "translation": "SAML Login war nicht erfolgreich da Verschlüsselung nicht aktiviert ist. Bitte kontaktieren Sie Ihren Systemadministrator."
@@ -5026,6 +5046,10 @@
"id": "model.config.is_valid.webserver_security.app_error", "id": "model.config.is_valid.webserver_security.app_error",
"translation": "Ungültiger Wert für Webserver-Verbindungssicherheit." "translation": "Ungültiger Wert für Webserver-Verbindungssicherheit."
}, },
{
"id": "model.config.is_valid.websocket_url.app_error",
"translation": "Die WebRTC-Gateway-Websocket-URL muss gesetzt und eine gültige URL sein sowie mit ws:// oder wss:// beginnen."
},
{ {
"id": "model.config.is_valid.write_timeout.app_error", "id": "model.config.is_valid.write_timeout.app_error",
"translation": "Ungültiger Wert für write timeout." "translation": "Ungültiger Wert für write timeout."

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

@@ -1830,14 +1830,14 @@
"id": "api.post.send_notifications_and_forget.push_image_only", "id": "api.post.send_notifications_and_forget.push_image_only",
"translation": " uploaded one or more files in " "translation": " uploaded one or more files in "
}, },
{
"id": "api.post.send_notifications_and_forget.push_image_only_no_channel",
"translation": " uploaded one or more files"
},
{ {
"id": "api.post.send_notifications_and_forget.push_image_only_dm", "id": "api.post.send_notifications_and_forget.push_image_only_dm",
"translation": " uploaded one or more files in a direct message" "translation": " uploaded one or more files in a direct message"
}, },
{
"id": "api.post.send_notifications_and_forget.push_image_only_no_channel",
"translation": " uploaded one or more files"
},
{ {
"id": "api.post.send_notifications_and_forget.push_in", "id": "api.post.send_notifications_and_forget.push_in",
"translation": " in " "translation": " in "
@@ -4258,6 +4258,10 @@
"id": "ent.migration.migratetoldap.user_not_found", "id": "ent.migration.migratetoldap.user_not_found",
"translation": "Unable to find user on AD/LDAP server: " "translation": "Unable to find user on AD/LDAP server: "
}, },
{
"id": "ent.migration.migratetosaml.email_already_used_by_other_user",
"translation": "Email already used by another SAML user."
},
{ {
"id": "ent.migration.migratetosaml.user_not_found_in_users_mapping_file", "id": "ent.migration.migratetosaml.user_not_found_in_users_mapping_file",
"translation": "User not found in the users file." "translation": "User not found in the users file."
@@ -4266,10 +4270,6 @@
"id": "ent.migration.migratetosaml.username_already_used_by_other_user", "id": "ent.migration.migratetosaml.username_already_used_by_other_user",
"translation": "Username already used by another Mattermost user." "translation": "Username already used by another Mattermost user."
}, },
{
"id": "ent.migration.migratetosaml.email_already_used_by_other_user",
"translation": "Email already used by another SAML user."
},
{ {
"id": "ent.saml.attribute.app_error", "id": "ent.saml.attribute.app_error",
"translation": "SAML login was unsuccessful because one of the attributes is incorrect. Please contact your System Administrator." "translation": "SAML login was unsuccessful because one of the attributes is incorrect. Please contact your System Administrator."
@@ -4958,6 +4958,30 @@
"id": "model.config.is_valid.message_export.batch_size.app_error", "id": "model.config.is_valid.message_export.batch_size.app_error",
"translation": "Message export job BatchSize must be a positive integer" "translation": "Message export job BatchSize must be a positive integer"
}, },
{
"id": "model.config.is_valid.message_export.export_type.app_error",
"translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'"
},
{
"id": "model.config.is_valid.message_export.global_relay.config_missing.app_error",
"translation": "Message export job ExportFormat is set to 'globalrelay', but GlobalRelaySettings are missing"
},
{
"id": "model.config.is_valid.message_export.global_relay.customer_type.app_error",
"translation": "Message export GlobalRelaySettings.CustomerType must be set to one of either 'A9' or 'A10'"
},
{
"id": "model.config.is_valid.message_export.global_relay.email_address.app_error",
"translation": "Message export job GlobalRelaySettings.EmailAddress must be set to a valid email address"
},
{
"id": "model.config.is_valid.message_export.global_relay.smtp_username.app_error",
"translation": "Message export job GlobalRelaySettings.SmtpUsername must be set"
},
{
"id": "model.config.is_valid.message_export.global_relay.smtp_password.app_error",
"translation": "Message export job GlobalRelaySettings.SmtpPassword must be set"
},
{ {
"id": "model.config.is_valid.message_export.daily_runtime.app_error", "id": "model.config.is_valid.message_export.daily_runtime.app_error",
"translation": "Message export job DailyRuntime must be a 24-hour time stamp in the form HH:MM." "translation": "Message export job DailyRuntime must be a 24-hour time stamp in the form HH:MM."
@@ -5046,10 +5070,6 @@
"id": "model.config.is_valid.site_url.app_error", "id": "model.config.is_valid.site_url.app_error",
"translation": "Site URL must be a valid URL and start with http:// or https://" "translation": "Site URL must be a valid URL and start with http:// or https://"
}, },
{
"id": "model.config.is_valid.websocket_url.app_error",
"translation": "Websocket URL must be a valid URL and start with ws:// or wss://"
},
{ {
"id": "model.config.is_valid.site_url_email_batching.app_error", "id": "model.config.is_valid.site_url_email_batching.app_error",
"translation": "Unable to enable email batching when SiteURL isn't set." "translation": "Unable to enable email batching when SiteURL isn't set."
@@ -5118,6 +5138,10 @@
"id": "model.config.is_valid.webserver_security.app_error", "id": "model.config.is_valid.webserver_security.app_error",
"translation": "Invalid value for webserver connection security." "translation": "Invalid value for webserver connection security."
}, },
{
"id": "model.config.is_valid.websocket_url.app_error",
"translation": "Websocket URL must be a valid URL and start with ws:// or wss://"
},
{ {
"id": "model.config.is_valid.write_timeout.app_error", "id": "model.config.is_valid.write_timeout.app_error",
"translation": "Invalid value for write timeout." "translation": "Invalid value for write timeout."

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error", "id": "api.file.upload_file.bad_parse.app_error",
"translation": "No se puedo cargar el archivo. El encabezado no puede ser analizado." "translation": "No se puedo cargar el archivo. El encabezado no puede ser analizado."
}, },
{
"id": "api.file.upload_file.incorrect_number_of_files.app_error",
"translation": "No se pudo subir los archivos. El número de archivos especificado es incorrecto."
},
{ {
"id": "api.file.upload_file.large_image.app_error", "id": "api.file.upload_file.large_image.app_error",
"translation": "No se pudo cargar el archivo que supera las dimensiones máximas: {{.Filename}}" "translation": "No se pudo cargar el archivo que supera las dimensiones máximas: {{.Filename}}"
@@ -1814,6 +1818,10 @@
"id": "api.post.send_notifications_and_forget.push_image_only_dm", "id": "api.post.send_notifications_and_forget.push_image_only_dm",
"translation": " subió uno o más archivos en un mensaje directo" "translation": " subió uno o más archivos en un mensaje directo"
}, },
{
"id": "api.post.send_notifications_and_forget.push_image_only_no_channel",
"translation": " subió uno o más archivos"
},
{ {
"id": "api.post.send_notifications_and_forget.push_in", "id": "api.post.send_notifications_and_forget.push_in",
"translation": " en " "translation": " en "
@@ -3648,7 +3656,7 @@
}, },
{ {
"id": "app.plugin.disabled.app_error", "id": "app.plugin.disabled.app_error",
"translation": "Los Plugins se han deshabilitado." "translation": "Los Plugins han sido inhabilitados. Por favor revisa los logs para más detalles."
}, },
{ {
"id": "app.plugin.extract.app_error", "id": "app.plugin.extract.app_error",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found", "id": "ent.migration.migratetoldap.user_not_found",
"translation": "No se puede encontrar el usuario en el servidor AD/LDAP: " "translation": "No se puede encontrar el usuario en el servidor AD/LDAP: "
}, },
{
"id": "ent.migration.migratetosaml.email_already_used_by_other_user",
"translation": "Dirección de correo electrónico ya se encuentra en uso por otro usuario SAML."
},
{
"id": "ent.migration.migratetosaml.user_not_found_in_users_mapping_file",
"translation": "Usuario no encontrado en el archivo de usuarios."
},
{
"id": "ent.migration.migratetosaml.username_already_used_by_other_user",
"translation": "Nombre de usuario ya se encuentra en uso por otro usuario de Mattermost."
},
{ {
"id": "ent.saml.attribute.app_error", "id": "ent.saml.attribute.app_error",
"translation": "El inicio de sesión con SAML no tuvo éxito porque uno de sus atributos es incorrecto. Por favor, póngase en contacto con su Administrador del Sistema." "translation": "El inicio de sesión con SAML no tuvo éxito porque uno de sus atributos es incorrecto. Por favor, póngase en contacto con su Administrador del Sistema."
@@ -5026,6 +5046,10 @@
"id": "model.config.is_valid.webserver_security.app_error", "id": "model.config.is_valid.webserver_security.app_error",
"translation": "Valor no válido para la seguridad de conexión del servidor." "translation": "Valor no válido para la seguridad de conexión del servidor."
}, },
{
"id": "model.config.is_valid.websocket_url.app_error",
"translation": "La dirección URL del Websocket debe ser una dirección válida y comenzar con ws:// o wss://."
},
{ {
"id": "model.config.is_valid.write_timeout.app_error", "id": "model.config.is_valid.write_timeout.app_error",
"translation": "Valor no válido para el tiempo de espera de escritura." "translation": "Valor no válido para el tiempo de espera de escritura."

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error", "id": "api.file.upload_file.bad_parse.app_error",
"translation": "Impossible d'envoyer le fichier. L'entête ne peut être analysé." "translation": "Impossible d'envoyer le fichier. L'entête ne peut être analysé."
}, },
{
"id": "api.file.upload_file.incorrect_number_of_files.app_error",
"translation": "Unable to upload files. Incorrect number of files specified."
},
{ {
"id": "api.file.upload_file.large_image.app_error", "id": "api.file.upload_file.large_image.app_error",
"translation": "Le fichier est au-dessus des limites de dimensions, il n'a pas pu être envoyé : {{.Filename}}" "translation": "Le fichier est au-dessus des limites de dimensions, il n'a pas pu être envoyé : {{.Filename}}"
@@ -1814,6 +1818,10 @@
"id": "api.post.send_notifications_and_forget.push_image_only_dm", "id": "api.post.send_notifications_and_forget.push_image_only_dm",
"translation": " a envoyé un ou plusieurs fichiers dans un message personnel" "translation": " a envoyé un ou plusieurs fichiers dans un message personnel"
}, },
{
"id": "api.post.send_notifications_and_forget.push_image_only_no_channel",
"translation": " a envoyé un ou plusieurs fichiers dans "
},
{ {
"id": "api.post.send_notifications_and_forget.push_in", "id": "api.post.send_notifications_and_forget.push_in",
"translation": " dans " "translation": " dans "
@@ -3648,7 +3656,7 @@
}, },
{ {
"id": "app.plugin.disabled.app_error", "id": "app.plugin.disabled.app_error",
"translation": "Les plugins ont été désactivés." "translation": "Plugins have been disabled. Please check your logs for details."
}, },
{ {
"id": "app.plugin.extract.app_error", "id": "app.plugin.extract.app_error",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found", "id": "ent.migration.migratetoldap.user_not_found",
"translation": "Impossible de trouver l'utilisateur sur le serveur AD/LDAP : " "translation": "Impossible de trouver l'utilisateur sur le serveur AD/LDAP : "
}, },
{
"id": "ent.migration.migratetosaml.email_already_used_by_other_user",
"translation": "Email already used by another SAML user."
},
{
"id": "ent.migration.migratetosaml.user_not_found_in_users_mapping_file",
"translation": "User not found in the users file."
},
{
"id": "ent.migration.migratetosaml.username_already_used_by_other_user",
"translation": "Username already used by another Mattermost user."
},
{ {
"id": "ent.saml.attribute.app_error", "id": "ent.saml.attribute.app_error",
"translation": "La connexion via SAML a échoué car un des attributs est incorrect. Veuillez contacter votre administrateur système." "translation": "La connexion via SAML a échoué car un des attributs est incorrect. Veuillez contacter votre administrateur système."
@@ -5026,6 +5046,10 @@
"id": "model.config.is_valid.webserver_security.app_error", "id": "model.config.is_valid.webserver_security.app_error",
"translation": "Valeur invalide pour la sécurité de la connexion au serveur web." "translation": "Valeur invalide pour la sécurité de la connexion au serveur web."
}, },
{
"id": "model.config.is_valid.websocket_url.app_error",
"translation": "URL de site invalide. Il doit s'agir d'une URL valide et commencer par http:// ou https://."
},
{ {
"id": "model.config.is_valid.write_timeout.app_error", "id": "model.config.is_valid.write_timeout.app_error",
"translation": "Valeur invalide pour le délai d'attente d'écriture." "translation": "Valeur invalide pour le délai d'attente d'écriture."

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error", "id": "api.file.upload_file.bad_parse.app_error",
"translation": "Impossibile caricare il file. Lettura dell'intestazione fallita." "translation": "Impossibile caricare il file. Lettura dell'intestazione fallita."
}, },
{
"id": "api.file.upload_file.incorrect_number_of_files.app_error",
"translation": "Impossibile caricare i file. Specifica numero di file non valido."
},
{ {
"id": "api.file.upload_file.large_image.app_error", "id": "api.file.upload_file.large_image.app_error",
"translation": "Non è stato possibile caricare il file che supera le dimensioni massime: {{.Filename}}" "translation": "Non è stato possibile caricare il file che supera le dimensioni massime: {{.Filename}}"
@@ -1814,6 +1818,10 @@
"id": "api.post.send_notifications_and_forget.push_image_only_dm", "id": "api.post.send_notifications_and_forget.push_image_only_dm",
"translation": " ha caricato uno o più file in un messaggio diretto" "translation": " ha caricato uno o più file in un messaggio diretto"
}, },
{
"id": "api.post.send_notifications_and_forget.push_image_only_no_channel",
"translation": " ha caricato uno o più file"
},
{ {
"id": "api.post.send_notifications_and_forget.push_in", "id": "api.post.send_notifications_and_forget.push_in",
"translation": " in " "translation": " in "
@@ -3648,7 +3656,7 @@
}, },
{ {
"id": "app.plugin.disabled.app_error", "id": "app.plugin.disabled.app_error",
"translation": "Il plugin è stato disattivato." "translation": "I plugin sono disattivati. Controllare i log per ulteriori informazioni."
}, },
{ {
"id": "app.plugin.extract.app_error", "id": "app.plugin.extract.app_error",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found", "id": "ent.migration.migratetoldap.user_not_found",
"translation": "Impossible trovare l'utente sul server AD/LDAP: " "translation": "Impossible trovare l'utente sul server AD/LDAP: "
}, },
{
"id": "ent.migration.migratetosaml.email_already_used_by_other_user",
"translation": "Email in uso da un altro utente SAML."
},
{
"id": "ent.migration.migratetosaml.user_not_found_in_users_mapping_file",
"translation": "Utente non trovato nel file degli utenti."
},
{
"id": "ent.migration.migratetosaml.username_already_used_by_other_user",
"translation": "Nome utente in uso da un altro utente Mattermost."
},
{ {
"id": "ent.saml.attribute.app_error", "id": "ent.saml.attribute.app_error",
"translation": "Il login SAML non ha avuto successo a causa di un attributo non corretto. Contattare l'amministratore di sistema." "translation": "Il login SAML non ha avuto successo a causa di un attributo non corretto. Contattare l'amministratore di sistema."
@@ -5026,6 +5046,10 @@
"id": "model.config.is_valid.webserver_security.app_error", "id": "model.config.is_valid.webserver_security.app_error",
"translation": "Valore non valido per la sicurezza connessione webserver." "translation": "Valore non valido per la sicurezza connessione webserver."
}, },
{
"id": "model.config.is_valid.websocket_url.app_error",
"translation": "L'URL del websocket deve essere un URL valido ed iniziare con ws:// o wss://"
},
{ {
"id": "model.config.is_valid.write_timeout.app_error", "id": "model.config.is_valid.write_timeout.app_error",
"translation": "Valore non valido per il timeout scrittura." "translation": "Valore non valido per il timeout scrittura."

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error", "id": "api.file.upload_file.bad_parse.app_error",
"translation": "ファイルをアップロードできません。ヘッダーを解析できません。" "translation": "ファイルをアップロードできません。ヘッダーを解析できません。"
}, },
{
"id": "api.file.upload_file.incorrect_number_of_files.app_error",
"translation": "ファイルをアップロードできませんでした。指定されたファイル数が正しくありません。"
},
{ {
"id": "api.file.upload_file.large_image.app_error", "id": "api.file.upload_file.large_image.app_error",
"translation": "最大サイズ以上のファイルはアップロードできません: {{.Filename}}" "translation": "最大サイズ以上のファイルはアップロードできません: {{.Filename}}"
@@ -1814,6 +1818,10 @@
"id": "api.post.send_notifications_and_forget.push_image_only_dm", "id": "api.post.send_notifications_and_forget.push_image_only_dm",
"translation": " ファイルをダイレクトメッセージにアップロードしました" "translation": " ファイルをダイレクトメッセージにアップロードしました"
}, },
{
"id": "api.post.send_notifications_and_forget.push_image_only_no_channel",
"translation": " ファイルをアップロードしました"
},
{ {
"id": "api.post.send_notifications_and_forget.push_in", "id": "api.post.send_notifications_and_forget.push_in",
"translation": " in " "translation": " in "
@@ -3648,7 +3656,7 @@
}, },
{ {
"id": "app.plugin.disabled.app_error", "id": "app.plugin.disabled.app_error",
"translation": "プラグインは無効化されています。" "translation": "プラグインは無効化されています。詳しくはログを確認してください。"
}, },
{ {
"id": "app.plugin.extract.app_error", "id": "app.plugin.extract.app_error",
@@ -3700,7 +3708,7 @@
}, },
{ {
"id": "app.team.join_user_to_team.max_accounts.app_error", "id": "app.team.join_user_to_team.max_accounts.app_error",
"translation": "このチームは登録ユーザー数の上限に達しました。システム管理者に上限値の設定を変更するよう依頼してください。" "translation": "このチームは登録ユーザー数の上限に達しました。システム管理者に上限値を上げるよう依頼してください。"
}, },
{ {
"id": "app.user_access_token.disabled", "id": "app.user_access_token.disabled",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found", "id": "ent.migration.migratetoldap.user_not_found",
"translation": "AD/LDAPサーバー上でユーザを見つけることができませんでした: " "translation": "AD/LDAPサーバー上でユーザを見つけることができませんでした: "
}, },
{
"id": "ent.migration.migratetosaml.email_already_used_by_other_user",
"translation": "電子メールアドレスは既に別のSAMLユーザーによって使用されています。"
},
{
"id": "ent.migration.migratetosaml.user_not_found_in_users_mapping_file",
"translation": "ユーザーはユーザーファイル内で見つかりませんでした。"
},
{
"id": "ent.migration.migratetosaml.username_already_used_by_other_user",
"translation": "ユーザー名は既に別のMattermostユーザーによって使用されています。"
},
{ {
"id": "ent.saml.attribute.app_error", "id": "ent.saml.attribute.app_error",
"translation": "SAMLログインは、属性の一つが不正のため、失敗しました。システム管理者に連絡してください。" "translation": "SAMLログインは、属性の一つが不正のため、失敗しました。システム管理者に連絡してください。"
@@ -5026,6 +5046,10 @@
"id": "model.config.is_valid.webserver_security.app_error", "id": "model.config.is_valid.webserver_security.app_error",
"translation": "ウェブサーバーの接続のセキュリティーが不正な値です。" "translation": "ウェブサーバーの接続のセキュリティーが不正な値です。"
}, },
{
"id": "model.config.is_valid.websocket_url.app_error",
"translation": "ウェブソケットURLは、ws://またはwss://で始まる有効なURLにしてください。"
},
{ {
"id": "model.config.is_valid.write_timeout.app_error", "id": "model.config.is_valid.write_timeout.app_error",
"translation": "書き込みタイムアウトが不正な値です。" "translation": "書き込みタイムアウトが不正な値です。"

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error", "id": "api.file.upload_file.bad_parse.app_error",
"translation": "파일을 업로드 할 수 없습니다. 머릿말 파싱에 실패하였습니다." "translation": "파일을 업로드 할 수 없습니다. 머릿말 파싱에 실패하였습니다."
}, },
{
"id": "api.file.upload_file.incorrect_number_of_files.app_error",
"translation": "Unable to upload files. Incorrect number of files specified."
},
{ {
"id": "api.file.upload_file.large_image.app_error", "id": "api.file.upload_file.large_image.app_error",
"translation": "File above maximum dimensions could not be uploaded: {{.Filename}}" "translation": "File above maximum dimensions could not be uploaded: {{.Filename}}"
@@ -1814,6 +1818,10 @@
"id": "api.post.send_notifications_and_forget.push_image_only_dm", "id": "api.post.send_notifications_and_forget.push_image_only_dm",
"translation": " uploaded one or more files in a direct message" "translation": " uploaded one or more files in a direct message"
}, },
{
"id": "api.post.send_notifications_and_forget.push_image_only_no_channel",
"translation": " uploaded one or more files"
},
{ {
"id": "api.post.send_notifications_and_forget.push_in", "id": "api.post.send_notifications_and_forget.push_in",
"translation": " in " "translation": " in "
@@ -3648,7 +3656,7 @@
}, },
{ {
"id": "app.plugin.disabled.app_error", "id": "app.plugin.disabled.app_error",
"translation": "Plugins have been disabled." "translation": "Plugins have been disabled. Please check your logs for details."
}, },
{ {
"id": "app.plugin.extract.app_error", "id": "app.plugin.extract.app_error",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found", "id": "ent.migration.migratetoldap.user_not_found",
"translation": "AD/LDAP 서버에서 사용자를 찾을 수 없음: " "translation": "AD/LDAP 서버에서 사용자를 찾을 수 없음: "
}, },
{
"id": "ent.migration.migratetosaml.email_already_used_by_other_user",
"translation": "Email already used by another SAML user."
},
{
"id": "ent.migration.migratetosaml.user_not_found_in_users_mapping_file",
"translation": "User not found in the users file."
},
{
"id": "ent.migration.migratetosaml.username_already_used_by_other_user",
"translation": "Username already used by another Mattermost user."
},
{ {
"id": "ent.saml.attribute.app_error", "id": "ent.saml.attribute.app_error",
"translation": "속성 중 하나가 올바르지 않아 SAML 로그인에 실패했습니다. 시스템 관리자에게 문의하십시오." "translation": "속성 중 하나가 올바르지 않아 SAML 로그인에 실패했습니다. 시스템 관리자에게 문의하십시오."
@@ -5026,6 +5046,10 @@
"id": "model.config.is_valid.webserver_security.app_error", "id": "model.config.is_valid.webserver_security.app_error",
"translation": "webserver connection security에 대해 허용되지 않은 값입니다." "translation": "webserver connection security에 대해 허용되지 않은 값입니다."
}, },
{
"id": "model.config.is_valid.websocket_url.app_error",
"translation": "WebRTC Gateway Websocket Url은 URL형식이여야 하며 ws:// 또는 wss://로 시작해야합니다."
},
{ {
"id": "model.config.is_valid.write_timeout.app_error", "id": "model.config.is_valid.write_timeout.app_error",
"translation": "write timeout에 대해 잘못된 값입니다." "translation": "write timeout에 대해 잘못된 값입니다."

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error", "id": "api.file.upload_file.bad_parse.app_error",
"translation": "Unable to upload file. Header cannot be parsed." "translation": "Unable to upload file. Header cannot be parsed."
}, },
{
"id": "api.file.upload_file.incorrect_number_of_files.app_error",
"translation": "Unable to upload files. Incorrect number of files specified."
},
{ {
"id": "api.file.upload_file.large_image.app_error", "id": "api.file.upload_file.large_image.app_error",
"translation": "File above maximum dimensions could not be uploaded: {{.Filename}}" "translation": "File above maximum dimensions could not be uploaded: {{.Filename}}"
@@ -1814,6 +1818,10 @@
"id": "api.post.send_notifications_and_forget.push_image_only_dm", "id": "api.post.send_notifications_and_forget.push_image_only_dm",
"translation": " uploaded one or more files in a direct message" "translation": " uploaded one or more files in a direct message"
}, },
{
"id": "api.post.send_notifications_and_forget.push_image_only_no_channel",
"translation": " uploaded one or more files"
},
{ {
"id": "api.post.send_notifications_and_forget.push_in", "id": "api.post.send_notifications_and_forget.push_in",
"translation": " in " "translation": " in "
@@ -3648,7 +3656,7 @@
}, },
{ {
"id": "app.plugin.disabled.app_error", "id": "app.plugin.disabled.app_error",
"translation": "Plugins have been disabled." "translation": "Plugins have been disabled. Please check your logs for details."
}, },
{ {
"id": "app.plugin.extract.app_error", "id": "app.plugin.extract.app_error",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found", "id": "ent.migration.migratetoldap.user_not_found",
"translation": "Kon gebruiker niet vinden op de AD/LDAP server: " "translation": "Kon gebruiker niet vinden op de AD/LDAP server: "
}, },
{
"id": "ent.migration.migratetosaml.email_already_used_by_other_user",
"translation": "Email already used by another SAML user."
},
{
"id": "ent.migration.migratetosaml.user_not_found_in_users_mapping_file",
"translation": "User not found in the users file."
},
{
"id": "ent.migration.migratetosaml.username_already_used_by_other_user",
"translation": "Username already used by another Mattermost user."
},
{ {
"id": "ent.saml.attribute.app_error", "id": "ent.saml.attribute.app_error",
"translation": "SAML aanmelding is mislukt vanwege verkeerde attributen. Neem contact op met de beheerder." "translation": "SAML aanmelding is mislukt vanwege verkeerde attributen. Neem contact op met de beheerder."
@@ -5026,6 +5046,10 @@
"id": "model.config.is_valid.webserver_security.app_error", "id": "model.config.is_valid.webserver_security.app_error",
"translation": "Invalid value for webserver connection security." "translation": "Invalid value for webserver connection security."
}, },
{
"id": "model.config.is_valid.websocket_url.app_error",
"translation": "WebRTC Gateway Websocket Url moet een geldige URL zijn en starten met ws:// of wss://."
},
{ {
"id": "model.config.is_valid.write_timeout.app_error", "id": "model.config.is_valid.write_timeout.app_error",
"translation": "Invalid value for write timeout." "translation": "Invalid value for write timeout."

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error", "id": "api.file.upload_file.bad_parse.app_error",
"translation": "Nie można pobrać pliku. Problem z parsowaniem nagłówka." "translation": "Nie można pobrać pliku. Problem z parsowaniem nagłówka."
}, },
{
"id": "api.file.upload_file.incorrect_number_of_files.app_error",
"translation": "Unable to upload files. Incorrect number of files specified."
},
{ {
"id": "api.file.upload_file.large_image.app_error", "id": "api.file.upload_file.large_image.app_error",
"translation": "Plik większy od maksymalnego rozmiaru nie został załadowany: {{.Filename}}" "translation": "Plik większy od maksymalnego rozmiaru nie został załadowany: {{.Filename}}"
@@ -1814,6 +1818,10 @@
"id": "api.post.send_notifications_and_forget.push_image_only_dm", "id": "api.post.send_notifications_and_forget.push_image_only_dm",
"translation": " Wysłał jeden lub więcej plików w bezpośredniej wiadomości" "translation": " Wysłał jeden lub więcej plików w bezpośredniej wiadomości"
}, },
{
"id": "api.post.send_notifications_and_forget.push_image_only_no_channel",
"translation": " Wyślij jeden albo więcej plików w "
},
{ {
"id": "api.post.send_notifications_and_forget.push_in", "id": "api.post.send_notifications_and_forget.push_in",
"translation": "w" "translation": "w"
@@ -3648,7 +3656,7 @@
}, },
{ {
"id": "app.plugin.disabled.app_error", "id": "app.plugin.disabled.app_error",
"translation": "Plugins have been disabled." "translation": "Plugins have been disabled. Please check your logs for details."
}, },
{ {
"id": "app.plugin.extract.app_error", "id": "app.plugin.extract.app_error",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found", "id": "ent.migration.migratetoldap.user_not_found",
"translation": "Nie można znaleźć użytkownika na serwerze AD/LDAP:" "translation": "Nie można znaleźć użytkownika na serwerze AD/LDAP:"
}, },
{
"id": "ent.migration.migratetosaml.email_already_used_by_other_user",
"translation": "Email already used by another SAML user."
},
{
"id": "ent.migration.migratetosaml.user_not_found_in_users_mapping_file",
"translation": "User not found in the users file."
},
{
"id": "ent.migration.migratetosaml.username_already_used_by_other_user",
"translation": "Username already used by another Mattermost user."
},
{ {
"id": "ent.saml.attribute.app_error", "id": "ent.saml.attribute.app_error",
"translation": "Logowanie przez SAML nie powiodło się, ponieważ jeden z atrybutów jest niepoprawny. Skontaktuj się z administratorem systemu." "translation": "Logowanie przez SAML nie powiodło się, ponieważ jeden z atrybutów jest niepoprawny. Skontaktuj się z administratorem systemu."
@@ -5026,6 +5046,10 @@
"id": "model.config.is_valid.webserver_security.app_error", "id": "model.config.is_valid.webserver_security.app_error",
"translation": "Nieprawidłowa wartość zabezpieczenia połeczenia dla serwera web." "translation": "Nieprawidłowa wartość zabezpieczenia połeczenia dla serwera web."
}, },
{
"id": "model.config.is_valid.websocket_url.app_error",
"translation": "Adres URL strony musi być prawidłowy i zaczynać się od http:// lub https://"
},
{ {
"id": "model.config.is_valid.write_timeout.app_error", "id": "model.config.is_valid.write_timeout.app_error",
"translation": "Nieprawidłowa wartość upłynięcia limitu czasu." "translation": "Nieprawidłowa wartość upłynięcia limitu czasu."

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error", "id": "api.file.upload_file.bad_parse.app_error",
"translation": "Não foi possível carregar o arquivo. O cabeçalho não pode ser analisado." "translation": "Não foi possível carregar o arquivo. O cabeçalho não pode ser analisado."
}, },
{
"id": "api.file.upload_file.incorrect_number_of_files.app_error",
"translation": "Unable to upload files. Incorrect number of files specified."
},
{ {
"id": "api.file.upload_file.large_image.app_error", "id": "api.file.upload_file.large_image.app_error",
"translation": "Arquivo acima das dimensões máximas não pode ser enviado: {{.Filename}}" "translation": "Arquivo acima das dimensões máximas não pode ser enviado: {{.Filename}}"
@@ -1814,6 +1818,10 @@
"id": "api.post.send_notifications_and_forget.push_image_only_dm", "id": "api.post.send_notifications_and_forget.push_image_only_dm",
"translation": " enviado um ou mais arquivos em uma mensagem direta" "translation": " enviado um ou mais arquivos em uma mensagem direta"
}, },
{
"id": "api.post.send_notifications_and_forget.push_image_only_no_channel",
"translation": " enviado um ou mais arquivos em "
},
{ {
"id": "api.post.send_notifications_and_forget.push_in", "id": "api.post.send_notifications_and_forget.push_in",
"translation": " em " "translation": " em "
@@ -3648,7 +3656,7 @@
}, },
{ {
"id": "app.plugin.disabled.app_error", "id": "app.plugin.disabled.app_error",
"translation": "Os plugins foram desabilitados." "translation": "Plugins have been disabled. Please check your logs for details."
}, },
{ {
"id": "app.plugin.extract.app_error", "id": "app.plugin.extract.app_error",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found", "id": "ent.migration.migratetoldap.user_not_found",
"translation": "Não foi possível localizar usuário no servidor AD/LDAP: " "translation": "Não foi possível localizar usuário no servidor AD/LDAP: "
}, },
{
"id": "ent.migration.migratetosaml.email_already_used_by_other_user",
"translation": "Email already used by another SAML user."
},
{
"id": "ent.migration.migratetosaml.user_not_found_in_users_mapping_file",
"translation": "User not found in the users file."
},
{
"id": "ent.migration.migratetosaml.username_already_used_by_other_user",
"translation": "Username already used by another Mattermost user."
},
{ {
"id": "ent.saml.attribute.app_error", "id": "ent.saml.attribute.app_error",
"translation": "SAML login não foi bem sucedido porque um dos atributos está incorreto. Entre em contato com o Administrador do Sistema." "translation": "SAML login não foi bem sucedido porque um dos atributos está incorreto. Entre em contato com o Administrador do Sistema."
@@ -5026,6 +5046,10 @@
"id": "model.config.is_valid.webserver_security.app_error", "id": "model.config.is_valid.webserver_security.app_error",
"translation": "Valor inválido para segurança de conexão do servidor web." "translation": "Valor inválido para segurança de conexão do servidor web."
}, },
{
"id": "model.config.is_valid.websocket_url.app_error",
"translation": "A URL Websocket do WebRTC Gateway deve ser uma URL válida e começar com ws:// ou wss://."
},
{ {
"id": "model.config.is_valid.write_timeout.app_error", "id": "model.config.is_valid.write_timeout.app_error",
"translation": "Valor inválido para limite de escrita." "translation": "Valor inválido para limite de escrita."

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error", "id": "api.file.upload_file.bad_parse.app_error",
"translation": "Невозможно загрузить файл. Заголовок не может быть распознан." "translation": "Невозможно загрузить файл. Заголовок не может быть распознан."
}, },
{
"id": "api.file.upload_file.incorrect_number_of_files.app_error",
"translation": "Unable to upload files. Incorrect number of files specified."
},
{ {
"id": "api.file.upload_file.large_image.app_error", "id": "api.file.upload_file.large_image.app_error",
"translation": "Размер файла превышает максимальный размер и не может быть загружен: {{.Filename}}" "translation": "Размер файла превышает максимальный размер и не может быть загружен: {{.Filename}}"
@@ -1814,6 +1818,10 @@
"id": "api.post.send_notifications_and_forget.push_image_only_dm", "id": "api.post.send_notifications_and_forget.push_image_only_dm",
"translation": " Загружены один или несколько файлов для сообщения" "translation": " Загружены один или несколько файлов для сообщения"
}, },
{
"id": "api.post.send_notifications_and_forget.push_image_only_no_channel",
"translation": " Загружено один или несколько файлов в "
},
{ {
"id": "api.post.send_notifications_and_forget.push_in", "id": "api.post.send_notifications_and_forget.push_in",
"translation": " в " "translation": " в "
@@ -3648,7 +3656,7 @@
}, },
{ {
"id": "app.plugin.disabled.app_error", "id": "app.plugin.disabled.app_error",
"translation": "Plugins have been disabled." "translation": "Plugins have been disabled. Please check your logs for details."
}, },
{ {
"id": "app.plugin.extract.app_error", "id": "app.plugin.extract.app_error",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found", "id": "ent.migration.migratetoldap.user_not_found",
"translation": "Не удалось найти пользователя на сервере AD/LDAP: " "translation": "Не удалось найти пользователя на сервере AD/LDAP: "
}, },
{
"id": "ent.migration.migratetosaml.email_already_used_by_other_user",
"translation": "Email already used by another SAML user."
},
{
"id": "ent.migration.migratetosaml.user_not_found_in_users_mapping_file",
"translation": "User not found in the users file."
},
{
"id": "ent.migration.migratetosaml.username_already_used_by_other_user",
"translation": "Username already used by another Mattermost user."
},
{ {
"id": "ent.saml.attribute.app_error", "id": "ent.saml.attribute.app_error",
"translation": "Попытка входа с использованием SAML не удалась из-за некорректного атрибута. Пожалуйста, свяжитесь с системным администратором." "translation": "Попытка входа с использованием SAML не удалась из-за некорректного атрибута. Пожалуйста, свяжитесь с системным администратором."
@@ -5026,6 +5046,10 @@
"id": "model.config.is_valid.webserver_security.app_error", "id": "model.config.is_valid.webserver_security.app_error",
"translation": "Недопустимое значение настроек безопасности соединения веб-сервера." "translation": "Недопустимое значение настроек безопасности соединения веб-сервера."
}, },
{
"id": "model.config.is_valid.websocket_url.app_error",
"translation": "Ссылка на шлюз WebRTC должна быть действующей и начинаться с ws:// или wss://."
},
{ {
"id": "model.config.is_valid.write_timeout.app_error", "id": "model.config.is_valid.write_timeout.app_error",
"translation": "Недопустимое значение для времени ожидания записи." "translation": "Недопустимое значение для времени ожидания записи."

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

@@ -1036,7 +1036,7 @@
}, },
{ {
"id": "api.context.mfa_required.app_error", "id": "api.context.mfa_required.app_error",
"translation": "Bu sunucuda çok aşamalı kimlik doğrulaması kullanılıyor." "translation": "Bu sunucuda çok aşamalı kimlik doğrulaması zorunludur."
}, },
{ {
"id": "api.context.missing_teamid.app_error", "id": "api.context.missing_teamid.app_error",
@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error", "id": "api.file.upload_file.bad_parse.app_error",
"translation": "Dosya yüklenemedi. Üst bilgi işlenemedi." "translation": "Dosya yüklenemedi. Üst bilgi işlenemedi."
}, },
{
"id": "api.file.upload_file.incorrect_number_of_files.app_error",
"translation": "Belirtilen dosya sayısı hatalı olduğundan dosyalar yüklenemedi."
},
{ {
"id": "api.file.upload_file.large_image.app_error", "id": "api.file.upload_file.large_image.app_error",
"translation": "Dosya en büyük boyutları aştığından yüklenemedi: {{.Filename}}" "translation": "Dosya en büyük boyutları aştığından yüklenemedi: {{.Filename}}"
@@ -1808,12 +1812,16 @@
}, },
{ {
"id": "api.post.send_notifications_and_forget.push_image_only", "id": "api.post.send_notifications_and_forget.push_image_only",
"translation": " şunun içine bir ya da bir kaç dosya yükledi " "translation": " şuraya bir ya da bir kaç dosya yükledi "
}, },
{ {
"id": "api.post.send_notifications_and_forget.push_image_only_dm", "id": "api.post.send_notifications_and_forget.push_image_only_dm",
"translation": " bir doğrudan ileti içine bir ya da bir kaç dosya yükledi" "translation": " bir doğrudan ileti içine bir ya da bir kaç dosya yükledi"
}, },
{
"id": "api.post.send_notifications_and_forget.push_image_only_no_channel",
"translation": " bir ya da bir kaç dosya yükledi "
},
{ {
"id": "api.post.send_notifications_and_forget.push_in", "id": "api.post.send_notifications_and_forget.push_in",
"translation": " içinde " "translation": " içinde "
@@ -2184,7 +2192,7 @@
}, },
{ {
"id": "api.team.add_user_to_team.missing_parameter.app_error", "id": "api.team.add_user_to_team.missing_parameter.app_error",
"translation": "Takıma kullanıcı eklemek için parametre gerekli." "translation": "Takıma kullanıcı eklemek için parametre zorunludur."
}, },
{ {
"id": "api.team.create_team.email_disabled.app_error", "id": "api.team.create_team.email_disabled.app_error",
@@ -2300,11 +2308,11 @@
}, },
{ {
"id": "api.team.move_channel.post.error", "id": "api.team.move_channel.post.error",
"translation": "Kanal amacı iletisi gönderilemedi" "translation": "Kanal taşındı iletisi gönderilemedi."
}, },
{ {
"id": "api.team.move_channel.success", "id": "api.team.move_channel.success",
"translation": "This channel has been moved to this team from %v." "translation": "Bu kanal %v üzerinden bu takıma taşındı."
}, },
{ {
"id": "api.team.permanent_delete_team.attempting.warn", "id": "api.team.permanent_delete_team.attempting.warn",
@@ -3072,7 +3080,7 @@
}, },
{ {
"id": "api.webhook.incoming.error", "id": "api.webhook.incoming.error",
"translation": "Could not decode the multipart payload of incoming webhook." "translation": "Gelen web bağlantsının birden çok parçalı yükünün kodu çözülemedi."
}, },
{ {
"id": "api.webhook.init.debug", "id": "api.webhook.init.debug",
@@ -3648,7 +3656,7 @@
}, },
{ {
"id": "app.plugin.disabled.app_error", "id": "app.plugin.disabled.app_error",
"translation": "Uygulama ekleri devre dışı bırakılmış." "translation": "Uygulama ekleri devre dışı bırakıldı. Ayrıntılar için günlük kayıtlarına bakın."
}, },
{ {
"id": "app.plugin.extract.app_error", "id": "app.plugin.extract.app_error",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found", "id": "ent.migration.migratetoldap.user_not_found",
"translation": "Kullanıcı AD/LDAP sunucusu üzerinde bulunamadı:" "translation": "Kullanıcı AD/LDAP sunucusu üzerinde bulunamadı:"
}, },
{
"id": "ent.migration.migratetosaml.email_already_used_by_other_user",
"translation": "E-posta başka bir SAML kullanıcısı tarafından kullanılıyor."
},
{
"id": "ent.migration.migratetosaml.user_not_found_in_users_mapping_file",
"translation": "Kullanıcı, kullanıcılar dosyasında bulunamadı."
},
{
"id": "ent.migration.migratetosaml.username_already_used_by_other_user",
"translation": "Bu kullanıcı adı başka bir Mattermost kullanıcısı tarafından kullanılıyor."
},
{ {
"id": "ent.saml.attribute.app_error", "id": "ent.saml.attribute.app_error",
"translation": "Özniteliklerden biri hatalı olduğundan SAML oturumu açılamadı. Lütfen sistem yöneticiniz ile görüşün." "translation": "Özniteliklerden biri hatalı olduğundan SAML oturumu açılamadı. Lütfen sistem yöneticiniz ile görüşün."
@@ -4884,7 +4904,7 @@
}, },
{ {
"id": "model.config.is_valid.message_export.export_type.app_error", "id": "model.config.is_valid.message_export.export_type.app_error",
"translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'" "translation": "İleti dışa aktarma görevinin Dışa Aktarma Biçimi 'actiance' ya da 'genelaktarım' olmalıdır"
}, },
{ {
"id": "model.config.is_valid.message_export.file_location.app_error", "id": "model.config.is_valid.message_export.file_location.app_error",
@@ -4896,7 +4916,7 @@
}, },
{ {
"id": "model.config.is_valid.message_export.global_relay_email_address.app_error", "id": "model.config.is_valid.message_export.global_relay_email_address.app_error",
"translation": "Message export job GlobalRelayEmailAddress must be set to a valid email address" "translation": "İleti dışa aktarma görevinin GenelAktarıcıE-postaAdresi geçerli bir e-posta adresi olmalıdır"
}, },
{ {
"id": "model.config.is_valid.password_length.app_error", "id": "model.config.is_valid.password_length.app_error",
@@ -5026,6 +5046,10 @@
"id": "model.config.is_valid.webserver_security.app_error", "id": "model.config.is_valid.webserver_security.app_error",
"translation": "Web sunucusu bağlantı güvenliği değeri geçersiz." "translation": "Web sunucusu bağlantı güvenliği değeri geçersiz."
}, },
{
"id": "model.config.is_valid.websocket_url.app_error",
"translation": "Web soketi adresi ws:// ya da wss:// ile başlayan geçerli bir adres olmalıdır"
},
{ {
"id": "model.config.is_valid.write_timeout.app_error", "id": "model.config.is_valid.write_timeout.app_error",
"translation": "Yazma zaman aşımı değeri geçersiz." "translation": "Yazma zaman aşımı değeri geçersiz."
@@ -7112,7 +7136,7 @@
}, },
{ {
"id": "utils.mail.sendMail.attachments.write_error", "id": "utils.mail.sendMail.attachments.write_error",
"translation": "Failed to write attachment to email" "translation": "Ek dosya e-postaya eklenemedi"
}, },
{ {
"id": "utils.mail.send_mail.close.app_error", "id": "utils.mail.send_mail.close.app_error",

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error", "id": "api.file.upload_file.bad_parse.app_error",
"translation": "无法上传文件。标题无法被解析。" "translation": "无法上传文件。标题无法被解析。"
}, },
{
"id": "api.file.upload_file.incorrect_number_of_files.app_error",
"translation": "无法上传文件。指定的文件数不匹配。"
},
{ {
"id": "api.file.upload_file.large_image.app_error", "id": "api.file.upload_file.large_image.app_error",
"translation": "无法上传超过最大尺寸的文件:{{.Filename}}" "translation": "无法上传超过最大尺寸的文件:{{.Filename}}"
@@ -1814,6 +1818,10 @@
"id": "api.post.send_notifications_and_forget.push_image_only_dm", "id": "api.post.send_notifications_and_forget.push_image_only_dm",
"translation": " 在私信里上传一个或更多个文件" "translation": " 在私信里上传一个或更多个文件"
}, },
{
"id": "api.post.send_notifications_and_forget.push_image_only_no_channel",
"translation": " 已上传一个或更多个文件"
},
{ {
"id": "api.post.send_notifications_and_forget.push_in", "id": "api.post.send_notifications_and_forget.push_in",
"translation": " 在 " "translation": " 在 "
@@ -3648,7 +3656,7 @@
}, },
{ {
"id": "app.plugin.disabled.app_error", "id": "app.plugin.disabled.app_error",
"translation": "插件已禁用。" "translation": "日志已停用。请检查您的日志了解详情。"
}, },
{ {
"id": "app.plugin.extract.app_error", "id": "app.plugin.extract.app_error",
@@ -3700,7 +3708,7 @@
}, },
{ {
"id": "app.team.join_user_to_team.max_accounts.app_error", "id": "app.team.join_user_to_team.max_accounts.app_error",
"translation": "这个团队已经达到允许的最大用户数量。请与系统管理员联系以设置更高的限制。" "translation": "这个团队已经达到允许的最大帐号数量。请与系统管理员联系以设置更高的限制。"
}, },
{ {
"id": "app.user_access_token.disabled", "id": "app.user_access_token.disabled",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found", "id": "ent.migration.migratetoldap.user_not_found",
"translation": "未在 AD/LDAP 服务器上找到用户:" "translation": "未在 AD/LDAP 服务器上找到用户:"
}, },
{
"id": "ent.migration.migratetosaml.email_already_used_by_other_user",
"translation": "电子邮箱地址已被其他 SAML 用户使用。"
},
{
"id": "ent.migration.migratetosaml.user_not_found_in_users_mapping_file",
"translation": "没有在用户文件里找到用户。"
},
{
"id": "ent.migration.migratetosaml.username_already_used_by_other_user",
"translation": "用户名已被其他 Mattermost 用户使用。"
},
{ {
"id": "ent.saml.attribute.app_error", "id": "ent.saml.attribute.app_error",
"translation": "SAML登入因不正确属性而失败。请联系您的系统管理员。" "translation": "SAML登入因不正确属性而失败。请联系您的系统管理员。"
@@ -5026,6 +5046,10 @@
"id": "model.config.is_valid.webserver_security.app_error", "id": "model.config.is_valid.webserver_security.app_error",
"translation": "错误的网页服务器连接安全值。" "translation": "错误的网页服务器连接安全值。"
}, },
{
"id": "model.config.is_valid.websocket_url.app_error",
"translation": "Websocket 网址必须时有效的网址并且以 ws:// 或 wss:// 开头。"
},
{ {
"id": "model.config.is_valid.write_timeout.app_error", "id": "model.config.is_valid.write_timeout.app_error",
"translation": "错误的写入超时值。" "translation": "错误的写入超时值。"
@@ -7112,7 +7136,7 @@
}, },
{ {
"id": "utils.mail.sendMail.attachments.write_error", "id": "utils.mail.sendMail.attachments.write_error",
"translation": "Failed to write attachment to email" "translation": "给邮件添加附件失败"
}, },
{ {
"id": "utils.mail.send_mail.close.app_error", "id": "utils.mail.send_mail.close.app_error",

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error", "id": "api.file.upload_file.bad_parse.app_error",
"translation": "無法上傳檔案。無法解析標頭。" "translation": "無法上傳檔案。無法解析標頭。"
}, },
{
"id": "api.file.upload_file.incorrect_number_of_files.app_error",
"translation": "Unable to upload files. Incorrect number of files specified."
},
{ {
"id": "api.file.upload_file.large_image.app_error", "id": "api.file.upload_file.large_image.app_error",
"translation": "無法上傳超過最大尺寸的檔案:{{.Filename}}" "translation": "無法上傳超過最大尺寸的檔案:{{.Filename}}"
@@ -1814,6 +1818,10 @@
"id": "api.post.send_notifications_and_forget.push_image_only_dm", "id": "api.post.send_notifications_and_forget.push_image_only_dm",
"translation": "在直接傳訊中已上傳一個或更多檔案" "translation": "在直接傳訊中已上傳一個或更多檔案"
}, },
{
"id": "api.post.send_notifications_and_forget.push_image_only_no_channel",
"translation": "已上傳一個或更多檔案"
},
{ {
"id": "api.post.send_notifications_and_forget.push_in", "id": "api.post.send_notifications_and_forget.push_in",
"translation": " 於 " "translation": " 於 "
@@ -3648,7 +3656,7 @@
}, },
{ {
"id": "app.plugin.disabled.app_error", "id": "app.plugin.disabled.app_error",
"translation": "模組已被停用。" "translation": "Plugins have been disabled. Please check your logs for details."
}, },
{ {
"id": "app.plugin.extract.app_error", "id": "app.plugin.extract.app_error",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found", "id": "ent.migration.migratetoldap.user_not_found",
"translation": "找不到使用者AD/LDAP 伺服器:" "translation": "找不到使用者AD/LDAP 伺服器:"
}, },
{
"id": "ent.migration.migratetosaml.email_already_used_by_other_user",
"translation": "Email already used by another SAML user."
},
{
"id": "ent.migration.migratetosaml.user_not_found_in_users_mapping_file",
"translation": "User not found in the users file."
},
{
"id": "ent.migration.migratetosaml.username_already_used_by_other_user",
"translation": "Username already used by another Mattermost user."
},
{ {
"id": "ent.saml.attribute.app_error", "id": "ent.saml.attribute.app_error",
"translation": "由於不正確的屬性SAML 登入失敗。請聯繫系統管理員。" "translation": "由於不正確的屬性SAML 登入失敗。請聯繫系統管理員。"
@@ -5026,6 +5046,10 @@
"id": "model.config.is_valid.webserver_security.app_error", "id": "model.config.is_valid.webserver_security.app_error",
"translation": "網頁伺服器連線安全的值不正確。" "translation": "網頁伺服器連線安全的值不正確。"
}, },
{
"id": "model.config.is_valid.websocket_url.app_error",
"translation": "WebRTC 閘道 Websocket 網址必須是以 ws:// 或 wss:// 起始的有效網址。"
},
{ {
"id": "model.config.is_valid.write_timeout.app_error", "id": "model.config.is_valid.write_timeout.app_error",
"translation": "寫入逾時的值不正確。" "translation": "寫入逾時的值不正確。"

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

@@ -1,106 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package jobs
import (
"context"
"time"
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/mattermost-server/model"
)
type TestWorker struct {
srv *JobServer
name string
stop chan bool
stopped chan bool
jobs chan model.Job
}
func (srv *JobServer) MakeTestWorker(name string) *TestWorker {
return &TestWorker{
srv: srv,
name: name,
stop: make(chan bool, 1),
stopped: make(chan bool, 1),
jobs: make(chan model.Job),
}
}
func (worker *TestWorker) Run() {
l4g.Debug("Worker %v: Started", worker.name)
defer func() {
l4g.Debug("Worker %v: Finished", worker.name)
worker.stopped <- true
}()
for {
select {
case <-worker.stop:
l4g.Debug("Worker %v: Received stop signal", worker.name)
return
case job := <-worker.jobs:
l4g.Debug("Worker %v: Received a new candidate job.", worker.name)
worker.DoJob(&job)
}
}
}
func (worker *TestWorker) DoJob(job *model.Job) {
if claimed, err := worker.srv.ClaimJob(job); err != nil {
l4g.Error("Job: %v: Error occurred while trying to claim job: %v", job.Id, err.Error())
return
} else if !claimed {
return
}
cancelCtx, cancelCancelWatcher := context.WithCancel(context.Background())
cancelWatcherChan := make(chan interface{}, 1)
go worker.srv.CancellationWatcher(cancelCtx, job.Id, cancelWatcherChan)
defer cancelCancelWatcher()
counter := 0
for {
select {
case <-cancelWatcherChan:
l4g.Debug("Job %v: Job has been canceled via CancellationWatcher.", job.Id)
if err := worker.srv.SetJobCanceled(job); err != nil {
l4g.Error("Failed to mark job: %v as canceled. Error: %v", job.Id, err.Error())
}
return
case <-worker.stop:
l4g.Debug("Job %v: Job has been canceled via Worker Stop.", job.Id)
if err := worker.srv.SetJobCanceled(job); err != nil {
l4g.Error("Failed to mark job: %v as canceled. Error: %v", job.Id, err.Error())
}
return
case <-time.After(5 * time.Second):
counter++
if counter > 10 {
l4g.Debug("Job %v: Job completed.", job.Id)
if err := worker.srv.SetJobSuccess(job); err != nil {
l4g.Error("Failed to mark job: %v as succeeded. Error: %v", job.Id, err.Error())
}
return
} else {
if err := worker.srv.SetJobProgress(job, int64(counter*10)); err != nil {
l4g.Error("Job: %v: an error occured while trying to set job progress: %v", job.Id, err.Error())
}
}
}
}
}
func (worker *TestWorker) Stop() {
l4g.Debug("Worker %v: Stopping", worker.name)
worker.stop <- true
<-worker.stopped
}
func (worker *TestWorker) JobChannel() chan<- model.Job {
return worker.jobs
}

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

@@ -0,0 +1,36 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"os"
"github.com/mattermost/mattermost-server/cmd"
_ "github.com/mattermost/mattermost-server/cmd/commands"
// Plugins
_ "github.com/mattermost/mattermost-server/model/gitlab"
// Enterprise Imports
_ "github.com/mattermost/mattermost-server/imports"
// Enterprise Deps
_ "github.com/dgryski/dgoogauth"
_ "github.com/go-ldap/ldap"
_ "github.com/hashicorp/memberlist"
_ "github.com/mattermost/rsc/qr"
_ "github.com/prometheus/client_golang/prometheus"
_ "github.com/prometheus/client_golang/prometheus/promhttp"
_ "github.com/tylerb/graceful"
_ "gopkg.in/olivere/elastic.v5"
// Temp imports for new dependencies
_ "github.com/gorilla/schema"
)
func main() {
if err := cmd.Run(os.Args[1:]); err != nil {
os.Exit(1)
}
}

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

@@ -86,12 +86,7 @@ func (o *Channel) Etag() string {
return Etag(o.Id, o.UpdateAt) return Etag(o.Id, o.UpdateAt)
} }
func (o *Channel) StatsEtag() string {
return Etag(o.Id, o.ExtraUpdateAt)
}
func (o *Channel) IsValid() *AppError { func (o *Channel) IsValid() *AppError {
if len(o.Id) != 26 { if len(o.Id) != 26 {
return NewAppError("Channel.IsValid", "model.channel.is_valid.id.app_error", nil, "", http.StatusBadRequest) return NewAppError("Channel.IsValid", "model.channel.is_valid.id.app_error", nil, "", http.StatusBadRequest)
} }

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

@@ -699,7 +699,7 @@ func (c *Client4) GetUsersNotInTeam(teamId string, page int, perPage int, etag s
} }
} }
// GetUsersInChannel returns a page of users on a team. Page counting starts at 0. // GetUsersInChannel returns a page of users in a channel. Page counting starts at 0.
func (c *Client4) GetUsersInChannel(channelId string, page int, perPage int, etag string) ([]*User, *Response) { func (c *Client4) GetUsersInChannel(channelId string, page int, perPage int, etag string) ([]*User, *Response) {
query := fmt.Sprintf("?in_channel=%v&page=%v&per_page=%v", channelId, page, perPage) query := fmt.Sprintf("?in_channel=%v&page=%v&per_page=%v", channelId, page, perPage)
if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil { if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil {
@@ -710,7 +710,18 @@ func (c *Client4) GetUsersInChannel(channelId string, page int, perPage int, eta
} }
} }
// GetUsersNotInChannel returns a page of users on a team. Page counting starts at 0. // GetUsersInChannelStatus returns a page of users in a channel. Page counting starts at 0. Sorted by Status
func (c *Client4) GetUsersInChannelByStatus(channelId string, page int, perPage int, etag string) ([]*User, *Response) {
query := fmt.Sprintf("?in_channel=%v&page=%v&per_page=%v&sort=status", channelId, page, perPage)
if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil {
return nil, BuildErrorResponse(r, err)
} else {
defer closeBody(r)
return UserListFromJson(r.Body), BuildResponse(r)
}
}
// GetUsersNotInChannel returns a page of users not in a channel. Page counting starts at 0.
func (c *Client4) GetUsersNotInChannel(teamId, channelId string, page int, perPage int, etag string) ([]*User, *Response) { func (c *Client4) GetUsersNotInChannel(teamId, channelId string, page int, perPage int, etag string) ([]*User, *Response) {
query := fmt.Sprintf("?in_team=%v&not_in_channel=%v&page=%v&per_page=%v", teamId, channelId, page, perPage) query := fmt.Sprintf("?in_team=%v&not_in_channel=%v&page=%v&per_page=%v", teamId, channelId, page, perPage)
if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil { if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil {

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

@@ -6,7 +6,6 @@ package model
import ( import (
"encoding/json" "encoding/json"
"io" "io"
"strings"
) )
type ClusterInfo struct { type ClusterInfo struct {
@@ -22,11 +21,6 @@ func (me *ClusterInfo) ToJson() string {
return string(b) return string(b)
} }
func (me *ClusterInfo) Copy() *ClusterInfo {
json := me.ToJson()
return ClusterInfoFromJson(strings.NewReader(json))
}
func ClusterInfoFromJson(data io.Reader) *ClusterInfo { func ClusterInfoFromJson(data io.Reader) *ClusterInfo {
var me *ClusterInfo var me *ClusterInfo
json.NewDecoder(data).Decode(&me) json.NewDecoder(data).Decode(&me)

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