[MM-56073] MMCTL delete post command (#27539)

Co-authored-by: Ben Schumacher <ben.schumacher@mattermost.com>
Этот коммит содержится в:
Ben Cooke
2024-10-08 10:45:31 -04:00
коммит произвёл GitHub
родитель a671f80d2c
Коммит b3c7ef0b97
39 изменённых файлов: 1246 добавлений и 152 удалений

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

@@ -626,13 +626,28 @@ func deletePost(c *Context, w http.ResponseWriter, _ *http.Request) {
return
}
permanent := c.Params.Permanent
auditRec := c.MakeAuditRecord("deletePost", audit.Fail)
defer c.LogAuditRecWithLevel(auditRec, app.LevelContent)
audit.AddEventParameter(auditRec, "post_id", c.Params.PostId)
audit.AddEventParameter(auditRec, "permanent", permanent)
post, err := c.App.GetSinglePost(c.AppContext, c.Params.PostId, false)
if err != nil {
c.SetPermissionError(model.PermissionDeletePost)
includeDeleted := permanent
if permanent && !*c.App.Config().ServiceSettings.EnableAPIPostDeletion {
c.Err = model.NewAppError("deletePost", "api.post.delete_post.not_enabled.app_error", nil, "postId="+c.Params.PostId, http.StatusNotImplemented)
return
}
if permanent && !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.SetPermissionError(model.PermissionManageSystem)
return
}
post, appErr := c.App.GetSinglePost(c.AppContext, c.Params.PostId, includeDeleted)
if appErr != nil {
c.Err = appErr
return
}
auditRec.AddEventPriorState(post)
@@ -650,8 +665,14 @@ func deletePost(c *Context, w http.ResponseWriter, _ *http.Request) {
}
}
if _, err := c.App.DeletePost(c.AppContext, c.Params.PostId, c.AppContext.Session().UserId); err != nil {
c.Err = err
if permanent {
appErr = c.App.PermanentDeletePost(c.AppContext, c.Params.PostId, c.AppContext.Session().UserId)
} else {
_, appErr = c.App.DeletePost(c.AppContext, c.Params.PostId, c.AppContext.Session().UserId)
}
if appErr != nil {
c.Err = appErr
return
}

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

@@ -3,10 +3,53 @@
package api4
import "net/http"
import (
"net/http"
"github.com/mattermost/mattermost/server/v8/channels/app"
"github.com/mattermost/mattermost/server/v8/channels/audit"
)
func (api *API) InitPostLocal() {
api.BaseRoutes.Post.Handle("", api.APILocal(getPost)).Methods(http.MethodGet)
api.BaseRoutes.PostsForChannel.Handle("", api.APILocal(getPostsForChannel)).Methods(http.MethodGet)
api.BaseRoutes.Post.Handle("", api.APILocal(localDeletePost)).Methods(http.MethodDelete)
}
func localDeletePost(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequirePostId()
if c.Err != nil {
return
}
permanent := c.Params.Permanent
auditRec := c.MakeAuditRecord("localDeletePost", audit.Fail)
defer c.LogAuditRecWithLevel(auditRec, app.LevelContent)
audit.AddEventParameter(auditRec, "post_id", c.Params.PostId)
audit.AddEventParameter(auditRec, "permanent", permanent)
includeDeleted := permanent
post, appErr := c.App.GetSinglePost(c.AppContext, c.Params.PostId, includeDeleted)
if appErr != nil {
c.Err = appErr
return
}
auditRec.AddEventPriorState(post)
auditRec.AddEventObjectType("post")
if permanent {
appErr = c.App.PermanentDeletePost(c.AppContext, c.Params.PostId, c.AppContext.Session().UserId)
} else {
_, appErr = c.App.DeletePost(c.AppContext, c.Params.PostId, c.AppContext.Session().UserId)
}
if appErr != nil {
c.Err = appErr
return
}
auditRec.Success()
ReturnStatusOK(w)
}

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

@@ -2817,39 +2817,124 @@ func TestDeletePost(t *testing.T) {
defer th.TearDown()
client := th.Client
resp, err := client.DeletePost(context.Background(), "")
require.Error(t, err)
CheckNotFoundStatus(t, resp)
t.Run("Post not found", func(t *testing.T) {
resp, err := client.DeletePost(context.Background(), "")
require.Error(t, err)
CheckNotFoundStatus(t, resp)
})
resp, err = client.DeletePost(context.Background(), "junk")
require.Error(t, err)
CheckBadRequestStatus(t, resp)
t.Run("Post doesn't exist", func(t *testing.T) {
resp, err := client.DeletePost(context.Background(), "junk")
require.Error(t, err)
CheckBadRequestStatus(t, resp)
})
resp, err = client.DeletePost(context.Background(), th.BasicPost.Id)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
t.Run("No permissions to delete a post", func(t *testing.T) {
resp, err := client.DeletePost(context.Background(), th.BasicPost.Id)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
})
client.Login(context.Background(), th.TeamAdminUser.Email, th.TeamAdminUser.Password)
_, err = client.DeletePost(context.Background(), th.BasicPost.Id)
require.NoError(t, err)
t.Run("Try to delete a post across different user roles", func(t *testing.T) {
client.Login(context.Background(), th.TeamAdminUser.Email, th.TeamAdminUser.Password)
_, cErr := client.DeletePost(context.Background(), th.BasicPost.Id)
require.NoError(t, cErr)
post := th.CreatePost()
user := th.CreateUser()
post := th.CreatePost()
post2 := th.CreatePost()
user := th.CreateUser()
client.Logout(context.Background())
client.Login(context.Background(), user.Email, user.Password)
client.Logout(context.Background())
client.Login(context.Background(), user.Email, user.Password)
resp, err = client.DeletePost(context.Background(), post.Id)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
resp, err := client.DeletePost(context.Background(), post.Id)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
client.Logout(context.Background())
resp, err = client.DeletePost(context.Background(), model.NewId())
require.Error(t, err)
CheckUnauthorizedStatus(t, resp)
client.Logout(context.Background())
resp, err = client.DeletePost(context.Background(), model.NewId())
require.Error(t, err)
CheckUnauthorizedStatus(t, resp)
_, err = th.SystemAdminClient.DeletePost(context.Background(), post.Id)
require.NoError(t, err)
_, err = th.SystemAdminClient.DeletePost(context.Background(), post.Id)
require.NoError(t, err)
_, err = th.LocalClient.DeletePost(context.Background(), post2.Id)
require.NoError(t, err)
})
}
func TestPermanentDeletePost(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
client := th.Client
enableAPIPostDeletion := *th.App.Config().ServiceSettings.EnableAPIPostDeletion
defer func() {
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableAPIPostDeletion = &enableAPIPostDeletion })
}()
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableAPIPostDeletion = false })
t.Run("Post not found", func(t *testing.T) {
resp, err := client.PermanentDeletePost(context.Background(), "")
require.Error(t, err)
CheckNotFoundStatus(t, resp)
})
t.Run("Post doesn't exist", func(t *testing.T) {
resp, err := client.PermanentDeletePost(context.Background(), "junk")
require.Error(t, err)
CheckBadRequestStatus(t, resp)
})
t.Run("Permanent deletion not available through API if EnableAPIPostDeletion is not set", func(t *testing.T) {
resp, err := th.SystemAdminClient.PermanentDeletePost(context.Background(), th.BasicPost.Id)
require.Error(t, err)
CheckNotImplementedStatus(t, resp)
})
t.Run("Permanent deletion available through local mode even if EnableAPIPostDeletion is not set", func(t *testing.T) {
post := th.CreatePost()
_, err := th.LocalClient.PermanentDeletePost(context.Background(), post.Id)
require.NoError(t, err)
})
t.Run("No permissions to permanently delete a post", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableAPIPostDeletion = true })
resp, err := client.PermanentDeletePost(context.Background(), th.BasicPost.Id)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
})
t.Run("Try to permanently delete a post across different user roles", func(t *testing.T) {
client.Login(context.Background(), th.TeamAdminUser.Email, th.TeamAdminUser.Password)
resp, err := client.PermanentDeletePost(context.Background(), th.BasicPost.Id)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
post := th.CreatePost()
post2 := th.CreatePost()
user := th.CreateUser()
client.Logout(context.Background())
client.Login(context.Background(), user.Email, user.Password)
resp, err = client.PermanentDeletePost(context.Background(), post.Id)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
client.Logout(context.Background())
resp, err = client.PermanentDeletePost(context.Background(), post.Id)
require.Error(t, err)
CheckUnauthorizedStatus(t, resp)
_, err = th.SystemAdminClient.PermanentDeletePost(context.Background(), post.Id)
require.NoError(t, err)
_, err = th.LocalClient.PermanentDeletePost(context.Background(), post2.Id)
require.NoError(t, err)
})
}
func TestDeletePostEvent(t *testing.T) {

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

@@ -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
}

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

@@ -4060,6 +4060,24 @@ func (s *OpenTracingLayerFileInfoStore) PermanentDeleteByUser(ctx request.CTX, u
return result, err
}
func (s *OpenTracingLayerFileInfoStore) PermanentDeleteForPost(rctx request.CTX, postID string) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "FileInfoStore.PermanentDeleteForPost")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
err := s.FileInfoStore.PermanentDeleteForPost(rctx, postID)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return err
}
func (s *OpenTracingLayerFileInfoStore) Save(ctx request.CTX, info *model.FileInfo) (*model.FileInfo, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "FileInfoStore.Save")
@@ -6874,6 +6892,24 @@ func (s *OpenTracingLayerPostStore) OverwriteMultiple(posts []*model.Post) ([]*m
return result, resultVar1, err
}
func (s *OpenTracingLayerPostStore) PermanentDelete(rctx request.CTX, postID string) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.PermanentDelete")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
err := s.PostStore.PermanentDelete(rctx, postID)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return err
}
func (s *OpenTracingLayerPostStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.PermanentDeleteBatch")

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

@@ -4559,6 +4559,27 @@ func (s *RetryLayerFileInfoStore) PermanentDeleteByUser(ctx request.CTX, userID
}
func (s *RetryLayerFileInfoStore) PermanentDeleteForPost(rctx request.CTX, postID string) error {
tries := 0
for {
err := s.FileInfoStore.PermanentDeleteForPost(rctx, postID)
if err == nil {
return nil
}
if !isRepeatableError(err) {
return err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerFileInfoStore) Save(ctx request.CTX, info *model.FileInfo) (*model.FileInfo, error) {
tries := 0
@@ -7796,6 +7817,27 @@ func (s *RetryLayerPostStore) OverwriteMultiple(posts []*model.Post) ([]*model.P
}
func (s *RetryLayerPostStore) PermanentDelete(rctx request.CTX, postID string) error {
tries := 0
for {
err := s.PostStore.PermanentDelete(rctx, postID)
if err == nil {
return nil
}
if !isRepeatableError(err) {
return err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerPostStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) {
tries := 0

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

@@ -4,6 +4,8 @@
package searchlayer
import (
"fmt"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/public/shared/request"
@@ -74,6 +76,7 @@ func (s SearchFileInfoStore) deleteFileIndexForUser(rctx request.CTX, userID str
}
}
//nolint:unused // Temporarily unused until the post_id is indexed with the file
func (s SearchFileInfoStore) deleteFileIndexForPost(rctx request.CTX, postID string) {
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
if engine.IsIndexingEnabled() {
@@ -133,14 +136,36 @@ func (s SearchFileInfoStore) AttachToPost(rctx request.CTX, fileId, postId, chan
return err
}
func (s SearchFileInfoStore) DeleteForPost(rctx request.CTX, postId string) (string, error) {
result, err := s.FileInfoStore.DeleteForPost(rctx, postId)
func (s SearchFileInfoStore) DeleteForPost(rctx request.CTX, postID string) (string, error) {
// temporary workaround because deleteFileIndexForPost is not working due to the post_id not being indexed with the file
files, err := s.FileInfoStore.GetForPost(postID, false, true, true)
if err != nil {
return "", fmt.Errorf("failed to get files for post %s: %w", postID, err)
}
result, err := s.FileInfoStore.DeleteForPost(rctx, postID)
if err == nil {
s.deleteFileIndexForPost(rctx, postId)
for _, file := range files {
s.deleteFileIndex(rctx, file.Id)
}
}
return result, err
}
func (s SearchFileInfoStore) PermanentDeleteForPost(rctx request.CTX, postID string) error {
// temporary workaround because deleteFileIndexForPost is not working due to the post_id not being indexed with the file
files, err := s.FileInfoStore.GetForPost(postID, false, true, true)
if err != nil {
return err
}
err = s.FileInfoStore.PermanentDeleteForPost(rctx, postID)
if err == nil {
for _, file := range files {
s.deleteFileIndex(rctx, file.Id)
}
}
return err
}
func (s SearchFileInfoStore) PermanentDelete(rctx request.CTX, fileId string) error {
err := s.FileInfoStore.PermanentDelete(rctx, fileId)
if err == nil {

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

@@ -4,8 +4,6 @@
package searchlayer
import (
"context"
"github.com/pkg/errors"
"github.com/mattermost/mattermost/server/public/model"
@@ -107,19 +105,29 @@ func (s SearchPostStore) Save(rctx request.CTX, post *model.Post) (*model.Post,
func (s SearchPostStore) Delete(rctx request.CTX, postId string, date int64, deletedByID string) error {
err := s.PostStore.Delete(rctx, postId, date, deletedByID)
if err == nil {
opts := model.GetPostsOptions{
SkipFetchThreads: true,
}
postList, err2 := s.PostStore.Get(context.Background(), postId, opts, "", map[string]bool{})
if postList != nil && len(postList.Order) > 0 {
if err2 != nil {
s.deletePostIndex(rctx, postList.Posts[postList.Order[0]])
}
}
if err != nil {
return err
}
return err
post, err := s.PostStore.GetSingle(rctx, postId, true)
if err != nil {
return err
}
s.deletePostIndex(rctx, post)
return nil
}
func (s SearchPostStore) PermanentDelete(rctx request.CTX, postID string) error {
// Get full post struct for later
post, err := s.PostStore.GetSingle(rctx, postID, true)
if err != nil {
return err
}
err = s.PostStore.PermanentDelete(rctx, postID)
if err != nil {
return err
}
s.deletePostIndex(rctx, post)
return nil
}
func (s SearchPostStore) PermanentDeleteByUser(rctx request.CTX, userID string) error {

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

@@ -183,6 +183,11 @@ var searchFileInfoStoreTests = []searchTest{
Fn: testFileInfoSearchEmailsWithoutQuotes,
Tags: []string{EngineElasticSearch},
},
{
Name: "Should be removed from search index when deleted",
Fn: testSearchFileDeletedPost,
Tags: []string{EngineAll},
},
{
Name: "Should not search files not attached to a post",
Fn: testFileInfoSearchNoResultForPostlessFileInfos,
@@ -1656,6 +1661,42 @@ func testFileInfoSearchEmailsWithoutQuotes(t *testing.T, th *SearchTestHelper) {
th.checkFileInfoInSearchResults(t, p1.Id, results.FileInfos)
}
func testSearchFileDeletedPost(t *testing.T, th *SearchTestHelper) {
t.Run("Should not return file info for soft deleted post", func(t *testing.T) {
post, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "deletedmessage", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createFileInfo(th.User.Id, post.Id, post.ChannelId, "deletedmessage", "deletedmessage", "jpg", "image/jpeg", 0, 0)
require.NoError(t, err)
_, err = th.Store.FileInfo().DeleteForPost(th.Context, post.Id)
require.NoError(t, err)
params := &model.SearchParams{Terms: "deletedmessage"}
results, err := th.Store.FileInfo().Search(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.FileInfos, 0)
})
t.Run("Should not return file info for hard deleted post", func(t *testing.T) {
post, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "deletedmessage", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
_, err = th.createFileInfo(th.User.Id, post.Id, post.ChannelId, "deletedmessage", "deletedmessage", "jpg", "image/jpeg", 0, 0)
require.NoError(t, err)
err = th.Store.FileInfo().PermanentDeleteForPost(th.Context, post.Id)
require.NoError(t, err)
params := &model.SearchParams{Terms: "deletedmessage"}
results, err := th.Store.FileInfo().Search(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.FileInfos, 0)
})
}
func testFileInfoSearchNoResultForPostlessFileInfos(t *testing.T, th *SearchTestHelper) {
_, err := th.createFileInfo(th.User.Id, "", th.ChannelBasic.Id, "message test@test.com", "message test@test.com", "jpg", "image/jpeg", 0, 0)
require.NoError(t, err)

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

@@ -275,6 +275,11 @@ var searchPostStoreTests = []searchTest{
Fn: testSearchAcrossTeams,
Tags: []string{EngineAll},
},
{
Name: "Should be removed from search index when deleted",
Fn: testSearchPostDeleted,
Tags: []string{EngineAll},
},
}
func TestSearchPostStore(t *testing.T, s store.Store, testEngine *SearchTestEngine) {
@@ -1955,3 +1960,31 @@ func testSearchAcrossTeams(t *testing.T, th *SearchTestHelper) {
require.Len(t, results.Posts, 2)
}
func testSearchPostDeleted(t *testing.T, th *SearchTestHelper) {
t.Run("Search for soft deleted post", func(t *testing.T) {
p1, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "message to delete", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
err = th.Store.Post().Delete(th.Context, p1.Id, p1.UpdateAt, th.User.Id)
require.NoError(t, err)
params := &model.SearchParams{Terms: "message to delete"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 0)
})
t.Run("Search for hard deleted post", func(t *testing.T) {
p2, err := th.createPost(th.User.Id, th.ChannelBasic.Id, "message to delete", "", model.PostTypeDefault, 0, false)
require.NoError(t, err)
err = th.Store.Post().PermanentDelete(th.Context, p2.Id)
require.NoError(t, err)
params := &model.SearchParams{Terms: "message to delete"}
results, err := th.Store.Post().SearchPostsForUser(th.Context, []*model.SearchParams{params}, th.User.Id, th.Team.Id, 0, 20)
require.NoError(t, err)
require.Len(t, results.Posts, 0)
})
}

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

@@ -455,6 +455,13 @@ func (fs SqlFileInfoStore) DeleteForPost(rctx request.CTX, postId string) (strin
return postId, nil
}
func (fs SqlFileInfoStore) PermanentDeleteForPost(rctx request.CTX, postID string) error {
if _, err := fs.GetMasterX().Exec(`DELETE FROM FileInfo WHERE PostId = ?`, postID); err != nil {
return errors.Wrapf(err, "failed to delete FileInfo with PostId=%s", postID)
}
return nil
}
func (fs SqlFileInfoStore) PermanentDelete(rctx request.CTX, fileId string) error {
if _, err := fs.GetMasterX().Exec(`DELETE FROM FileInfo WHERE Id = ?`, fileId); err != nil {
return errors.Wrapf(err, "failed to delete FileInfo with id=%s", fileId)

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

@@ -955,6 +955,10 @@ func (s *SqlPostStore) Delete(rctx request.CTX, postID string, time int64, delet
return nil
}
func (s *SqlPostStore) PermanentDelete(rctx request.CTX, postID string) (err error) {
return s.permanentDelete([]string{postID})
}
func (s *SqlPostStore) permanentDelete(postIds []string) (err error) {
transaction, err := s.GetMasterX().Beginx()
if err != nil {

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

@@ -361,6 +361,7 @@ type PostStore interface {
Get(ctx context.Context, id string, opts model.GetPostsOptions, userID string, sanitizeOptions map[string]bool) (*model.PostList, error)
GetSingle(rctx request.CTX, id string, inclDeleted bool) (*model.Post, error)
Delete(rctx request.CTX, postID string, timestamp int64, deleteByID string) error
PermanentDelete(rctx request.CTX, postID string) error
PermanentDeleteByUser(rctx request.CTX, userID string) error
PermanentDeleteByChannel(rctx request.CTX, channelID string) error
GetPosts(options model.GetPostsOptions, allowFromCache bool, sanitizeOptions map[string]bool) (*model.PostList, error)
@@ -720,6 +721,7 @@ type FileInfoStore interface {
InvalidateFileInfosForPostCache(postID string, deleted bool)
AttachToPost(c request.CTX, fileID string, postID string, channelID, creatorID string) error
DeleteForPost(c request.CTX, postID string) (string, error)
PermanentDeleteForPost(rctx request.CTX, postID string) error
PermanentDelete(c request.CTX, fileID string) error
PermanentDeleteBatch(ctx request.CTX, endTime int64, limit int64) (int64, error)
PermanentDeleteByUser(ctx request.CTX, userID string) (int64, error)

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

@@ -37,6 +37,7 @@ func TestFileInfoStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStor
t.Run("CountAll", func(t *testing.T) { testFileInfoStoreCountAll(t, rctx, ss) })
t.Run("GetStorageUsage", func(t *testing.T) { testFileInfoGetStorageUsage(t, rctx, ss) })
t.Run("GetUptoNSizeFileTime", func(t *testing.T) { testGetUptoNSizeFileTime(t, rctx, ss, s) })
t.Run("FileInfoPermanentDeleteForPost", func(t *testing.T) { testPermanentDeleteForPost(t, rctx, ss) })
}
func testFileInfoSaveGet(t *testing.T, rctx request.CTX, ss store.Store) {
@@ -941,3 +942,28 @@ func testGetUptoNSizeFileTime(t *testing.T, rctx request.CTX, ss store.Store, s
require.NoError(t, err)
assert.Equal(t, f2.CreateAt, createAt)
}
func testPermanentDeleteForPost(t *testing.T, rctx request.CTX, ss store.Store) {
postId := model.NewId()
_, err := ss.FileInfo().Save(rctx, &model.FileInfo{
PostId: postId,
CreatorId: model.NewId(),
Size: 10,
Path: "file1.txt",
CreateAt: utils.MillisFromTime(time.Now()),
})
require.NoError(t, err)
err = ss.FileInfo().PermanentDeleteForPost(rctx, postId)
require.NoError(t, err)
postInfos, err := ss.FileInfo().GetForPost(
postId,
true,
true,
false,
)
require.NoError(t, err)
assert.Len(t, postInfos, 0)
}

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

@@ -469,6 +469,24 @@ func (_m *FileInfoStore) PermanentDeleteByUser(ctx request.CTX, userID string) (
return r0, r1
}
// PermanentDeleteForPost provides a mock function with given fields: rctx, postID
func (_m *FileInfoStore) PermanentDeleteForPost(rctx request.CTX, postID string) error {
ret := _m.Called(rctx, postID)
if len(ret) == 0 {
panic("no return value specified for PermanentDeleteForPost")
}
var r0 error
if rf, ok := ret.Get(0).(func(request.CTX, string) error); ok {
r0 = rf(rctx, postID)
} else {
r0 = ret.Error(0)
}
return r0
}
// Save provides a mock function with given fields: ctx, info
func (_m *FileInfoStore) Save(ctx request.CTX, info *model.FileInfo) (*model.FileInfo, error) {
ret := _m.Called(ctx, info)

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

@@ -1046,6 +1046,24 @@ func (_m *PostStore) OverwriteMultiple(posts []*model.Post) ([]*model.Post, int,
return r0, r1, r2
}
// PermanentDelete provides a mock function with given fields: rctx, postID
func (_m *PostStore) PermanentDelete(rctx request.CTX, postID string) error {
ret := _m.Called(rctx, postID)
if len(ret) == 0 {
panic("no return value specified for PermanentDelete")
}
var r0 error
if rf, ok := ret.Get(0).(func(request.CTX, string) error); ok {
r0 = rf(rctx, postID)
} else {
r0 = ret.Error(0)
}
return r0
}
// PermanentDeleteBatch provides a mock function with given fields: endTime, limit
func (_m *PostStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) {
ret := _m.Called(endTime, limit)

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

@@ -3707,6 +3707,22 @@ func (s *TimerLayerFileInfoStore) PermanentDeleteByUser(ctx request.CTX, userID
return result, err
}
func (s *TimerLayerFileInfoStore) PermanentDeleteForPost(rctx request.CTX, postID string) error {
start := time.Now()
err := s.FileInfoStore.PermanentDeleteForPost(rctx, postID)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("FileInfoStore.PermanentDeleteForPost", success, elapsed)
}
return err
}
func (s *TimerLayerFileInfoStore) Save(ctx request.CTX, info *model.FileInfo) (*model.FileInfo, error) {
start := time.Now()
@@ -6217,6 +6233,22 @@ func (s *TimerLayerPostStore) OverwriteMultiple(posts []*model.Post) ([]*model.P
return result, resultVar1, err
}
func (s *TimerLayerPostStore) PermanentDelete(rctx request.CTX, postID string) error {
start := time.Now()
err := s.PostStore.PermanentDelete(rctx, postID)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("PostStore.PermanentDelete", success, elapsed)
}
return err
}
func (s *TimerLayerPostStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, error) {
start := time.Now()