From e9c6cc269b5c9fe82e5f38d63344a07365bccd6b Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Mon, 13 Mar 2017 09:23:16 -0400 Subject: Move command logic into app layer (#5617) --- api/auto_channels.go | 74 ------- api/auto_constants.go | 36 ---- api/auto_environment.go | 99 --------- api/auto_posts.go | 103 ---------- api/auto_teams.go | 81 -------- api/auto_users.go | 110 ---------- api/command.go | 434 +++++++++------------------------------ api/command_away.go | 43 ---- api/command_echo.go | 97 --------- api/command_expand_collapse.go | 87 -------- api/command_invite_people.go | 64 ------ api/command_join.go | 57 ------ api/command_loadtest.go | 439 ---------------------------------------- api/command_logout.go | 48 ----- api/command_me.go | 37 ---- api/command_msg.go | 95 --------- api/command_offline.go | 43 ---- api/command_online.go | 43 ---- api/command_shortcuts.go | 94 --------- api/command_shrug.go | 42 ---- app/auto_channels.go | 74 +++++++ app/auto_constants.go | 36 ++++ app/auto_environment.go | 99 +++++++++ app/auto_posts.go | 103 ++++++++++ app/auto_teams.go | 81 ++++++++ app/auto_users.go | 109 ++++++++++ app/command.go | 304 ++++++++++++++++++++++++++++ app/command_away.go | 43 ++++ app/command_echo.go | 97 +++++++++ app/command_expand_collapse.go | 87 ++++++++ app/command_invite_people.go | 64 ++++++ app/command_join.go | 62 ++++++ app/command_loadtest.go | 437 +++++++++++++++++++++++++++++++++++++++ app/command_logout.go | 48 +++++ app/command_me.go | 38 ++++ app/command_msg.go | 110 ++++++++++ app/command_offline.go | 43 ++++ app/command_online.go | 43 ++++ app/command_shortcuts.go | 95 +++++++++ app/command_shrug.go | 43 ++++ manualtesting/manual_testing.go | 4 +- model/command_args.go | 15 +- 42 files changed, 2129 insertions(+), 2032 deletions(-) delete mode 100644 api/auto_channels.go delete mode 100644 api/auto_constants.go delete mode 100644 api/auto_environment.go delete mode 100644 api/auto_posts.go delete mode 100644 api/auto_teams.go delete mode 100644 api/auto_users.go delete mode 100644 api/command_away.go delete mode 100644 api/command_echo.go delete mode 100644 api/command_expand_collapse.go delete mode 100644 api/command_invite_people.go delete mode 100644 api/command_join.go delete mode 100644 api/command_loadtest.go delete mode 100644 api/command_logout.go delete mode 100644 api/command_me.go delete mode 100644 api/command_msg.go delete mode 100644 api/command_offline.go delete mode 100644 api/command_online.go delete mode 100644 api/command_shortcuts.go delete mode 100644 api/command_shrug.go create mode 100644 app/auto_channels.go create mode 100644 app/auto_constants.go create mode 100644 app/auto_environment.go create mode 100644 app/auto_posts.go create mode 100644 app/auto_teams.go create mode 100644 app/auto_users.go create mode 100644 app/command_away.go create mode 100644 app/command_echo.go create mode 100644 app/command_expand_collapse.go create mode 100644 app/command_invite_people.go create mode 100644 app/command_join.go create mode 100644 app/command_loadtest.go create mode 100644 app/command_logout.go create mode 100644 app/command_me.go create mode 100644 app/command_msg.go create mode 100644 app/command_offline.go create mode 100644 app/command_online.go create mode 100644 app/command_shortcuts.go create mode 100644 app/command_shrug.go diff --git a/api/auto_channels.go b/api/auto_channels.go deleted file mode 100644 index 1d0f0e7d9..000000000 --- a/api/auto_channels.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "github.com/mattermost/platform/model" - "github.com/mattermost/platform/utils" -) - -type AutoChannelCreator struct { - client *model.Client - team *model.Team - Fuzzy bool - DisplayNameLen utils.Range - DisplayNameCharset string - NameLen utils.Range - NameCharset string - ChannelType string -} - -func NewAutoChannelCreator(client *model.Client, team *model.Team) *AutoChannelCreator { - return &AutoChannelCreator{ - client: client, - team: team, - Fuzzy: false, - DisplayNameLen: CHANNEL_DISPLAY_NAME_LEN, - DisplayNameCharset: utils.ALPHANUMERIC, - NameLen: CHANNEL_NAME_LEN, - NameCharset: utils.LOWERCASE, - ChannelType: CHANNEL_TYPE, - } -} - -func (cfg *AutoChannelCreator) createRandomChannel() (*model.Channel, bool) { - var displayName string - if cfg.Fuzzy { - displayName = utils.FuzzName() - } else { - displayName = utils.RandomName(cfg.NameLen, cfg.NameCharset) - } - name := utils.RandomName(cfg.NameLen, cfg.NameCharset) - - channel := &model.Channel{ - TeamId: cfg.team.Id, - DisplayName: displayName, - Name: name, - Type: cfg.ChannelType} - - println(cfg.client.GetTeamRoute()) - result, err := cfg.client.CreateChannel(channel) - if err != nil { - err.Translate(utils.T) - println(err.Error()) - println(err.DetailedError) - return nil, false - } - return result.Data.(*model.Channel), true -} - -func (cfg *AutoChannelCreator) CreateTestChannels(num utils.Range) ([]*model.Channel, bool) { - numChannels := utils.RandIntFromRange(num) - channels := make([]*model.Channel, numChannels) - - for i := 0; i < numChannels; i++ { - var err bool - channels[i], err = cfg.createRandomChannel() - if err != true { - return channels, false - } - } - - return channels, true -} diff --git a/api/auto_constants.go b/api/auto_constants.go deleted file mode 100644 index a10ae99f2..000000000 --- a/api/auto_constants.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "github.com/mattermost/platform/model" - "github.com/mattermost/platform/utils" -) - -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" -) - -var ( - TEAM_NAME_LEN = utils.Range{Begin: 10, End: 20} - TEAM_DOMAIN_NAME_LEN = utils.Range{Begin: 10, End: 20} - TEAM_EMAIL_LEN = utils.Range{Begin: 15, End: 30} - USER_NAME_LEN = utils.Range{Begin: 5, End: 20} - 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"} -) diff --git a/api/auto_environment.go b/api/auto_environment.go deleted file mode 100644 index 6c7bc2d0a..000000000 --- a/api/auto_environment.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "github.com/mattermost/platform/model" - "github.com/mattermost/platform/utils" - "math/rand" - "time" -) - -type TestEnvironment struct { - Teams []*model.Team - Environments []TeamEnvironment -} - -func CreateTestEnvironmentWithTeams(client *model.Client, rangeTeams utils.Range, rangeChannels utils.Range, rangeUsers utils.Range, rangePosts utils.Range, fuzzy bool) (TestEnvironment, bool) { - rand.Seed(time.Now().UTC().UnixNano()) - - teamCreator := NewAutoTeamCreator(client) - teamCreator.Fuzzy = fuzzy - teams, err := teamCreator.CreateTestTeams(rangeTeams) - if err != true { - return TestEnvironment{}, false - } - - environment := TestEnvironment{teams, make([]TeamEnvironment, len(teams))} - - for i, team := range teams { - userCreator := NewAutoUserCreator(client, team) - userCreator.Fuzzy = fuzzy - randomUser, err := userCreator.createRandomUser() - if err != true { - return TestEnvironment{}, false - } - client.LoginById(randomUser.Id, USER_PASSWORD) - client.SetTeamId(team.Id) - teamEnvironment, err := CreateTestEnvironmentInTeam(client, team, rangeChannels, rangeUsers, rangePosts, fuzzy) - if err != true { - return TestEnvironment{}, false - } - environment.Environments[i] = teamEnvironment - } - - return environment, true -} - -func CreateTestEnvironmentInTeam(client *model.Client, team *model.Team, rangeChannels utils.Range, rangeUsers utils.Range, rangePosts utils.Range, fuzzy bool) (TeamEnvironment, bool) { - rand.Seed(time.Now().UTC().UnixNano()) - - // We need to create at least one user - if rangeUsers.Begin <= 0 { - rangeUsers.Begin = 1 - } - - userCreator := NewAutoUserCreator(client, team) - userCreator.Fuzzy = fuzzy - users, err := userCreator.CreateTestUsers(rangeUsers) - if err != true { - return TeamEnvironment{}, false - } - usernames := make([]string, len(users)) - for i, user := range users { - usernames[i] = user.Username - } - - channelCreator := NewAutoChannelCreator(client, team) - channelCreator.Fuzzy = fuzzy - channels, err := channelCreator.CreateTestChannels(rangeChannels) - - // Have every user join every channel - for _, user := range users { - for _, channel := range channels { - client.LoginById(user.Id, USER_PASSWORD) - client.JoinChannel(channel.Id) - } - } - - if err != true { - return TeamEnvironment{}, false - } - - numPosts := utils.RandIntFromRange(rangePosts) - numImages := utils.RandIntFromRange(rangePosts) / 4 - for j := 0; j < numPosts; j++ { - user := users[utils.RandIntFromRange(utils.Range{Begin: 0, End: len(users) - 1})] - client.LoginById(user.Id, USER_PASSWORD) - for i, channel := range channels { - postCreator := NewAutoPostCreator(client, channel.Id) - postCreator.HasImage = i < numImages - postCreator.Users = usernames - postCreator.Fuzzy = fuzzy - postCreator.CreateRandomPost() - } - } - - return TeamEnvironment{users, channels}, true -} diff --git a/api/auto_posts.go b/api/auto_posts.go deleted file mode 100644 index bb20aadae..000000000 --- a/api/auto_posts.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "bytes" - "github.com/mattermost/platform/model" - "github.com/mattermost/platform/utils" - "io" - "os" -) - -type AutoPostCreator struct { - client *model.Client - channelid string - Fuzzy bool - TextLength utils.Range - HasImage bool - ImageFilenames []string - Users []string - Mentions utils.Range - Tags utils.Range -} - -// Automatic poster used for testing -func NewAutoPostCreator(client *model.Client, channelid string) *AutoPostCreator { - return &AutoPostCreator{ - client: client, - channelid: channelid, - Fuzzy: false, - TextLength: utils.Range{Begin: 100, End: 200}, - HasImage: false, - ImageFilenames: TEST_IMAGE_FILENAMES, - Users: []string{}, - Mentions: utils.Range{Begin: 0, End: 5}, - Tags: utils.Range{Begin: 0, End: 7}, - } -} - -func (cfg *AutoPostCreator) UploadTestFile() ([]string, bool) { - filename := cfg.ImageFilenames[utils.RandIntFromRange(utils.Range{Begin: 0, End: len(cfg.ImageFilenames) - 1})] - - path := utils.FindDir("web/static/images") - file, err := os.Open(path + "/" + filename) - defer file.Close() - - data := &bytes.Buffer{} - _, err = io.Copy(data, file) - if err != nil { - return nil, false - } - - resp, appErr := cfg.client.UploadPostAttachment(data.Bytes(), cfg.channelid, filename) - if appErr != nil { - return nil, false - } - - return []string{resp.FileInfos[0].Id}, true -} - -func (cfg *AutoPostCreator) CreateRandomPost() (*model.Post, bool) { - var fileIds []string - if cfg.HasImage { - var err1 bool - fileIds, err1 = cfg.UploadTestFile() - if err1 == false { - return nil, false - } - } - - var postText string - if cfg.Fuzzy { - postText = utils.FuzzPost() - } else { - postText = utils.RandomText(cfg.TextLength, cfg.Tags, cfg.Mentions, cfg.Users) - } - - post := &model.Post{ - ChannelId: cfg.channelid, - Message: postText, - FileIds: fileIds} - result, err2 := cfg.client.CreatePost(post) - if err2 != nil { - return nil, false - } - 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 != true { - return posts, false - } - } - - return posts, true -} diff --git a/api/auto_teams.go b/api/auto_teams.go deleted file mode 100644 index b2e1ace85..000000000 --- a/api/auto_teams.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "github.com/mattermost/platform/model" - "github.com/mattermost/platform/utils" -) - -type TeamEnvironment struct { - Users []*model.User - Channels []*model.Channel -} - -type AutoTeamCreator struct { - client *model.Client - Fuzzy bool - NameLength utils.Range - NameCharset string - DomainLength utils.Range - DomainCharset string - EmailLength utils.Range - EmailCharset string -} - -func NewAutoTeamCreator(client *model.Client) *AutoTeamCreator { - return &AutoTeamCreator{ - client: client, - Fuzzy: false, - NameLength: TEAM_NAME_LEN, - NameCharset: utils.LOWERCASE, - DomainLength: TEAM_DOMAIN_NAME_LEN, - DomainCharset: utils.LOWERCASE, - EmailLength: TEAM_EMAIL_LEN, - EmailCharset: utils.LOWERCASE, - } -} - -func (cfg *AutoTeamCreator) createRandomTeam() (*model.Team, bool) { - var teamEmail string - var teamDisplayName string - var teamName string - if cfg.Fuzzy { - teamEmail = "success+" + model.NewId() + "simulator.amazonses.com" - teamDisplayName = utils.FuzzName() - teamName = utils.FuzzName() - } else { - teamEmail = "success+" + model.NewId() + "simulator.amazonses.com" - teamDisplayName = utils.RandomName(cfg.NameLength, cfg.NameCharset) - teamName = utils.RandomName(cfg.NameLength, cfg.NameCharset) + model.NewId() - } - team := &model.Team{ - DisplayName: teamDisplayName, - Name: teamName, - Email: teamEmail, - Type: model.TEAM_OPEN, - } - - result, err := cfg.client.CreateTeam(team) - if err != nil { - return nil, false - } - createdTeam := result.Data.(*model.Team) - return createdTeam, true -} - -func (cfg *AutoTeamCreator) CreateTestTeams(num utils.Range) ([]*model.Team, bool) { - numTeams := utils.RandIntFromRange(num) - teams := make([]*model.Team, numTeams) - - for i := 0; i < numTeams; i++ { - var err bool - teams[i], err = cfg.createRandomTeam() - if err != true { - return teams, false - } - } - - return teams, true -} diff --git a/api/auto_users.go b/api/auto_users.go deleted file mode 100644 index d8cd8d3a3..000000000 --- a/api/auto_users.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "github.com/mattermost/platform/app" - "github.com/mattermost/platform/model" - "github.com/mattermost/platform/store" - "github.com/mattermost/platform/utils" - - l4g "github.com/alecthomas/log4go" -) - -type AutoUserCreator struct { - client *model.Client - team *model.Team - EmailLength utils.Range - EmailCharset string - NameLength utils.Range - NameCharset string - Fuzzy bool -} - -func NewAutoUserCreator(client *model.Client, team *model.Team) *AutoUserCreator { - return &AutoUserCreator{ - client: client, - team: team, - EmailLength: USER_EMAIL_LEN, - EmailCharset: utils.LOWERCASE, - NameLength: USER_NAME_LEN, - NameCharset: utils.LOWERCASE, - Fuzzy: false, - } -} - -// Basic test team and user so you always know one -func CreateBasicUser(client *model.Client) *model.AppError { - result, _ := client.FindTeamByName(BTEST_TEAM_NAME) - if result.Data.(bool) == false { - newteam := &model.Team{DisplayName: BTEST_TEAM_DISPLAY_NAME, Name: BTEST_TEAM_NAME, Email: BTEST_TEAM_EMAIL, Type: BTEST_TEAM_TYPE} - result, err := client.CreateTeam(newteam) - if err != nil { - return err - } - basicteam := result.Data.(*model.Team) - newuser := &model.User{Email: BTEST_USER_EMAIL, Nickname: BTEST_USER_NAME, Password: BTEST_USER_PASSWORD} - result, err = client.CreateUser(newuser, "") - if err != nil { - return err - } - ruser := result.Data.(*model.User) - store.Must(app.Srv.Store.User().VerifyEmail(ruser.Id)) - store.Must(app.Srv.Store.Team().SaveMember(&model.TeamMember{TeamId: basicteam.Id, UserId: ruser.Id})) - } - return nil -} - -func (cfg *AutoUserCreator) createRandomUser() (*model.User, bool) { - var userEmail string - var userName string - if cfg.Fuzzy { - userEmail = "success+" + model.NewId() + "simulator.amazonses.com" - userName = utils.FuzzName() - } else { - userEmail = "success+" + model.NewId() + "simulator.amazonses.com" - userName = utils.RandomName(cfg.NameLength, cfg.NameCharset) - } - - user := &model.User{ - Email: userEmail, - Nickname: userName, - Password: USER_PASSWORD} - - result, err := cfg.client.CreateUserWithInvite(user, "", "", cfg.team.InviteId) - if err != nil { - err.Translate(utils.T) - l4g.Error(err.Error()) - return nil, false - } - - ruser := result.Data.(*model.User) - - status := &model.Status{UserId: ruser.Id, Status: model.STATUS_ONLINE, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: ""} - if result := <-app.Srv.Store.Status().SaveOrUpdate(status); result.Err != nil { - result.Err.Translate(utils.T) - l4g.Error(result.Err.Error()) - return nil, false - } - - // We need to cheat to verify the user's email - store.Must(app.Srv.Store.User().VerifyEmail(ruser.Id)) - - return result.Data.(*model.User), true -} - -func (cfg *AutoUserCreator) CreateTestUsers(num utils.Range) ([]*model.User, bool) { - numUsers := utils.RandIntFromRange(num) - users := make([]*model.User, numUsers) - - for i := 0; i < numUsers; i++ { - var err bool - users[i], err = cfg.createRandomUser() - if err != true { - return users, false - } - } - - return users, true -} diff --git a/api/command.go b/api/command.go index c3a64702f..2248caf76 100644 --- a/api/command.go +++ b/api/command.go @@ -4,11 +4,8 @@ package api import ( - "crypto/tls" - "fmt" "io/ioutil" "net/http" - "net/url" "strings" l4g "github.com/alecthomas/log4go" @@ -18,27 +15,6 @@ import ( "github.com/mattermost/platform/utils" ) -type CommandProvider interface { - GetTrigger() string - GetCommand(c *Context) *model.Command - DoCommand(c *Context, args *model.CommandArgs, message string) *model.CommandResponse -} - -var commandProviders = make(map[string]CommandProvider) - -func RegisterCommandProvider(newProvider CommandProvider) { - commandProviders[newProvider.GetTrigger()] = newProvider -} - -func GetCommandProvider(name string) CommandProvider { - provider, ok := commandProviders[name] - if ok { - return provider - } - - return nil -} - func InitCommand() { l4g.Debug(utils.T("api.command.init.debug")) @@ -58,31 +34,10 @@ func InitCommand() { } func listCommands(c *Context, w http.ResponseWriter, r *http.Request) { - commands := make([]*model.Command, 0, 32) - seen := make(map[string]bool) - for _, value := range commandProviders { - cpy := *value.GetCommand(c) - if cpy.AutoComplete && !seen[cpy.Id] { - cpy.Sanitize() - seen[cpy.Trigger] = true - commands = append(commands, &cpy) - } - } - - if *utils.Cfg.ServiceSettings.EnableCommands { - if result := <-app.Srv.Store.Command().GetByTeam(c.TeamId); result.Err != nil { - c.Err = result.Err - return - } else { - teamCmds := result.Data.([]*model.Command) - for _, cmd := range teamCmds { - if cmd.AutoComplete && !seen[cmd.Id] { - cmd.Sanitize() - seen[cmd.Trigger] = true - commands = append(commands, cmd) - } - } - } + commands, err := app.ListCommands(c.TeamId, c.T) + if err != nil { + c.Err = err + return } w.Write([]byte(model.CommandListToJson(commands))) @@ -90,9 +45,13 @@ func listCommands(c *Context, w http.ResponseWriter, r *http.Request) { func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) { commandArgs := model.CommandArgsFromJson(r.Body) + if commandArgs == nil { + c.SetInvalidParam("executeCommand", "command_args") + return + } if len(commandArgs.Command) <= 1 || strings.Index(commandArgs.Command, "/") != 0 { - c.Err = model.NewLocAppError("executeCommand", "api.command.execute_command.start.app_error", nil, "") + c.Err = model.NewAppError("executeCommand", "api.command.execute_command.start.app_error", nil, "", http.StatusBadRequest) return } @@ -103,232 +62,50 @@ func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) { } } - parts := strings.Split(commandArgs.Command, " ") - trigger := parts[0][1:] - trigger = strings.ToLower(trigger) - message := strings.Join(parts[1:], " ") - provider := GetCommandProvider(trigger) - - if provider != nil { - response := provider.DoCommand(c, commandArgs, message) - handleResponse(c, w, response, commandArgs, provider.GetCommand(c), true) - return - } else { - - if !*utils.Cfg.ServiceSettings.EnableCommands { - c.Err = model.NewLocAppError("executeCommand", "api.command.disabled.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - - chanChan := app.Srv.Store.Channel().Get(commandArgs.ChannelId, true) - teamChan := app.Srv.Store.Team().Get(c.TeamId) - userChan := app.Srv.Store.User().Get(c.Session.UserId) - - if result := <-app.Srv.Store.Command().GetByTeam(c.TeamId); result.Err != nil { - c.Err = result.Err - return - } else { - - var team *model.Team - if tr := <-teamChan; tr.Err != nil { - c.Err = tr.Err - return - } else { - team = tr.Data.(*model.Team) - } - - var user *model.User - if ur := <-userChan; ur.Err != nil { - c.Err = ur.Err - return - } else { - user = ur.Data.(*model.User) - } - - var channel *model.Channel - if cr := <-chanChan; cr.Err != nil { - c.Err = cr.Err - return - } else { - channel = cr.Data.(*model.Channel) - } - - teamCmds := result.Data.([]*model.Command) - for _, cmd := range teamCmds { - if trigger == cmd.Trigger { - l4g.Debug(fmt.Sprintf(utils.T("api.command.execute_command.debug"), trigger, c.Session.UserId)) - - p := url.Values{} - p.Set("token", cmd.Token) - - p.Set("team_id", cmd.TeamId) - p.Set("team_domain", team.Name) - - p.Set("channel_id", commandArgs.ChannelId) - p.Set("channel_name", channel.Name) - - p.Set("user_id", c.Session.UserId) - p.Set("user_name", user.Username) - - p.Set("command", "/"+trigger) - p.Set("text", message) - p.Set("response_url", "not supported yet") - - method := "POST" - if cmd.Method == model.COMMAND_METHOD_GET { - method = "GET" - } - - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections}, - } - client := &http.Client{Transport: tr} - - req, _ := http.NewRequest(method, cmd.URL, strings.NewReader(p.Encode())) - req.Header.Set("Accept", "application/json") - if cmd.Method == model.COMMAND_METHOD_POST { - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - } - - if resp, err := client.Do(req); err != nil { - c.Err = model.NewLocAppError("command", "api.command.execute_command.failed.app_error", map[string]interface{}{"Trigger": trigger}, err.Error()) - } else { - if resp.StatusCode == http.StatusOK { - response := model.CommandResponseFromJson(resp.Body) - if response == nil { - c.Err = model.NewLocAppError("command", "api.command.execute_command.failed_empty.app_error", map[string]interface{}{"Trigger": trigger}, "") - } else { - handleResponse(c, w, response, commandArgs, cmd, false) - } - } else { - defer resp.Body.Close() - body, _ := ioutil.ReadAll(resp.Body) - c.Err = model.NewLocAppError("command", "api.command.execute_command.failed_resp.app_error", map[string]interface{}{"Trigger": trigger, "Status": resp.Status}, string(body)) - } - } - - return - } - } - - } - } - - c.Err = model.NewLocAppError("command", "api.command.execute_command.not_found.app_error", map[string]interface{}{"Trigger": trigger}, "") -} + commandArgs.TeamId = c.TeamId + commandArgs.UserId = c.Session.UserId + commandArgs.T = c.T + commandArgs.Session = c.Session + commandArgs.SiteURL = c.GetSiteURL() -func handleResponse(c *Context, w http.ResponseWriter, response *model.CommandResponse, commandArgs *model.CommandArgs, cmd *model.Command, builtIn bool) { - if c.Err != nil { + response, err := app.ExecuteCommand(commandArgs) + if err != nil { + c.Err = err return } - post := &model.Post{} - post.ChannelId = commandArgs.ChannelId - post.RootId = commandArgs.RootId - post.ParentId = commandArgs.ParentId - post.UserId = c.Session.UserId - - if !builtIn { - post.AddProp("from_webhook", "true") - } - - if utils.Cfg.ServiceSettings.EnablePostUsernameOverride { - if len(cmd.Username) != 0 { - post.AddProp("override_username", cmd.Username) - } else if len(response.Username) != 0 { - post.AddProp("override_username", response.Username) - } - } - - if utils.Cfg.ServiceSettings.EnablePostIconOverride { - if len(cmd.IconURL) != 0 { - post.AddProp("override_icon_url", cmd.IconURL) - } else if len(response.IconURL) != 0 { - post.AddProp("override_icon_url", response.IconURL) - } else { - post.AddProp("override_icon_url", "") - } - } - - if _, err := app.CreateCommandPost(post, c.TeamId, response); err != nil { - l4g.Error(err.Error()) - } - w.Write([]byte(response.ToJson())) } func createCommand(c *Context, w http.ResponseWriter, r *http.Request) { - if !*utils.Cfg.ServiceSettings.EnableCommands { - c.Err = model.NewLocAppError("createCommand", "api.command.disabled.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } + cmd := model.CommandFromJson(r.Body) - if !app.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { - c.Err = model.NewLocAppError("createCommand", "api.command.admin_only.app_error", nil, "") - c.Err.StatusCode = http.StatusForbidden + if cmd == nil { + c.SetInvalidParam("createCommand", "command") return } c.LogAudit("attempt") - cmd := model.CommandFromJson(r.Body) - - if cmd == nil { - c.SetInvalidParam("createCommand", "command") + if !app.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { + c.SetPermissionError(model.PERMISSION_MANAGE_SLASH_COMMANDS) return } - cmd.Trigger = strings.ToLower(cmd.Trigger) cmd.CreatorId = c.Session.UserId cmd.TeamId = c.TeamId - if result := <-app.Srv.Store.Command().GetByTeam(c.TeamId); result.Err != nil { - c.Err = result.Err + rcmd, err := app.CreateCommand(cmd) + if err != nil { + c.Err = err return - } else { - teamCmds := result.Data.([]*model.Command) - for _, existingCommand := range teamCmds { - if cmd.Trigger == existingCommand.Trigger { - c.Err = model.NewLocAppError("createCommand", "api.command.duplicate_trigger.app_error", nil, "") - return - } - } - for _, builtInProvider := range commandProviders { - builtInCommand := *builtInProvider.GetCommand(c) - if cmd.Trigger == builtInCommand.Trigger { - c.Err = model.NewLocAppError("createCommand", "api.command.duplicate_trigger.app_error", nil, "") - return - } - } } - if result := <-app.Srv.Store.Command().Save(cmd); result.Err != nil { - c.Err = result.Err - return - } else { - c.LogAudit("success") - rcmd := result.Data.(*model.Command) - w.Write([]byte(rcmd.ToJson())) - } + c.LogAudit("success") + w.Write([]byte(rcmd.ToJson())) } func updateCommand(c *Context, w http.ResponseWriter, r *http.Request) { - if !*utils.Cfg.ServiceSettings.EnableCommands { - c.Err = model.NewLocAppError("updateCommand", "api.command.disabled.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - - if !app.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { - c.Err = model.NewLocAppError("updateCommand", "api.command.admin_only.app_error", nil, "") - c.Err.StatusCode = http.StatusForbidden - return - } - - c.LogAudit("attempt") - cmd := model.CommandFromJson(r.Body) if cmd == nil { @@ -336,80 +113,58 @@ func updateCommand(c *Context, w http.ResponseWriter, r *http.Request) { return } - cmd.Trigger = strings.ToLower(cmd.Trigger) + c.LogAudit("attempt") - var oldCmd *model.Command - if result := <-app.Srv.Store.Command().Get(cmd.Id); result.Err != nil { - c.Err = result.Err + oldCmd, err := app.GetCommand(cmd.Id) + if err != nil { + c.Err = err return - } else { - oldCmd = result.Data.(*model.Command) - - if c.Session.UserId != oldCmd.CreatorId && !app.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) { - c.LogAudit("fail - inappropriate permissions") - c.Err = model.NewLocAppError("updateCommand", "api.command.update.app_error", nil, "user_id="+c.Session.UserId) - return - } - - if c.TeamId != oldCmd.TeamId { - c.Err = model.NewLocAppError("updateCommand", "api.command.team_mismatch.app_error", nil, "user_id="+c.Session.UserId) - return - } - - cmd.Id = oldCmd.Id - cmd.Token = oldCmd.Token - cmd.CreateAt = oldCmd.CreateAt - cmd.UpdateAt = model.GetMillis() - cmd.DeleteAt = oldCmd.DeleteAt - cmd.CreatorId = oldCmd.CreatorId - cmd.TeamId = oldCmd.TeamId } - if result := <-app.Srv.Store.Command().Update(cmd); result.Err != nil { - c.Err = result.Err + if c.TeamId != oldCmd.TeamId { + c.Err = model.NewAppError("updateCommand", "api.command.team_mismatch.app_error", nil, "user_id="+c.Session.UserId, http.StatusBadRequest) return - } else { - w.Write([]byte(result.Data.(*model.Command).ToJson())) } -} -func listTeamCommands(c *Context, w http.ResponseWriter, r *http.Request) { - if !*utils.Cfg.ServiceSettings.EnableCommands { - c.Err = model.NewLocAppError("listTeamCommands", "api.command.disabled.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented + if !app.SessionHasPermissionToTeam(c.Session, oldCmd.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { + c.LogAudit("fail - inappropriate permissions") + c.SetPermissionError(model.PERMISSION_MANAGE_SLASH_COMMANDS) return } - if !app.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { - c.Err = model.NewLocAppError("listTeamCommands", "api.command.admin_only.app_error", nil, "") - c.Err.StatusCode = http.StatusForbidden + if c.Session.UserId != oldCmd.CreatorId && !app.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) { + c.LogAudit("fail - inappropriate permissions") + c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) return } - if result := <-app.Srv.Store.Command().GetByTeam(c.TeamId); result.Err != nil { - c.Err = result.Err + rcmd, err := app.UpdateCommand(oldCmd, cmd) + if err != nil { + c.Err = err return - } else { - cmds := result.Data.([]*model.Command) - w.Write([]byte(model.CommandListToJson(cmds))) } + + c.LogAudit("success") + + w.Write([]byte(rcmd.ToJson())) } -func regenCommandToken(c *Context, w http.ResponseWriter, r *http.Request) { - if !*utils.Cfg.ServiceSettings.EnableCommands { - c.Err = model.NewLocAppError("regenCommandToken", "api.command.disabled.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented +func listTeamCommands(c *Context, w http.ResponseWriter, r *http.Request) { + if !app.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { + c.SetPermissionError(model.PERMISSION_MANAGE_SLASH_COMMANDS) return } - if !app.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { - c.Err = model.NewLocAppError("regenCommandToken", "api.command.admin_only.app_error", nil, "") - c.Err.StatusCode = http.StatusForbidden + cmds, err := app.ListTeamCommands(c.TeamId) + if err != nil { + c.Err = err return } - c.LogAudit("attempt") + w.Write([]byte(model.CommandListToJson(cmds))) +} +func regenCommandToken(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) id := props["id"] @@ -418,45 +173,41 @@ func regenCommandToken(c *Context, w http.ResponseWriter, r *http.Request) { return } - var cmd *model.Command - if result := <-app.Srv.Store.Command().Get(id); result.Err != nil { - c.Err = result.Err - return - } else { - cmd = result.Data.(*model.Command) + c.LogAudit("attempt") - if c.TeamId != cmd.TeamId || (c.Session.UserId != cmd.CreatorId && !app.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS)) { - c.LogAudit("fail - inappropriate permissions") - c.Err = model.NewLocAppError("regenToken", "api.command.regen.app_error", nil, "user_id="+c.Session.UserId) - return - } + cmd, err := app.GetCommand(id) + if err != nil { + c.Err = err + return } - cmd.Token = model.NewId() + if c.TeamId != cmd.TeamId { + c.Err = model.NewAppError("regenCommandToken", "api.command.team_mismatch.app_error", nil, "user_id="+c.Session.UserId, http.StatusBadRequest) + return + } - if result := <-app.Srv.Store.Command().Update(cmd); result.Err != nil { - c.Err = result.Err + if !app.SessionHasPermissionToTeam(c.Session, cmd.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { + c.LogAudit("fail - inappropriate permissions") + c.SetPermissionError(model.PERMISSION_MANAGE_SLASH_COMMANDS) return - } else { - w.Write([]byte(result.Data.(*model.Command).ToJson())) } -} -func deleteCommand(c *Context, w http.ResponseWriter, r *http.Request) { - if !*utils.Cfg.ServiceSettings.EnableCommands { - c.Err = model.NewLocAppError("deleteCommand", "api.command.disabled.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented + if c.Session.UserId != cmd.CreatorId && !app.SessionHasPermissionToTeam(c.Session, cmd.TeamId, model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) { + c.LogAudit("fail - inappropriate permissions") + c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) return } - if !app.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { - c.Err = model.NewLocAppError("deleteCommand", "api.command.admin_only.app_error", nil, "") - c.Err.StatusCode = http.StatusForbidden + rcmd, err := app.RegenCommandToken(cmd) + if err != nil { + c.Err = err return } - c.LogAudit("attempt") + w.Write([]byte(rcmd.ToJson())) +} +func deleteCommand(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) id := props["id"] @@ -465,18 +216,33 @@ func deleteCommand(c *Context, w http.ResponseWriter, r *http.Request) { return } - if result := <-app.Srv.Store.Command().Get(id); result.Err != nil { - c.Err = result.Err + c.LogAudit("attempt") + + cmd, err := app.GetCommand(id) + if err != nil { + c.Err = err + return + } + + if c.TeamId != cmd.TeamId { + c.Err = model.NewAppError("deleteCommand", "api.command.team_mismatch.app_error", nil, "user_id="+c.Session.UserId, http.StatusBadRequest) + return + } + + if !app.SessionHasPermissionToTeam(c.Session, cmd.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { + c.SetPermissionError(model.PERMISSION_MANAGE_SLASH_COMMANDS) + c.LogAudit("fail - inappropriate permissions") + return + } + + if c.Session.UserId != cmd.CreatorId && !app.SessionHasPermissionToTeam(c.Session, cmd.TeamId, model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) { + c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) + c.LogAudit("fail - inappropriate permissions") return - } else { - if c.TeamId != result.Data.(*model.Command).TeamId || (c.Session.UserId != result.Data.(*model.Command).CreatorId && !app.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS)) { - c.LogAudit("fail - inappropriate permissions") - c.Err = model.NewLocAppError("deleteCommand", "api.command.delete.app_error", nil, "user_id="+c.Session.UserId) - return - } } - if err := (<-app.Srv.Store.Command().Delete(id, model.GetMillis())).Err; err != nil { + err = app.DeleteCommand(cmd.Id) + if err != nil { c.Err = err return } diff --git a/api/command_away.go b/api/command_away.go deleted file mode 100644 index 6a488c081..000000000 --- a/api/command_away.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "github.com/mattermost/platform/app" - "github.com/mattermost/platform/model" -) - -type AwayProvider struct { -} - -const ( - CMD_AWAY = "away" -) - -func init() { - RegisterCommandProvider(&AwayProvider{}) -} - -func (me *AwayProvider) GetTrigger() string { - return CMD_AWAY -} - -func (me *AwayProvider) GetCommand(c *Context) *model.Command { - return &model.Command{ - Trigger: CMD_AWAY, - AutoComplete: true, - AutoCompleteDesc: c.T("api.command_away.desc"), - DisplayName: c.T("api.command_away.name"), - } -} - -func (me *AwayProvider) DoCommand(c *Context, args *model.CommandArgs, message string) *model.CommandResponse { - rmsg := c.T("api.command_away.success") - if len(message) > 0 { - rmsg = message + " " + rmsg - } - app.SetStatusAwayIfNeeded(c.Session.UserId, true) - - return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: rmsg} -} diff --git a/api/command_echo.go b/api/command_echo.go deleted file mode 100644 index 2e931e414..000000000 --- a/api/command_echo.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "strconv" - "strings" - "time" - - l4g "github.com/alecthomas/log4go" - "github.com/mattermost/platform/app" - "github.com/mattermost/platform/model" -) - -var echoSem chan bool - -type EchoProvider struct { -} - -const ( - CMD_ECHO = "echo" -) - -func init() { - RegisterCommandProvider(&EchoProvider{}) -} - -func (me *EchoProvider) GetTrigger() string { - return CMD_ECHO -} - -func (me *EchoProvider) GetCommand(c *Context) *model.Command { - return &model.Command{ - Trigger: CMD_ECHO, - AutoComplete: true, - AutoCompleteDesc: c.T("api.command_echo.desc"), - AutoCompleteHint: c.T("api.command_echo.hint"), - DisplayName: c.T("api.command_echo.name"), - } -} - -func (me *EchoProvider) DoCommand(c *Context, args *model.CommandArgs, message string) *model.CommandResponse { - if len(message) == 0 { - return &model.CommandResponse{Text: c.T("api.command_echo.message.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } - - maxThreads := 100 - - delay := 0 - if endMsg := strings.LastIndex(message, "\""); string(message[0]) == "\"" && endMsg > 1 { - if checkDelay, err := strconv.Atoi(strings.Trim(message[endMsg:], " \"")); err == nil { - delay = checkDelay - } - message = message[1:endMsg] - } else if strings.Index(message, " ") > -1 { - delayIdx := strings.LastIndex(message, " ") - delayStr := strings.Trim(message[delayIdx:], " ") - - if checkDelay, err := strconv.Atoi(delayStr); err == nil { - delay = checkDelay - message = message[:delayIdx] - } - } - - if delay > 10000 { - return &model.CommandResponse{Text: c.T("api.command_echo.delay.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } - - if echoSem == nil { - // We want one additional thread allowed so we never reach channel lockup - echoSem = make(chan bool, maxThreads+1) - } - - if len(echoSem) >= maxThreads { - return &model.CommandResponse{Text: c.T("api.command_echo.high_volume.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } - - echoSem <- true - go func() { - defer func() { <-echoSem }() - post := &model.Post{} - post.ChannelId = args.ChannelId - post.RootId = args.RootId - post.ParentId = args.ParentId - post.Message = message - post.UserId = c.Session.UserId - - time.Sleep(time.Duration(delay) * time.Second) - - if _, err := app.CreatePost(post, c.TeamId, true); err != nil { - l4g.Error(c.T("api.command_echo.create.app_error"), err) - } - }() - - return &model.CommandResponse{} -} diff --git a/api/command_expand_collapse.go b/api/command_expand_collapse.go deleted file mode 100644 index 5adbf4bab..000000000 --- a/api/command_expand_collapse.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "strconv" - - "github.com/mattermost/platform/app" - "github.com/mattermost/platform/model" -) - -type ExpandProvider struct { -} - -type CollapseProvider struct { -} - -const ( - CMD_EXPAND = "expand" - CMD_COLLAPSE = "collapse" -) - -func init() { - RegisterCommandProvider(&ExpandProvider{}) - RegisterCommandProvider(&CollapseProvider{}) -} - -func (me *ExpandProvider) GetTrigger() string { - return CMD_EXPAND -} - -func (me *CollapseProvider) GetTrigger() string { - return CMD_COLLAPSE -} - -func (me *ExpandProvider) GetCommand(c *Context) *model.Command { - return &model.Command{ - Trigger: CMD_EXPAND, - AutoComplete: true, - AutoCompleteDesc: c.T("api.command_expand.desc"), - DisplayName: c.T("api.command_expand.name"), - } -} - -func (me *CollapseProvider) GetCommand(c *Context) *model.Command { - return &model.Command{ - Trigger: CMD_COLLAPSE, - AutoComplete: true, - AutoCompleteDesc: c.T("api.command_collapse.desc"), - DisplayName: c.T("api.command_collapse.name"), - } -} - -func (me *ExpandProvider) DoCommand(c *Context, args *model.CommandArgs, message string) *model.CommandResponse { - return setCollapsePreference(c, false) -} - -func (me *CollapseProvider) DoCommand(c *Context, args *model.CommandArgs, message string) *model.CommandResponse { - return setCollapsePreference(c, true) -} - -func setCollapsePreference(c *Context, isCollapse bool) *model.CommandResponse { - pref := model.Preference{ - UserId: c.Session.UserId, - Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, - Name: model.PREFERENCE_NAME_COLLAPSE_SETTING, - Value: strconv.FormatBool(isCollapse), - } - - if result := <-app.Srv.Store.Preference().Save(&model.Preferences{pref}); result.Err != nil { - return &model.CommandResponse{Text: c.T("api.command_expand_collapse.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } - - socketMessage := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PREFERENCE_CHANGED, "", "", c.Session.UserId, nil) - socketMessage.Add("preference", pref.ToJson()) - go app.Publish(socketMessage) - - var rmsg string - - if isCollapse { - rmsg = c.T("api.command_collapse.success") - } else { - rmsg = c.T("api.command_expand.success") - } - return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: rmsg} -} diff --git a/api/command_invite_people.go b/api/command_invite_people.go deleted file mode 100644 index b8f1827b0..000000000 --- a/api/command_invite_people.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "strings" - - l4g "github.com/alecthomas/log4go" - "github.com/mattermost/platform/app" - "github.com/mattermost/platform/model" - "github.com/mattermost/platform/utils" -) - -type InvitePeopleProvider struct { -} - -const ( - CMD_INVITE_PEOPLE = "invite_people" -) - -func init() { - RegisterCommandProvider(&InvitePeopleProvider{}) -} - -func (me *InvitePeopleProvider) GetTrigger() string { - return CMD_INVITE_PEOPLE -} - -func (me *InvitePeopleProvider) GetCommand(c *Context) *model.Command { - return &model.Command{ - Trigger: CMD_INVITE_PEOPLE, - AutoComplete: true, - AutoCompleteDesc: c.T("api.command.invite_people.desc"), - AutoCompleteHint: c.T("api.command.invite_people.hint"), - DisplayName: c.T("api.command.invite_people.name"), - } -} - -func (me *InvitePeopleProvider) DoCommand(c *Context, args *model.CommandArgs, message string) *model.CommandResponse { - if !utils.Cfg.EmailSettings.SendEmailNotifications { - return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: c.T("api.command.invite_people.email_off")} - } - - emailList := strings.Fields(message) - - for i := len(emailList) - 1; i >= 0; i-- { - emailList[i] = strings.Trim(emailList[i], ",") - if !strings.Contains(emailList[i], "@") { - emailList = append(emailList[:i], emailList[i+1:]...) - } - } - - if len(emailList) == 0 { - return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: c.T("api.command.invite_people.no_email")} - } - - if err := app.InviteNewUsersToTeam(emailList, c.TeamId, c.Session.UserId, c.GetSiteURL()); err != nil { - l4g.Error(err.Error()) - return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: c.T("api.command.invite_people.fail")} - } - - return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: c.T("api.command.invite_people.sent")} -} diff --git a/api/command_join.go b/api/command_join.go deleted file mode 100644 index 17deb02b7..000000000 --- a/api/command_join.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "github.com/mattermost/platform/app" - "github.com/mattermost/platform/model" -) - -type JoinProvider struct { -} - -const ( - CMD_JOIN = "join" -) - -func init() { - RegisterCommandProvider(&JoinProvider{}) -} - -func (me *JoinProvider) GetTrigger() string { - return CMD_JOIN -} - -func (me *JoinProvider) GetCommand(c *Context) *model.Command { - return &model.Command{ - Trigger: CMD_JOIN, - AutoComplete: true, - AutoCompleteDesc: c.T("api.command_join.desc"), - AutoCompleteHint: c.T("api.command_join.hint"), - DisplayName: c.T("api.command_join.name"), - } -} - -func (me *JoinProvider) DoCommand(c *Context, args *model.CommandArgs, message string) *model.CommandResponse { - if result := <-app.Srv.Store.Channel().GetByName(c.TeamId, message, true); result.Err != nil { - return &model.CommandResponse{Text: c.T("api.command_join.list.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } else { - channel := result.Data.(*model.Channel) - - if channel.Name == message { - - if channel.Type != model.CHANNEL_OPEN { - return &model.CommandResponse{Text: c.T("api.command_join.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } - - if err := app.JoinChannel(channel, c.Session.UserId); err != nil { - return &model.CommandResponse{Text: c.T("api.command_join.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } - - return &model.CommandResponse{GotoLocation: c.GetTeamURL() + "/channels/" + channel.Name, Text: c.T("api.command_join.success"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } - } - - return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: c.T("api.command_join.missing.app_error")} -} diff --git a/api/command_loadtest.go b/api/command_loadtest.go deleted file mode 100644 index dfbbadc3b..000000000 --- a/api/command_loadtest.go +++ /dev/null @@ -1,439 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "io" - "net/http" - "path" - "strconv" - "strings" - - l4g "github.com/alecthomas/log4go" - "github.com/mattermost/platform/app" - "github.com/mattermost/platform/model" - "github.com/mattermost/platform/utils" -) - -var usage = `Mattermost load testing commands to help configure the system - - COMMANDS: - - Setup - Creates a testing environment in current team. - /loadtest setup [teams] [fuzz] - - Example: - /loadtest setup teams fuzz 10 20 50 - - Users - Add a specified number of random users with fuzz text to current team. - /loadtest users [fuzz] - - Example: - /loadtest users fuzz 5 10 - - Channels - Add a specified number of random channels with fuzz text to current team. - /loadtest channels [fuzz] - - Example: - /loadtest channels fuzz 5 10 - - Posts - Add some random posts with fuzz text to current channel. - /loadtest posts [fuzz] - - Example: - /loadtest posts fuzz 5 10 3 - - Url - Add a post containing the text from a given url to current channel. - /loadtest url - - Example: - /loadtest http://www.example.com/sample_file.md - - Json - Add a post using the JSON file as payload to the current channel. - /loadtest json url - - Example - /loadtest json http://www.example.com/sample_body.json - -` - -const ( - CMD_LOADTEST = "loadtest" -) - -type LoadTestProvider struct { -} - -func init() { - if !utils.Cfg.ServiceSettings.EnableTesting { - RegisterCommandProvider(&LoadTestProvider{}) - } -} - -func (me *LoadTestProvider) GetTrigger() string { - return CMD_LOADTEST -} - -func (me *LoadTestProvider) GetCommand(c *Context) *model.Command { - return &model.Command{ - Trigger: CMD_LOADTEST, - AutoComplete: false, - AutoCompleteDesc: "Debug Load Testing", - AutoCompleteHint: "help", - DisplayName: "loadtest", - } -} - -func (me *LoadTestProvider) DoCommand(c *Context, args *model.CommandArgs, message string) *model.CommandResponse { - channelId := args.ChannelId - - //This command is only available when EnableTesting is true - if !utils.Cfg.ServiceSettings.EnableTesting { - return &model.CommandResponse{} - } - - if strings.HasPrefix(message, "setup") { - return me.SetupCommand(c, channelId, message) - } - - if strings.HasPrefix(message, "users") { - return me.UsersCommand(c, channelId, message) - } - - if strings.HasPrefix(message, "channels") { - return me.ChannelsCommand(c, channelId, message) - } - - if strings.HasPrefix(message, "posts") { - return me.PostsCommand(c, channelId, message) - } - - if strings.HasPrefix(message, "url") { - return me.UrlCommand(c, channelId, message) - } - if strings.HasPrefix(message, "json") { - return me.JsonCommand(c, channelId, message) - } - return me.HelpCommand(c, channelId, message) -} - -func (me *LoadTestProvider) HelpCommand(c *Context, channelId string, message string) *model.CommandResponse { - return &model.CommandResponse{Text: usage, ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} -} - -func (me *LoadTestProvider) SetupCommand(c *Context, channelId string, message string) *model.CommandResponse { - tokens := strings.Fields(strings.TrimPrefix(message, "setup")) - doTeams := contains(tokens, "teams") - doFuzz := contains(tokens, "fuzz") - - numArgs := 0 - if doTeams { - numArgs++ - } - if doFuzz { - numArgs++ - } - - var numTeams int - var numChannels int - var numUsers int - var numPosts int - - // Defaults - numTeams = 10 - numChannels = 10 - numUsers = 10 - numPosts = 10 - - if doTeams { - if (len(tokens) - numArgs) >= 4 { - numTeams, _ = strconv.Atoi(tokens[numArgs+0]) - numChannels, _ = strconv.Atoi(tokens[numArgs+1]) - numUsers, _ = strconv.Atoi(tokens[numArgs+2]) - numPosts, _ = strconv.Atoi(tokens[numArgs+3]) - } - } else { - if (len(tokens) - numArgs) >= 3 { - numChannels, _ = strconv.Atoi(tokens[numArgs+0]) - numUsers, _ = strconv.Atoi(tokens[numArgs+1]) - numPosts, _ = strconv.Atoi(tokens[numArgs+2]) - } - } - client := model.NewClient(c.GetSiteURL()) - - if doTeams { - if err := CreateBasicUser(client); err != nil { - return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } - client.Login(BTEST_USER_EMAIL, BTEST_USER_PASSWORD) - environment, err := CreateTestEnvironmentWithTeams( - client, - utils.Range{Begin: numTeams, End: numTeams}, - utils.Range{Begin: numChannels, End: numChannels}, - utils.Range{Begin: numUsers, End: numUsers}, - utils.Range{Begin: numPosts, End: numPosts}, - doFuzz) - if err != true { - return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } else { - l4g.Info("Testing environment created") - for i := 0; i < len(environment.Teams); i++ { - l4g.Info("Team Created: " + environment.Teams[i].Name) - l4g.Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + USER_PASSWORD) - } - } - } else { - - var team *model.Team - if tr := <-app.Srv.Store.Team().Get(c.TeamId); tr.Err != nil { - return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } else { - team = tr.Data.(*model.Team) - } - - client.MockSession(c.Session.Token) - client.SetTeamId(c.TeamId) - CreateTestEnvironmentInTeam( - client, - team, - utils.Range{Begin: numChannels, End: numChannels}, - utils.Range{Begin: numUsers, End: numUsers}, - utils.Range{Begin: numPosts, End: numPosts}, - doFuzz) - } - - return &model.CommandResponse{Text: "Created enviroment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} -} - -func (me *LoadTestProvider) UsersCommand(c *Context, channelId string, message string) *model.CommandResponse { - cmd := strings.TrimSpace(strings.TrimPrefix(message, "users")) - - doFuzz := false - if strings.Index(cmd, "fuzz") == 0 { - doFuzz = true - cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz")) - } - - usersr, err := parseRange(cmd, "") - if err == false { - usersr = utils.Range{Begin: 2, End: 5} - } - - var team *model.Team - if tr := <-app.Srv.Store.Team().Get(c.TeamId); tr.Err != nil { - return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } else { - team = tr.Data.(*model.Team) - } - - client := model.NewClient(c.GetSiteURL()) - client.SetTeamId(team.Id) - userCreator := NewAutoUserCreator(client, team) - userCreator.Fuzzy = doFuzz - userCreator.CreateTestUsers(usersr) - - return &model.CommandResponse{Text: "Added users", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} -} - -func (me *LoadTestProvider) ChannelsCommand(c *Context, channelId string, message string) *model.CommandResponse { - cmd := strings.TrimSpace(strings.TrimPrefix(message, "channels")) - - doFuzz := false - if strings.Index(cmd, "fuzz") == 0 { - doFuzz = true - cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz")) - } - - channelsr, err := parseRange(cmd, "") - if err == false { - channelsr = utils.Range{Begin: 2, End: 5} - } - - var team *model.Team - if tr := <-app.Srv.Store.Team().Get(c.TeamId); tr.Err != nil { - return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } else { - team = tr.Data.(*model.Team) - } - - client := model.NewClient(c.GetSiteURL()) - client.SetTeamId(team.Id) - client.MockSession(c.Session.Token) - channelCreator := NewAutoChannelCreator(client, team) - channelCreator.Fuzzy = doFuzz - channelCreator.CreateTestChannels(channelsr) - - return &model.CommandResponse{Text: "Added channels", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} -} - -func (me *LoadTestProvider) PostsCommand(c *Context, channelId string, message string) *model.CommandResponse { - cmd := strings.TrimSpace(strings.TrimPrefix(message, "posts")) - - doFuzz := false - if strings.Index(cmd, "fuzz") == 0 { - doFuzz = true - cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz")) - } - - postsr, err := parseRange(cmd, "") - if err == false { - postsr = utils.Range{Begin: 20, End: 30} - } - - tokens := strings.Fields(cmd) - rimages := utils.Range{Begin: 0, End: 0} - if len(tokens) >= 3 { - if numImages, err := strconv.Atoi(tokens[2]); err == nil { - rimages = utils.Range{Begin: numImages, End: numImages} - } - } - - var usernames []string - if result := <-app.Srv.Store.User().GetProfiles(c.TeamId, 0, 1000); result.Err == nil { - profileUsers := result.Data.([]*model.User) - usernames = make([]string, len(profileUsers)) - i := 0 - for _, userprof := range profileUsers { - usernames[i] = userprof.Username - i++ - } - } - - client := model.NewClient(c.GetSiteURL()) - client.SetTeamId(c.TeamId) - client.MockSession(c.Session.Token) - testPoster := NewAutoPostCreator(client, channelId) - testPoster.Fuzzy = doFuzz - testPoster.Users = usernames - - numImages := utils.RandIntFromRange(rimages) - numPosts := utils.RandIntFromRange(postsr) - for i := 0; i < numPosts; i++ { - testPoster.HasImage = (i < numImages) - testPoster.CreateRandomPost() - } - - return &model.CommandResponse{Text: "Added posts", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} -} - -func (me *LoadTestProvider) UrlCommand(c *Context, channelId string, message string) *model.CommandResponse { - url := strings.TrimSpace(strings.TrimPrefix(message, "url")) - if len(url) == 0 { - return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } - - // provide a shortcut to easily access tests stored in doc/developer/tests - if !strings.HasPrefix(url, "http") { - url = "https://raw.githubusercontent.com/mattermost/platform/master/tests/" + url - - if path.Ext(url) == "" { - url += ".md" - } - } - - var contents io.ReadCloser - if r, err := http.Get(url); err != nil { - return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } else if r.StatusCode > 400 { - return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } else { - contents = r.Body - } - - bytes := make([]byte, 4000) - - // break contents into 4000 byte posts - for { - length, err := contents.Read(bytes) - if err != nil && err != io.EOF { - return &model.CommandResponse{Text: "Encountered error reading file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } - - if length == 0 { - break - } - - post := &model.Post{} - post.Message = string(bytes[:length]) - post.ChannelId = channelId - post.UserId = c.Session.UserId - - if _, err := app.CreatePost(post, c.TeamId, false); err != nil { - return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } - } - - return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} -} - -func (me *LoadTestProvider) JsonCommand(c *Context, channelId string, message string) *model.CommandResponse { - url := strings.TrimSpace(strings.TrimPrefix(message, "json")) - if len(url) == 0 { - return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } - - // provide a shortcut to easily access tests stored in doc/developer/tests - if !strings.HasPrefix(url, "http") { - url = "https://raw.githubusercontent.com/mattermost/platform/master/tests/" + url - - if path.Ext(url) == "" { - url += ".json" - } - } - - var contents io.ReadCloser - if r, err := http.Get(url); err != nil { - return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } else if r.StatusCode > 400 { - return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } else { - contents = r.Body - } - - post := model.PostFromJson(contents) - post.ChannelId = channelId - post.UserId = c.Session.UserId - if post.Message == "" { - post.Message = message - } - - if _, err := app.CreatePost(post, c.TeamId, false); err != nil { - return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } - return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} -} - -func parseRange(command string, cmd string) (utils.Range, bool) { - tokens := strings.Fields(strings.TrimPrefix(command, cmd)) - var begin int - var end int - var err1 error - var err2 error - switch { - case len(tokens) == 1: - begin, err1 = strconv.Atoi(tokens[0]) - end = begin - if err1 != nil { - return utils.Range{Begin: 0, End: 0}, false - } - case len(tokens) >= 2: - begin, err1 = strconv.Atoi(tokens[0]) - end, err2 = strconv.Atoi(tokens[1]) - if err1 != nil || err2 != nil { - return utils.Range{Begin: 0, End: 0}, false - } - default: - return utils.Range{Begin: 0, End: 0}, false - } - return utils.Range{Begin: begin, End: end}, true -} - -func contains(items []string, token string) bool { - for _, elem := range items { - if elem == token { - return true - } - } - return false -} diff --git a/api/command_logout.go b/api/command_logout.go deleted file mode 100644 index 0eaa9a0ba..000000000 --- a/api/command_logout.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "github.com/mattermost/platform/app" - "github.com/mattermost/platform/model" -) - -type LogoutProvider struct { -} - -const ( - CMD_LOGOUT = "logout" -) - -func init() { - RegisterCommandProvider(&LogoutProvider{}) -} - -func (me *LogoutProvider) GetTrigger() string { - return CMD_LOGOUT -} - -func (me *LogoutProvider) GetCommand(c *Context) *model.Command { - return &model.Command{ - Trigger: CMD_LOGOUT, - AutoComplete: true, - AutoCompleteDesc: c.T("api.command_logout.desc"), - AutoCompleteHint: "", - DisplayName: c.T("api.command_logout.name"), - } -} - -func (me *LogoutProvider) DoCommand(c *Context, args *model.CommandArgs, message string) *model.CommandResponse { - FAIL := &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: c.T("api.command_logout.fail_message")} - SUCCESS := &model.CommandResponse{GotoLocation: "/login"} - - // We can't actually remove the user's cookie from here so we just dump their session and let the browser figure it out - if c.Session.Id != "" { - if err := app.RevokeSessionById(c.Session.Id); err != nil { - return FAIL - } - return SUCCESS - } - return FAIL -} diff --git a/api/command_me.go b/api/command_me.go deleted file mode 100644 index a3cda472a..000000000 --- a/api/command_me.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "github.com/mattermost/platform/model" -) - -type MeProvider struct { -} - -const ( - CMD_ME = "me" -) - -func init() { - RegisterCommandProvider(&MeProvider{}) -} - -func (me *MeProvider) GetTrigger() string { - return CMD_ME -} - -func (me *MeProvider) GetCommand(c *Context) *model.Command { - return &model.Command{ - Trigger: CMD_ME, - AutoComplete: true, - AutoCompleteDesc: c.T("api.command_me.desc"), - AutoCompleteHint: c.T("api.command_me.hint"), - DisplayName: c.T("api.command_me.name"), - } -} - -func (me *MeProvider) DoCommand(c *Context, args *model.CommandArgs, message string) *model.CommandResponse { - return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL, Text: "*" + message + "*"} -} diff --git a/api/command_msg.go b/api/command_msg.go deleted file mode 100644 index f8c8fae1c..000000000 --- a/api/command_msg.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "strings" - - l4g "github.com/alecthomas/log4go" - "github.com/mattermost/platform/app" - "github.com/mattermost/platform/model" -) - -type msgProvider struct { -} - -const ( - CMD_MSG = "msg" -) - -func init() { - RegisterCommandProvider(&msgProvider{}) -} - -func (me *msgProvider) GetTrigger() string { - return CMD_MSG -} - -func (me *msgProvider) GetCommand(c *Context) *model.Command { - return &model.Command{ - Trigger: CMD_MSG, - AutoComplete: true, - AutoCompleteDesc: c.T("api.command_msg.desc"), - AutoCompleteHint: c.T("api.command_msg.hint"), - DisplayName: c.T("api.command_msg.name"), - } -} - -func (me *msgProvider) DoCommand(c *Context, args *model.CommandArgs, message string) *model.CommandResponse { - - splitMessage := strings.SplitN(message, " ", 2) - - parsedMessage := "" - targetUsername := "" - - if len(splitMessage) > 1 { - parsedMessage = strings.SplitN(message, " ", 2)[1] - } - targetUsername = strings.SplitN(message, " ", 2)[0] - targetUsername = strings.TrimPrefix(targetUsername, "@") - - var userProfile *model.User - if result := <-app.Srv.Store.User().GetByUsername(targetUsername); result.Err != nil { - l4g.Error(result.Err.Error()) - return &model.CommandResponse{Text: c.T("api.command_msg.missing.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } else { - userProfile = result.Data.(*model.User) - } - - if userProfile.Id == c.Session.UserId { - return &model.CommandResponse{Text: c.T("api.command_msg.missing.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } - - // Find the channel based on this user - channelName := model.GetDMNameFromIds(c.Session.UserId, userProfile.Id) - - targetChannelId := "" - if channel := <-app.Srv.Store.Channel().GetByName(c.TeamId, channelName, true); channel.Err != nil { - if channel.Err.Id == "store.sql_channel.get_by_name.missing.app_error" { - if directChannel, err := app.CreateDirectChannel(c.Session.UserId, userProfile.Id); err != nil { - l4g.Error(err.Error()) - return &model.CommandResponse{Text: c.T("api.command_msg.dm_fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } else { - targetChannelId = directChannel.Id - } - } else { - l4g.Error(channel.Err.Error()) - return &model.CommandResponse{Text: c.T("api.command_msg.dm_fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } - } else { - targetChannelId = channel.Data.(*model.Channel).Id - } - - if len(parsedMessage) > 0 { - post := &model.Post{} - post.Message = parsedMessage - post.ChannelId = targetChannelId - post.UserId = c.Session.UserId - if _, err := app.CreatePost(post, c.TeamId, true); err != nil { - return &model.CommandResponse{Text: c.T("api.command_msg.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} - } - } - - return &model.CommandResponse{GotoLocation: c.GetTeamURL() + "/channels/" + channelName, Text: "", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} -} diff --git a/api/command_offline.go b/api/command_offline.go deleted file mode 100644 index a4bcdf8a5..000000000 --- a/api/command_offline.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "github.com/mattermost/platform/app" - "github.com/mattermost/platform/model" -) - -type OfflineProvider struct { -} - -const ( - CMD_OFFLINE = "offline" -) - -func init() { - RegisterCommandProvider(&OfflineProvider{}) -} - -func (me *OfflineProvider) GetTrigger() string { - return CMD_OFFLINE -} - -func (me *OfflineProvider) GetCommand(c *Context) *model.Command { - return &model.Command{ - Trigger: CMD_OFFLINE, - AutoComplete: true, - AutoCompleteDesc: c.T("api.command_offline.desc"), - DisplayName: c.T("api.command_offline.name"), - } -} - -func (me *OfflineProvider) DoCommand(c *Context, args *model.CommandArgs, message string) *model.CommandResponse { - rmsg := c.T("api.command_offline.success") - if len(message) > 0 { - rmsg = message + " " + rmsg - } - app.SetStatusOffline(c.Session.UserId, true) - - return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: rmsg} -} diff --git a/api/command_online.go b/api/command_online.go deleted file mode 100644 index 81d3e1fd6..000000000 --- a/api/command_online.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "github.com/mattermost/platform/app" - "github.com/mattermost/platform/model" -) - -type OnlineProvider struct { -} - -const ( - CMD_ONLINE = "online" -) - -func init() { - RegisterCommandProvider(&OnlineProvider{}) -} - -func (me *OnlineProvider) GetTrigger() string { - return CMD_ONLINE -} - -func (me *OnlineProvider) GetCommand(c *Context) *model.Command { - return &model.Command{ - Trigger: CMD_ONLINE, - AutoComplete: true, - AutoCompleteDesc: c.T("api.command_online.desc"), - DisplayName: c.T("api.command_online.name"), - } -} - -func (me *OnlineProvider) DoCommand(c *Context, args *model.CommandArgs, message string) *model.CommandResponse { - rmsg := c.T("api.command_online.success") - if len(message) > 0 { - rmsg = message + " " + rmsg - } - app.SetStatusOnline(c.Session.UserId, c.Session.Id, true) - - return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: rmsg} -} diff --git a/api/command_shortcuts.go b/api/command_shortcuts.go deleted file mode 100644 index 1664221c1..000000000 --- a/api/command_shortcuts.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "bytes" - "strings" - - "github.com/mattermost/platform/model" -) - -type ShortcutsProvider struct { -} - -const ( - CMD_SHORTCUTS = "shortcuts" -) - -func init() { - RegisterCommandProvider(&ShortcutsProvider{}) -} - -func (me *ShortcutsProvider) GetTrigger() string { - return CMD_SHORTCUTS -} - -func (me *ShortcutsProvider) GetCommand(c *Context) *model.Command { - return &model.Command{ - Trigger: CMD_SHORTCUTS, - AutoComplete: true, - AutoCompleteDesc: c.T("api.command_shortcuts.desc"), - AutoCompleteHint: "", - DisplayName: c.T("api.command_shortcuts.name"), - } -} - -func (me *ShortcutsProvider) DoCommand(c *Context, args *model.CommandArgs, message string) *model.CommandResponse { - shortcutIds := [28]string{ - "api.command_shortcuts.header", - // Nav shortcuts - "api.command_shortcuts.nav.header", - "api.command_shortcuts.nav.prev", - "api.command_shortcuts.nav.next", - "api.command_shortcuts.nav.unread_prev", - "api.command_shortcuts.nav.unread_next", - "api.command_shortcuts.nav.switcher", - "api.command_shortcuts.nav.settings", - "api.command_shortcuts.nav.recent_mentions", - // Files shortcuts - "api.command_shortcuts.files.header", - "api.command_shortcuts.files.upload", - // Msg shortcuts - "api.command_shortcuts.msgs.header", - "api.command_shortcuts.msgs.mark_as_read", - "api.command_shortcuts.msgs.reprint_prev", - "api.command_shortcuts.msgs.reprint_next", - "api.command_shortcuts.msgs.edit", - "api.command_shortcuts.msgs.comp_username", - "api.command_shortcuts.msgs.comp_channel", - "api.command_shortcuts.msgs.comp_emoji", - // Browser shortcuts - "api.command_shortcuts.browser.header", - "api.command_shortcuts.browser.channel_prev", - "api.command_shortcuts.browser.channel_next", - "api.command_shortcuts.browser.font_increase", - "api.command_shortcuts.browser.font_decrease", - "api.command_shortcuts.browser.highlight_prev", - "api.command_shortcuts.browser.highlight_next", - "api.command_shortcuts.browser.newline", - } - - var osDependentWords map[string]interface{} - if strings.Contains(message, "mac") { - osDependentWords = map[string]interface{}{ - "CmdOrCtrl": c.T("api.command_shortcuts.cmd"), - "ChannelPrevCmd": c.T("api.command_shortcuts.browser.channel_prev.cmd_mac"), - "ChannelNextCmd": c.T("api.command_shortcuts.browser.channel_next.cmd_mac"), - } - } else { - osDependentWords = map[string]interface{}{ - "CmdOrCtrl": c.T("api.command_shortcuts.ctrl"), - "ChannelPrevCmd": c.T("api.command_shortcuts.browser.channel_prev.cmd"), - "ChannelNextCmd": c.T("api.command_shortcuts.browser.channel_next.cmd"), - } - } - - var buffer bytes.Buffer - for _, element := range shortcutIds { - buffer.WriteString(c.T(element, osDependentWords)) - } - - return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: buffer.String()} -} diff --git a/api/command_shrug.go b/api/command_shrug.go deleted file mode 100644 index 899fcab33..000000000 --- a/api/command_shrug.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api - -import ( - "github.com/mattermost/platform/model" -) - -type ShrugProvider struct { -} - -const ( - CMD_SHRUG = "shrug" -) - -func init() { - RegisterCommandProvider(&ShrugProvider{}) -} - -func (me *ShrugProvider) GetTrigger() string { - return CMD_SHRUG -} - -func (me *ShrugProvider) GetCommand(c *Context) *model.Command { - return &model.Command{ - Trigger: CMD_SHRUG, - AutoComplete: true, - AutoCompleteDesc: c.T("api.command_shrug.desc"), - AutoCompleteHint: c.T("api.command_shrug.hint"), - DisplayName: c.T("api.command_shrug.name"), - } -} - -func (me *ShrugProvider) DoCommand(c *Context, args *model.CommandArgs, message string) *model.CommandResponse { - rmsg := `¯\\\_(ツ)\_/¯` - if len(message) > 0 { - rmsg = message + " " + rmsg - } - - return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL, Text: rmsg} -} diff --git a/app/auto_channels.go b/app/auto_channels.go new file mode 100644 index 000000000..3945a5a4f --- /dev/null +++ b/app/auto_channels.go @@ -0,0 +1,74 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" +) + +type AutoChannelCreator struct { + client *model.Client + team *model.Team + Fuzzy bool + DisplayNameLen utils.Range + DisplayNameCharset string + NameLen utils.Range + NameCharset string + ChannelType string +} + +func NewAutoChannelCreator(client *model.Client, team *model.Team) *AutoChannelCreator { + return &AutoChannelCreator{ + client: client, + team: team, + Fuzzy: false, + DisplayNameLen: CHANNEL_DISPLAY_NAME_LEN, + DisplayNameCharset: utils.ALPHANUMERIC, + NameLen: CHANNEL_NAME_LEN, + NameCharset: utils.LOWERCASE, + ChannelType: CHANNEL_TYPE, + } +} + +func (cfg *AutoChannelCreator) createRandomChannel() (*model.Channel, bool) { + var displayName string + if cfg.Fuzzy { + displayName = utils.FuzzName() + } else { + displayName = utils.RandomName(cfg.NameLen, cfg.NameCharset) + } + name := utils.RandomName(cfg.NameLen, cfg.NameCharset) + + channel := &model.Channel{ + TeamId: cfg.team.Id, + DisplayName: displayName, + Name: name, + Type: cfg.ChannelType} + + println(cfg.client.GetTeamRoute()) + result, err := cfg.client.CreateChannel(channel) + if err != nil { + err.Translate(utils.T) + println(err.Error()) + println(err.DetailedError) + return nil, false + } + return result.Data.(*model.Channel), true +} + +func (cfg *AutoChannelCreator) CreateTestChannels(num utils.Range) ([]*model.Channel, bool) { + numChannels := utils.RandIntFromRange(num) + channels := make([]*model.Channel, numChannels) + + for i := 0; i < numChannels; i++ { + var err bool + channels[i], err = cfg.createRandomChannel() + if err != true { + return channels, false + } + } + + return channels, true +} diff --git a/app/auto_constants.go b/app/auto_constants.go new file mode 100644 index 000000000..c8c903e32 --- /dev/null +++ b/app/auto_constants.go @@ -0,0 +1,36 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" +) + +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" +) + +var ( + TEAM_NAME_LEN = utils.Range{Begin: 10, End: 20} + TEAM_DOMAIN_NAME_LEN = utils.Range{Begin: 10, End: 20} + TEAM_EMAIL_LEN = utils.Range{Begin: 15, End: 30} + USER_NAME_LEN = utils.Range{Begin: 5, End: 20} + 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"} +) diff --git a/app/auto_environment.go b/app/auto_environment.go new file mode 100644 index 000000000..b0a4f54b8 --- /dev/null +++ b/app/auto_environment.go @@ -0,0 +1,99 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" + "math/rand" + "time" +) + +type TestEnvironment struct { + Teams []*model.Team + Environments []TeamEnvironment +} + +func CreateTestEnvironmentWithTeams(client *model.Client, rangeTeams utils.Range, rangeChannels utils.Range, rangeUsers utils.Range, rangePosts utils.Range, fuzzy bool) (TestEnvironment, bool) { + rand.Seed(time.Now().UTC().UnixNano()) + + teamCreator := NewAutoTeamCreator(client) + teamCreator.Fuzzy = fuzzy + teams, err := teamCreator.CreateTestTeams(rangeTeams) + if err != true { + return TestEnvironment{}, false + } + + environment := TestEnvironment{teams, make([]TeamEnvironment, len(teams))} + + for i, team := range teams { + userCreator := NewAutoUserCreator(client, team) + userCreator.Fuzzy = fuzzy + randomUser, err := userCreator.createRandomUser() + if err != true { + return TestEnvironment{}, false + } + client.LoginById(randomUser.Id, USER_PASSWORD) + client.SetTeamId(team.Id) + teamEnvironment, err := CreateTestEnvironmentInTeam(client, team, rangeChannels, rangeUsers, rangePosts, fuzzy) + if err != true { + return TestEnvironment{}, false + } + environment.Environments[i] = teamEnvironment + } + + return environment, true +} + +func CreateTestEnvironmentInTeam(client *model.Client, team *model.Team, rangeChannels utils.Range, rangeUsers utils.Range, rangePosts utils.Range, fuzzy bool) (TeamEnvironment, bool) { + rand.Seed(time.Now().UTC().UnixNano()) + + // We need to create at least one user + if rangeUsers.Begin <= 0 { + rangeUsers.Begin = 1 + } + + userCreator := NewAutoUserCreator(client, team) + userCreator.Fuzzy = fuzzy + users, err := userCreator.CreateTestUsers(rangeUsers) + if err != true { + return TeamEnvironment{}, false + } + usernames := make([]string, len(users)) + for i, user := range users { + usernames[i] = user.Username + } + + channelCreator := NewAutoChannelCreator(client, team) + channelCreator.Fuzzy = fuzzy + channels, err := channelCreator.CreateTestChannels(rangeChannels) + + // Have every user join every channel + for _, user := range users { + for _, channel := range channels { + client.LoginById(user.Id, USER_PASSWORD) + client.JoinChannel(channel.Id) + } + } + + if err != true { + return TeamEnvironment{}, false + } + + numPosts := utils.RandIntFromRange(rangePosts) + numImages := utils.RandIntFromRange(rangePosts) / 4 + for j := 0; j < numPosts; j++ { + user := users[utils.RandIntFromRange(utils.Range{Begin: 0, End: len(users) - 1})] + client.LoginById(user.Id, USER_PASSWORD) + for i, channel := range channels { + postCreator := NewAutoPostCreator(client, channel.Id) + postCreator.HasImage = i < numImages + postCreator.Users = usernames + postCreator.Fuzzy = fuzzy + postCreator.CreateRandomPost() + } + } + + return TeamEnvironment{users, channels}, true +} diff --git a/app/auto_posts.go b/app/auto_posts.go new file mode 100644 index 000000000..b32407539 --- /dev/null +++ b/app/auto_posts.go @@ -0,0 +1,103 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "bytes" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" + "io" + "os" +) + +type AutoPostCreator struct { + client *model.Client + channelid string + Fuzzy bool + TextLength utils.Range + HasImage bool + ImageFilenames []string + Users []string + Mentions utils.Range + Tags utils.Range +} + +// Automatic poster used for testing +func NewAutoPostCreator(client *model.Client, channelid string) *AutoPostCreator { + return &AutoPostCreator{ + client: client, + channelid: channelid, + Fuzzy: false, + TextLength: utils.Range{Begin: 100, End: 200}, + HasImage: false, + ImageFilenames: TEST_IMAGE_FILENAMES, + Users: []string{}, + Mentions: utils.Range{Begin: 0, End: 5}, + Tags: utils.Range{Begin: 0, End: 7}, + } +} + +func (cfg *AutoPostCreator) UploadTestFile() ([]string, bool) { + filename := cfg.ImageFilenames[utils.RandIntFromRange(utils.Range{Begin: 0, End: len(cfg.ImageFilenames) - 1})] + + path := utils.FindDir("web/static/images") + file, err := os.Open(path + "/" + filename) + defer file.Close() + + data := &bytes.Buffer{} + _, err = io.Copy(data, file) + if err != nil { + return nil, false + } + + resp, appErr := cfg.client.UploadPostAttachment(data.Bytes(), cfg.channelid, filename) + if appErr != nil { + return nil, false + } + + return []string{resp.FileInfos[0].Id}, true +} + +func (cfg *AutoPostCreator) CreateRandomPost() (*model.Post, bool) { + var fileIds []string + if cfg.HasImage { + var err1 bool + fileIds, err1 = cfg.UploadTestFile() + if err1 == false { + return nil, false + } + } + + var postText string + if cfg.Fuzzy { + postText = utils.FuzzPost() + } else { + postText = utils.RandomText(cfg.TextLength, cfg.Tags, cfg.Mentions, cfg.Users) + } + + post := &model.Post{ + ChannelId: cfg.channelid, + Message: postText, + FileIds: fileIds} + result, err2 := cfg.client.CreatePost(post) + if err2 != nil { + return nil, false + } + 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 != true { + return posts, false + } + } + + return posts, true +} diff --git a/app/auto_teams.go b/app/auto_teams.go new file mode 100644 index 000000000..6e66f4446 --- /dev/null +++ b/app/auto_teams.go @@ -0,0 +1,81 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" +) + +type TeamEnvironment struct { + Users []*model.User + Channels []*model.Channel +} + +type AutoTeamCreator struct { + client *model.Client + Fuzzy bool + NameLength utils.Range + NameCharset string + DomainLength utils.Range + DomainCharset string + EmailLength utils.Range + EmailCharset string +} + +func NewAutoTeamCreator(client *model.Client) *AutoTeamCreator { + return &AutoTeamCreator{ + client: client, + Fuzzy: false, + NameLength: TEAM_NAME_LEN, + NameCharset: utils.LOWERCASE, + DomainLength: TEAM_DOMAIN_NAME_LEN, + DomainCharset: utils.LOWERCASE, + EmailLength: TEAM_EMAIL_LEN, + EmailCharset: utils.LOWERCASE, + } +} + +func (cfg *AutoTeamCreator) createRandomTeam() (*model.Team, bool) { + var teamEmail string + var teamDisplayName string + var teamName string + if cfg.Fuzzy { + teamEmail = "success+" + model.NewId() + "simulator.amazonses.com" + teamDisplayName = utils.FuzzName() + teamName = utils.FuzzName() + } else { + teamEmail = "success+" + model.NewId() + "simulator.amazonses.com" + teamDisplayName = utils.RandomName(cfg.NameLength, cfg.NameCharset) + teamName = utils.RandomName(cfg.NameLength, cfg.NameCharset) + model.NewId() + } + team := &model.Team{ + DisplayName: teamDisplayName, + Name: teamName, + Email: teamEmail, + Type: model.TEAM_OPEN, + } + + result, err := cfg.client.CreateTeam(team) + if err != nil { + return nil, false + } + createdTeam := result.Data.(*model.Team) + return createdTeam, true +} + +func (cfg *AutoTeamCreator) CreateTestTeams(num utils.Range) ([]*model.Team, bool) { + numTeams := utils.RandIntFromRange(num) + teams := make([]*model.Team, numTeams) + + for i := 0; i < numTeams; i++ { + var err bool + teams[i], err = cfg.createRandomTeam() + if err != true { + return teams, false + } + } + + return teams, true +} diff --git a/app/auto_users.go b/app/auto_users.go new file mode 100644 index 000000000..7a99cc90b --- /dev/null +++ b/app/auto_users.go @@ -0,0 +1,109 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" + "github.com/mattermost/platform/utils" + + l4g "github.com/alecthomas/log4go" +) + +type AutoUserCreator struct { + client *model.Client + team *model.Team + EmailLength utils.Range + EmailCharset string + NameLength utils.Range + NameCharset string + Fuzzy bool +} + +func NewAutoUserCreator(client *model.Client, team *model.Team) *AutoUserCreator { + return &AutoUserCreator{ + client: client, + team: team, + EmailLength: USER_EMAIL_LEN, + EmailCharset: utils.LOWERCASE, + NameLength: USER_NAME_LEN, + NameCharset: utils.LOWERCASE, + Fuzzy: false, + } +} + +// Basic test team and user so you always know one +func CreateBasicUser(client *model.Client) *model.AppError { + result, _ := client.FindTeamByName(BTEST_TEAM_NAME) + if result.Data.(bool) == false { + newteam := &model.Team{DisplayName: BTEST_TEAM_DISPLAY_NAME, Name: BTEST_TEAM_NAME, Email: BTEST_TEAM_EMAIL, Type: BTEST_TEAM_TYPE} + result, err := client.CreateTeam(newteam) + if err != nil { + return err + } + basicteam := result.Data.(*model.Team) + newuser := &model.User{Email: BTEST_USER_EMAIL, Nickname: BTEST_USER_NAME, Password: BTEST_USER_PASSWORD} + result, err = client.CreateUser(newuser, "") + if err != nil { + return err + } + ruser := result.Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(ruser.Id)) + store.Must(Srv.Store.Team().SaveMember(&model.TeamMember{TeamId: basicteam.Id, UserId: ruser.Id})) + } + return nil +} + +func (cfg *AutoUserCreator) createRandomUser() (*model.User, bool) { + var userEmail string + var userName string + if cfg.Fuzzy { + userEmail = "success+" + model.NewId() + "simulator.amazonses.com" + userName = utils.FuzzName() + } else { + userEmail = "success+" + model.NewId() + "simulator.amazonses.com" + userName = utils.RandomName(cfg.NameLength, cfg.NameCharset) + } + + user := &model.User{ + Email: userEmail, + Nickname: userName, + Password: USER_PASSWORD} + + result, err := cfg.client.CreateUserWithInvite(user, "", "", cfg.team.InviteId) + if err != nil { + err.Translate(utils.T) + l4g.Error(err.Error()) + return nil, false + } + + ruser := result.Data.(*model.User) + + status := &model.Status{UserId: ruser.Id, Status: model.STATUS_ONLINE, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: ""} + if result := <-Srv.Store.Status().SaveOrUpdate(status); result.Err != nil { + result.Err.Translate(utils.T) + l4g.Error(result.Err.Error()) + return nil, false + } + + // We need to cheat to verify the user's email + store.Must(Srv.Store.User().VerifyEmail(ruser.Id)) + + return result.Data.(*model.User), true +} + +func (cfg *AutoUserCreator) CreateTestUsers(num utils.Range) ([]*model.User, bool) { + numUsers := utils.RandIntFromRange(num) + users := make([]*model.User, numUsers) + + for i := 0; i < numUsers; i++ { + var err bool + users[i], err = cfg.createRandomUser() + if err != true { + return users, false + } + } + + return users, true +} diff --git a/app/command.go b/app/command.go index 2d5861206..491813efe 100644 --- a/app/command.go +++ b/app/command.go @@ -4,9 +4,40 @@ package app import ( + "crypto/tls" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + + l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" + goi18n "github.com/nicksnyder/go-i18n/i18n" ) +type CommandProvider interface { + GetTrigger() string + GetCommand(T goi18n.TranslateFunc) *model.Command + DoCommand(args *model.CommandArgs, message string) *model.CommandResponse +} + +var commandProviders = make(map[string]CommandProvider) + +func RegisterCommandProvider(newProvider CommandProvider) { + commandProviders[newProvider.GetTrigger()] = newProvider +} + +func GetCommandProvider(name string) CommandProvider { + provider, ok := commandProviders[name] + if ok { + return provider + } + + return nil +} + func CreateCommandPost(post *model.Post, teamId string, response *model.CommandResponse) (*model.Post, *model.AppError) { post.Message = parseSlackLinksToMarkdown(response.Text) post.CreateAt = model.GetMillis() @@ -29,3 +60,276 @@ func CreateCommandPost(post *model.Post, teamId string, response *model.CommandR return post, nil } + +func ListCommands(teamId string, T goi18n.TranslateFunc) ([]*model.Command, *model.AppError) { + commands := make([]*model.Command, 0, 32) + seen := make(map[string]bool) + for _, value := range commandProviders { + cpy := *value.GetCommand(T) + if cpy.AutoComplete && !seen[cpy.Id] { + cpy.Sanitize() + seen[cpy.Trigger] = true + commands = append(commands, &cpy) + } + } + + if *utils.Cfg.ServiceSettings.EnableCommands { + if result := <-Srv.Store.Command().GetByTeam(teamId); result.Err != nil { + return nil, result.Err + } else { + teamCmds := result.Data.([]*model.Command) + for _, cmd := range teamCmds { + if cmd.AutoComplete && !seen[cmd.Id] { + cmd.Sanitize() + seen[cmd.Trigger] = true + commands = append(commands, cmd) + } + } + } + } + + return commands, nil +} + +func ListTeamCommands(teamId string) ([]*model.Command, *model.AppError) { + if !*utils.Cfg.ServiceSettings.EnableCommands { + return nil, model.NewAppError("ListTeamCommands", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented) + } + + if result := <-Srv.Store.Command().GetByTeam(teamId); result.Err != nil { + return nil, result.Err + } else { + return result.Data.([]*model.Command), nil + } +} + +func ExecuteCommand(args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { + parts := strings.Split(args.Command, " ") + trigger := parts[0][1:] + trigger = strings.ToLower(trigger) + message := strings.Join(parts[1:], " ") + provider := GetCommandProvider(trigger) + + if provider != nil { + response := provider.DoCommand(args, message) + return HandleCommandResponse(provider.GetCommand(args.T), args, response, true) + } else { + if !*utils.Cfg.ServiceSettings.EnableCommands { + return nil, model.NewAppError("ExecuteCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented) + } + + chanChan := Srv.Store.Channel().Get(args.ChannelId, true) + teamChan := Srv.Store.Team().Get(args.TeamId) + userChan := Srv.Store.User().Get(args.UserId) + + if result := <-Srv.Store.Command().GetByTeam(args.TeamId); result.Err != nil { + return nil, result.Err + } else { + + var team *model.Team + if tr := <-teamChan; tr.Err != nil { + return nil, tr.Err + } else { + team = tr.Data.(*model.Team) + } + + var user *model.User + if ur := <-userChan; ur.Err != nil { + return nil, ur.Err + } else { + user = ur.Data.(*model.User) + } + + var channel *model.Channel + if cr := <-chanChan; cr.Err != nil { + return nil, cr.Err + } else { + channel = cr.Data.(*model.Channel) + } + + teamCmds := result.Data.([]*model.Command) + for _, cmd := range teamCmds { + if trigger == cmd.Trigger { + l4g.Debug(fmt.Sprintf(utils.T("api.command.execute_command.debug"), trigger, args.UserId)) + + p := url.Values{} + p.Set("token", cmd.Token) + + p.Set("team_id", cmd.TeamId) + p.Set("team_domain", team.Name) + + p.Set("channel_id", args.ChannelId) + p.Set("channel_name", channel.Name) + + p.Set("user_id", args.UserId) + p.Set("user_name", user.Username) + + p.Set("command", "/"+trigger) + p.Set("text", message) + p.Set("response_url", "not supported yet") + + method := "POST" + if cmd.Method == model.COMMAND_METHOD_GET { + method = "GET" + } + + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections}, + } + client := &http.Client{Transport: tr} + + req, _ := http.NewRequest(method, cmd.URL, strings.NewReader(p.Encode())) + req.Header.Set("Accept", "application/json") + if cmd.Method == model.COMMAND_METHOD_POST { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + } + + if resp, err := client.Do(req); err != nil { + return nil, model.NewAppError("command", "api.command.execute_command.failed.app_error", map[string]interface{}{"Trigger": trigger}, err.Error(), http.StatusInternalServerError) + } else { + if resp.StatusCode == http.StatusOK { + response := model.CommandResponseFromJson(resp.Body) + if response == nil { + return nil, model.NewAppError("command", "api.command.execute_command.failed_empty.app_error", map[string]interface{}{"Trigger": trigger}, "", http.StatusInternalServerError) + } else { + return HandleCommandResponse(cmd, args, response, false) + } + } else { + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + return nil, model.NewAppError("command", "api.command.execute_command.failed_resp.app_error", map[string]interface{}{"Trigger": trigger, "Status": resp.Status}, string(body), http.StatusInternalServerError) + } + } + } + } + } + } + + return nil, model.NewAppError("command", "api.command.execute_command.not_found.app_error", map[string]interface{}{"Trigger": trigger}, "", http.StatusNotFound) +} + +func HandleCommandResponse(command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.CommandResponse, *model.AppError) { + post := &model.Post{} + post.ChannelId = args.ChannelId + post.RootId = args.RootId + post.ParentId = args.ParentId + post.UserId = args.UserId + + if !builtIn { + post.AddProp("from_webhook", "true") + } + + if utils.Cfg.ServiceSettings.EnablePostUsernameOverride { + if len(command.Username) != 0 { + post.AddProp("override_username", command.Username) + } else if len(response.Username) != 0 { + post.AddProp("override_username", response.Username) + } + } + + if utils.Cfg.ServiceSettings.EnablePostIconOverride { + if len(command.IconURL) != 0 { + post.AddProp("override_icon_url", command.IconURL) + } else if len(response.IconURL) != 0 { + post.AddProp("override_icon_url", response.IconURL) + } else { + post.AddProp("override_icon_url", "") + } + } + + if _, err := CreateCommandPost(post, args.TeamId, response); err != nil { + l4g.Error(err.Error()) + } + + return response, nil +} + +func CreateCommand(cmd *model.Command) (*model.Command, *model.AppError) { + if !*utils.Cfg.ServiceSettings.EnableCommands { + return nil, model.NewAppError("CreateCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented) + } + + cmd.Trigger = strings.ToLower(cmd.Trigger) + + if result := <-Srv.Store.Command().GetByTeam(cmd.TeamId); result.Err != nil { + return nil, result.Err + } else { + teamCmds := result.Data.([]*model.Command) + for _, existingCommand := range teamCmds { + if cmd.Trigger == existingCommand.Trigger { + return nil, model.NewAppError("CreateCommand", "api.command.duplicate_trigger.app_error", nil, "", http.StatusBadRequest) + } + } + for _, builtInProvider := range commandProviders { + builtInCommand := *builtInProvider.GetCommand(utils.T) + if cmd.Trigger == builtInCommand.Trigger { + return nil, model.NewAppError("CreateCommand", "api.command.duplicate_trigger.app_error", nil, "", http.StatusBadRequest) + } + } + } + + if result := <-Srv.Store.Command().Save(cmd); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.Command), nil + } +} + +func GetCommand(commandId string) (*model.Command, *model.AppError) { + if !*utils.Cfg.ServiceSettings.EnableCommands { + return nil, model.NewAppError("GetCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented) + } + + if result := <-Srv.Store.Command().Get(commandId); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.Command), nil + } +} + +func UpdateCommand(oldCmd, updatedCmd *model.Command) (*model.Command, *model.AppError) { + if !*utils.Cfg.ServiceSettings.EnableCommands { + return nil, model.NewAppError("UpdateCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented) + } + + updatedCmd.Trigger = strings.ToLower(updatedCmd.Trigger) + updatedCmd.Id = oldCmd.Id + updatedCmd.Token = oldCmd.Token + updatedCmd.CreateAt = oldCmd.CreateAt + updatedCmd.UpdateAt = model.GetMillis() + updatedCmd.DeleteAt = oldCmd.DeleteAt + updatedCmd.CreatorId = oldCmd.CreatorId + updatedCmd.TeamId = oldCmd.TeamId + + if result := <-Srv.Store.Command().Update(updatedCmd); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.Command), nil + } +} + +func RegenCommandToken(cmd *model.Command) (*model.Command, *model.AppError) { + if !*utils.Cfg.ServiceSettings.EnableCommands { + return nil, model.NewAppError("RegenCommandToken", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented) + } + + cmd.Token = model.NewId() + + if result := <-Srv.Store.Command().Update(cmd); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.Command), nil + } +} + +func DeleteCommand(commandId string) *model.AppError { + if !*utils.Cfg.ServiceSettings.EnableCommands { + return model.NewAppError("DeleteCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented) + } + + if err := (<-Srv.Store.Command().Delete(commandId, model.GetMillis())).Err; err != nil { + return err + } + + return nil +} diff --git a/app/command_away.go b/app/command_away.go new file mode 100644 index 000000000..55553fa3f --- /dev/null +++ b/app/command_away.go @@ -0,0 +1,43 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "github.com/mattermost/platform/model" + goi18n "github.com/nicksnyder/go-i18n/i18n" +) + +type AwayProvider struct { +} + +const ( + CMD_AWAY = "away" +) + +func init() { + RegisterCommandProvider(&AwayProvider{}) +} + +func (me *AwayProvider) GetTrigger() string { + return CMD_AWAY +} + +func (me *AwayProvider) GetCommand(T goi18n.TranslateFunc) *model.Command { + return &model.Command{ + Trigger: CMD_AWAY, + AutoComplete: true, + AutoCompleteDesc: T("api.command_away.desc"), + DisplayName: T("api.command_away.name"), + } +} + +func (me *AwayProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse { + rmsg := args.T("api.command_away.success") + if len(message) > 0 { + rmsg = message + " " + rmsg + } + SetStatusAwayIfNeeded(args.UserId, true) + + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: rmsg} +} diff --git a/app/command_echo.go b/app/command_echo.go new file mode 100644 index 000000000..40d70e54a --- /dev/null +++ b/app/command_echo.go @@ -0,0 +1,97 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "strconv" + "strings" + "time" + + l4g "github.com/alecthomas/log4go" + "github.com/mattermost/platform/model" + goi18n "github.com/nicksnyder/go-i18n/i18n" +) + +var echoSem chan bool + +type EchoProvider struct { +} + +const ( + CMD_ECHO = "echo" +) + +func init() { + RegisterCommandProvider(&EchoProvider{}) +} + +func (me *EchoProvider) GetTrigger() string { + return CMD_ECHO +} + +func (me *EchoProvider) GetCommand(T goi18n.TranslateFunc) *model.Command { + return &model.Command{ + Trigger: CMD_ECHO, + AutoComplete: true, + AutoCompleteDesc: T("api.command_echo.desc"), + AutoCompleteHint: T("api.command_echo.hint"), + DisplayName: T("api.command_echo.name"), + } +} + +func (me *EchoProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse { + if len(message) == 0 { + return &model.CommandResponse{Text: args.T("api.command_echo.message.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + maxThreads := 100 + + delay := 0 + if endMsg := strings.LastIndex(message, "\""); string(message[0]) == "\"" && endMsg > 1 { + if checkDelay, err := strconv.Atoi(strings.Trim(message[endMsg:], " \"")); err == nil { + delay = checkDelay + } + message = message[1:endMsg] + } else if strings.Index(message, " ") > -1 { + delayIdx := strings.LastIndex(message, " ") + delayStr := strings.Trim(message[delayIdx:], " ") + + if checkDelay, err := strconv.Atoi(delayStr); err == nil { + delay = checkDelay + message = message[:delayIdx] + } + } + + if delay > 10000 { + return &model.CommandResponse{Text: args.T("api.command_echo.delay.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + if echoSem == nil { + // We want one additional thread allowed so we never reach channel lockup + echoSem = make(chan bool, maxThreads+1) + } + + if len(echoSem) >= maxThreads { + return &model.CommandResponse{Text: args.T("api.command_echo.high_volume.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + echoSem <- true + go func() { + defer func() { <-echoSem }() + post := &model.Post{} + post.ChannelId = args.ChannelId + post.RootId = args.RootId + post.ParentId = args.ParentId + post.Message = message + post.UserId = args.UserId + + time.Sleep(time.Duration(delay) * time.Second) + + if _, err := CreatePost(post, args.TeamId, true); err != nil { + l4g.Error(args.T("api.command_echo.create.app_error"), err) + } + }() + + return &model.CommandResponse{} +} diff --git a/app/command_expand_collapse.go b/app/command_expand_collapse.go new file mode 100644 index 000000000..a4a152c60 --- /dev/null +++ b/app/command_expand_collapse.go @@ -0,0 +1,87 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "strconv" + + "github.com/mattermost/platform/model" + goi18n "github.com/nicksnyder/go-i18n/i18n" +) + +type ExpandProvider struct { +} + +type CollapseProvider struct { +} + +const ( + CMD_EXPAND = "expand" + CMD_COLLAPSE = "collapse" +) + +func init() { + RegisterCommandProvider(&ExpandProvider{}) + RegisterCommandProvider(&CollapseProvider{}) +} + +func (me *ExpandProvider) GetTrigger() string { + return CMD_EXPAND +} + +func (me *CollapseProvider) GetTrigger() string { + return CMD_COLLAPSE +} + +func (me *ExpandProvider) GetCommand(T goi18n.TranslateFunc) *model.Command { + return &model.Command{ + Trigger: CMD_EXPAND, + AutoComplete: true, + AutoCompleteDesc: T("api.command_expand.desc"), + DisplayName: T("api.command_expand.name"), + } +} + +func (me *CollapseProvider) GetCommand(T goi18n.TranslateFunc) *model.Command { + return &model.Command{ + Trigger: CMD_COLLAPSE, + AutoComplete: true, + AutoCompleteDesc: T("api.command_collapse.desc"), + DisplayName: T("api.command_collapse.name"), + } +} + +func (me *ExpandProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse { + return setCollapsePreference(args, false) +} + +func (me *CollapseProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse { + return setCollapsePreference(args, true) +} + +func setCollapsePreference(args *model.CommandArgs, isCollapse bool) *model.CommandResponse { + pref := model.Preference{ + UserId: args.UserId, + Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, + Name: model.PREFERENCE_NAME_COLLAPSE_SETTING, + Value: strconv.FormatBool(isCollapse), + } + + if result := <-Srv.Store.Preference().Save(&model.Preferences{pref}); result.Err != nil { + return &model.CommandResponse{Text: args.T("api.command_expand_collapse.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + socketMessage := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PREFERENCE_CHANGED, "", "", args.UserId, nil) + socketMessage.Add("preference", pref.ToJson()) + go Publish(socketMessage) + + var rmsg string + + if isCollapse { + rmsg = args.T("api.command_collapse.success") + } else { + rmsg = args.T("api.command_expand.success") + } + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: rmsg} +} diff --git a/app/command_invite_people.go b/app/command_invite_people.go new file mode 100644 index 000000000..12ef03f45 --- /dev/null +++ b/app/command_invite_people.go @@ -0,0 +1,64 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "strings" + + l4g "github.com/alecthomas/log4go" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" + goi18n "github.com/nicksnyder/go-i18n/i18n" +) + +type InvitePeopleProvider struct { +} + +const ( + CMD_INVITE_PEOPLE = "invite_people" +) + +func init() { + RegisterCommandProvider(&InvitePeopleProvider{}) +} + +func (me *InvitePeopleProvider) GetTrigger() string { + return CMD_INVITE_PEOPLE +} + +func (me *InvitePeopleProvider) GetCommand(T goi18n.TranslateFunc) *model.Command { + return &model.Command{ + Trigger: CMD_INVITE_PEOPLE, + AutoComplete: true, + AutoCompleteDesc: T("api.command.invite_people.desc"), + AutoCompleteHint: T("api.command.invite_people.hint"), + DisplayName: T("api.command.invite_people.name"), + } +} + +func (me *InvitePeopleProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse { + if !utils.Cfg.EmailSettings.SendEmailNotifications { + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: args.T("api.command.invite_people.email_off")} + } + + emailList := strings.Fields(message) + + for i := len(emailList) - 1; i >= 0; i-- { + emailList[i] = strings.Trim(emailList[i], ",") + if !strings.Contains(emailList[i], "@") { + emailList = append(emailList[:i], emailList[i+1:]...) + } + } + + if len(emailList) == 0 { + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: args.T("api.command.invite_people.no_email")} + } + + if err := InviteNewUsersToTeam(emailList, args.TeamId, args.UserId, args.SiteURL); err != nil { + l4g.Error(err.Error()) + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: args.T("api.command.invite_people.fail")} + } + + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: args.T("api.command.invite_people.sent")} +} diff --git a/app/command_join.go b/app/command_join.go new file mode 100644 index 000000000..5b19dd7a0 --- /dev/null +++ b/app/command_join.go @@ -0,0 +1,62 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "github.com/mattermost/platform/model" + goi18n "github.com/nicksnyder/go-i18n/i18n" +) + +type JoinProvider struct { +} + +const ( + CMD_JOIN = "join" +) + +func init() { + RegisterCommandProvider(&JoinProvider{}) +} + +func (me *JoinProvider) GetTrigger() string { + return CMD_JOIN +} + +func (me *JoinProvider) GetCommand(T goi18n.TranslateFunc) *model.Command { + return &model.Command{ + Trigger: CMD_JOIN, + AutoComplete: true, + AutoCompleteDesc: T("api.command_join.desc"), + AutoCompleteHint: T("api.command_join.hint"), + DisplayName: T("api.command_join.name"), + } +} + +func (me *JoinProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse { + if result := <-Srv.Store.Channel().GetByName(args.TeamId, message, true); result.Err != nil { + return &model.CommandResponse{Text: args.T("api.command_join.list.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + channel := result.Data.(*model.Channel) + + if channel.Name == message { + + if channel.Type != model.CHANNEL_OPEN { + return &model.CommandResponse{Text: args.T("api.command_join.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + if err := JoinChannel(channel, args.UserId); err != nil { + return &model.CommandResponse{Text: args.T("api.command_join.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + team, err := GetTeam(channel.TeamId) + if err != nil { + return &model.CommandResponse{Text: args.T("api.command_join.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + return &model.CommandResponse{GotoLocation: args.SiteURL + "/" + team.Name + "/channels/" + channel.Name, Text: args.T("api.command_join.success"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + } + + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: args.T("api.command_join.missing.app_error")} +} diff --git a/app/command_loadtest.go b/app/command_loadtest.go new file mode 100644 index 000000000..d3c7474ae --- /dev/null +++ b/app/command_loadtest.go @@ -0,0 +1,437 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "io" + "net/http" + "path" + "strconv" + "strings" + + l4g "github.com/alecthomas/log4go" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" + goi18n "github.com/nicksnyder/go-i18n/i18n" +) + +var usage = `Mattermost load testing commands to help configure the system + + COMMANDS: + + Setup - Creates a testing environment in current team. + /loadtest setup [teams] [fuzz] + + Example: + /loadtest setup teams fuzz 10 20 50 + + Users - Add a specified number of random users with fuzz text to current team. + /loadtest users [fuzz] + + Example: + /loadtest users fuzz 5 10 + + Channels - Add a specified number of random channels with fuzz text to current team. + /loadtest channels [fuzz] + + Example: + /loadtest channels fuzz 5 10 + + Posts - Add some random posts with fuzz text to current channel. + /loadtest posts [fuzz] + + Example: + /loadtest posts fuzz 5 10 3 + + Url - Add a post containing the text from a given url to current channel. + /loadtest url + + Example: + /loadtest http://www.example.com/sample_file.md + + Json - Add a post using the JSON file as payload to the current channel. + /loadtest json url + + Example + /loadtest json http://www.example.com/sample_body.json + +` + +const ( + CMD_LOADTEST = "loadtest" +) + +type LoadTestProvider struct { +} + +func init() { + if !utils.Cfg.ServiceSettings.EnableTesting { + RegisterCommandProvider(&LoadTestProvider{}) + } +} + +func (me *LoadTestProvider) GetTrigger() string { + return CMD_LOADTEST +} + +func (me *LoadTestProvider) GetCommand(T goi18n.TranslateFunc) *model.Command { + return &model.Command{ + Trigger: CMD_LOADTEST, + AutoComplete: false, + AutoCompleteDesc: "Debug Load Testing", + AutoCompleteHint: "help", + DisplayName: "loadtest", + } +} + +func (me *LoadTestProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse { + //This command is only available when EnableTesting is true + if !utils.Cfg.ServiceSettings.EnableTesting { + return &model.CommandResponse{} + } + + if strings.HasPrefix(message, "setup") { + return me.SetupCommand(args, message) + } + + if strings.HasPrefix(message, "users") { + return me.UsersCommand(args, message) + } + + if strings.HasPrefix(message, "channels") { + return me.ChannelsCommand(args, message) + } + + if strings.HasPrefix(message, "posts") { + return me.PostsCommand(args, message) + } + + if strings.HasPrefix(message, "url") { + return me.UrlCommand(args, message) + } + if strings.HasPrefix(message, "json") { + return me.JsonCommand(args, message) + } + return me.HelpCommand(args, message) +} + +func (me *LoadTestProvider) HelpCommand(args *model.CommandArgs, message string) *model.CommandResponse { + return &model.CommandResponse{Text: usage, ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) SetupCommand(args *model.CommandArgs, message string) *model.CommandResponse { + tokens := strings.Fields(strings.TrimPrefix(message, "setup")) + doTeams := contains(tokens, "teams") + doFuzz := contains(tokens, "fuzz") + + numArgs := 0 + if doTeams { + numArgs++ + } + if doFuzz { + numArgs++ + } + + var numTeams int + var numChannels int + var numUsers int + var numPosts int + + // Defaults + numTeams = 10 + numChannels = 10 + numUsers = 10 + numPosts = 10 + + if doTeams { + if (len(tokens) - numArgs) >= 4 { + numTeams, _ = strconv.Atoi(tokens[numArgs+0]) + numChannels, _ = strconv.Atoi(tokens[numArgs+1]) + numUsers, _ = strconv.Atoi(tokens[numArgs+2]) + numPosts, _ = strconv.Atoi(tokens[numArgs+3]) + } + } else { + if (len(tokens) - numArgs) >= 3 { + numChannels, _ = strconv.Atoi(tokens[numArgs+0]) + numUsers, _ = strconv.Atoi(tokens[numArgs+1]) + numPosts, _ = strconv.Atoi(tokens[numArgs+2]) + } + } + client := model.NewClient(args.SiteURL) + + if doTeams { + if err := CreateBasicUser(client); err != nil { + return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + client.Login(BTEST_USER_EMAIL, BTEST_USER_PASSWORD) + environment, err := CreateTestEnvironmentWithTeams( + client, + utils.Range{Begin: numTeams, End: numTeams}, + utils.Range{Begin: numChannels, End: numChannels}, + utils.Range{Begin: numUsers, End: numUsers}, + utils.Range{Begin: numPosts, End: numPosts}, + doFuzz) + if err != true { + return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + l4g.Info("Testing environment created") + for i := 0; i < len(environment.Teams); i++ { + l4g.Info("Team Created: " + environment.Teams[i].Name) + l4g.Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + USER_PASSWORD) + } + } + } else { + + var team *model.Team + if tr := <-Srv.Store.Team().Get(args.TeamId); tr.Err != nil { + return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + team = tr.Data.(*model.Team) + } + + client.MockSession(args.Session.Token) + client.SetTeamId(args.TeamId) + CreateTestEnvironmentInTeam( + client, + team, + utils.Range{Begin: numChannels, End: numChannels}, + utils.Range{Begin: numUsers, End: numUsers}, + utils.Range{Begin: numPosts, End: numPosts}, + doFuzz) + } + + return &model.CommandResponse{Text: "Created enviroment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) UsersCommand(args *model.CommandArgs, message string) *model.CommandResponse { + cmd := strings.TrimSpace(strings.TrimPrefix(message, "users")) + + doFuzz := false + if strings.Index(cmd, "fuzz") == 0 { + doFuzz = true + cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz")) + } + + usersr, err := parseRange(cmd, "") + if err == false { + usersr = utils.Range{Begin: 2, End: 5} + } + + var team *model.Team + if tr := <-Srv.Store.Team().Get(args.TeamId); tr.Err != nil { + return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + team = tr.Data.(*model.Team) + } + + client := model.NewClient(args.SiteURL) + client.SetTeamId(team.Id) + userCreator := NewAutoUserCreator(client, team) + userCreator.Fuzzy = doFuzz + userCreator.CreateTestUsers(usersr) + + return &model.CommandResponse{Text: "Added users", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) ChannelsCommand(args *model.CommandArgs, message string) *model.CommandResponse { + cmd := strings.TrimSpace(strings.TrimPrefix(message, "channels")) + + doFuzz := false + if strings.Index(cmd, "fuzz") == 0 { + doFuzz = true + cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz")) + } + + channelsr, err := parseRange(cmd, "") + if err == false { + channelsr = utils.Range{Begin: 2, End: 5} + } + + var team *model.Team + if tr := <-Srv.Store.Team().Get(args.TeamId); tr.Err != nil { + return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + team = tr.Data.(*model.Team) + } + + client := model.NewClient(args.SiteURL) + client.SetTeamId(team.Id) + client.MockSession(args.Session.Token) + channelCreator := NewAutoChannelCreator(client, team) + channelCreator.Fuzzy = doFuzz + channelCreator.CreateTestChannels(channelsr) + + return &model.CommandResponse{Text: "Added channels", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) PostsCommand(args *model.CommandArgs, message string) *model.CommandResponse { + cmd := strings.TrimSpace(strings.TrimPrefix(message, "posts")) + + doFuzz := false + if strings.Index(cmd, "fuzz") == 0 { + doFuzz = true + cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz")) + } + + postsr, err := parseRange(cmd, "") + if err == false { + postsr = utils.Range{Begin: 20, End: 30} + } + + tokens := strings.Fields(cmd) + rimages := utils.Range{Begin: 0, End: 0} + if len(tokens) >= 3 { + if numImages, err := strconv.Atoi(tokens[2]); err == nil { + rimages = utils.Range{Begin: numImages, End: numImages} + } + } + + var usernames []string + if result := <-Srv.Store.User().GetProfiles(args.TeamId, 0, 1000); result.Err == nil { + profileUsers := result.Data.([]*model.User) + usernames = make([]string, len(profileUsers)) + i := 0 + for _, userprof := range profileUsers { + usernames[i] = userprof.Username + i++ + } + } + + client := model.NewClient(args.SiteURL) + client.SetTeamId(args.TeamId) + client.MockSession(args.Session.Token) + testPoster := NewAutoPostCreator(client, args.ChannelId) + testPoster.Fuzzy = doFuzz + testPoster.Users = usernames + + numImages := utils.RandIntFromRange(rimages) + numPosts := utils.RandIntFromRange(postsr) + for i := 0; i < numPosts; i++ { + testPoster.HasImage = (i < numImages) + testPoster.CreateRandomPost() + } + + return &model.CommandResponse{Text: "Added posts", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) UrlCommand(args *model.CommandArgs, message string) *model.CommandResponse { + url := strings.TrimSpace(strings.TrimPrefix(message, "url")) + if len(url) == 0 { + return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + // provide a shortcut to easily access tests stored in doc/developer/tests + if !strings.HasPrefix(url, "http") { + url = "https://raw.githubusercontent.com/mattermost/platform/master/tests/" + url + + if path.Ext(url) == "" { + url += ".md" + } + } + + var contents io.ReadCloser + if r, err := http.Get(url); err != nil { + return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else if r.StatusCode > 400 { + return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + contents = r.Body + } + + bytes := make([]byte, 4000) + + // break contents into 4000 byte posts + for { + length, err := contents.Read(bytes) + if err != nil && err != io.EOF { + return &model.CommandResponse{Text: "Encountered error reading file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + if length == 0 { + break + } + + post := &model.Post{} + post.Message = string(bytes[:length]) + post.ChannelId = args.ChannelId + post.UserId = args.UserId + + if _, err := CreatePost(post, args.TeamId, false); err != nil { + return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + } + + return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func (me *LoadTestProvider) JsonCommand(args *model.CommandArgs, message string) *model.CommandResponse { + url := strings.TrimSpace(strings.TrimPrefix(message, "json")) + if len(url) == 0 { + return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + // provide a shortcut to easily access tests stored in doc/developer/tests + if !strings.HasPrefix(url, "http") { + url = "https://raw.githubusercontent.com/mattermost/platform/master/tests/" + url + + if path.Ext(url) == "" { + url += ".json" + } + } + + var contents io.ReadCloser + if r, err := http.Get(url); err != nil { + return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else if r.StatusCode > 400 { + return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + contents = r.Body + } + + post := model.PostFromJson(contents) + post.ChannelId = args.ChannelId + post.UserId = args.UserId + if post.Message == "" { + post.Message = message + } + + if _, err := CreatePost(post, args.TeamId, false); err != nil { + return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} + +func parseRange(command string, cmd string) (utils.Range, bool) { + tokens := strings.Fields(strings.TrimPrefix(command, cmd)) + var begin int + var end int + var err1 error + var err2 error + switch { + case len(tokens) == 1: + begin, err1 = strconv.Atoi(tokens[0]) + end = begin + if err1 != nil { + return utils.Range{Begin: 0, End: 0}, false + } + case len(tokens) >= 2: + begin, err1 = strconv.Atoi(tokens[0]) + end, err2 = strconv.Atoi(tokens[1]) + if err1 != nil || err2 != nil { + return utils.Range{Begin: 0, End: 0}, false + } + default: + return utils.Range{Begin: 0, End: 0}, false + } + return utils.Range{Begin: begin, End: end}, true +} + +func contains(items []string, token string) bool { + for _, elem := range items { + if elem == token { + return true + } + } + return false +} diff --git a/app/command_logout.go b/app/command_logout.go new file mode 100644 index 000000000..1a353056e --- /dev/null +++ b/app/command_logout.go @@ -0,0 +1,48 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "github.com/mattermost/platform/model" + goi18n "github.com/nicksnyder/go-i18n/i18n" +) + +type LogoutProvider struct { +} + +const ( + CMD_LOGOUT = "logout" +) + +func init() { + RegisterCommandProvider(&LogoutProvider{}) +} + +func (me *LogoutProvider) GetTrigger() string { + return CMD_LOGOUT +} + +func (me *LogoutProvider) GetCommand(T goi18n.TranslateFunc) *model.Command { + return &model.Command{ + Trigger: CMD_LOGOUT, + AutoComplete: true, + AutoCompleteDesc: T("api.command_logout.desc"), + AutoCompleteHint: "", + DisplayName: T("api.command_logout.name"), + } +} + +func (me *LogoutProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse { + FAIL := &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: args.T("api.command_logout.fail_message")} + SUCCESS := &model.CommandResponse{GotoLocation: "/login"} + + // We can't actually remove the user's cookie from here so we just dump their session and let the browser figure it out + if args.Session.Id != "" { + if err := RevokeSessionById(args.Session.Id); err != nil { + return FAIL + } + return SUCCESS + } + return FAIL +} diff --git a/app/command_me.go b/app/command_me.go new file mode 100644 index 000000000..bb29ec1e0 --- /dev/null +++ b/app/command_me.go @@ -0,0 +1,38 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "github.com/mattermost/platform/model" + goi18n "github.com/nicksnyder/go-i18n/i18n" +) + +type MeProvider struct { +} + +const ( + CMD_ME = "me" +) + +func init() { + RegisterCommandProvider(&MeProvider{}) +} + +func (me *MeProvider) GetTrigger() string { + return CMD_ME +} + +func (me *MeProvider) GetCommand(T goi18n.TranslateFunc) *model.Command { + return &model.Command{ + Trigger: CMD_ME, + AutoComplete: true, + AutoCompleteDesc: T("api.command_me.desc"), + AutoCompleteHint: T("api.command_me.hint"), + DisplayName: T("api.command_me.name"), + } +} + +func (me *MeProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse { + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL, Text: "*" + message + "*"} +} diff --git a/app/command_msg.go b/app/command_msg.go new file mode 100644 index 000000000..fd4ace61a --- /dev/null +++ b/app/command_msg.go @@ -0,0 +1,110 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "strings" + + l4g "github.com/alecthomas/log4go" + "github.com/mattermost/platform/model" + goi18n "github.com/nicksnyder/go-i18n/i18n" +) + +type msgProvider struct { +} + +const ( + CMD_MSG = "msg" +) + +func init() { + RegisterCommandProvider(&msgProvider{}) +} + +func (me *msgProvider) GetTrigger() string { + return CMD_MSG +} + +func (me *msgProvider) GetCommand(T goi18n.TranslateFunc) *model.Command { + return &model.Command{ + Trigger: CMD_MSG, + AutoComplete: true, + AutoCompleteDesc: T("api.command_msg.desc"), + AutoCompleteHint: T("api.command_msg.hint"), + DisplayName: T("api.command_msg.name"), + } +} + +func (me *msgProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse { + + splitMessage := strings.SplitN(message, " ", 2) + + parsedMessage := "" + targetUsername := "" + teamId := "" + + if len(splitMessage) > 1 { + parsedMessage = strings.SplitN(message, " ", 2)[1] + } + targetUsername = strings.SplitN(message, " ", 2)[0] + targetUsername = strings.TrimPrefix(targetUsername, "@") + + var userProfile *model.User + if result := <-Srv.Store.User().GetByUsername(targetUsername); result.Err != nil { + l4g.Error(result.Err.Error()) + return &model.CommandResponse{Text: args.T("api.command_msg.missing.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + userProfile = result.Data.(*model.User) + } + + if userProfile.Id == args.UserId { + return &model.CommandResponse{Text: args.T("api.command_msg.missing.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + // Find the channel based on this user + channelName := model.GetDMNameFromIds(args.UserId, userProfile.Id) + + targetChannelId := "" + if channel := <-Srv.Store.Channel().GetByName(args.TeamId, channelName, true); channel.Err != nil { + if channel.Err.Id == "store.sql_channel.get_by_name.missing.app_error" { + if directChannel, err := CreateDirectChannel(args.UserId, userProfile.Id); err != nil { + l4g.Error(err.Error()) + return &model.CommandResponse{Text: args.T("api.command_msg.dm_fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } else { + targetChannelId = directChannel.Id + } + } else { + l4g.Error(channel.Err.Error()) + return &model.CommandResponse{Text: args.T("api.command_msg.dm_fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + } else { + channel := channel.Data.(*model.Channel) + targetChannelId = channel.Id + teamId = channel.TeamId + } + + if len(parsedMessage) > 0 { + post := &model.Post{} + post.Message = parsedMessage + post.ChannelId = targetChannelId + post.UserId = args.UserId + if _, err := CreatePost(post, args.TeamId, true); err != nil { + return &model.CommandResponse{Text: args.T("api.command_msg.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + } + + if teamId == "" { + if len(args.Session.TeamMembers) == 0 { + return &model.CommandResponse{Text: args.T("api.command_msg.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + teamId = args.Session.TeamMembers[0].TeamId + } + + team, err := GetTeam(teamId) + if err != nil { + return &model.CommandResponse{Text: args.T("api.command_msg.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + return &model.CommandResponse{GotoLocation: args.SiteURL + "/" + team.Name + "/channels/" + channelName, Text: "", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} +} diff --git a/app/command_offline.go b/app/command_offline.go new file mode 100644 index 000000000..6e2c125f8 --- /dev/null +++ b/app/command_offline.go @@ -0,0 +1,43 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "github.com/mattermost/platform/model" + goi18n "github.com/nicksnyder/go-i18n/i18n" +) + +type OfflineProvider struct { +} + +const ( + CMD_OFFLINE = "offline" +) + +func init() { + RegisterCommandProvider(&OfflineProvider{}) +} + +func (me *OfflineProvider) GetTrigger() string { + return CMD_OFFLINE +} + +func (me *OfflineProvider) GetCommand(T goi18n.TranslateFunc) *model.Command { + return &model.Command{ + Trigger: CMD_OFFLINE, + AutoComplete: true, + AutoCompleteDesc: T("api.command_offline.desc"), + DisplayName: T("api.command_offline.name"), + } +} + +func (me *OfflineProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse { + rmsg := args.T("api.command_offline.success") + if len(message) > 0 { + rmsg = message + " " + rmsg + } + SetStatusOffline(args.UserId, true) + + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: rmsg} +} diff --git a/app/command_online.go b/app/command_online.go new file mode 100644 index 000000000..bd6fbab60 --- /dev/null +++ b/app/command_online.go @@ -0,0 +1,43 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "github.com/mattermost/platform/model" + goi18n "github.com/nicksnyder/go-i18n/i18n" +) + +type OnlineProvider struct { +} + +const ( + CMD_ONLINE = "online" +) + +func init() { + RegisterCommandProvider(&OnlineProvider{}) +} + +func (me *OnlineProvider) GetTrigger() string { + return CMD_ONLINE +} + +func (me *OnlineProvider) GetCommand(T goi18n.TranslateFunc) *model.Command { + return &model.Command{ + Trigger: CMD_ONLINE, + AutoComplete: true, + AutoCompleteDesc: T("api.command_online.desc"), + DisplayName: T("api.command_online.name"), + } +} + +func (me *OnlineProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse { + rmsg := args.T("api.command_online.success") + if len(message) > 0 { + rmsg = message + " " + rmsg + } + SetStatusOnline(args.UserId, args.Session.Id, true) + + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: rmsg} +} diff --git a/app/command_shortcuts.go b/app/command_shortcuts.go new file mode 100644 index 000000000..93e5f0f51 --- /dev/null +++ b/app/command_shortcuts.go @@ -0,0 +1,95 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "bytes" + "strings" + + "github.com/mattermost/platform/model" + goi18n "github.com/nicksnyder/go-i18n/i18n" +) + +type ShortcutsProvider struct { +} + +const ( + CMD_SHORTCUTS = "shortcuts" +) + +func init() { + RegisterCommandProvider(&ShortcutsProvider{}) +} + +func (me *ShortcutsProvider) GetTrigger() string { + return CMD_SHORTCUTS +} + +func (me *ShortcutsProvider) GetCommand(T goi18n.TranslateFunc) *model.Command { + return &model.Command{ + Trigger: CMD_SHORTCUTS, + AutoComplete: true, + AutoCompleteDesc: T("api.command_shortcuts.desc"), + AutoCompleteHint: "", + DisplayName: T("api.command_shortcuts.name"), + } +} + +func (me *ShortcutsProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse { + shortcutIds := [28]string{ + "api.command_shortcuts.header", + // Nav shortcuts + "api.command_shortcuts.nav.header", + "api.command_shortcuts.nav.prev", + "api.command_shortcuts.nav.next", + "api.command_shortcuts.nav.unread_prev", + "api.command_shortcuts.nav.unread_next", + "api.command_shortcuts.nav.switcher", + "api.command_shortcuts.nav.settings", + "api.command_shortcuts.nav.recent_mentions", + // Files shortcuts + "api.command_shortcuts.files.header", + "api.command_shortcuts.files.upload", + // Msg shortcuts + "api.command_shortcuts.msgs.header", + "api.command_shortcuts.msgs.mark_as_read", + "api.command_shortcuts.msgs.reprint_prev", + "api.command_shortcuts.msgs.reprint_next", + "api.command_shortcuts.msgs.edit", + "api.command_shortcuts.msgs.comp_username", + "api.command_shortcuts.msgs.comp_channel", + "api.command_shortcuts.msgs.comp_emoji", + // Browser shortcuts + "api.command_shortcuts.browser.header", + "api.command_shortcuts.browser.channel_prev", + "api.command_shortcuts.browser.channel_next", + "api.command_shortcuts.browser.font_increase", + "api.command_shortcuts.browser.font_decrease", + "api.command_shortcuts.browser.highlight_prev", + "api.command_shortcuts.browser.highlight_next", + "api.command_shortcuts.browser.newline", + } + + var osDependentWords map[string]interface{} + if strings.Contains(message, "mac") { + osDependentWords = map[string]interface{}{ + "CmdOrCtrl": args.T("api.command_shortcuts.cmd"), + "ChannelPrevCmd": args.T("api.command_shortcuts.browser.channel_prev.cmd_mac"), + "ChannelNextCmd": args.T("api.command_shortcuts.browser.channel_next.cmd_mac"), + } + } else { + osDependentWords = map[string]interface{}{ + "CmdOrCtrl": args.T("api.command_shortcuts.ctrl"), + "ChannelPrevCmd": args.T("api.command_shortcuts.browser.channel_prev.cmd"), + "ChannelNextCmd": args.T("api.command_shortcuts.browser.channel_next.cmd"), + } + } + + var buffer bytes.Buffer + for _, element := range shortcutIds { + buffer.WriteString(args.T(element, osDependentWords)) + } + + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: buffer.String()} +} diff --git a/app/command_shrug.go b/app/command_shrug.go new file mode 100644 index 000000000..12d1039ec --- /dev/null +++ b/app/command_shrug.go @@ -0,0 +1,43 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "github.com/mattermost/platform/model" + goi18n "github.com/nicksnyder/go-i18n/i18n" +) + +type ShrugProvider struct { +} + +const ( + CMD_SHRUG = "shrug" +) + +func init() { + RegisterCommandProvider(&ShrugProvider{}) +} + +func (me *ShrugProvider) GetTrigger() string { + return CMD_SHRUG +} + +func (me *ShrugProvider) GetCommand(T goi18n.TranslateFunc) *model.Command { + return &model.Command{ + Trigger: CMD_SHRUG, + AutoComplete: true, + AutoCompleteDesc: T("api.command_shrug.desc"), + AutoCompleteHint: T("api.command_shrug.hint"), + DisplayName: T("api.command_shrug.name"), + } +} + +func (me *ShrugProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse { + rmsg := `¯\\\_(ツ)\_/¯` + if len(message) > 0 { + rmsg = message + " " + rmsg + } + + return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL, Text: rmsg} +} diff --git a/manualtesting/manual_testing.go b/manualtesting/manual_testing.go index 33192ff5e..677b9999d 100644 --- a/manualtesting/manual_testing.go +++ b/manualtesting/manual_testing.go @@ -92,7 +92,7 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) { user := &model.User{ Email: "success+" + model.NewId() + "simulator.amazonses.com", Nickname: username[0], - Password: api.USER_PASSWORD} + Password: app.USER_PASSWORD} result, err := client.CreateUser(user, "") if err != nil { @@ -107,7 +107,7 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) { userID = newuser.Id // Login as user to generate auth token - _, err = client.LoginById(newuser.Id, api.USER_PASSWORD) + _, err = client.LoginById(newuser.Id, app.USER_PASSWORD) if err != nil { c.Err = err return diff --git a/model/command_args.go b/model/command_args.go index 4da5dc760..f512410a3 100644 --- a/model/command_args.go +++ b/model/command_args.go @@ -6,13 +6,20 @@ package model import ( "encoding/json" "io" + + goi18n "github.com/nicksnyder/go-i18n/i18n" ) type CommandArgs struct { - ChannelId string `json:"channel_id"` - RootId string `json:"root_id"` - ParentId string `json:"parent_id"` - Command string `json:"command"` + UserId string `json:"user_id"` + ChannelId string `json:"channel_id"` + TeamId string `json:"team_id"` + RootId string `json:"root_id"` + ParentId string `json:"parent_id"` + Command string `json:"command"` + SiteURL string `json:"-"` + T goi18n.TranslateFunc `json:"-"` + Session Session `json:"-"` } func (o *CommandArgs) ToJson() string { -- cgit v1.2.3-1-g7c22