[MM-56073] MMCTL delete post command (#27539)
Co-authored-by: Ben Schumacher <ben.schumacher@mattermost.com>
Этот коммит содержится в:
@@ -491,6 +491,7 @@ type AppIface interface {
|
||||
CheckUserPostflightAuthenticationCriteria(rctx request.CTX, user *model.User) *model.AppError
|
||||
CheckUserPreflightAuthenticationCriteria(rctx request.CTX, user *model.User, mfaToken string) *model.AppError
|
||||
CheckWebConn(userID, connectionID string) *platform.CheckConnResult
|
||||
CleanUpAfterPostDeletion(c request.CTX, post *model.Post, deleteByID string) *model.AppError
|
||||
CleanupReportChunks(format string, prefix string, numberOfChunks int) *model.AppError
|
||||
ClearChannelMembersCache(c request.CTX, channelID string) error
|
||||
ClearLatestVersionCache(rctx request.CTX)
|
||||
@@ -579,7 +580,7 @@ type AppIface interface {
|
||||
DeleteOAuthApp(rctx request.CTX, appID string) *model.AppError
|
||||
DeleteOutgoingWebhook(hookID string) *model.AppError
|
||||
DeletePluginKey(pluginID string, key string) *model.AppError
|
||||
DeletePost(c request.CTX, postID, deleteByID string) (*model.Post, *model.AppError)
|
||||
DeletePost(rctx request.CTX, postID, deleteByID string) (*model.Post, *model.AppError)
|
||||
DeletePreferences(c request.CTX, userID string, preferences model.Preferences) *model.AppError
|
||||
DeleteReactionForPost(c request.CTX, reaction *model.Reaction) *model.AppError
|
||||
DeleteRemoteCluster(remoteClusterId string) (bool, *model.AppError)
|
||||
@@ -986,9 +987,11 @@ type AppIface interface {
|
||||
PatchUser(c request.CTX, userID string, patch *model.UserPatch, asAdmin bool) (*model.User, *model.AppError)
|
||||
PermanentDeleteAllUsers(c request.CTX) *model.AppError
|
||||
PermanentDeleteChannel(c request.CTX, channel *model.Channel) *model.AppError
|
||||
PermanentDeleteFilesByPost(rctx request.CTX, postID string) *model.AppError
|
||||
PermanentDeletePost(rctx request.CTX, postID, deleteByID string) *model.AppError
|
||||
PermanentDeleteTeam(c request.CTX, team *model.Team) *model.AppError
|
||||
PermanentDeleteTeamId(c request.CTX, teamID string) *model.AppError
|
||||
PermanentDeleteUser(c request.CTX, user *model.User) *model.AppError
|
||||
PermanentDeleteUser(rctx request.CTX, user *model.User) *model.AppError
|
||||
PostActionCookieSecret() []byte
|
||||
PostAddToChannelMessage(c request.CTX, user *model.User, addedUser *model.User, channel *model.Channel, postRootId string) *model.AppError
|
||||
PostPatchWithProxyRemovedFromImageURLs(patch *model.PostPatch) *model.PostPatch
|
||||
@@ -1022,6 +1025,8 @@ type AppIface interface {
|
||||
RemoveDirectory(path string) *model.AppError
|
||||
RemoveExportFile(path string) *model.AppError
|
||||
RemoveFile(path string) *model.AppError
|
||||
RemoveFileFromFileStore(rctx request.CTX, path string)
|
||||
RemoveFilesFromFileStore(rctx request.CTX, fileInfos []*model.FileInfo)
|
||||
RemoveLdapPrivateCertificate() *model.AppError
|
||||
RemoveLdapPublicCertificate() *model.AppError
|
||||
RemoveNotifications(c request.CTX, post *model.Post, channel *model.Channel) error
|
||||
|
||||
@@ -1594,3 +1594,65 @@ func getFileExtFromMimeType(mimeType string) string {
|
||||
}
|
||||
return "jpg"
|
||||
}
|
||||
|
||||
func (a *App) PermanentDeleteFilesByPost(rctx request.CTX, postID string) *model.AppError {
|
||||
fileInfos, err := a.Srv().Store().FileInfo().GetForPost(postID, false, true, true)
|
||||
if err != nil {
|
||||
return model.NewAppError("PermanentDeleteFilesByPost", "app.file_info.get_by_post_id.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
if len(fileInfos) == 0 {
|
||||
rctx.Logger().Debug("No files found for post", mlog.String("post_id", postID))
|
||||
return nil
|
||||
}
|
||||
|
||||
a.RemoveFilesFromFileStore(rctx, fileInfos)
|
||||
|
||||
err = a.Srv().Store().FileInfo().PermanentDeleteForPost(rctx, postID)
|
||||
if err != nil {
|
||||
return model.NewAppError("PermanentDeleteFilesByPost", "app.file_info.permanent_delete_for_post.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
a.Srv().Store().FileInfo().InvalidateFileInfosForPostCache(postID, true)
|
||||
a.Srv().Store().FileInfo().InvalidateFileInfosForPostCache(postID, false)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) RemoveFilesFromFileStore(rctx request.CTX, fileInfos []*model.FileInfo) {
|
||||
for _, info := range fileInfos {
|
||||
a.RemoveFileFromFileStore(rctx, info.Path)
|
||||
if info.PreviewPath != "" {
|
||||
a.RemoveFileFromFileStore(rctx, info.PreviewPath)
|
||||
}
|
||||
if info.ThumbnailPath != "" {
|
||||
a.RemoveFileFromFileStore(rctx, info.ThumbnailPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) RemoveFileFromFileStore(rctx request.CTX, path string) {
|
||||
res, appErr := a.FileExists(path)
|
||||
if appErr != nil {
|
||||
rctx.Logger().Warn(
|
||||
"Error checking existence of file",
|
||||
mlog.String("path", path),
|
||||
mlog.Err(appErr),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if !res {
|
||||
rctx.Logger().Warn("File not found", mlog.String("path", path))
|
||||
return
|
||||
}
|
||||
|
||||
appErr = a.RemoveFile(path)
|
||||
if appErr != nil {
|
||||
rctx.Logger().Warn(
|
||||
"Unable to remove file",
|
||||
mlog.String("path", path),
|
||||
mlog.Err(appErr),
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -730,3 +730,59 @@ func TestSetFileSearchableContent(t *testing.T) {
|
||||
require.Nil(t, appErr)
|
||||
assert.Equal(t, 1, len(result.Order))
|
||||
}
|
||||
|
||||
func TestPermanentDeleteFilesByPost(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("should delete files for post", func(t *testing.T) {
|
||||
// Create a post with a file attachment.
|
||||
teamID := th.BasicTeam.Id
|
||||
channelID := th.BasicChannel.Id
|
||||
userID := th.BasicUser.Id
|
||||
filename := "test"
|
||||
data := []byte("abcd")
|
||||
|
||||
info1, err := th.App.DoUploadFile(th.Context, time.Date(2007, 2, 4, 1, 2, 3, 4, time.Local), teamID, channelID, userID, filename, data, true)
|
||||
require.Nil(t, err)
|
||||
|
||||
post := &model.Post{
|
||||
Message: "asd",
|
||||
ChannelId: channelID,
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: userID,
|
||||
CreateAt: 0,
|
||||
FileIds: []string{info1.Id},
|
||||
}
|
||||
|
||||
post, err = th.App.CreatePost(th.Context, post, th.BasicChannel, false, true)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = th.App.PermanentDeleteFilesByPost(th.Context, post.Id)
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = th.App.GetFileInfo(th.Context, info1.Id)
|
||||
require.NotNil(t, err)
|
||||
})
|
||||
|
||||
t.Run("should not delete files for post that doesn't exist", func(t *testing.T) {
|
||||
err := th.App.PermanentDeleteFilesByPost(th.Context, "postId1")
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("should handle empty file list", func(t *testing.T) {
|
||||
post := &model.Post{
|
||||
Message: "asd",
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: th.BasicUser.Id,
|
||||
CreateAt: 0,
|
||||
}
|
||||
|
||||
post, err := th.App.CreatePost(th.Context, post, th.BasicChannel, false, true)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = th.App.PermanentDeleteFilesByPost(th.Context, post.Id)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1455,6 +1455,28 @@ func (a *OpenTracingAppLayer) CheckWebConn(userID string, connectionID string) *
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) CleanUpAfterPostDeletion(c request.CTX, post *model.Post, deleteByID string) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CleanUpAfterPostDeletion")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.CleanUpAfterPostDeletion(c, post, deleteByID)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) CleanupReportChunks(format string, prefix string, numberOfChunks int) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CleanupReportChunks")
|
||||
@@ -3588,7 +3610,7 @@ func (a *OpenTracingAppLayer) DeletePluginKey(pluginID string, key string) *mode
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) DeletePost(c request.CTX, postID string, deleteByID string) (*model.Post, *model.AppError) {
|
||||
func (a *OpenTracingAppLayer) DeletePost(rctx request.CTX, postID string, deleteByID string) (*model.Post, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DeletePost")
|
||||
|
||||
@@ -3600,7 +3622,7 @@ func (a *OpenTracingAppLayer) DeletePost(c request.CTX, postID string, deleteByI
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.DeletePost(c, postID, deleteByID)
|
||||
resultVar0, resultVar1 := a.app.DeletePost(rctx, postID, deleteByID)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
@@ -13618,6 +13640,50 @@ func (a *OpenTracingAppLayer) PermanentDeleteChannel(c request.CTX, channel *mod
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) PermanentDeleteFilesByPost(rctx request.CTX, postID string) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PermanentDeleteFilesByPost")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.PermanentDeleteFilesByPost(rctx, postID)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) PermanentDeletePost(rctx request.CTX, postID string, deleteByID string) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PermanentDeletePost")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.PermanentDeletePost(rctx, postID, deleteByID)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) PermanentDeleteTeam(c request.CTX, team *model.Team) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PermanentDeleteTeam")
|
||||
@@ -13662,7 +13728,7 @@ func (a *OpenTracingAppLayer) PermanentDeleteTeamId(c request.CTX, teamID string
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) PermanentDeleteUser(c request.CTX, user *model.User) *model.AppError {
|
||||
func (a *OpenTracingAppLayer) PermanentDeleteUser(rctx request.CTX, user *model.User) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PermanentDeleteUser")
|
||||
|
||||
@@ -13674,7 +13740,7 @@ func (a *OpenTracingAppLayer) PermanentDeleteUser(c request.CTX, user *model.Use
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.PermanentDeleteUser(c, user)
|
||||
resultVar0 := a.app.PermanentDeleteUser(rctx, user)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
@@ -14454,6 +14520,36 @@ func (a *OpenTracingAppLayer) RemoveFile(path string) *model.AppError {
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) RemoveFileFromFileStore(rctx request.CTX, path string) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RemoveFileFromFileStore")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
a.app.RemoveFileFromFileStore(rctx, path)
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) RemoveFilesFromFileStore(rctx request.CTX, fileInfos []*model.FileInfo) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RemoveFilesFromFileStore")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
a.app.RemoveFilesFromFileStore(rctx, fileInfos)
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) RemoveLdapPrivateCertificate() *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RemoveLdapPrivateCertificate")
|
||||
|
||||
@@ -1361,23 +1361,22 @@ func (a *App) GetPostsForChannelAroundLastUnread(c request.CTX, channelID, userI
|
||||
return postList, nil
|
||||
}
|
||||
|
||||
func (a *App) DeletePost(c request.CTX, postID, deleteByID string) (*model.Post, *model.AppError) {
|
||||
post, err := a.Srv().Store().Post().GetSingle(sqlstore.RequestContextWithMaster(c), postID, false)
|
||||
func (a *App) DeletePost(rctx request.CTX, postID, deleteByID string) (*model.Post, *model.AppError) {
|
||||
post, err := a.Srv().Store().Post().GetSingle(sqlstore.RequestContextWithMaster(rctx), postID, false)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("DeletePost", "app.post.get.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
}
|
||||
|
||||
channel, appErr := a.GetChannel(c, post.ChannelId)
|
||||
channel, appErr := a.GetChannel(rctx, post.ChannelId)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
if channel.DeleteAt != 0 {
|
||||
appErr := model.NewAppError("DeletePost", "api.post.delete_post.can_not_delete_post_in_deleted.error", nil, "", http.StatusBadRequest)
|
||||
return nil, appErr
|
||||
return nil, model.NewAppError("DeletePost", "api.post.delete_post.can_not_delete_post_in_deleted.error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
err = a.Srv().Store().Post().Delete(c, postID, model.GetMillis(), deleteByID)
|
||||
err = a.Srv().Store().Post().Delete(rctx, postID, model.GetMillis(), deleteByID)
|
||||
if err != nil {
|
||||
var nfErr *store.ErrNotFound
|
||||
switch {
|
||||
@@ -1388,60 +1387,18 @@ func (a *App) DeletePost(c request.CTX, postID, deleteByID string) (*model.Post,
|
||||
}
|
||||
}
|
||||
|
||||
if post.RootId == "" {
|
||||
if appErr := a.DeletePersistentNotification(c, post); appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
}
|
||||
|
||||
postJSON, err := json.Marshal(post)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("DeletePost", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
userMessage := model.NewWebSocketEvent(model.WebsocketEventPostDeleted, "", post.ChannelId, "", nil, "")
|
||||
userMessage.Add("post", string(postJSON))
|
||||
userMessage.GetBroadcast().ContainsSanitizedData = true
|
||||
a.Publish(userMessage)
|
||||
|
||||
adminMessage := model.NewWebSocketEvent(model.WebsocketEventPostDeleted, "", post.ChannelId, "", nil, "")
|
||||
adminMessage.Add("post", string(postJSON))
|
||||
adminMessage.Add("delete_by", deleteByID)
|
||||
adminMessage.GetBroadcast().ContainsSensitiveData = true
|
||||
a.Publish(adminMessage)
|
||||
|
||||
if len(post.FileIds) > 0 {
|
||||
a.Srv().Go(func() {
|
||||
a.deletePostFiles(c, post.Id)
|
||||
a.deletePostFiles(rctx, post.Id)
|
||||
})
|
||||
a.Srv().Store().FileInfo().InvalidateFileInfosForPostCache(postID, true)
|
||||
a.Srv().Store().FileInfo().InvalidateFileInfosForPostCache(postID, false)
|
||||
}
|
||||
a.Srv().Go(func() {
|
||||
a.deleteFlaggedPosts(c, post.Id)
|
||||
})
|
||||
|
||||
pluginPost := post.ForPlugin()
|
||||
pluginContext := pluginContext(c)
|
||||
a.Srv().Go(func() {
|
||||
a.ch.RunMultiHook(func(hooks plugin.Hooks) bool {
|
||||
hooks.MessageHasBeenDeleted(pluginContext, pluginPost)
|
||||
return true
|
||||
}, plugin.MessageHasBeenDeletedID)
|
||||
})
|
||||
|
||||
a.Srv().Go(func() {
|
||||
if err = a.RemoveNotifications(c, post, channel); err != nil {
|
||||
c.Logger().Error("DeletePost failed to delete notification", mlog.Err(err))
|
||||
}
|
||||
})
|
||||
|
||||
// delete drafts associated with the post when deleting the post
|
||||
a.Srv().Go(func() {
|
||||
a.deleteDraftsAssociatedWithPost(c, channel, post)
|
||||
})
|
||||
|
||||
a.invalidateCacheForChannelPosts(post.ChannelId)
|
||||
appErr = a.CleanUpAfterPostDeletion(rctx, post, deleteByID)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
return post, nil
|
||||
}
|
||||
@@ -2646,3 +2603,86 @@ func (a *App) MoveThread(c request.CTX, postID string, sourceChannelID, channelI
|
||||
c.Logger().Info(msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) PermanentDeletePost(rctx request.CTX, postID, deleteByID string) *model.AppError {
|
||||
post, err := a.Srv().Store().Post().GetSingle(sqlstore.RequestContextWithMaster(rctx), postID, true)
|
||||
if err != nil {
|
||||
return model.NewAppError("DeletePost", "app.post.get.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
}
|
||||
|
||||
if len(post.FileIds) > 0 {
|
||||
appErr := a.PermanentDeleteFilesByPost(rctx, post.Id)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
}
|
||||
|
||||
err = a.Srv().Store().Post().PermanentDelete(rctx, post.Id)
|
||||
if err != nil {
|
||||
return model.NewAppError("PermanentDeletePost", "app.post.permanent_delete_post.error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
appErr := a.CleanUpAfterPostDeletion(rctx, post, deleteByID)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) CleanUpAfterPostDeletion(c request.CTX, post *model.Post, deleteByID string) *model.AppError {
|
||||
channel, appErr := a.GetChannel(c, post.ChannelId)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
if post.RootId == "" {
|
||||
if appErr := a.DeletePersistentNotification(c, post); appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
}
|
||||
|
||||
postJSON, err := json.Marshal(post)
|
||||
if err != nil {
|
||||
return model.NewAppError("DeletePost", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
userMessage := model.NewWebSocketEvent(model.WebsocketEventPostDeleted, "", post.ChannelId, "", nil, "")
|
||||
userMessage.Add("post", string(postJSON))
|
||||
userMessage.GetBroadcast().ContainsSanitizedData = true
|
||||
a.Publish(userMessage)
|
||||
|
||||
adminMessage := model.NewWebSocketEvent(model.WebsocketEventPostDeleted, "", post.ChannelId, "", nil, "")
|
||||
adminMessage.Add("post", string(postJSON))
|
||||
adminMessage.Add("delete_by", deleteByID)
|
||||
adminMessage.GetBroadcast().ContainsSensitiveData = true
|
||||
a.Publish(adminMessage)
|
||||
|
||||
a.Srv().Go(func() {
|
||||
a.deleteFlaggedPosts(c, post.Id)
|
||||
})
|
||||
|
||||
pluginPost := post.ForPlugin()
|
||||
pluginContext := pluginContext(c)
|
||||
a.Srv().Go(func() {
|
||||
a.ch.RunMultiHook(func(hooks plugin.Hooks) bool {
|
||||
hooks.MessageHasBeenDeleted(pluginContext, pluginPost)
|
||||
return true
|
||||
}, plugin.MessageHasBeenDeletedID)
|
||||
})
|
||||
|
||||
a.Srv().Go(func() {
|
||||
if err = a.RemoveNotifications(c, post, channel); err != nil {
|
||||
c.Logger().Error("DeletePost failed to delete notification", mlog.Err(err))
|
||||
}
|
||||
})
|
||||
|
||||
// delete drafts associated with the post when deleting the post
|
||||
a.Srv().Go(func() {
|
||||
a.deleteDraftsAssociatedWithPost(c, channel, post)
|
||||
})
|
||||
|
||||
a.invalidateCacheForChannelPosts(post.ChannelId)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3569,3 +3569,94 @@ func TestValidateMoveOrCopy(t *testing.T) {
|
||||
require.Equal(t, "the thread is 2 posts long, but this command is configured to only move threads of up to 1 posts", e.Error())
|
||||
})
|
||||
}
|
||||
|
||||
func TestPermanentDeletePost(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("should permanently delete a post and its file attachment", func(t *testing.T) {
|
||||
// Create a post with a file attachment.
|
||||
teamID := th.BasicTeam.Id
|
||||
channelID := th.BasicChannel.Id
|
||||
userID := th.BasicUser.Id
|
||||
filename := "test"
|
||||
data := []byte("abcd")
|
||||
|
||||
info1, err := th.App.DoUploadFile(th.Context, time.Date(2007, 2, 4, 1, 2, 3, 4, time.Local), teamID, channelID, userID, filename, data, true)
|
||||
assert.Nil(t, err)
|
||||
|
||||
post := &model.Post{
|
||||
Message: "asd",
|
||||
ChannelId: channelID,
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: userID,
|
||||
CreateAt: 0,
|
||||
FileIds: []string{info1.Id},
|
||||
}
|
||||
|
||||
post, err = th.App.CreatePost(th.Context, post, th.BasicChannel, false, true)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Delete the post.
|
||||
err = th.App.PermanentDeletePost(th.Context, post.Id, userID)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Wait for the cleanup routine to finish.
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
|
||||
// Check that the post can no longer be reached.
|
||||
_, err = th.App.GetSinglePost(th.Context, post.Id, true)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// Check that the file can no longer be reached.
|
||||
_, err = th.App.GetFileInfo(th.Context, info1.Id)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
|
||||
t.Run("should permanently delete a post that is soft deleted", func(t *testing.T) {
|
||||
// Create a post with a file attachment.
|
||||
teamID := th.BasicTeam.Id
|
||||
channelID := th.BasicChannel.Id
|
||||
userID := th.BasicUser.Id
|
||||
filename := "test"
|
||||
data := []byte("abcd")
|
||||
|
||||
info1, err := th.App.DoUploadFile(th.Context, time.Date(2007, 2, 4, 1, 2, 3, 4, time.Local), teamID, channelID, userID, filename, data, true)
|
||||
require.Nil(t, err)
|
||||
|
||||
post := &model.Post{
|
||||
Message: "asd",
|
||||
ChannelId: channelID,
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: userID,
|
||||
CreateAt: 0,
|
||||
FileIds: []string{info1.Id},
|
||||
}
|
||||
|
||||
post, err = th.App.CreatePost(th.Context, post, th.BasicChannel, false, true)
|
||||
assert.Nil(t, err)
|
||||
|
||||
infos, sErr := th.App.Srv().Store().FileInfo().GetForPost(post.Id, true, true, false)
|
||||
require.NoError(t, sErr)
|
||||
assert.Len(t, infos, 1)
|
||||
|
||||
// Soft delete the post.
|
||||
_, err = th.App.DeletePost(th.Context, post.Id, userID)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Wait for the cleanup routine to finish.
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
|
||||
// Delete the post.
|
||||
err = th.App.PermanentDeletePost(th.Context, post.Id, userID)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Check that the post can no longer be reached.
|
||||
_, err = th.App.GetSinglePost(th.Context, post.Id, true)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
infos, sErr = th.App.Srv().Store().FileInfo().GetForPost(post.Id, true, true, false)
|
||||
require.NoError(t, sErr)
|
||||
assert.Len(t, infos, 0)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1759,13 +1759,13 @@ func (a *App) UpdateUserRolesWithUser(c request.CTX, user *model.User, newRoles
|
||||
return ruser, nil
|
||||
}
|
||||
|
||||
func (a *App) PermanentDeleteUser(c request.CTX, user *model.User) *model.AppError {
|
||||
c.Logger().Warn("Attempting to permanently delete account", mlog.String("user_id", user.Id), mlog.String("user_email", user.Email))
|
||||
func (a *App) PermanentDeleteUser(rctx request.CTX, user *model.User) *model.AppError {
|
||||
rctx.Logger().Warn("Attempting to permanently delete account", mlog.String("user_id", user.Id), mlog.String("user_email", user.Email))
|
||||
if user.IsInRole(model.SystemAdminRoleId) {
|
||||
c.Logger().Warn("You are deleting a user that is a system administrator. You may need to set another account as the system administrator using the command line tools.", mlog.String("user_email", user.Email))
|
||||
rctx.Logger().Warn("You are deleting a user that is a system administrator. You may need to set another account as the system administrator using the command line tools.", mlog.String("user_email", user.Email))
|
||||
}
|
||||
|
||||
if _, err := a.UpdateActive(c, user, false); err != nil {
|
||||
if _, err := a.UpdateActive(rctx, user, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1797,7 +1797,7 @@ func (a *App) PermanentDeleteUser(c request.CTX, user *model.User) *model.AppErr
|
||||
return model.NewAppError("PermanentDeleteUser", "app.preference.permanent_delete_by_user.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
if err := a.Srv().Store().Channel().PermanentDeleteMembersByUser(c, user.Id); err != nil {
|
||||
if err := a.Srv().Store().Channel().PermanentDeleteMembersByUser(rctx, user.Id); err != nil {
|
||||
return model.NewAppError("PermanentDeleteUser", "app.channel.permanent_delete_members_by_user.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
@@ -1805,7 +1805,7 @@ func (a *App) PermanentDeleteUser(c request.CTX, user *model.User) *model.AppErr
|
||||
return model.NewAppError("PermanentDeleteUser", "app.group.permanent_delete_members_by_user.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
if err := a.Srv().Store().Post().PermanentDeleteByUser(c, user.Id); err != nil {
|
||||
if err := a.Srv().Store().Post().PermanentDeleteByUser(rctx, user.Id); err != nil {
|
||||
return model.NewAppError("PermanentDeleteUser", "app.post.permanent_delete_by_user.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
@@ -1825,35 +1825,10 @@ func (a *App) PermanentDeleteUser(c request.CTX, user *model.User) *model.AppErr
|
||||
|
||||
infos, err := a.Srv().Store().FileInfo().GetForUser(user.Id)
|
||||
if err != nil {
|
||||
c.Logger().Warn("Error getting file list for user from FileInfoStore", mlog.Err(err))
|
||||
rctx.Logger().Warn("Error getting file list for user from FileInfoStore", mlog.Err(err))
|
||||
}
|
||||
|
||||
for _, info := range infos {
|
||||
res, err := a.FileExists(info.Path)
|
||||
if err != nil {
|
||||
c.Logger().Warn(
|
||||
"Error checking existence of file",
|
||||
mlog.String("path", info.Path),
|
||||
mlog.Err(err),
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
if !res {
|
||||
c.Logger().Warn("File not found", mlog.String("path", info.Path))
|
||||
continue
|
||||
}
|
||||
|
||||
err = a.RemoveFile(info.Path)
|
||||
|
||||
if err != nil {
|
||||
c.Logger().Warn(
|
||||
"Unable to remove file",
|
||||
mlog.String("path", info.Path),
|
||||
mlog.Err(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
a.RemoveFilesFromFileStore(rctx, infos)
|
||||
|
||||
// delete directory containing user's profile image
|
||||
profileImageDirectory := getProfileImageDirectory(user.Id)
|
||||
@@ -1864,7 +1839,7 @@ func (a *App) PermanentDeleteUser(c request.CTX, user *model.User) *model.AppErr
|
||||
|
||||
if errProfileImageExists != nil {
|
||||
fileHandlingErrorsFound = true
|
||||
c.Logger().Warn(
|
||||
rctx.Logger().Warn(
|
||||
"Error checking existence of profile image.",
|
||||
mlog.String("path", profileImagePath),
|
||||
mlog.Err(errProfileImageExists),
|
||||
@@ -1876,7 +1851,7 @@ func (a *App) PermanentDeleteUser(c request.CTX, user *model.User) *model.AppErr
|
||||
|
||||
if errRemoveDirectory != nil {
|
||||
fileHandlingErrorsFound = true
|
||||
c.Logger().Warn(
|
||||
rctx.Logger().Warn(
|
||||
"Unable to remove profile image directory",
|
||||
mlog.String("path", profileImageDirectory),
|
||||
mlog.Err(errRemoveDirectory),
|
||||
@@ -1884,11 +1859,11 @@ func (a *App) PermanentDeleteUser(c request.CTX, user *model.User) *model.AppErr
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := a.Srv().Store().FileInfo().PermanentDeleteByUser(c, user.Id); err != nil {
|
||||
if _, err := a.Srv().Store().FileInfo().PermanentDeleteByUser(rctx, user.Id); err != nil {
|
||||
return model.NewAppError("PermanentDeleteUser", "app.file_info.permanent_delete_by_user.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
if err := a.Srv().Store().User().PermanentDelete(c, user.Id); err != nil {
|
||||
if err := a.Srv().Store().User().PermanentDelete(rctx, user.Id); err != nil {
|
||||
return model.NewAppError("PermanentDeleteUser", "app.user.permanent_delete.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
@@ -1896,7 +1871,7 @@ func (a *App) PermanentDeleteUser(c request.CTX, user *model.User) *model.AppErr
|
||||
return model.NewAppError("PermanentDeleteUser", "app.audit.permanent_delete_by_user.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
if err := a.Srv().Store().Team().RemoveAllMembersByUser(c, user.Id); err != nil {
|
||||
if err := a.Srv().Store().Team().RemoveAllMembersByUser(rctx, user.Id); err != nil {
|
||||
return model.NewAppError("PermanentDeleteUser", "app.team.remove_member.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
@@ -1906,7 +1881,7 @@ func (a *App) PermanentDeleteUser(c request.CTX, user *model.User) *model.AppErr
|
||||
return model.NewAppError("PermanentDeleteUser", "app.file_info.permanent_delete_by_user.app_error", nil, "Couldn't delete profile image of the user.", http.StatusAccepted)
|
||||
}
|
||||
|
||||
c.Logger().Warn("Permanently deleted account", mlog.String("user_email", user.Email), mlog.String("user_id", user.Id))
|
||||
rctx.Logger().Warn("Permanently deleted account", mlog.String("user_email", user.Email), mlog.String("user_id", user.Id))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user