[MM-56073] MMCTL delete post command (#27539)
Co-authored-by: Ben Schumacher <ben.schumacher@mattermost.com>
Этот коммит содержится в:
@@ -159,4 +159,6 @@ type Client interface {
|
||||
GetPreferenceByCategoryAndName(ctx context.Context, userId, category, preferenceName string) (*model.Preference, *model.Response, error)
|
||||
UpdatePreferences(ctx context.Context, userId string, preferences model.Preferences) (*model.Response, error)
|
||||
DeletePreferences(ctx context.Context, userId string, preferences model.Preferences) (*model.Response, error)
|
||||
PermanentDeletePost(ctx context.Context, postID string) (*model.Response, error)
|
||||
DeletePost(ctx context.Context, postId string) (*model.Response, error)
|
||||
}
|
||||
|
||||
@@ -41,6 +41,22 @@ var PostListCmd = &cobra.Command{
|
||||
RunE: withClient(postListCmdF),
|
||||
}
|
||||
|
||||
var PostDeleteCmd = &cobra.Command{
|
||||
Use: "delete [posts]",
|
||||
Short: "Mark posts as deleted or permanently delete posts with the --permanent flag",
|
||||
Long: `This command will mark the post as deleted and remove it from the user's clients, but it does not permanently delete the post from the database. Please use the --permanent flag to permanently delete a post and its attachments from your database.`,
|
||||
Example: ` # Mark Post as deleted
|
||||
$ mmctl post delete udjmt396tjghi8wnsk3a1qs1sw
|
||||
|
||||
# Permanently delete a post and it's file contents from the database and filestore
|
||||
$ mmctl post delete udjmt396tjghi8wnsk3a1qs1sw --permanent
|
||||
|
||||
# Permanently delete multiple posts and their file contents from the database and filestore
|
||||
$ mmctl post delete udjmt396tjghi8wnsk3a1qs1sw 7jgcjt7tyjyyu83qz81wo84w6o --permanent`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: withClient(deletePostsCmdF),
|
||||
}
|
||||
|
||||
const (
|
||||
ISO8601Layout = "2006-01-02T15:04:05-07:00"
|
||||
PostTimeFormat = "2006-01-02 15:04:05-07:00"
|
||||
@@ -55,9 +71,13 @@ func init() {
|
||||
PostListCmd.Flags().BoolP("follow", "f", false, "Output appended data as new messages are posted to the channel")
|
||||
PostListCmd.Flags().StringP("since", "s", "", "List messages posted after a certain time (ISO 8601)")
|
||||
|
||||
PostDeleteCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the post and a DB backup has been performed")
|
||||
PostDeleteCmd.Flags().Bool("permanent", false, "Permanently delete the post and its contents from the database")
|
||||
|
||||
PostCmd.AddCommand(
|
||||
PostCreateCmd,
|
||||
PostListCmd,
|
||||
PostDeleteCmd,
|
||||
)
|
||||
|
||||
RootCmd.AddCommand(PostCmd)
|
||||
@@ -217,3 +237,42 @@ func postListCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
return multiErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
func deletePostsCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
||||
permanent, err := cmd.Flags().GetBool("permanent")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
confirmFlag, _ := cmd.Flags().GetBool("confirm")
|
||||
if !confirmFlag && permanent {
|
||||
if err = getConfirmation("Are you sure you want to delete the posts specified?", true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var result *multierror.Error
|
||||
var deleteFunc func(ctx context.Context, postID string) (*model.Response, error)
|
||||
|
||||
if permanent {
|
||||
deleteFunc = c.PermanentDeletePost
|
||||
} else {
|
||||
deleteFunc = c.DeletePost
|
||||
}
|
||||
|
||||
for _, postID := range args {
|
||||
isValidId := model.IsValidId(postID)
|
||||
if !isValidId {
|
||||
printer.PrintError(fmt.Sprintf("Invalid postID: %s", postID))
|
||||
result = multierror.Append(result, err)
|
||||
continue
|
||||
}
|
||||
if _, err := deleteFunc(context.TODO(), postID); err != nil {
|
||||
printer.PrintError(fmt.Sprintf("Error deleting post: %s. Error: %s", postID, err.Error()))
|
||||
result = multierror.Append(result, err)
|
||||
continue
|
||||
}
|
||||
printer.Print(fmt.Sprintf("%s successfully deleted", postID))
|
||||
}
|
||||
return result.ErrorOrNil()
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
@@ -257,3 +258,122 @@ func (s *MmctlUnitTestSuite) TestPostListCmdF() {
|
||||
s.Len(printer.GetErrorLines(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *MmctlUnitTestSuite) TestDeletePostsCmdF() {
|
||||
postID1 := "ux9bxc1b8bf1zdoj1tfu14836e"
|
||||
postID2 := "ux9bxc1b8bf1zdoj1tfu14836f"
|
||||
|
||||
s.Run("invalid post id", func() {
|
||||
id := "invalid-id"
|
||||
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Flags().Bool("confirm", true, "")
|
||||
cmd.Flags().Bool("permanent", false, "")
|
||||
|
||||
err := deletePostsCmdF(s.client, cmd, []string{id})
|
||||
s.Require().Nil(err)
|
||||
s.Require().Equal("Invalid postID: invalid-id", printer.GetErrorLines()[0])
|
||||
})
|
||||
|
||||
s.Run("successfully permanently delete one post", func() {
|
||||
printer.Clean()
|
||||
s.client.
|
||||
EXPECT().
|
||||
PermanentDeletePost(context.TODO(), postID1).
|
||||
Return(&model.Response{StatusCode: http.StatusOK}, nil).
|
||||
Times(1)
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Flags().Bool("confirm", true, "")
|
||||
cmd.Flags().Bool("permanent", true, "")
|
||||
|
||||
err := deletePostsCmdF(s.client, cmd, []string{postID1})
|
||||
s.Require().Nil(err)
|
||||
s.Require().Equal(postID1+" successfully deleted", printer.GetLines()[0])
|
||||
})
|
||||
|
||||
s.Run("successfully soft delete one post", func() {
|
||||
printer.Clean()
|
||||
s.client.
|
||||
EXPECT().
|
||||
DeletePost(context.TODO(), postID1).
|
||||
Return(&model.Response{StatusCode: http.StatusOK}, nil).
|
||||
Times(1)
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Flags().Bool("confirm", true, "")
|
||||
cmd.Flags().Bool("permanent", false, "")
|
||||
|
||||
err := deletePostsCmdF(s.client, cmd, []string{postID1})
|
||||
s.Require().Nil(err)
|
||||
s.Require().Equal(postID1+" successfully deleted", printer.GetLines()[0])
|
||||
})
|
||||
|
||||
s.Run("successfully delete multiple posts", func() {
|
||||
printer.Clean()
|
||||
s.client.
|
||||
EXPECT().
|
||||
PermanentDeletePost(context.TODO(), postID1).
|
||||
Return(&model.Response{StatusCode: http.StatusOK}, nil).
|
||||
Times(1)
|
||||
s.client.
|
||||
EXPECT().
|
||||
PermanentDeletePost(context.TODO(), postID2).
|
||||
Return(&model.Response{StatusCode: http.StatusOK}, nil).
|
||||
Times(1)
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Flags().Bool("confirm", true, "")
|
||||
cmd.Flags().Bool("permanent", true, "")
|
||||
|
||||
err := deletePostsCmdF(s.client, cmd, []string{postID1, postID2})
|
||||
s.Require().Nil(err)
|
||||
s.Require().Equal(postID1+" successfully deleted", printer.GetLines()[0])
|
||||
s.Require().Equal(postID2+" successfully deleted", printer.GetLines()[1])
|
||||
})
|
||||
|
||||
s.Run("PermanentDeletePost api request returns an error", func() {
|
||||
printer.Clean()
|
||||
|
||||
mockError := errors.New("an error occurred on deleting a post")
|
||||
|
||||
s.client.
|
||||
EXPECT().
|
||||
PermanentDeletePost(context.TODO(), postID1).
|
||||
Return(&model.Response{StatusCode: http.StatusBadRequest}, mockError).
|
||||
Times(1)
|
||||
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Flags().Bool("confirm", true, "")
|
||||
cmd.Flags().Bool("permanent", true, "")
|
||||
|
||||
err := deletePostsCmdF(s.client, cmd, []string{postID1})
|
||||
s.Require().ErrorContains(err, "an error occurred on deleting a post")
|
||||
s.Require().Len(printer.GetErrorLines(), 1)
|
||||
s.Require().Equal("Error deleting post: "+postID1+". Error: an error occurred on deleting a post",
|
||||
printer.GetErrorLines()[0])
|
||||
})
|
||||
|
||||
s.Run("Delete multiple posts but one fails with an error", func() {
|
||||
printer.Clean()
|
||||
mockError := errors.New("an error occurred on deleting a post")
|
||||
s.client.
|
||||
EXPECT().
|
||||
PermanentDeletePost(context.TODO(), postID1).
|
||||
Return(&model.Response{StatusCode: http.StatusOK}, nil).
|
||||
Times(1)
|
||||
s.client.
|
||||
EXPECT().
|
||||
PermanentDeletePost(context.TODO(), postID2).
|
||||
Return(&model.Response{StatusCode: http.StatusBadRequest}, mockError).
|
||||
Times(1)
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Flags().Bool("confirm", true, "")
|
||||
cmd.Flags().Bool("permanent", true, "")
|
||||
|
||||
err := deletePostsCmdF(s.client, cmd, []string{postID1, postID2})
|
||||
s.Require().ErrorContains(err, "an error occurred on deleting a post")
|
||||
s.Require().Len(printer.GetLines(), 1)
|
||||
s.Require().Len(printer.GetErrorLines(), 1)
|
||||
s.Require().Equal(postID1+" successfully deleted", printer.GetLines()[0])
|
||||
s.Require().Equal("Error deleting post: "+postID2+". Error: an error occurred on deleting a post",
|
||||
printer.GetErrorLines()[0])
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,5 +38,6 @@ SEE ALSO
|
||||
|
||||
* `mmctl <mmctl.rst>`_ - Remote client for the Open Source, self-hosted Slack-alternative
|
||||
* `mmctl post create <mmctl_post_create.rst>`_ - Create a post
|
||||
* `mmctl post delete <mmctl_post_delete.rst>`_ - Mark posts as deleted or permanently delete posts with the --permanent flag
|
||||
* `mmctl post list <mmctl_post_list.rst>`_ - List posts for a channel
|
||||
|
||||
|
||||
60
server/cmd/mmctl/docs/mmctl_post_delete.rst
Обычный файл
60
server/cmd/mmctl/docs/mmctl_post_delete.rst
Обычный файл
@@ -0,0 +1,60 @@
|
||||
.. _mmctl_post_delete:
|
||||
|
||||
mmctl post delete
|
||||
-----------------
|
||||
|
||||
Mark posts as deleted or permanently delete posts with the --permanent flag
|
||||
|
||||
Synopsis
|
||||
~~~~~~~~
|
||||
|
||||
|
||||
This command will mark the post as deleted and remove it from the user's clients, but it does not permanently delete the post from the database. Please use the --permanent flag to permanently delete a post and its attachments from your database.
|
||||
|
||||
::
|
||||
|
||||
mmctl post delete [posts] [flags]
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
# Mark Post as deleted
|
||||
$ mmctl post delete udjmt396tjghi8wnsk3a1qs1sw
|
||||
|
||||
# Permanently delete a post and it's file contents from the database and filestore
|
||||
$ mmctl post delete udjmt396tjghi8wnsk3a1qs1sw --permanent
|
||||
|
||||
# Permanently delete multiple posts and their file contents from the database and filestore
|
||||
$ mmctl post delete udjmt396tjghi8wnsk3a1qs1sw 7jgcjt7tyjyyu83qz81wo84w6o --permanent
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
--confirm Confirm you really want to delete the post and a DB backup has been performed
|
||||
-h, --help help for delete
|
||||
--permanent Permanently delete the post and its contents from the database
|
||||
|
||||
Options inherited from parent commands
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
--config string path to the configuration file (default "$XDG_CONFIG_HOME/mmctl/config")
|
||||
--disable-pager disables paged output
|
||||
--insecure-sha1-intermediate allows to use insecure TLS protocols, such as SHA-1
|
||||
--insecure-tls-version allows to use TLS versions 1.0 and 1.1
|
||||
--json the output format will be in json format
|
||||
--local allows communicating with the server through a unix socket
|
||||
--quiet prevent mmctl to generate output for the commands
|
||||
--strict will only run commands if the mmctl version matches the server one
|
||||
--suppress-warnings disables printing warning messages
|
||||
|
||||
SEE ALSO
|
||||
~~~~~~~~
|
||||
|
||||
* `mmctl post <mmctl_post.rst>`_ - Management of posts
|
||||
|
||||
@@ -417,6 +417,21 @@ func (mr *MockClientMockRecorder) DeleteOutgoingWebhook(arg0, arg1 interface{})
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOutgoingWebhook", reflect.TypeOf((*MockClient)(nil).DeleteOutgoingWebhook), arg0, arg1)
|
||||
}
|
||||
|
||||
// DeletePost mocks base method.
|
||||
func (m *MockClient) DeletePost(arg0 context.Context, arg1 string) (*model.Response, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeletePost", arg0, arg1)
|
||||
ret0, _ := ret[0].(*model.Response)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// DeletePost indicates an expected call of DeletePost.
|
||||
func (mr *MockClientMockRecorder) DeletePost(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePost", reflect.TypeOf((*MockClient)(nil).DeletePost), arg0, arg1)
|
||||
}
|
||||
|
||||
// DeletePreferences mocks base method.
|
||||
func (m *MockClient) DeletePreferences(arg0 context.Context, arg1 string, arg2 model.Preferences) (*model.Response, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -1750,6 +1765,21 @@ func (mr *MockClientMockRecorder) PermanentDeleteChannel(arg0, arg1 interface{})
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PermanentDeleteChannel", reflect.TypeOf((*MockClient)(nil).PermanentDeleteChannel), arg0, arg1)
|
||||
}
|
||||
|
||||
// PermanentDeletePost mocks base method.
|
||||
func (m *MockClient) PermanentDeletePost(arg0 context.Context, arg1 string) (*model.Response, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "PermanentDeletePost", arg0, arg1)
|
||||
ret0, _ := ret[0].(*model.Response)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// PermanentDeletePost indicates an expected call of PermanentDeletePost.
|
||||
func (mr *MockClientMockRecorder) PermanentDeletePost(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PermanentDeletePost", reflect.TypeOf((*MockClient)(nil).PermanentDeletePost), arg0, arg1)
|
||||
}
|
||||
|
||||
// PermanentDeleteTeam mocks base method.
|
||||
func (m *MockClient) PermanentDeleteTeam(arg0 context.Context, arg1 string) (*model.Response, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
Ссылка в новой задаче
Block a user