diff --git a/app/command.go b/app/command.go index be53b00fdb..fb2d81fdf6 100644 --- a/app/command.go +++ b/app/command.go @@ -603,6 +603,10 @@ func (a *App) CreateCommand(cmd *model.Command) (*model.Command, *model.AppError return nil, model.NewAppError("CreateCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented) } + return a.createCommand(cmd) +} + +func (a *App) createCommand(cmd *model.Command) (*model.Command, *model.AppError) { cmd.Trigger = strings.ToLower(cmd.Trigger) teamCmds, err := a.Srv().Store.Command().GetByTeam(cmd.TeamId) @@ -667,6 +671,7 @@ func (a *App) UpdateCommand(oldCmd, updatedCmd *model.Command) (*model.Command, updatedCmd.UpdateAt = model.GetMillis() updatedCmd.DeleteAt = oldCmd.DeleteAt updatedCmd.CreatorId = oldCmd.CreatorId + updatedCmd.PluginId = oldCmd.PluginId updatedCmd.TeamId = oldCmd.TeamId command, err := a.Srv().Store.Command().Update(updatedCmd) diff --git a/app/plugin_api.go b/app/plugin_api.go index 774534c3ab..02c54e9d17 100644 --- a/app/plugin_api.go +++ b/app/plugin_api.go @@ -929,3 +929,110 @@ func (api *PluginAPI) PluginHTTP(request *http.Request) *http.Response { api.app.ServeInterPluginRequest(responseTransfer, request, api.id, destinationPluginId) return responseTransfer.GenerateResponse() } + +func (api *PluginAPI) CreateCommand(cmd *model.Command) (*model.Command, error) { + cmd.CreatorId = "" + cmd.PluginId = api.id + + cmd, appErr := api.app.createCommand(cmd) + + if appErr != nil { + return cmd, appErr + } + + return cmd, nil +} + +func (api *PluginAPI) ListCommands(teamID string) ([]*model.Command, error) { + ret := make([]*model.Command, 0) + + cmds, err := api.ListPluginCommands(teamID) + if err != nil { + return nil, err + } + ret = append(ret, cmds...) + + cmds, err = api.ListBuiltInCommands() + if err != nil { + return nil, err + } + ret = append(ret, cmds...) + + cmds, err = api.ListCustomCommands(teamID) + if err != nil { + return nil, err + } + ret = append(ret, cmds...) + + return ret, nil +} + +func (api *PluginAPI) ListCustomCommands(teamID string) ([]*model.Command, error) { + // Plugins are allowed to bypass the a.Config().ServiceSettings.EnableCommands setting. + return api.app.Srv().Store.Command().GetByTeam(teamID) +} + +func (api *PluginAPI) ListPluginCommands(teamID string) ([]*model.Command, error) { + commands := make([]*model.Command, 0) + seen := make(map[string]bool) + + for _, cmd := range api.app.PluginCommandsForTeam(teamID) { + if !seen[cmd.Trigger] { + seen[cmd.Trigger] = true + commands = append(commands, cmd) + } + } + + return commands, nil +} + +func (api *PluginAPI) ListBuiltInCommands() ([]*model.Command, error) { + commands := make([]*model.Command, 0) + seen := make(map[string]bool) + + for _, value := range commandProviders { + if cmd := value.GetCommand(api.app, utils.T); cmd != nil { + cpy := *cmd + if cpy.AutoComplete && !seen[cpy.Trigger] { + cpy.Sanitize() + seen[cpy.Trigger] = true + commands = append(commands, &cpy) + } + } + } + + return commands, nil +} + +func (api *PluginAPI) GetCommand(commandID string) (*model.Command, error) { + return api.app.Srv().Store.Command().Get(commandID) +} + +func (api *PluginAPI) UpdateCommand(commandID string, updatedCmd *model.Command) (*model.Command, error) { + oldCmd, err := api.GetCommand(commandID) + if err != nil { + return nil, err + } + + 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.PluginId = api.id + if updatedCmd.TeamId == "" { + updatedCmd.TeamId = oldCmd.TeamId + } + + return api.app.Srv().Store.Command().Update(updatedCmd) +} + +func (api *PluginAPI) DeleteCommand(commandID string) error { + err := api.app.Srv().Store.Command().Delete(commandID, model.GetMillis()) + if err != nil { + return err + } + + return nil +} diff --git a/app/plugin_api_test.go b/app/plugin_api_test.go index f400bea9d3..39ea854259 100644 --- a/app/plugin_api_test.go +++ b/app/plugin_api_test.go @@ -1730,3 +1730,88 @@ func TestPluginAPISearchPostsInTeamByUser(t *testing.T) { }) } } + +func TestPluginAPICreateCommandAndListCommands(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + api := th.SetupPluginAPI() + + foundCommand := func(listXCommand func(teamId string) ([]*model.Command, error)) bool { + cmds, appErr := listXCommand(th.BasicTeam.Id) + require.Nil(t, appErr) + + for _, cmd := range cmds { + if cmd.Trigger == "testcmd" { + return true + } + } + return false + } + + require.False(t, foundCommand(api.ListCommands)) + + cmd := &model.Command{ + TeamId: th.BasicTeam.Id, + Trigger: "testcmd", + Method: "G", + URL: "http://test.com/testcmd", + } + + cmd, appErr := api.CreateCommand(cmd) + require.Nil(t, appErr) + + newCmd, appErr := api.GetCommand(cmd.Id) + require.Nil(t, appErr) + require.Equal(t, "pluginid", newCmd.PluginId) + require.Equal(t, "", newCmd.CreatorId) + require.True(t, foundCommand(api.ListCommands)) + require.True(t, foundCommand(api.ListCustomCommands)) + require.False(t, foundCommand(api.ListPluginCommands)) +} + +func TestPluginAPIUpdateCommand(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + api := th.SetupPluginAPI() + + cmd := &model.Command{ + TeamId: th.BasicTeam.Id, + Trigger: "testcmd", + Method: "G", + URL: "http://test.com/testcmd", + } + + cmd, appErr := api.CreateCommand(cmd) + require.Nil(t, appErr) + + newCmd, appErr := api.GetCommand(cmd.Id) + require.Nil(t, appErr) + require.Equal(t, "pluginid", newCmd.PluginId) + require.Equal(t, "", newCmd.CreatorId) + + newCmd.Trigger = "NewTrigger" + newCmd.PluginId = "CannotChangeMe" + newCmd2, appErr := api.UpdateCommand(newCmd.Id, newCmd) + require.Nil(t, appErr) + require.Equal(t, "pluginid", newCmd2.PluginId) + require.Equal(t, "newtrigger", newCmd2.Trigger) + + team1 := th.CreateTeam() + + newCmd2.PluginId = "CannotChangeMe" + newCmd2.Trigger = "anotherNewTrigger" + newCmd2.TeamId = team1.Id + newCmd3, appErr := api.UpdateCommand(newCmd2.Id, newCmd2) + require.Nil(t, appErr) + require.Equal(t, "pluginid", newCmd3.PluginId) + require.Equal(t, "anothernewtrigger", newCmd3.Trigger) + require.Equal(t, team1.Id, newCmd3.TeamId) + + newCmd3.Trigger = "anotherNewTriggerAgain" + newCmd3.TeamId = "" + newCmd4, appErr := api.UpdateCommand(newCmd2.Id, newCmd2) + require.Nil(t, appErr) + require.Equal(t, "anothernewtriggeragain", newCmd4.Trigger) + require.Equal(t, team1.Id, newCmd4.TeamId) + +} diff --git a/i18n/en.json b/i18n/en.json index 842588376b..680bc5ee8c 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -5594,6 +5594,10 @@ "id": "model.command.is_valid.method.app_error", "translation": "Invalid Method." }, + { + "id": "model.command.is_valid.plugin_id.app_error", + "translation": "Invalid plugin id." + }, { "id": "model.command.is_valid.team_id.app_error", "translation": "Invalid team ID." diff --git a/model/command.go b/model/command.go index 6dcf52aecf..0013046bba 100644 --- a/model/command.go +++ b/model/command.go @@ -18,23 +18,26 @@ const ( ) type Command struct { - Id string `json:"id"` - Token string `json:"token"` - CreateAt int64 `json:"create_at"` - UpdateAt int64 `json:"update_at"` - DeleteAt int64 `json:"delete_at"` - CreatorId string `json:"creator_id"` - TeamId string `json:"team_id"` - Trigger string `json:"trigger"` - Method string `json:"method"` - Username string `json:"username"` - IconURL string `json:"icon_url"` - AutoComplete bool `json:"auto_complete"` - AutoCompleteDesc string `json:"auto_complete_desc"` - AutoCompleteHint string `json:"auto_complete_hint"` - DisplayName string `json:"display_name"` - Description string `json:"description"` - URL string `json:"url"` + Id string `json:"id"` + Token string `json:"token"` + CreateAt int64 `json:"create_at"` + UpdateAt int64 `json:"update_at"` + DeleteAt int64 `json:"delete_at"` + CreatorId string `json:"creator_id"` + TeamId string `json:"team_id"` + Trigger string `json:"trigger"` + Method string `json:"method"` + Username string `json:"username"` + IconURL string `json:"icon_url"` + AutoComplete bool `json:"auto_complete"` + AutoCompleteDesc string `json:"auto_complete_desc"` + AutoCompleteHint string `json:"auto_complete_hint"` + DisplayName string `json:"display_name"` + Description string `json:"description"` + URL string `json:"url"` + // PluginId records the id of the plugin that created this Command. If it is blank, the Command + // was not created by a plugin. + PluginId string `json:"plugin_id"` AutocompleteData *AutocompleteData `db:"-" json:"autocomplete_data,omitempty"` // AutocompleteIconData is a base64 encoded svg AutocompleteIconData string `db:"-" json:"autocomplete_icon_data,omitempty"` @@ -80,10 +83,20 @@ func (o *Command) IsValid() *AppError { return NewAppError("Command.IsValid", "model.command.is_valid.update_at.app_error", nil, "", http.StatusBadRequest) } - if !IsValidId(o.CreatorId) { + // If the CreatorId is blank, this should be a command created by a plugin. + if o.CreatorId == "" && !IsValidPluginId(o.PluginId) { + return NewAppError("Command.IsValid", "model.command.is_valid.plugin_id.app_error", nil, "", http.StatusBadRequest) + } + + // If the PluginId is blank, this should be a command associated with a userId. + if o.PluginId == "" && !IsValidId(o.CreatorId) { return NewAppError("Command.IsValid", "model.command.is_valid.user_id.app_error", nil, "", http.StatusBadRequest) } + if o.CreatorId != "" && o.PluginId != "" { + return NewAppError("Command.IsValid", "model.command.is_valid.plugin_id.app_error", nil, "command cannot have both a CreatorId and a PluginId", http.StatusBadRequest) + } + if !IsValidId(o.TeamId) { return NewAppError("Command.IsValid", "model.command.is_valid.team_id.app_error", nil, "", http.StatusBadRequest) } diff --git a/plugin/api.go b/plugin/api.go index a6e498cff5..c5d8317747 100644 --- a/plugin/api.go +++ b/plugin/api.go @@ -972,6 +972,65 @@ type API interface { // @tag User // Minimum server version: 5.26 PublishUserTyping(userId, channelId, parentId string) *model.AppError + + // CreateCommand creates a server-owned slash command that is not handled by the plugin + // itself, and which will persist past the life of the plugin. The command will have its + // CreatorId set to "" and its PluginId set to the id of the plugin that created it. + // + // @tag SlashCommand + // Minimum server version: 5.28 + CreateCommand(cmd *model.Command) (*model.Command, error) + + // ListCommands returns the list of all slash commands for teamID. E.g., custom commands + // (those created through the integrations menu, the REST api, or the plugin api CreateCommand), + // plugin commands (those created with plugin api RegisterCommand), and builtin commands + // (those added internally through RegisterCommandProvider). + // + // @tag SlashCommand + // Minimum server version: 5.28 + ListCommands(teamID string) ([]*model.Command, error) + + // ListCustomCommands returns the list of slash commands for teamID that where created + // through the integrations menu, the REST api, or the plugin api CreateCommand. + // + // @tag SlashCommand + // Minimum server version: 5.28 + ListCustomCommands(teamID string) ([]*model.Command, error) + + // ListPluginCommands returns the list of slash commands for teamID that were created + // with the plugin api RegisterCommand. + // + // @tag SlashCommand + // Minimum server version: 5.28 + ListPluginCommands(teamID string) ([]*model.Command, error) + + // ListBuiltInCommands returns the list of slash commands that are builtin commands + // (those added internally through RegisterCommandProvider). + // + // @tag SlashCommand + // Minimum server version: 5.28 + ListBuiltInCommands() ([]*model.Command, error) + + // GetCommand returns the command definition based on a command id string. + // + // @tag SlashCommand + // Minimum server version: 5.28 + GetCommand(commandID string) (*model.Command, error) + + // UpdateCommand updates a single command (commandID) with the information provided in the + // updatedCmd model.Command struct. The following fields in the command cannot be updated: + // Id, Token, CreateAt, DeleteAt, and PluginId. If updatedCmd.TeamId is blank, it + // will be set to commandID's TeamId. + // + // @tag SlashCommand + // Minimum server version: 5.28 + UpdateCommand(commandID string, updatedCmd *model.Command) (*model.Command, error) + + // DeleteCommand deletes a slash command (commandID). + // + // @tag SlashCommand + // Minimum server version: 5.28 + DeleteCommand(commandID string) error } var handshake = plugin.HandshakeConfig{ diff --git a/plugin/api_timer_layer_generated.go b/plugin/api_timer_layer_generated.go index 73204ef053..5e668e0267 100644 --- a/plugin/api_timer_layer_generated.go +++ b/plugin/api_timer_layer_generated.go @@ -1036,3 +1036,59 @@ func (api *apiTimerLayer) PublishUserTyping(userId, channelId, parentId string) api.recordTime(startTime, "PublishUserTyping", _returnsA == nil) return _returnsA } + +func (api *apiTimerLayer) CreateCommand(cmd *model.Command) (*model.Command, error) { + startTime := timePkg.Now() + _returnsA, _returnsB := api.apiImpl.CreateCommand(cmd) + api.recordTime(startTime, "CreateCommand", _returnsB == nil) + return _returnsA, _returnsB +} + +func (api *apiTimerLayer) ListCommands(teamID string) ([]*model.Command, error) { + startTime := timePkg.Now() + _returnsA, _returnsB := api.apiImpl.ListCommands(teamID) + api.recordTime(startTime, "ListCommands", _returnsB == nil) + return _returnsA, _returnsB +} + +func (api *apiTimerLayer) ListCustomCommands(teamID string) ([]*model.Command, error) { + startTime := timePkg.Now() + _returnsA, _returnsB := api.apiImpl.ListCustomCommands(teamID) + api.recordTime(startTime, "ListCustomCommands", _returnsB == nil) + return _returnsA, _returnsB +} + +func (api *apiTimerLayer) ListPluginCommands(teamID string) ([]*model.Command, error) { + startTime := timePkg.Now() + _returnsA, _returnsB := api.apiImpl.ListPluginCommands(teamID) + api.recordTime(startTime, "ListPluginCommands", _returnsB == nil) + return _returnsA, _returnsB +} + +func (api *apiTimerLayer) ListBuiltInCommands() ([]*model.Command, error) { + startTime := timePkg.Now() + _returnsA, _returnsB := api.apiImpl.ListBuiltInCommands() + api.recordTime(startTime, "ListBuiltInCommands", _returnsB == nil) + return _returnsA, _returnsB +} + +func (api *apiTimerLayer) GetCommand(commandID string) (*model.Command, error) { + startTime := timePkg.Now() + _returnsA, _returnsB := api.apiImpl.GetCommand(commandID) + api.recordTime(startTime, "GetCommand", _returnsB == nil) + return _returnsA, _returnsB +} + +func (api *apiTimerLayer) UpdateCommand(commandID string, updatedCmd *model.Command) (*model.Command, error) { + startTime := timePkg.Now() + _returnsA, _returnsB := api.apiImpl.UpdateCommand(commandID, updatedCmd) + api.recordTime(startTime, "UpdateCommand", _returnsB == nil) + return _returnsA, _returnsB +} + +func (api *apiTimerLayer) DeleteCommand(commandID string) error { + startTime := timePkg.Now() + _returnsA := api.apiImpl.DeleteCommand(commandID) + api.recordTime(startTime, "DeleteCommand", _returnsA == nil) + return _returnsA +} diff --git a/plugin/client_rpc_generated.go b/plugin/client_rpc_generated.go index fdc1a09b9b..7335e0fccd 100644 --- a/plugin/client_rpc_generated.go +++ b/plugin/client_rpc_generated.go @@ -4512,3 +4512,242 @@ func (s *apiRPCServer) PublishUserTyping(args *Z_PublishUserTypingArgs, returns } return nil } + +type Z_CreateCommandArgs struct { + A *model.Command +} + +type Z_CreateCommandReturns struct { + A *model.Command + B error +} + +func (g *apiRPCClient) CreateCommand(cmd *model.Command) (*model.Command, error) { + _args := &Z_CreateCommandArgs{cmd} + _returns := &Z_CreateCommandReturns{} + if err := g.client.Call("Plugin.CreateCommand", _args, _returns); err != nil { + log.Printf("RPC call to CreateCommand API failed: %s", err.Error()) + } + return _returns.A, _returns.B +} + +func (s *apiRPCServer) CreateCommand(args *Z_CreateCommandArgs, returns *Z_CreateCommandReturns) error { + if hook, ok := s.impl.(interface { + CreateCommand(cmd *model.Command) (*model.Command, error) + }); ok { + returns.A, returns.B = hook.CreateCommand(args.A) + returns.B = encodableError(returns.B) + } else { + return encodableError(fmt.Errorf("API CreateCommand called but not implemented.")) + } + return nil +} + +type Z_ListCommandsArgs struct { + A string +} + +type Z_ListCommandsReturns struct { + A []*model.Command + B error +} + +func (g *apiRPCClient) ListCommands(teamID string) ([]*model.Command, error) { + _args := &Z_ListCommandsArgs{teamID} + _returns := &Z_ListCommandsReturns{} + if err := g.client.Call("Plugin.ListCommands", _args, _returns); err != nil { + log.Printf("RPC call to ListCommands API failed: %s", err.Error()) + } + return _returns.A, _returns.B +} + +func (s *apiRPCServer) ListCommands(args *Z_ListCommandsArgs, returns *Z_ListCommandsReturns) error { + if hook, ok := s.impl.(interface { + ListCommands(teamID string) ([]*model.Command, error) + }); ok { + returns.A, returns.B = hook.ListCommands(args.A) + returns.B = encodableError(returns.B) + } else { + return encodableError(fmt.Errorf("API ListCommands called but not implemented.")) + } + return nil +} + +type Z_ListCustomCommandsArgs struct { + A string +} + +type Z_ListCustomCommandsReturns struct { + A []*model.Command + B error +} + +func (g *apiRPCClient) ListCustomCommands(teamID string) ([]*model.Command, error) { + _args := &Z_ListCustomCommandsArgs{teamID} + _returns := &Z_ListCustomCommandsReturns{} + if err := g.client.Call("Plugin.ListCustomCommands", _args, _returns); err != nil { + log.Printf("RPC call to ListCustomCommands API failed: %s", err.Error()) + } + return _returns.A, _returns.B +} + +func (s *apiRPCServer) ListCustomCommands(args *Z_ListCustomCommandsArgs, returns *Z_ListCustomCommandsReturns) error { + if hook, ok := s.impl.(interface { + ListCustomCommands(teamID string) ([]*model.Command, error) + }); ok { + returns.A, returns.B = hook.ListCustomCommands(args.A) + returns.B = encodableError(returns.B) + } else { + return encodableError(fmt.Errorf("API ListCustomCommands called but not implemented.")) + } + return nil +} + +type Z_ListPluginCommandsArgs struct { + A string +} + +type Z_ListPluginCommandsReturns struct { + A []*model.Command + B error +} + +func (g *apiRPCClient) ListPluginCommands(teamID string) ([]*model.Command, error) { + _args := &Z_ListPluginCommandsArgs{teamID} + _returns := &Z_ListPluginCommandsReturns{} + if err := g.client.Call("Plugin.ListPluginCommands", _args, _returns); err != nil { + log.Printf("RPC call to ListPluginCommands API failed: %s", err.Error()) + } + return _returns.A, _returns.B +} + +func (s *apiRPCServer) ListPluginCommands(args *Z_ListPluginCommandsArgs, returns *Z_ListPluginCommandsReturns) error { + if hook, ok := s.impl.(interface { + ListPluginCommands(teamID string) ([]*model.Command, error) + }); ok { + returns.A, returns.B = hook.ListPluginCommands(args.A) + returns.B = encodableError(returns.B) + } else { + return encodableError(fmt.Errorf("API ListPluginCommands called but not implemented.")) + } + return nil +} + +type Z_ListBuiltInCommandsArgs struct { +} + +type Z_ListBuiltInCommandsReturns struct { + A []*model.Command + B error +} + +func (g *apiRPCClient) ListBuiltInCommands() ([]*model.Command, error) { + _args := &Z_ListBuiltInCommandsArgs{} + _returns := &Z_ListBuiltInCommandsReturns{} + if err := g.client.Call("Plugin.ListBuiltInCommands", _args, _returns); err != nil { + log.Printf("RPC call to ListBuiltInCommands API failed: %s", err.Error()) + } + return _returns.A, _returns.B +} + +func (s *apiRPCServer) ListBuiltInCommands(args *Z_ListBuiltInCommandsArgs, returns *Z_ListBuiltInCommandsReturns) error { + if hook, ok := s.impl.(interface { + ListBuiltInCommands() ([]*model.Command, error) + }); ok { + returns.A, returns.B = hook.ListBuiltInCommands() + returns.B = encodableError(returns.B) + } else { + return encodableError(fmt.Errorf("API ListBuiltInCommands called but not implemented.")) + } + return nil +} + +type Z_GetCommandArgs struct { + A string +} + +type Z_GetCommandReturns struct { + A *model.Command + B error +} + +func (g *apiRPCClient) GetCommand(commandID string) (*model.Command, error) { + _args := &Z_GetCommandArgs{commandID} + _returns := &Z_GetCommandReturns{} + if err := g.client.Call("Plugin.GetCommand", _args, _returns); err != nil { + log.Printf("RPC call to GetCommand API failed: %s", err.Error()) + } + return _returns.A, _returns.B +} + +func (s *apiRPCServer) GetCommand(args *Z_GetCommandArgs, returns *Z_GetCommandReturns) error { + if hook, ok := s.impl.(interface { + GetCommand(commandID string) (*model.Command, error) + }); ok { + returns.A, returns.B = hook.GetCommand(args.A) + returns.B = encodableError(returns.B) + } else { + return encodableError(fmt.Errorf("API GetCommand called but not implemented.")) + } + return nil +} + +type Z_UpdateCommandArgs struct { + A string + B *model.Command +} + +type Z_UpdateCommandReturns struct { + A *model.Command + B error +} + +func (g *apiRPCClient) UpdateCommand(commandID string, updatedCmd *model.Command) (*model.Command, error) { + _args := &Z_UpdateCommandArgs{commandID, updatedCmd} + _returns := &Z_UpdateCommandReturns{} + if err := g.client.Call("Plugin.UpdateCommand", _args, _returns); err != nil { + log.Printf("RPC call to UpdateCommand API failed: %s", err.Error()) + } + return _returns.A, _returns.B +} + +func (s *apiRPCServer) UpdateCommand(args *Z_UpdateCommandArgs, returns *Z_UpdateCommandReturns) error { + if hook, ok := s.impl.(interface { + UpdateCommand(commandID string, updatedCmd *model.Command) (*model.Command, error) + }); ok { + returns.A, returns.B = hook.UpdateCommand(args.A, args.B) + returns.B = encodableError(returns.B) + } else { + return encodableError(fmt.Errorf("API UpdateCommand called but not implemented.")) + } + return nil +} + +type Z_DeleteCommandArgs struct { + A string +} + +type Z_DeleteCommandReturns struct { + A error +} + +func (g *apiRPCClient) DeleteCommand(commandID string) error { + _args := &Z_DeleteCommandArgs{commandID} + _returns := &Z_DeleteCommandReturns{} + if err := g.client.Call("Plugin.DeleteCommand", _args, _returns); err != nil { + log.Printf("RPC call to DeleteCommand API failed: %s", err.Error()) + } + return _returns.A +} + +func (s *apiRPCServer) DeleteCommand(args *Z_DeleteCommandArgs, returns *Z_DeleteCommandReturns) error { + if hook, ok := s.impl.(interface { + DeleteCommand(commandID string) error + }); ok { + returns.A = hook.DeleteCommand(args.A) + returns.A = encodableError(returns.A) + } else { + return encodableError(fmt.Errorf("API DeleteCommand called but not implemented.")) + } + return nil +} diff --git a/plugin/plugintest/api.go b/plugin/plugintest/api.go index 191c0cc4e3..c97499ac09 100644 --- a/plugin/plugintest/api.go +++ b/plugin/plugintest/api.go @@ -168,6 +168,29 @@ func (_m *API) CreateChannel(channel *model.Channel) (*model.Channel, *model.App return r0, r1 } +// CreateCommand provides a mock function with given fields: cmd +func (_m *API) CreateCommand(cmd *model.Command) (*model.Command, error) { + ret := _m.Called(cmd) + + var r0 *model.Command + if rf, ok := ret.Get(0).(func(*model.Command) *model.Command); ok { + r0 = rf(cmd) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.Command) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*model.Command) error); ok { + r1 = rf(cmd) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // CreatePost provides a mock function with given fields: post func (_m *API) CreatePost(post *model.Post) (*model.Post, *model.AppError) { ret := _m.Called(post) @@ -366,6 +389,20 @@ func (_m *API) DeleteChannelMember(channelId string, userId string) *model.AppEr return r0 } +// DeleteCommand provides a mock function with given fields: commandID +func (_m *API) DeleteCommand(commandID string) error { + ret := _m.Called(commandID) + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(commandID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // DeleteEphemeralPost provides a mock function with given fields: userId, postId func (_m *API) DeleteEphemeralPost(userId string, postId string) { _m.Called(userId, postId) @@ -827,6 +864,29 @@ func (_m *API) GetChannelsForTeamForUser(teamId string, userId string, includeDe return r0, r1 } +// GetCommand provides a mock function with given fields: commandID +func (_m *API) GetCommand(commandID string) (*model.Command, error) { + ret := _m.Called(commandID) + + var r0 *model.Command + if rf, ok := ret.Get(0).(func(string) *model.Command); ok { + r0 = rf(commandID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.Command) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(commandID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetConfig provides a mock function with given fields: func (_m *API) GetConfig() *model.Config { ret := _m.Called() @@ -2347,6 +2407,98 @@ func (_m *API) KVSetWithOptions(key string, value []byte, options model.PluginKV return r0, r1 } +// ListBuiltInCommands provides a mock function with given fields: +func (_m *API) ListBuiltInCommands() ([]*model.Command, error) { + ret := _m.Called() + + var r0 []*model.Command + if rf, ok := ret.Get(0).(func() []*model.Command); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.Command) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListCommands provides a mock function with given fields: teamID +func (_m *API) ListCommands(teamID string) ([]*model.Command, error) { + ret := _m.Called(teamID) + + var r0 []*model.Command + if rf, ok := ret.Get(0).(func(string) []*model.Command); ok { + r0 = rf(teamID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.Command) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(teamID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListCustomCommands provides a mock function with given fields: teamID +func (_m *API) ListCustomCommands(teamID string) ([]*model.Command, error) { + ret := _m.Called(teamID) + + var r0 []*model.Command + if rf, ok := ret.Get(0).(func(string) []*model.Command); ok { + r0 = rf(teamID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.Command) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(teamID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListPluginCommands provides a mock function with given fields: teamID +func (_m *API) ListPluginCommands(teamID string) ([]*model.Command, error) { + ret := _m.Called(teamID) + + var r0 []*model.Command + if rf, ok := ret.Get(0).(func(string) []*model.Command); ok { + r0 = rf(teamID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.Command) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(teamID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // LoadPluginConfiguration provides a mock function with given fields: dest func (_m *API) LoadPluginConfiguration(dest interface{}) error { ret := _m.Called(dest) @@ -2925,6 +3077,29 @@ func (_m *API) UpdateChannelMemberRoles(channelId string, userId string, newRole return r0, r1 } +// UpdateCommand provides a mock function with given fields: commandID, updatedCmd +func (_m *API) UpdateCommand(commandID string, updatedCmd *model.Command) (*model.Command, error) { + ret := _m.Called(commandID, updatedCmd) + + var r0 *model.Command + if rf, ok := ret.Get(0).(func(string, *model.Command) *model.Command); ok { + r0 = rf(commandID, updatedCmd) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.Command) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, *model.Command) error); ok { + r1 = rf(commandID, updatedCmd) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // UpdateEphemeralPost provides a mock function with given fields: userId, post func (_m *API) UpdateEphemeralPost(userId string, post *model.Post) *model.Post { ret := _m.Called(userId, post) diff --git a/store/sqlstore/command_store.go b/store/sqlstore/command_store.go index fa58aa8cfb..acbe149f0d 100644 --- a/store/sqlstore/command_store.go +++ b/store/sqlstore/command_store.go @@ -40,6 +40,7 @@ func newSqlCommandStore(sqlStore SqlStore) store.CommandStore { tableo.ColMap("AutoCompleteHint").SetMaxSize(1024) tableo.ColMap("DisplayName").SetMaxSize(64) tableo.ColMap("Description").SetMaxSize(128) + tableo.ColMap("PluginId").SetMaxSize(190) } return s diff --git a/store/sqlstore/upgrade.go b/store/sqlstore/upgrade.go index 94efbaac19..30b12bba69 100644 --- a/store/sqlstore/upgrade.go +++ b/store/sqlstore/upgrade.go @@ -828,6 +828,7 @@ func upgradeDatabaseToVersion526(sqlStore SqlStore) { func upgradeDatabaseToVersion527(sqlStore SqlStore) { // TODO: uncomment when the time arrive to upgrade the DB for 5.27 // if shouldPerformUpgrade(sqlStore, VERSION_5_26_0, VERSION_5_27_0) { + sqlStore.CreateColumnIfNotExistsNoDefault("Commands", "PluginId", "VARCHAR(190)", "VARCHAR(190)") // saveSchemaVersion(sqlStore, VERSION_5_27_0) // }