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
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
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 \
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 \
echo restarting mattermost-inbucket; \
docker start mattermost-inbucket > /dev/null; \

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

@@ -7,10 +7,10 @@ import (
"net/http"
"strconv"
"github.com/avct/uasurfer"
"github.com/gorilla/mux"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
"github.com/mssola/user_agent"
)
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
// attach extra headers to trigger a download on IE, Edge, and Safari
ua := user_agent.New(r.UserAgent())
bname, _ := ua.Browser()
ua := uasurfer.Parse(r.UserAgent())
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
w.Header().Set("Content-Type", "application/octet-stream")
}

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

@@ -11,7 +11,6 @@ import (
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
"github.com/mattermost/mattermost-server/store/sqlstore"
)
func TestCreateChannel(t *testing.T) {
@@ -345,7 +344,7 @@ func TestUpdateChannel(t *testing.T) {
th.MakeUserChannelUser(th.BasicUser, channel2)
th.MakeUserChannelUser(th.BasicUser, channel3)
sqlstore.ClearChannelCaches()
th.App.Srv.Store.Channel().ClearCaches()
if _, err := Client.UpdateChannel(channel2); err == nil {
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, channel3)
sqlstore.ClearChannelCaches()
th.App.Srv.Store.Channel().ClearCaches()
if _, err := Client.UpdateChannel(channel2); err != nil {
t.Fatal(err)
@@ -508,7 +507,7 @@ func TestUpdateChannelHeader(t *testing.T) {
th.MakeUserChannelUser(th.BasicUser, channel2)
th.MakeUserChannelUser(th.BasicUser, channel3)
sqlstore.ClearChannelCaches()
th.App.Srv.Store.Channel().ClearCaches()
if _, err := Client.UpdateChannelHeader(data2); err == nil {
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, channel3)
sqlstore.ClearChannelCaches()
th.App.Srv.Store.Channel().ClearCaches()
if _, err := Client.UpdateChannelHeader(data2); err != nil {
t.Fatal(err)
@@ -629,7 +628,7 @@ func TestUpdateChannelPurpose(t *testing.T) {
th.MakeUserChannelUser(th.BasicUser, channel2)
th.MakeUserChannelUser(th.BasicUser, channel3)
sqlstore.ClearChannelCaches()
th.App.Srv.Store.Channel().ClearCaches()
if _, err := Client.UpdateChannelPurpose(data2); err == nil {
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, channel3)
sqlstore.ClearChannelCaches()
th.App.Srv.Store.Channel().ClearCaches()
if _, err := Client.UpdateChannelPurpose(data2); err != nil {
t.Fatal(err)
@@ -1154,7 +1153,7 @@ func TestDeleteChannel(t *testing.T) {
th.MakeUserChannelAdmin(th.BasicUser, channel2)
th.MakeUserChannelAdmin(th.BasicUser, channel3)
sqlstore.ClearChannelCaches()
th.App.Srv.Store.Channel().ClearCaches()
if _, err := Client.DeleteChannel(channel2.Id); err != nil {
t.Fatal(err)

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

@@ -364,10 +364,6 @@ func NewInvalidParamError(where string, name string) *model.AppError {
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) {
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, "/")
}
// TODO see where these are used
func (c *Context) GetTeamURLFromTeam(team *model.Team) string {
return c.GetSiteURLHeader() + "/" + team.Name
}
func (c *Context) GetTeamURL() string {
if !c.teamURLValid {
c.SetTeamURLFromSession()
@@ -406,10 +397,6 @@ func (c *Context) GetSiteURLHeader() string {
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 {
metrics := c.App.Metrics
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.Add("post", c.App.PostWithProxyAddedToImageURLs(rpost).ToJson())
c.App.Go(func() {
c.App.Publish(message)
})
c.App.Publish(message)
c.App.InvalidateCacheForChannelPosts(rpost.ChannelId)

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

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

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

@@ -468,6 +468,22 @@ func (me *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) {
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 {
if me.App.Config().EmailSettings.SMTPServer != "dockerhost" && os.Getenv("CI_INBUCKET_PORT") == "" {
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) {
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) {
path, _ := utils.FindDir("tests")
file, err := os.Open(path + "/" + name)

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

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

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

@@ -7,8 +7,8 @@ import (
"net/http"
"strconv"
"github.com/avct/uasurfer"
"github.com/mattermost/mattermost-server/model"
"github.com/mssola/user_agent"
)
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
// attach extra headers to trigger a download on IE, Edge, and Safari
ua := user_agent.New(r.UserAgent())
bname, _ := ua.Browser()
ua := uasurfer.Parse(r.UserAgent())
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
w.Header().Set("Content-Type", "application/octet-stream")
}

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

@@ -8,7 +8,6 @@ import (
"io"
"net/http"
"runtime"
"strconv"
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/mattermost-server/model"
@@ -247,14 +246,7 @@ func getClientConfig(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
respCfg := map[string]string{}
for k, v := range c.App.ClientConfig() {
respCfg[k] = v
}
respCfg["NoAccounts"] = strconv.FormatBool(c.App.IsFirstUserAccount())
w.Write([]byte(model.MapToJson(respCfg)))
w.Write([]byte(model.MapToJson(c.App.ClientConfigWithNoAccounts())))
}
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
}
if sort != "" && sort != "last_activity_at" && sort != "create_at" {
if sort != "" && sort != "last_activity_at" && sort != "create_at" && sort != "status" {
c.SetInvalidUrlParam("sort")
return
}
// 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 != "") {
c.SetInvalidUrlParam("sort")
return
}
if sort == "status" && inChannelId == "" {
c.SetInvalidUrlParam("sort")
return
}
var profiles []*model.User
var err *model.AppError
@@ -355,8 +360,11 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
return
}
profiles, err = c.App.GetUsersInChannelPage(inChannelId, c.Params.Page, c.Params.PerPage, c.IsSystemAdmin())
if sort == "status" {
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 {
// No permission check required

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

@@ -2650,3 +2650,146 @@ func TestUserAccessTokenDisableConfig(t *testing.T) {
_, resp = Client.GetMe("")
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) {
decodeError := &model.AppError{}
incomingWebhookPayload, decodeError := model.IncomingWebhookRequestFromJson(payload)
if decodeError != nil {

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

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

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

@@ -133,8 +133,24 @@ func New(options ...Option) (outApp *App, outErr error) {
app.configListenerId = app.AddConfigListener(func(_, _ *model.Config) {
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()
l4g.Info(utils.T("api.server.new_server.init.info"))

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

@@ -143,10 +143,6 @@ func (me *TestHelper) InitBasic() *TestHelper {
return me
}
func (me *TestHelper) MakeUsername() string {
return "un_" + model.NewId()
}
func (me *TestHelper) MakeEmail() string {
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)
}
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 {
id := model.NewId()
@@ -262,6 +254,22 @@ func (me *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) {
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() {
me.App.Shutdown()
os.Remove(me.tempConfigPath)

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

@@ -9,16 +9,15 @@ import (
)
const (
USER_PASSWORD = "passwd"
CHANNEL_TYPE = model.CHANNEL_OPEN
FUZZ_USER_EMAIL_PREFIX_LEN = 10
BTEST_TEAM_DISPLAY_NAME = "TestTeam"
BTEST_TEAM_NAME = "z-z-testdomaina"
BTEST_TEAM_EMAIL = "test@nowhere.com"
BTEST_TEAM_TYPE = model.TEAM_OPEN
BTEST_USER_NAME = "Mr. Testing Tester"
BTEST_USER_EMAIL = "success+ttester@simulator.amazonses.com"
BTEST_USER_PASSWORD = "passwd"
USER_PASSWORD = "passwd"
CHANNEL_TYPE = model.CHANNEL_OPEN
BTEST_TEAM_DISPLAY_NAME = "TestTeam"
BTEST_TEAM_NAME = "z-z-testdomaina"
BTEST_TEAM_EMAIL = "test@nowhere.com"
BTEST_TEAM_TYPE = model.TEAM_OPEN
BTEST_USER_NAME = "Mr. Testing Tester"
BTEST_USER_EMAIL = "success+ttester@simulator.amazonses.com"
BTEST_USER_PASSWORD = "passwd"
)
var (
@@ -29,8 +28,5 @@ var (
USER_EMAIL_LEN = utils.Range{Begin: 15, End: 30}
CHANNEL_DISPLAY_NAME_LEN = utils.Range{Begin: 10, 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"}
)

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

@@ -90,18 +90,3 @@ func (cfg *AutoPostCreator) CreateRandomPost() (*model.Post, bool) {
}
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 {
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
}
}
@@ -369,7 +377,7 @@ func (a *App) postChannelPrivacyMessage(user *model.User, channel *model.Channel
})[channel.Type]
post := &model.Post{
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,
UserId: user.Id,
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.Add("channel_id", channel.Id)
a.Publish(message)
}
@@ -1059,7 +1066,7 @@ func (a *App) LeaveChannel(channelId string, userId string) *model.AppError {
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
}
@@ -1097,7 +1104,9 @@ func (a *App) PostAddToChannelMessage(user *model.User, addedUser *model.User, c
UserId: user.Id,
RootId: postRootId,
Props: model.StringInterface{
"userId": user.Id,
"username": user.Username,
"addedUserId": addedUser.Id,
"addedUsername": addedUser.Username,
},
}
@@ -1117,7 +1126,9 @@ func (a *App) postAddToTeamMessage(user *model.User, addedUser *model.User, chan
UserId: user.Id,
RootId: postRootId,
Props: model.StringInterface{
"userId": user.Id,
"username": user.Username,
"addedUserId": addedUser.Id,
"addedUsername": addedUser.Username,
},
}
@@ -1136,6 +1147,7 @@ func (a *App) postRemoveFromChannelMessage(removerUserId string, removedUser *mo
Type: model.POST_REMOVE_FROM_CHANNEL,
UserId: removerUserId,
Props: model.StringInterface{
"removedUserId": removedUser.Id,
"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.Add("user_id", userIdToRemove)
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
userMsg := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_REMOVED, "", "", userIdToRemove, nil)
userMsg.Add("channel_id", channel.Id)
userMsg.Add("remover_id", removerUserId)
a.Go(func() {
a.Publish(userMsg)
})
a.Publish(userMsg)
return nil
}
@@ -1250,9 +1258,7 @@ func (a *App) UpdateChannelLastViewedAt(channelIds []string, userId string) *mod
for _, channelId := range channelIds {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, "", "", userId, nil)
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) {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, "", "", userId, nil)
message.Add("channel_id", view.ChannelId)
a.Go(func() {
a.Publish(message)
})
a.Publish(message)
}
return times, nil
@@ -1434,7 +1438,16 @@ func (a *App) GetDirectChannel(userId1, userId2 string) (*model.Channel, *model.
}
a.InvalidateCacheForUser(userId1)
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 {
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()
defer th.TearDown()
@@ -136,7 +136,7 @@ func TestJoinDefaultChannelsTownSquare(t *testing.T) {
assert.True(t, found)
}
func TestJoinDefaultChannelsOffTopic(t *testing.T) {
func TestJoinDefaultChannelsCreatesChannelMemberHistoryRecordOffTopic(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
@@ -162,7 +162,7 @@ func TestJoinDefaultChannelsOffTopic(t *testing.T) {
assert.True(t, found)
}
func TestCreateChannelPublic(t *testing.T) {
func TestCreateChannelPublicCreatesChannelMemberHistoryRecord(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
@@ -176,7 +176,7 @@ func TestCreateChannelPublic(t *testing.T) {
assert.Equal(t, publicChannel.Id, histories[0].ChannelId)
}
func TestCreateChannelPrivate(t *testing.T) {
func TestCreateChannelPrivateCreatesChannelMemberHistoryRecord(t *testing.T) {
th := Setup().InitBasic()
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()
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()
defer th.TearDown()
@@ -263,7 +318,7 @@ func TestAddUserToChannel(t *testing.T) {
assert.Equal(t, groupUserIds, channelMemberHistoryUserIds)
}
func TestRemoveUserFromChannel(t *testing.T) {
func TestRemoveUserFromChannelUpdatesChannelMemberHistoryRecord(t *testing.T) {
th := Setup().InitBasic()
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.Add("preference", pref.ToJson())
a.Go(func() {
a.Publish(socketMessage)
})
a.Publish(socketMessage)
var rmsg string

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

@@ -14,6 +14,7 @@ import (
"fmt"
"net/url"
"runtime/debug"
"strconv"
"strings"
l4g "github.com/alecthomas/log4go"
@@ -34,6 +35,7 @@ func (a *App) UpdateConfig(f func(*model.Config)) {
updated := old.Clone()
f(updated)
a.config.Store(updated)
a.InvokeConfigListeners(old, updated)
}
@@ -269,3 +271,16 @@ func (a *App) GetCookieDomain() string {
func (a *App) GetSiteURL() string {
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.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{}{
"enable_message_export": *cfg.MessageExportSettings.EnableExport,
"export_format": *cfg.MessageExportSettings.ExportFormat,
"daily_run_time": *cfg.MessageExportSettings.DailyRunTime,
"default_export_from_timestamp": *cfg.MessageExportSettings.ExportFromTimestamp,
"batch_size": *cfg.MessageExportSettings.BatchSize,
"enable_message_export": *cfg.MessageExportSettings.EnableExport,
"export_format": *cfg.MessageExportSettings.ExportFormat,
"daily_run_time": *cfg.MessageExportSettings.DailyRunTime,
"default_export_from_timestamp": *cfg.MessageExportSettings.ExportFromTimestamp,
"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 {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_EMOJI_ADDED, "", "", "", nil)
message.Add("emoji", emoji.ToJson())
a.Publish(message)
return result.Data.(*model.Emoji), nil
}

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

@@ -9,8 +9,8 @@ import (
"strings"
"time"
"github.com/avct/uasurfer"
"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) {
@@ -71,19 +71,19 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User,
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 == "" {
plat = "unknown"
}
os := ua.OS()
os := ua.OS.Name.String()
if os == "" {
os = "unknown"
}
bname, bversion := ua.Browser()
bname := ua.Browser.Name.String()
if bname == "" {
bname = "unknown"
}
@@ -92,9 +92,7 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User,
bname = "Desktop App"
}
if bversion == "" {
bversion = "0.0"
}
bversion := ua.Browser.Version
session.AddProp(model.SESSION_PROP_PLATFORM, plat)
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 {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, "", "", post.UserId, nil)
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.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson())
a.Go(func() {
a.Publish(message)
})
a.Publish(message)
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) {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", post.ChannelId, "", nil)
message.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson())
a.Go(func() {
a.Publish(message)
})
a.Publish(message)
}
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.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson())
a.Publish(message)
a.Go(func() {
a.Publish(message)
})
a.Go(func() {
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.Add("preferences", preferences.ToJson())
a.Go(func() {
a.Publish(message)
})
a.Publish(message)
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.Add("preferences", preferences.ToJson())
a.Go(func() {
a.Publish(message)
})
a.Publish(message)
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
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) {
if r.Host == "" {
http.Error(w, "Not Found", http.StatusNotFound)
@@ -223,31 +201,6 @@ func (a *App) StartServer() error {
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() {
if a.Srv.Server != nil {
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
a.UpdateConfig(func(cfg *model.Config) {
*cfg.RateLimitSettings.Enable = true
*cfg.RateLimitSettings.Enable = true
*cfg.RateLimitSettings.MaxBurst = -100
})

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

@@ -138,6 +138,9 @@ func (a *App) ClearSessionCacheForUserSkipClusterSend(userId string) {
session := ts.(*model.Session)
if session.UserId == userId {
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)
var users []SlackUser
if err := decoder.Decode(&users); err != nil {
// This actually returns errors that are ignored.
// In this case it is erroring because of a null that Slack
// introduced. So we just return the users here.
return users, err
}
return users, nil
err := decoder.Decode(&users)
// This actually returns errors that are ignored.
// In this case it is erroring because of a null that Slack
// introduced. So we just return the users here.
return users, err
}
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.Add("status", status.Status)
event.Add("user_id", status.UserId)
a.Go(func() {
a.Publish(event)
})
a.Publish(event)
}
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: ""}
a.AddStatusCache(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)
})
a.SaveAndBroadcastStatus(status)
}
func (a *App) SetStatusAwayIfNeeded(userId string, manual bool) {
@@ -281,18 +268,7 @@ func (a *App) SetStatusAwayIfNeeded(userId string, manual bool) {
status.Manual = manual
status.ActiveChannel = ""
a.AddStatusCache(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)
})
a.SaveAndBroadcastStatus(status)
}
func (a *App) SetStatusDoNotDisturb(userId string) {
@@ -309,18 +285,22 @@ func (a *App) SetStatusDoNotDisturb(userId string) {
status.Status = model.STATUS_DND
status.Manual = true
a.SaveAndBroadcastStatus(status)
}
func (a *App) SaveAndBroadcastStatus(status *model.Status) *model.AppError {
a.AddStatusCache(status)
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.Add("status", model.STATUS_DND)
event.Add("status", status.Status)
event.Add("user_id", status.UserId)
a.Go(func() {
a.Publish(event)
})
a.Publish(event)
return nil
}
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.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) {
@@ -182,10 +180,7 @@ func (a *App) UpdateTeamMemberRoles(teamId string, userId string, newRoles strin
func (a *App) sendUpdatedMemberRoleEvent(userId string, member *model.TeamMember) {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_MEMBERROLE_UPDATED, "", "", userId, nil)
message.Add("member", member.ToJson())
a.Go(func() {
a.Publish(message)
})
a.Publish(message)
}
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_VERIFY_EMAIL = "verify_email"
PASSWORD_RECOVER_EXPIRY_TIME = 1000 * 60 * 60 // 1 hour
VERIFY_EMAIL_EXPIRY_TIME = 1000 * 60 * 60 // 1 hour
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
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_NEW_USER, "", "", "", nil)
message.Add("user_id", ruser.Id)
a.Go(func() {
a.Publish(message)
})
a.Publish(message)
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) {
users, err := a.GetUsersInChannel(channelId, offset, limit)
if err != nil {
@@ -533,6 +538,15 @@ func (a *App) GetUsersInChannelPage(channelId string, page int, perPage int, asA
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) {
if result := <-a.Srv.Store.User().GetProfilesNotInChannel(teamId, channelId, offset, limit); result.Err != nil {
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.Add("user", user)
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]
options := a.Config().GetSanitizeOptions()
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.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) {

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

@@ -299,3 +299,132 @@ func createGitlabUser(t *testing.T, a *App, email string, username string) (*mod
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) {
// parse links into Markdown format
linkWithTextRegex := regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
linkWithTextRegex := regexp.MustCompile(`<([^\n<\|>]+)\|([^\n>]+)>`)
text = linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})")
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 {
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) {

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

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

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

@@ -3,15 +3,27 @@ dist: | check-style test package
build-linux:
@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:
@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:
@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

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.
// See License.txt for license information.
package main
package cmd
import (
"flag"
@@ -30,7 +30,7 @@ func execArgs(t *testing.T, args []string) []string {
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()
require.NoError(t, err)
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"))
}
func runCommand(t *testing.T, args ...string) error {
func RunCommand(t *testing.T, args ...string) error {
path, err := os.Executable()
require.NoError(t, err)
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.
// See License.txt for license information.
package main
package commands
import (
"errors"
"fmt"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra"
)
var channelCmd = &cobra.Command{
var ChannelCmd = &cobra.Command{
Use: "channel",
Short: "Management of channels",
}
var channelCreateCmd = &cobra.Command{
var ChannelCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a channel",
Long: `Create a channel.`,
@@ -25,7 +27,7 @@ var channelCreateCmd = &cobra.Command{
RunE: createChannelCmdF,
}
var removeChannelUsersCmd = &cobra.Command{
var RemoveChannelUsersCmd = &cobra.Command{
Use: "remove [channel] [users]",
Short: "Remove users from channel",
Long: "Remove some users from channel",
@@ -33,7 +35,7 @@ var removeChannelUsersCmd = &cobra.Command{
RunE: removeChannelUsersCmdF,
}
var addChannelUsersCmd = &cobra.Command{
var AddChannelUsersCmd = &cobra.Command{
Use: "add [channel] [users]",
Short: "Add users to channel",
Long: "Add some users to channel",
@@ -41,7 +43,7 @@ var addChannelUsersCmd = &cobra.Command{
RunE: addChannelUsersCmdF,
}
var archiveChannelsCmd = &cobra.Command{
var ArchiveChannelsCmd = &cobra.Command{
Use: "archive [channels]",
Short: "Archive channels",
Long: `Archive some channels.
@@ -51,7 +53,7 @@ Channels can be specified by [team]:[channel]. ie. myteam:mychannel or by channe
RunE: archiveChannelsCmdF,
}
var deleteChannelsCmd = &cobra.Command{
var DeleteChannelsCmd = &cobra.Command{
Use: "delete [channels]",
Short: "Delete 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,
}
var listChannelsCmd = &cobra.Command{
var ListChannelsCmd = &cobra.Command{
Use: "list [teams]",
Short: "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,
}
var moveChannelsCmd = &cobra.Command{
var MoveChannelsCmd = &cobra.Command{
Use: "move [team] [channels]",
Short: "Moves 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,
}
var restoreChannelsCmd = &cobra.Command{
var RestoreChannelsCmd = &cobra.Command{
Use: "restore [channels]",
Short: "Restore some channels",
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,
}
var modifyChannelCmd = &cobra.Command{
var ModifyChannelCmd = &cobra.Command{
Use: "modify [channel]",
Short: "Modify a channel's public/private type",
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() {
channelCreateCmd.Flags().String("name", "", "Channel Name")
channelCreateCmd.Flags().String("display_name", "", "Channel Display Name")
channelCreateCmd.Flags().String("team", "", "Team name or ID")
channelCreateCmd.Flags().String("header", "", "Channel header")
channelCreateCmd.Flags().String("purpose", "", "Channel purpose")
channelCreateCmd.Flags().Bool("private", false, "Create a private channel.")
ChannelCreateCmd.Flags().String("name", "", "Channel Name")
ChannelCreateCmd.Flags().String("display_name", "", "Channel Display Name")
ChannelCreateCmd.Flags().String("team", "", "Team name or ID")
ChannelCreateCmd.Flags().String("header", "", "Channel header")
ChannelCreateCmd.Flags().String("purpose", "", "Channel purpose")
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("public", false, "Convert the channel to a public channel")
modifyChannelCmd.Flags().String("username", "", "Required. Username who changes the channel privacy.")
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().String("username", "", "Required. Username who changes the channel privacy.")
channelCmd.AddCommand(
channelCreateCmd,
removeChannelUsersCmd,
addChannelUsersCmd,
archiveChannelsCmd,
deleteChannelsCmd,
listChannelsCmd,
moveChannelsCmd,
restoreChannelsCmd,
modifyChannelCmd,
ChannelCmd.AddCommand(
ChannelCreateCmd,
RemoveChannelUsersCmd,
AddChannelUsersCmd,
ArchiveChannelsCmd,
DeleteChannelsCmd,
ListChannelsCmd,
MoveChannelsCmd,
RestoreChannelsCmd,
ModifyChannelCmd,
)
cmd.RootCmd.AddCommand(ChannelCmd)
}
func createChannelCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func createChannelCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
name, errn := cmd.Flags().GetString("name")
name, errn := command.Flags().GetString("name")
if errn != nil || name == "" {
return errors.New("Name is required")
}
displayname, errdn := cmd.Flags().GetString("display_name")
displayname, errdn := command.Flags().GetString("display_name")
if errdn != nil || displayname == "" {
return errors.New("Display Name is required")
}
teamArg, errteam := cmd.Flags().GetString("team")
teamArg, errteam := command.Flags().GetString("team")
if errteam != nil || teamArg == "" {
return errors.New("Team is required")
}
header, _ := cmd.Flags().GetString("header")
purpose, _ := cmd.Flags().GetString("purpose")
useprivate, _ := cmd.Flags().GetBool("private")
header, _ := command.Flags().GetString("header")
purpose, _ := command.Flags().GetString("purpose")
useprivate, _ := command.Flags().GetBool("private")
channelType := model.CHANNEL_OPEN
if useprivate {
@@ -176,8 +180,8 @@ func createChannelCmdF(cmd *cobra.Command, args []string) error {
return nil
}
func removeChannelUsersCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func removeChannelUsersCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
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) {
if user == nil {
CommandPrintErrorln("Can't find user '" + userArg + "'")
cmd.CommandPrintErrorln("Can't find user '" + userArg + "'")
return
}
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 {
a, err := initDBCommandContextCobra(cmd)
func addChannelUsersCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
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) {
if user == nil {
CommandPrintErrorln("Can't find user '" + userArg + "'")
cmd.CommandPrintErrorln("Can't find user '" + userArg + "'")
return
}
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 {
a, err := initDBCommandContextCobra(cmd)
func archiveChannelsCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -255,19 +259,19 @@ func archiveChannelsCmdF(cmd *cobra.Command, args []string) error {
channels := getChannelsFromChannelArgs(a, args)
for i, channel := range channels {
if channel == nil {
CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
cmd.CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
continue
}
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
}
func deleteChannelsCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func deleteChannelsCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -276,10 +280,10 @@ func deleteChannelsCmdF(cmd *cobra.Command, args []string) error {
return errors.New("Enter at least one channel to delete.")
}
confirmFlag, _ := cmd.Flags().GetBool("confirm")
confirmFlag, _ := command.Flags().GetBool("confirm")
if !confirmFlag {
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)
if confirm != "YES" {
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)
for i, channel := range channels {
if channel == nil {
CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
cmd.CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
continue
}
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 {
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)
}
func moveChannelsCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func moveChannelsCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -321,7 +325,7 @@ func moveChannelsCmdF(cmd *cobra.Command, args []string) error {
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 == "" {
return errors.New("Username is required")
}
@@ -330,14 +334,14 @@ func moveChannelsCmdF(cmd *cobra.Command, args []string) error {
channels := getChannelsFromChannelArgs(a, args[1:])
for i, channel := range channels {
if channel == nil {
CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
cmd.CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
continue
}
originTeamID := channel.TeamId
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 {
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 {
webhook.TeamId = team.Id
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 {
webhook.TeamId = team.Id
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
}
func listChannelsCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func listChannelsCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -393,19 +397,19 @@ func listChannelsCmdF(cmd *cobra.Command, args []string) error {
teams := getTeamsFromTeamArgs(a, args)
for i, team := range teams {
if team == nil {
CommandPrintErrorln("Unable to find team '" + args[i] + "'")
cmd.CommandPrintErrorln("Unable to find team '" + args[i] + "'")
continue
}
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 {
channels := result.Data.([]*model.Channel)
for _, channel := range channels {
if channel.DeleteAt > 0 {
CommandPrettyPrintln(channel.Name + " (archived)")
cmd.CommandPrettyPrintln(channel.Name + " (archived)")
} else {
CommandPrettyPrintln(channel.Name)
cmd.CommandPrettyPrintln(channel.Name)
}
}
}
@@ -414,8 +418,8 @@ func listChannelsCmdF(cmd *cobra.Command, args []string) error {
return nil
}
func restoreChannelsCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func restoreChannelsCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -427,19 +431,19 @@ func restoreChannelsCmdF(cmd *cobra.Command, args []string) error {
channels := getChannelsFromChannelArgs(a, args)
for i, channel := range channels {
if channel == nil {
CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
cmd.CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
continue
}
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
}
func modifyChannelCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func modifyChannelCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -448,13 +452,13 @@ func modifyChannelCmdF(cmd *cobra.Command, args []string) error {
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 == "" {
return errors.New("Username is required")
}
public, _ := cmd.Flags().GetBool("public")
private, _ := cmd.Flags().GetBool("private")
public, _ := command.Flags().GetBool("public")
private, _ := command.Flags().GetBool("private")
if public == 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.
// See License.txt for license information.
package main
package commands
import (
"strings"
"testing"
"github.com/mattermost/mattermost-server/api"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/require"
)
@@ -18,13 +19,13 @@ func TestJoinChannel(t *testing.T) {
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
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
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) {
@@ -33,15 +34,15 @@ func TestRemoveChannel(t *testing.T) {
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
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
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) {
@@ -63,12 +64,12 @@ func TestMoveChannel(t *testing.T) {
origin := team1.Name + ":" + channel.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
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) {
@@ -78,7 +79,7 @@ func TestListChannels(t *testing.T) {
channel := th.CreateChannel(th.BasicClient, th.BasicTeam)
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") {
t.Fatal("should have channels")
@@ -96,10 +97,10 @@ func TestRestoreChannel(t *testing.T) {
channel := th.CreateChannel(th.BasicClient, th.BasicTeam)
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
checkCommand(t, "channel", "restore", th.BasicTeam.Name+":"+channel.Name)
cmd.CheckCommand(t, "channel", "restore", th.BasicTeam.Name+":"+channel.Name)
}
func TestCreateChannel(t *testing.T) {
@@ -109,8 +110,8 @@ func TestCreateChannel(t *testing.T) {
id := model.NewId()
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"
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.
// See License.txt for license information.
package main
package commands
import (
"fmt"

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

@@ -1,20 +1,22 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
package commands
import (
"errors"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra"
)
var commandCmd = &cobra.Command{
var CommandCmd = &cobra.Command{
Use: "command",
Short: "Management of slash commands",
}
var commandMoveCmd = &cobra.Command{
var CommandMoveCmd = &cobra.Command{
Use: "move",
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.`,
@@ -23,13 +25,14 @@ var commandMoveCmd = &cobra.Command{
}
func init() {
commandCmd.AddCommand(
commandMoveCmd,
CommandCmd.AddCommand(
CommandMoveCmd,
)
cmd.RootCmd.AddCommand(CommandCmd)
}
func moveCommandCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func moveCommandCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -44,16 +47,16 @@ func moveCommandCmdF(cmd *cobra.Command, args []string) error {
}
commands := getCommandsFromCommandArgs(a, args[1:])
CommandPrintErrorln(commands)
cmd.CommandPrintErrorln(commands)
for i, command := range commands {
if command == nil {
CommandPrintErrorln("Unable to find command '" + args[i+1] + "'")
cmd.CommandPrintErrorln("Unable to find command '" + args[i+1] + "'")
continue
}
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 {
CommandPrettyPrintln("Moved command '" + command.Trigger + "'")
cmd.CommandPrettyPrintln("Moved command '" + command.Trigger + "'")
}
}

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

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

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

@@ -1,23 +1,25 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
package commands
import (
"encoding/json"
"errors"
"os"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
"github.com/spf13/cobra"
)
var configCmd = &cobra.Command{
var ConfigCmd = &cobra.Command{
Use: "config",
Short: "Configuration",
}
var validateConfigCmd = &cobra.Command{
var ValidateConfigCmd = &cobra.Command{
Use: "validate",
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.",
@@ -25,15 +27,16 @@ var validateConfigCmd = &cobra.Command{
}
func init() {
configCmd.AddCommand(
validateConfigCmd,
ConfigCmd.AddCommand(
ValidateConfigCmd,
)
cmd.RootCmd.AddCommand(ConfigCmd)
}
func configValidateCmdF(cmd *cobra.Command, args []string) error {
func configValidateCmdF(command *cobra.Command, args []string) error {
utils.TranslationsPreInit()
model.AppErrorInit(utils.T)
filePath, err := cmd.Flags().GetString("config")
filePath, err := command.Flags().GetString("config")
if err != nil {
return err
}
@@ -60,6 +63,6 @@ func configValidateCmdF(cmd *cobra.Command, args []string) error {
return errors.New(utils.T(err.Id))
}
CommandPrettyPrintln("The document is valid")
cmd.CommandPrettyPrintln("The document is valid")
return nil
}

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

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

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

@@ -1,7 +1,7 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
package commands
import (
"io/ioutil"
@@ -9,6 +9,7 @@ import (
"path/filepath"
"testing"
"github.com/mattermost/mattermost-server/cmd"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -25,6 +26,6 @@ func TestConfigValidate(t *testing.T) {
config.SetDefaults()
require.NoError(t, ioutil.WriteFile(path, []byte(config.ToJson()), 0600))
assert.Error(t, runCommand(t, "--config", "foo.json", "config", "validate"))
assert.NoError(t, runCommand(t, "--config", path, "config", "validate"))
assert.Error(t, cmd.RunCommand(t, "--config", "foo.json", "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.
// See License.txt for license information.
package main
package commands
import (
"errors"
@@ -8,15 +9,16 @@ import (
"fmt"
"github.com/mattermost/mattermost-server/cmd"
"github.com/spf13/cobra"
)
var importCmd = &cobra.Command{
var ImportCmd = &cobra.Command{
Use: "import",
Short: "Import data.",
}
var slackImportCmd = &cobra.Command{
var SlackImportCmd = &cobra.Command{
Use: "slack [team] [file]",
Short: "Import a team from Slack.",
Long: "Import a team from a Slack export zip file.",
@@ -24,7 +26,7 @@ var slackImportCmd = &cobra.Command{
RunE: slackImportCmdF,
}
var bulkImportCmd = &cobra.Command{
var BulkImportCmd = &cobra.Command{
Use: "bulk [file]",
Short: "Import bulk data.",
Long: "Import data from a Mattermost Bulk Import File.",
@@ -33,18 +35,19 @@ var bulkImportCmd = &cobra.Command{
}
func init() {
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().Int("workers", 2, "How many workers to run whilst doing the import.")
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().Int("workers", 2, "How many workers to run whilst doing the import.")
importCmd.AddCommand(
bulkImportCmd,
slackImportCmd,
ImportCmd.AddCommand(
BulkImportCmd,
SlackImportCmd,
)
cmd.RootCmd.AddCommand(ImportCmd)
}
func slackImportCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func slackImportCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -69,32 +72,32 @@ func slackImportCmdF(cmd *cobra.Command, args []string) error {
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)
CommandPrettyPrintln("Finished Slack Import.")
cmd.CommandPrettyPrintln("Finished Slack Import.")
return nil
}
func bulkImportCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func bulkImportCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
apply, err := cmd.Flags().GetBool("apply")
apply, err := command.Flags().GetBool("apply")
if err != nil {
return errors.New("Apply flag error")
}
validate, err := cmd.Flags().GetBool("validate")
validate, err := command.Flags().GetBool("validate")
if err != nil {
return errors.New("Validate flag error")
}
workers, err := cmd.Flags().GetInt("workers")
workers, err := command.Flags().GetInt("workers")
if err != nil {
return errors.New("Workers flag error")
}
@@ -110,28 +113,28 @@ func bulkImportCmdF(cmd *cobra.Command, args []string) error {
defer fileReader.Close()
if apply && validate {
CommandPrettyPrintln("Use only one of --apply or --validate.")
cmd.CommandPrettyPrintln("Use only one of --apply or --validate.")
return nil
} 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 {
CommandPrettyPrintln("Running Bulk Import Data Validation.")
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("Running Bulk Import Data Validation.")
cmd.CommandPrettyPrintln("** This checks the validity of the entities in the data file, but does not persist any changes **")
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 {
CommandPrettyPrintln(err.Error())
cmd.CommandPrettyPrintln(err.Error())
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 {
if apply {
CommandPrettyPrintln("Finished Bulk Import.")
cmd.CommandPrettyPrintln("Finished Bulk Import.")
} 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.
// See License.txt for license information.
package main
package commands
import (
"os"
@@ -8,27 +9,30 @@ import (
"syscall"
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/mattermost-server/cmd"
"github.com/spf13/cobra"
)
var jobserverCmd = &cobra.Command{
var JobserverCmd = &cobra.Command{
Use: "jobserver",
Short: "Start the Mattermost job server",
Run: jobserverCmdF,
}
func init() {
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("nojobs", false, "Do not run jobs on 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
noJobs, _ := cmd.Flags().GetBool("nojobs")
noSchedule, _ := cmd.Flags().GetBool("noschedule")
noJobs, _ := command.Flags().GetBool("nojobs")
noSchedule, _ := command.Flags().GetBool("noschedule")
// Initialize
a, err := initDBCommandContext("config.json")
a, err := cmd.InitDBCommandContext("config.json")
if err != nil {
panic(err.Error())
}

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

@@ -1,18 +1,20 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
package commands
import (
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra"
)
var ldapCmd = &cobra.Command{
var LdapCmd = &cobra.Command{
Use: "ldap",
Short: "LDAP related utilities",
}
var ldapSyncCmd = &cobra.Command{
var LdapSyncCmd = &cobra.Command{
Use: "sync",
Short: "Synchronize now",
Long: "Synchronize all LDAP users now.",
@@ -21,13 +23,14 @@ var ldapSyncCmd = &cobra.Command{
}
func init() {
ldapCmd.AddCommand(
ldapSyncCmd,
LdapCmd.AddCommand(
LdapSyncCmd,
)
cmd.RootCmd.AddCommand(LdapCmd)
}
func ldapSyncCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func ldapSyncCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -35,9 +38,9 @@ func ldapSyncCmdF(cmd *cobra.Command, args []string) error {
if ldapI := a.Ldap; ldapI != nil {
job, err := ldapI.StartSynchronizeJob(true)
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 {
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.
// See License.txt for license information.
package main
package commands
import (
"errors"
"io/ioutil"
"github.com/mattermost/mattermost-server/cmd"
"github.com/spf13/cobra"
)
var licenseCmd = &cobra.Command{
var LicenseCmd = &cobra.Command{
Use: "license",
Short: "Licensing commands",
}
var uploadLicenseCmd = &cobra.Command{
var UploadLicenseCmd = &cobra.Command{
Use: "upload [license]",
Short: "Upload a license.",
Long: "Upload a license. Replaces current license.",
@@ -23,11 +25,12 @@ var uploadLicenseCmd = &cobra.Command{
}
func init() {
licenseCmd.AddCommand(uploadLicenseCmd)
LicenseCmd.AddCommand(UploadLicenseCmd)
cmd.RootCmd.AddCommand(LicenseCmd)
}
func uploadLicenseCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func uploadLicenseCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -45,7 +48,7 @@ func uploadLicenseCmdF(cmd *cobra.Command, args []string) error {
return err
}
CommandPrettyPrintln("Uploaded license file")
cmd.CommandPrettyPrintln("Uploaded license file")
return nil
}

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

@@ -1,7 +1,7 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
package commands
import (
"errors"
@@ -10,11 +10,12 @@ import (
"time"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra"
)
var messageExportCmd = &cobra.Command{
var MessageExportCmd = &cobra.Command{
Use: "export",
Short: "Export data from Mattermost",
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() {
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().Int("timeoutSeconds", -1, "The maximum number of seconds to wait for the job to complete before timing out.")
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().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 {
a, err := initDBCommandContextCobra(cmd)
func messageExportCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
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
if format, err := cmd.Flags().GetString("format"); err != nil {
if format, err := command.Flags().GetString("format"); err != nil {
return errors.New("format flag error")
} else if format != "actiance" {
return errors.New("unsupported export format")
}
startTime, err := cmd.Flags().GetInt64("exportFrom")
startTime, err := command.Flags().GetInt64("exportFrom")
if err != nil {
return errors.New("exportFrom flag error")
} else if startTime < 0 {
return errors.New("exportFrom must be a positive integer")
}
timeoutSeconds, err := cmd.Flags().GetInt("timeoutSeconds")
timeoutSeconds, err := command.Flags().GetInt("timeoutSeconds")
if err != nil {
return errors.New("timeoutSeconds error")
} else if timeoutSeconds < 0 {
@@ -69,9 +71,9 @@ func messageExportCmdF(cmd *cobra.Command, args []string) error {
job, err := messageExportI.StartSynchronizeJob(ctx, startTime)
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 {
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.
// See License.txt for license information.
package main
package commands
import (
"io/ioutil"
@@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
)
@@ -24,7 +25,7 @@ func TestMessageExportNotEnabled(t *testing.T) {
defer os.RemoveAll(filepath.Dir(configPath))
// 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) {
@@ -32,7 +33,7 @@ func TestMessageExportInvalidFormat(t *testing.T) {
defer os.RemoveAll(filepath.Dir(configPath))
// 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) {
@@ -40,7 +41,7 @@ func TestMessageExportNegativeExportFrom(t *testing.T) {
defer os.RemoveAll(filepath.Dir(configPath))
// 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) {
@@ -48,7 +49,7 @@ func TestMessageExportNegativeTimeoutSeconds(t *testing.T) {
defer os.RemoveAll(filepath.Dir(configPath))
// 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 {

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

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

@@ -1,12 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
package commands
import (
"testing"
"github.com/mattermost/mattermost-server/api"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model"
)
@@ -14,7 +15,7 @@ func TestAssignRole(t *testing.T) {
th := api.Setup().InitBasic()
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 {
t.Fatal()

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

@@ -1,6 +1,7 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
package commands
import (
"encoding/json"
@@ -16,15 +17,34 @@ import (
"github.com/icrowley/fake"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/cmd"
"github.com/spf13/cobra"
)
var sampleDataCmd = &cobra.Command{
var SampleDataCmd = &cobra.Command{
Use: "sampledata",
Short: "Generate sample data",
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 {
for _, v := range vs {
if v == t {
@@ -109,81 +129,64 @@ func randomMessage(users []string) string {
return message
}
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.")
}
func sampleDataCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func sampleDataCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
seed, err := cmd.Flags().GetInt64("seed")
seed, err := command.Flags().GetInt64("seed")
if err != nil {
return errors.New("Invalid seed parameter")
}
bulk, err := cmd.Flags().GetString("bulk")
bulk, err := command.Flags().GetString("bulk")
if err != nil {
return errors.New("Invalid bulk parameter")
}
teams, err := cmd.Flags().GetInt("teams")
teams, err := command.Flags().GetInt("teams")
if err != nil || teams < 0 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
return errors.New("Invalid posts-per-group-channel parameter")
}
workers, err := cmd.Flags().GetInt("workers")
workers, err := command.Flags().GetInt("workers")
if err != nil {
return errors.New("Invalid workers parameter")
}
profileImagesPath, err := cmd.Flags().GetString("profile-images")
profileImagesPath, err := command.Flags().GetString("profile-images")
if err != nil {
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)
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 != "-" {
err := bulkFile.Close()
@@ -395,7 +398,7 @@ func createUser(idx int, teamMemberships int, channelMemberships int, teamsAndCh
position := rand.Intn(len(possibleTeams))
team := possibleTeams[position]
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))
}
}
@@ -429,10 +432,7 @@ func createTeamMembership(numOfchannels int, teamChannels []string, teamName *st
roles = "team_user team_admin"
}
channels := []app.UserChannelImportData{}
teamChannelsCopy := []string{}
for _, value := range teamChannels {
teamChannelsCopy = append(teamChannelsCopy, value)
}
teamChannelsCopy := append([]string(nil), teamChannels...)
for x := 0; x < numOfchannels; x++ {
if len(teamChannelsCopy) == 0 {
break

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

@@ -1,12 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
package commands
import (
"testing"
"github.com/mattermost/mattermost-server/api"
"github.com/mattermost/mattermost-server/cmd"
"github.com/stretchr/testify/require"
)
@@ -15,11 +16,11 @@ func TestSampledataBadParameters(t *testing.T) {
defer th.TearDown()
// 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
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
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.
// See License.txt for license information.
package main
package commands
import (
"net"
@@ -14,6 +14,7 @@ import (
"github.com/mattermost/mattermost-server/api"
"github.com/mattermost/mattermost-server/api4"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/manualtesting"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
@@ -31,17 +32,22 @@ var MaxNotificationsPerChannelDefault int64 = 1000000
var serverCmd = &cobra.Command{
Use: "server",
Short: "Run the Mattermost server",
RunE: runServerCmd,
RunE: serverCmdF,
SilenceUsage: true,
}
func runServerCmd(cmd *cobra.Command, args []string) error {
config, err := cmd.Flags().GetString("config")
func init() {
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 {
return err
}
disableConfigWatch, _ := cmd.Flags().GetBool("disableconfigwatch")
disableConfigWatch, _ := command.Flags().GetBool("disableconfigwatch")
interruptChan := make(chan os.Signal, 1)
return runServer(config, disableConfigWatch, interruptChan)

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

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

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

@@ -1,22 +1,24 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
package commands
import (
"errors"
"fmt"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra"
)
var teamCmd = &cobra.Command{
var TeamCmd = &cobra.Command{
Use: "team",
Short: "Management of teams",
}
var teamCreateCmd = &cobra.Command{
var TeamCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a team",
Long: `Create a team.`,
@@ -25,7 +27,7 @@ var teamCreateCmd = &cobra.Command{
RunE: createTeamCmdF,
}
var removeUsersCmd = &cobra.Command{
var RemoveUsersCmd = &cobra.Command{
Use: "remove [team] [users]",
Short: "Remove users from team",
Long: "Remove some users from team",
@@ -33,7 +35,7 @@ var removeUsersCmd = &cobra.Command{
RunE: removeUsersCmdF,
}
var addUsersCmd = &cobra.Command{
var AddUsersCmd = &cobra.Command{
Use: "add [team] [users]",
Short: "Add users to team",
Long: "Add some users to team",
@@ -41,7 +43,7 @@ var addUsersCmd = &cobra.Command{
RunE: addUsersCmdF,
}
var deleteTeamsCmd = &cobra.Command{
var DeleteTeamsCmd = &cobra.Command{
Use: "delete [teams]",
Short: "Delete teams",
Long: `Permanently delete some teams.
@@ -51,37 +53,38 @@ Permanently deletes a team along with all related information including posts fr
}
func init() {
teamCreateCmd.Flags().String("name", "", "Team Name")
teamCreateCmd.Flags().String("display_name", "", "Team Display Name")
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("name", "", "Team Name")
TeamCreateCmd.Flags().String("display_name", "", "Team Display Name")
TeamCreateCmd.Flags().Bool("private", false, "Create a private team.")
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(
teamCreateCmd,
removeUsersCmd,
addUsersCmd,
deleteTeamsCmd,
TeamCmd.AddCommand(
TeamCreateCmd,
RemoveUsersCmd,
AddUsersCmd,
DeleteTeamsCmd,
)
cmd.RootCmd.AddCommand(TeamCmd)
}
func createTeamCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func createTeamCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
name, errn := cmd.Flags().GetString("name")
name, errn := command.Flags().GetString("name")
if errn != nil || name == "" {
return errors.New("Name is required")
}
displayname, errdn := cmd.Flags().GetString("display_name")
displayname, errdn := command.Flags().GetString("display_name")
if errdn != nil || displayname == "" {
return errors.New("Display Name is required")
}
email, _ := cmd.Flags().GetString("email")
useprivate, _ := cmd.Flags().GetBool("private")
email, _ := command.Flags().GetString("email")
useprivate, _ := command.Flags().GetBool("private")
teamType := model.TEAM_OPEN
if useprivate {
@@ -102,8 +105,8 @@ func createTeamCmdF(cmd *cobra.Command, args []string) error {
return nil
}
func removeUsersCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func removeUsersCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
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) {
if user == nil {
CommandPrintErrorln("Can't find user '" + userArg + "'")
cmd.CommandPrintErrorln("Can't find user '" + userArg + "'")
return
}
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 {
a, err := initDBCommandContextCobra(cmd)
func addUsersCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
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) {
if user == nil {
CommandPrintErrorln("Can't find user '" + userArg + "'")
cmd.CommandPrintErrorln("Can't find user '" + userArg + "'")
return
}
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 {
a, err := initDBCommandContextCobra(cmd)
func deleteTeamsCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -178,16 +181,16 @@ func deleteTeamsCmdF(cmd *cobra.Command, args []string) error {
return errors.New("Not enough arguments.")
}
confirmFlag, _ := cmd.Flags().GetBool("confirm")
confirmFlag, _ := command.Flags().GetBool("confirm")
if !confirmFlag {
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)
if confirm != "YES" {
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)
if confirm != "YES" {
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)
for i, team := range teams {
if team == nil {
CommandPrintErrorln("Unable to find team '" + args[i] + "'")
cmd.CommandPrintErrorln("Unable to find team '" + args[i] + "'")
continue
}
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 {
CommandPrettyPrintln("Deleted team '" + team.Name + "'")
cmd.CommandPrettyPrintln("Deleted team '" + team.Name + "'")
}
}

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

@@ -1,12 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
package commands
import (
"testing"
"github.com/mattermost/mattermost-server/api"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model"
)
@@ -18,7 +19,7 @@ func TestCreateTeam(t *testing.T) {
name := "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)
@@ -31,7 +32,7 @@ func TestJoinTeam(t *testing.T) {
th := api.Setup().InitSystemAdmin().InitBasic()
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)
@@ -53,7 +54,7 @@ func TestLeaveTeam(t *testing.T) {
th := api.Setup().InitBasic()
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)

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

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

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

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

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

@@ -1,6 +1,7 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
package commands
import (
"encoding/json"
@@ -10,16 +11,17 @@ import (
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra"
)
var userCmd = &cobra.Command{
var UserCmd = &cobra.Command{
Use: "user",
Short: "Management of users",
}
var userActivateCmd = &cobra.Command{
var UserActivateCmd = &cobra.Command{
Use: "activate [emails, usernames, userIds]",
Short: "Activate users",
Long: "Activate users that have been deactivated.",
@@ -28,7 +30,7 @@ var userActivateCmd = &cobra.Command{
RunE: userActivateCmdF,
}
var userDeactivateCmd = &cobra.Command{
var UserDeactivateCmd = &cobra.Command{
Use: "deactivate [emails, usernames, userIds]",
Short: "Deactivate users",
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,
}
var userCreateCmd = &cobra.Command{
var UserCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a user",
Long: "Create a user",
@@ -45,7 +47,7 @@ var userCreateCmd = &cobra.Command{
RunE: userCreateCmdF,
}
var userInviteCmd = &cobra.Command{
var UserInviteCmd = &cobra.Command{
Use: "invite [email] [teams]",
Short: "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,
}
var resetUserPasswordCmd = &cobra.Command{
var ResetUserPasswordCmd = &cobra.Command{
Use: "password [user] [password]",
Short: "Set a user's password",
Long: "Set a user's password",
@@ -64,7 +66,16 @@ var resetUserPasswordCmd = &cobra.Command{
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]",
Short: "Turn off MFA",
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,
}
var deleteUserCmd = &cobra.Command{
var DeleteUserCmd = &cobra.Command{
Use: "delete [users]",
Short: "Delete users and all posts",
Long: "Permanently delete user and all related information including posts.",
@@ -81,7 +92,7 @@ var deleteUserCmd = &cobra.Command{
RunE: deleteUserCmdF,
}
var deleteAllUsersCmd = &cobra.Command{
var DeleteAllUsersCmd = &cobra.Command{
Use: "deleteall",
Short: "Delete all users and all posts",
Long: "Permanently delete all users and all related information including posts.",
@@ -89,7 +100,7 @@ var deleteAllUsersCmd = &cobra.Command{
RunE: deleteAllUsersCommandF,
}
var migrateAuthCmd = &cobra.Command{
var MigrateAuthCmd = &cobra.Command{
Use: "migrate_auth [from_auth] [to_auth] [migration-options]",
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.`,
@@ -127,7 +138,7 @@ var migrateAuthCmd = &cobra.Command{
RunE: migrateAuthCmdF,
}
var verifyUserCmd = &cobra.Command{
var VerifyUserCmd = &cobra.Command{
Use: "verify [users]",
Short: "Verify email of users",
Long: "Verify the emails of some users.",
@@ -135,7 +146,7 @@ var verifyUserCmd = &cobra.Command{
RunE: verifyUserCmdF,
}
var searchUserCmd = &cobra.Command{
var SearchUserCmd = &cobra.Command{
Use: "search [users]",
Short: "Search for users",
Long: "Search for users based on username, email, or user ID.",
@@ -144,23 +155,23 @@ var searchUserCmd = &cobra.Command{
}
func init() {
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("password", "", "Required. The password 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("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().Bool("system_admin", false, "Optional. If supplied, the new user will be a system administrator. Defaults to false.")
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("password", "", "Required. The password 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("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().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("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.SetUsageTemplate(`Usage:
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("dryRun", false, "Run a simulation of the migration process without changing the database.")
MigrateAuthCmd.SetUsageTemplate(`Usage:
platform user migrate_auth [from_auth] [to_auth] [migration-options] [flags]
Examples:
@@ -184,7 +195,7 @@ Flags:
Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}
`)
migrateAuthCmd.SetHelpTemplate(`Usage:
MigrateAuthCmd.SetHelpTemplate(`Usage:
platform user migrate_auth [from_auth] [to_auth] [migration-options] [flags]
Examples:
@@ -221,23 +232,25 @@ Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}
`)
userCmd.AddCommand(
userActivateCmd,
userDeactivateCmd,
userCreateCmd,
userInviteCmd,
resetUserPasswordCmd,
resetUserMfaCmd,
deleteUserCmd,
deleteAllUsersCmd,
migrateAuthCmd,
verifyUserCmd,
searchUserCmd,
UserCmd.AddCommand(
UserActivateCmd,
UserDeactivateCmd,
UserCreateCmd,
UserInviteCmd,
ResetUserPasswordCmd,
updateUserEmailCmd,
ResetUserMfaCmd,
DeleteUserCmd,
DeleteAllUsersCmd,
MigrateAuthCmd,
VerifyUserCmd,
SearchUserCmd,
)
cmd.RootCmd.AddCommand(UserCmd)
}
func userActivateCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func userActivateCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -256,7 +269,7 @@ func changeUsersActiveStatus(a *app.App, userArgs []string, active bool) {
err := changeUserActiveStatus(a, user, userArgs[i], active)
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
}
func userDeactivateCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func userDeactivateCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -289,29 +302,29 @@ func userDeactivateCmdF(cmd *cobra.Command, args []string) error {
return nil
}
func userCreateCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func userCreateCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
username, erru := cmd.Flags().GetString("username")
username, erru := command.Flags().GetString("username")
if erru != nil || username == "" {
return errors.New("Username is required")
}
email, erre := cmd.Flags().GetString("email")
email, erre := command.Flags().GetString("email")
if erre != nil || email == "" {
return errors.New("Email is required")
}
password, errp := cmd.Flags().GetString("password")
password, errp := command.Flags().GetString("password")
if errp != nil || password == "" {
return errors.New("Password is required")
}
nickname, _ := cmd.Flags().GetString("nickname")
firstname, _ := cmd.Flags().GetString("firstname")
lastname, _ := cmd.Flags().GetString("lastname")
locale, _ := cmd.Flags().GetString("locale")
systemAdmin, _ := cmd.Flags().GetBool("system_admin")
nickname, _ := command.Flags().GetString("nickname")
firstname, _ := command.Flags().GetString("firstname")
lastname, _ := command.Flags().GetString("lastname")
locale, _ := command.Flags().GetString("locale")
systemAdmin, _ := command.Flags().GetBool("system_admin")
user := &model.User{
Username: username,
@@ -329,13 +342,13 @@ func userCreateCmdF(cmd *cobra.Command, args []string) error {
a.UpdateUserRoles(ruser.Id, "system_user system_admin", false)
}
CommandPrettyPrintln("Created User")
cmd.CommandPrettyPrintln("Created User")
return nil
}
func userInviteCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func userInviteCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -354,7 +367,7 @@ func userInviteCmdF(cmd *cobra.Command, args []string) error {
err := inviteUser(a, email, team, args[i+1])
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)
CommandPrettyPrintln("Invites may or may not have been sent.")
cmd.CommandPrettyPrintln("Invites may or may not have been sent.")
return nil
}
func resetUserPasswordCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func resetUserPasswordCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -396,8 +409,38 @@ func resetUserPasswordCmdF(cmd *cobra.Command, args []string) error {
return nil
}
func resetUserMfaCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func updateUserEmailCmdF(command *cobra.Command, args []string) error {
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 {
return err
}
@@ -421,8 +464,8 @@ func resetUserMfaCmdF(cmd *cobra.Command, args []string) error {
return nil
}
func deleteUserCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func deleteUserCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
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.")
}
confirmFlag, _ := cmd.Flags().GetBool("confirm")
confirmFlag, _ := command.Flags().GetBool("confirm")
if !confirmFlag {
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)
if confirm != "YES" {
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)
if confirm != "YES" {
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
}
func deleteAllUsersCommandF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func deleteAllUsersCommandF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -472,16 +515,16 @@ func deleteAllUsersCommandF(cmd *cobra.Command, args []string) error {
return errors.New("Expected zero arguments.")
}
confirmFlag, _ := cmd.Flags().GetBool("confirm")
confirmFlag, _ := command.Flags().GetBool("confirm")
if !confirmFlag {
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)
if confirm != "YES" {
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)
if confirm != "YES" {
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
}
CommandPrettyPrintln("All user accounts successfully deleted.")
cmd.CommandPrettyPrintln("All user accounts successfully deleted.")
return nil
}
func migrateAuthCmdF(cmd *cobra.Command, args []string) error {
func migrateAuthCmdF(command *cobra.Command, args []string) error {
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 {
a, err := initDBCommandContextCobra(cmd)
func migrateAuthToLdapCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -525,28 +568,28 @@ func migrateAuthToLdapCmdF(cmd *cobra.Command, args []string) error {
return errors.New("Invalid match_field argument")
}
forceFlag, _ := cmd.Flags().GetBool("force")
dryRunFlag, _ := cmd.Flags().GetBool("dryRun")
forceFlag, _ := command.Flags().GetBool("force")
dryRunFlag, _ := command.Flags().GetBool("dryRun")
if migrate := a.AccountMigration; migrate != nil {
if err := migrate.MigrateToLdap(fromAuth, matchField, forceFlag, dryRunFlag); err != nil {
return errors.New("Error while migrating users: " + err.Error())
}
CommandPrettyPrintln("Sucessfully migrated accounts.")
cmd.CommandPrettyPrintln("Sucessfully migrated accounts.")
}
return nil
}
func migrateAuthToSamlCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func migrateAuthToSamlCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
dryRunFlag, _ := cmd.Flags().GetBool("dryRun")
autoFlag, _ := cmd.Flags().GetBool("auto")
dryRunFlag, _ := command.Flags().GetBool("dryRun")
autoFlag, _ := command.Flags().GetBool("auto")
matchesFile := ""
matches := map[string]string{}
@@ -570,7 +613,7 @@ func migrateAuthToSamlCmdF(cmd *cobra.Command, args []string) error {
if autoFlag && !dryRunFlag {
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)
if confirm != "YES" {
@@ -588,14 +631,14 @@ func migrateAuthToSamlCmdF(cmd *cobra.Command, args []string) error {
return errors.New("Error while migrating users: " + err.Error())
}
l4g.Close()
CommandPrettyPrintln("Sucessfully migrated accounts.")
cmd.CommandPrettyPrintln("Sucessfully migrated accounts.")
}
return nil
}
func verifyUserCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func verifyUserCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -608,19 +651,19 @@ func verifyUserCmdF(cmd *cobra.Command, args []string) error {
for i, user := range users {
if user == nil {
CommandPrintErrorln("Unable to find user '" + args[i] + "'")
cmd.CommandPrintErrorln("Unable to find user '" + args[i] + "'")
continue
}
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
}
func searchUserCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func searchUserCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -633,21 +676,21 @@ func searchUserCmdF(cmd *cobra.Command, args []string) error {
for i, user := range users {
if i > 0 {
CommandPrettyPrintln("------------------------------")
cmd.CommandPrettyPrintln("------------------------------")
}
if user == nil {
CommandPrintErrorln("Unable to find user '" + args[i] + "'")
cmd.CommandPrintErrorln("Unable to find user '" + args[i] + "'")
continue
}
CommandPrettyPrintln("id: " + user.Id)
CommandPrettyPrintln("username: " + user.Username)
CommandPrettyPrintln("nickname: " + user.Nickname)
CommandPrettyPrintln("position: " + user.Position)
CommandPrettyPrintln("first_name: " + user.FirstName)
CommandPrettyPrintln("last_name: " + user.LastName)
CommandPrettyPrintln("email: " + user.Email)
CommandPrettyPrintln("auth_service: " + user.AuthService)
cmd.CommandPrettyPrintln("id: " + user.Id)
cmd.CommandPrettyPrintln("username: " + user.Username)
cmd.CommandPrettyPrintln("nickname: " + user.Nickname)
cmd.CommandPrettyPrintln("position: " + user.Position)
cmd.CommandPrettyPrintln("first_name: " + user.FirstName)
cmd.CommandPrettyPrintln("last_name: " + user.LastName)
cmd.CommandPrettyPrintln("email: " + user.Email)
cmd.CommandPrettyPrintln("auth_service: " + user.AuthService)
}
return nil

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

@@ -1,13 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
package commands
import (
"testing"
"github.com/mattermost/mattermost-server/api"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/require"
)
func TestCreateUserWithTeam(t *testing.T) {
@@ -18,9 +20,9 @@ func TestCreateUserWithTeam(t *testing.T) {
email := "success+" + id + "@simulator.amazonses.com"
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)
@@ -46,7 +48,7 @@ func TestCreateUserWithoutTeam(t *testing.T) {
email := "success+" + id + "@simulator.amazonses.com"
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 {
t.Fatal()
@@ -62,7 +64,7 @@ func TestResetPassword(t *testing.T) {
th := api.Setup().InitBasic()
defer th.TearDown()
checkCommand(t, "user", "password", th.BasicUser.Email, "password2")
cmd.CheckCommand(t, "user", "password", th.BasicUser.Email, "password2")
th.BasicClient.Logout()
th.BasicUser.Password = "password2"
@@ -74,8 +76,35 @@ func TestMakeUserActiveAndInactive(t *testing.T) {
defer th.TearDown()
// first inactivate the user
checkCommand(t, "user", "deactivate", th.BasicUser.Email)
cmd.CheckCommand(t, "user", "deactivate", th.BasicUser.Email)
// 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.
// See License.txt for license information.
package main
package commands
import (
"github.com/mattermost/mattermost-server/app"

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

@@ -1,23 +1,29 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
package commands
import (
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/cmd"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
"github.com/mattermost/mattermost-server/store/sqlstore"
"github.com/spf13/cobra"
)
var versionCmd = &cobra.Command{
var VersionCmd = &cobra.Command{
Use: "version",
Short: "Display version information",
RunE: versionCmdF,
}
func versionCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
func init() {
cmd.RootCmd.AddCommand(VersionCmd)
}
func versionCmdF(command *cobra.Command, args []string) error {
a, err := cmd.InitDBCommandContextCobra(command)
if err != nil {
return err
}
@@ -28,12 +34,12 @@ func versionCmdF(cmd *cobra.Command, args []string) error {
}
func printVersion(a *app.App) {
CommandPrintln("Version: " + model.CurrentVersion)
CommandPrintln("Build Number: " + model.BuildNumber)
CommandPrintln("Build Date: " + model.BuildDate)
CommandPrintln("Build Hash: " + model.BuildHash)
CommandPrintln("Build Enterprise Ready: " + model.BuildEnterpriseReady)
cmd.CommandPrintln("Version: " + model.CurrentVersion)
cmd.CommandPrintln("Build Number: " + model.BuildNumber)
cmd.CommandPrintln("Build Date: " + model.BuildDate)
cmd.CommandPrintln("Build Hash: " + model.BuildHash)
cmd.CommandPrintln("Build Enterprise Ready: " + model.BuildEnterpriseReady)
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.
// See License.txt for license information.
package main
package commands
import (
"testing"
"github.com/mattermost/mattermost-server/cmd"
)
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.
// See License.txt for license information.
package main
package cmd
import (
"github.com/mattermost/mattermost-server/app"
@@ -10,13 +10,13 @@ import (
"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")
if err != nil {
return nil, err
}
a, err := initDBCommandContext(config)
a, err := InitDBCommandContext(config)
if err != nil {
// Returning an error just prints the usage message, so actually panic
panic(err)
@@ -25,7 +25,7 @@ func initDBCommandContextCobra(cmd *cobra.Command) (*app.App, error) {
return a, nil
}
func initDBCommandContext(configFileLocation string) (*app.App, error) {
func InitDBCommandContext(configFileLocation string) (*app.App, error) {
if err := utils.TranslationsPreInit(); err != nil {
return nil, err
}

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

@@ -1,6 +1,7 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
package cmd
import (
"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",
"ExportFromTimestamp": 0,
"FileLocation": "export",
"BatchSize": 10000
"BatchSize": 10000,
"GlobalRelaySettings": {
"CustomerType": "A9",
"SmtpUsername": "",
"SmtpPassword": "",
"EmailAddress": ""
}
},
"JobSettings": {
"RunJobs": true,

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

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

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

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

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

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

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error",
"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",
"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",
"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",
"translation": " in "
@@ -3648,7 +3656,7 @@
},
{
"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",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found",
"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",
"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",
"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",
"translation": "Ungültiger Wert für write timeout."

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

@@ -1830,14 +1830,14 @@
"id": "api.post.send_notifications_and_forget.push_image_only",
"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",
"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",
"translation": " in "
@@ -4258,6 +4258,10 @@
"id": "ent.migration.migratetoldap.user_not_found",
"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",
"translation": "User not found in the users file."
@@ -4266,10 +4270,6 @@
"id": "ent.migration.migratetosaml.username_already_used_by_other_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",
"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",
"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",
"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",
"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",
"translation": "Unable to enable email batching when SiteURL isn't set."
@@ -5118,6 +5138,10 @@
"id": "model.config.is_valid.webserver_security.app_error",
"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",
"translation": "Invalid value for write timeout."

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error",
"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",
"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",
"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",
"translation": " en "
@@ -3648,7 +3656,7 @@
},
{
"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",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found",
"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",
"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",
"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",
"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",
"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",
"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",
"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",
"translation": " dans "
@@ -3648,7 +3656,7 @@
},
{
"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",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found",
"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",
"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",
"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",
"translation": "Valeur invalide pour le délai d'attente d'écriture."

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error",
"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",
"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",
"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",
"translation": " in "
@@ -3648,7 +3656,7 @@
},
{
"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",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found",
"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",
"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",
"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",
"translation": "Valore non valido per il timeout scrittura."

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

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

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error",
"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",
"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",
"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",
"translation": " in "
@@ -3648,7 +3656,7 @@
},
{
"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",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found",
"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",
"translation": "속성 중 하나가 올바르지 않아 SAML 로그인에 실패했습니다. 시스템 관리자에게 문의하십시오."
@@ -5026,6 +5046,10 @@
"id": "model.config.is_valid.webserver_security.app_error",
"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",
"translation": "write timeout에 대해 잘못된 값입니다."

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error",
"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",
"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",
"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",
"translation": " in "
@@ -3648,7 +3656,7 @@
},
{
"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",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found",
"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",
"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",
"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",
"translation": "Invalid value for write timeout."

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error",
"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",
"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",
"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",
"translation": "w"
@@ -3648,7 +3656,7 @@
},
{
"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",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found",
"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",
"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",
"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",
"translation": "Nieprawidłowa wartość upłynięcia limitu czasu."

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

@@ -1368,6 +1368,10 @@
"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."
},
{
"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",
"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",
"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",
"translation": " em "
@@ -3648,7 +3656,7 @@
},
{
"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",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found",
"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",
"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",
"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",
"translation": "Valor inválido para limite de escrita."

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error",
"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",
"translation": "Размер файла превышает максимальный размер и не может быть загружен: {{.Filename}}"
@@ -1814,6 +1818,10 @@
"id": "api.post.send_notifications_and_forget.push_image_only_dm",
"translation": " Загружены один или несколько файлов для сообщения"
},
{
"id": "api.post.send_notifications_and_forget.push_image_only_no_channel",
"translation": " Загружено один или несколько файлов в "
},
{
"id": "api.post.send_notifications_and_forget.push_in",
"translation": " в "
@@ -3648,7 +3656,7 @@
},
{
"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",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found",
"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",
"translation": "Попытка входа с использованием SAML не удалась из-за некорректного атрибута. Пожалуйста, свяжитесь с системным администратором."
@@ -5026,6 +5046,10 @@
"id": "model.config.is_valid.webserver_security.app_error",
"translation": "Недопустимое значение настроек безопасности соединения веб-сервера."
},
{
"id": "model.config.is_valid.websocket_url.app_error",
"translation": "Ссылка на шлюз WebRTC должна быть действующей и начинаться с ws:// или wss://."
},
{
"id": "model.config.is_valid.write_timeout.app_error",
"translation": "Недопустимое значение для времени ожидания записи."

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

@@ -1036,7 +1036,7 @@
},
{
"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",
@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error",
"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",
"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",
"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",
"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",
"translation": " içinde "
@@ -2184,7 +2192,7 @@
},
{
"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",
@@ -2300,11 +2308,11 @@
},
{
"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",
"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",
@@ -3072,7 +3080,7 @@
},
{
"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",
@@ -3648,7 +3656,7 @@
},
{
"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",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found",
"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",
"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",
"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",
@@ -4896,7 +4916,7 @@
},
{
"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",
@@ -5026,6 +5046,10 @@
"id": "model.config.is_valid.webserver_security.app_error",
"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",
"translation": "Yazma zaman aşımı değeri geçersiz."
@@ -7112,7 +7136,7 @@
},
{
"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",

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

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

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

@@ -1368,6 +1368,10 @@
"id": "api.file.upload_file.bad_parse.app_error",
"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",
"translation": "無法上傳超過最大尺寸的檔案:{{.Filename}}"
@@ -1814,6 +1818,10 @@
"id": "api.post.send_notifications_and_forget.push_image_only_dm",
"translation": "在直接傳訊中已上傳一個或更多檔案"
},
{
"id": "api.post.send_notifications_and_forget.push_image_only_no_channel",
"translation": "已上傳一個或更多檔案"
},
{
"id": "api.post.send_notifications_and_forget.push_in",
"translation": " 於 "
@@ -3648,7 +3656,7 @@
},
{
"id": "app.plugin.disabled.app_error",
"translation": "模組已被停用。"
"translation": "Plugins have been disabled. Please check your logs for details."
},
{
"id": "app.plugin.extract.app_error",
@@ -4182,6 +4190,18 @@
"id": "ent.migration.migratetoldap.user_not_found",
"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",
"translation": "由於不正確的屬性SAML 登入失敗。請聯繫系統管理員。"
@@ -5026,6 +5046,10 @@
"id": "model.config.is_valid.webserver_security.app_error",
"translation": "網頁伺服器連線安全的值不正確。"
},
{
"id": "model.config.is_valid.websocket_url.app_error",
"translation": "WebRTC 閘道 Websocket 網址必須是以 ws:// 或 wss:// 起始的有效網址。"
},
{
"id": "model.config.is_valid.write_timeout.app_error",
"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)
}
func (o *Channel) StatsEtag() string {
return Etag(o.Id, o.ExtraUpdateAt)
}
func (o *Channel) IsValid() *AppError {
if len(o.Id) != 26 {
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) {
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 {
@@ -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) {
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 {

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

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

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