[MM-56073] MMCTL delete post command (#27539)
Co-authored-by: Ben Schumacher <ben.schumacher@mattermost.com>
Этот коммит содержится в:
@@ -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()
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user