package main import ( "errors" "time" "github.com/mattermost/mattermost/server/public/model" pkgerrors "github.com/pkg/errors" ) var errPermissionDenied = errors.New("permission denied: system admin required") const postsPerPage = 200 type postService interface { GetPostsForChannel(channelID string, page, perPage int) (*model.PostList, error) DeletePost(postID string) error } type errorLogger interface { Error(message string, keyValuePairs ...any) } func (p *Plugin) isSystemAdmin(userID string) bool { return p.client.User.HasPermissionTo(userID, model.PermissionManageSystem) } func deleteChannelPosts(postSvc postService, channelID string, olderThanMillis int64, log errorLogger) (int, error) { deleted := 0 page := 0 for { postList, err := postSvc.GetPostsForChannel(channelID, page, postsPerPage) if err != nil { return deleted, pkgerrors.Wrap(err, "failed to get channel posts") } if len(postList.Order) == 0 { break } // Deleting posts shifts later results into earlier page offsets. Only advance // the page when nothing was removed so we walk past posts that should be kept // (e.g. messages newer than an auto-cleanup cutoff). deletedOnPage := 0 for _, postID := range postList.Order { post, ok := postList.Posts[postID] if !ok { continue } if olderThanMillis > 0 && post.CreateAt >= olderThanMillis { continue } if err := postSvc.DeletePost(postID); err != nil { if log != nil { log.Error("Failed to delete post during channel cleanup", "channel_id", channelID, "post_id", postID, "error", err) } continue } deleted++ deletedOnPage++ } if deletedOnPage == 0 { page++ } } return deleted, nil } func (p *Plugin) cleanupChannel(userID, channelID string) (int, error) { if !p.isSystemAdmin(userID) { return 0, errPermissionDenied } if _, err := p.client.Channel.Get(channelID); err != nil { return 0, pkgerrors.Wrap(err, "failed to get channel") } return deleteChannelPosts(&p.client.Post, channelID, 0, &p.client.Log) } func (p *Plugin) autoCleanupChannel(channelID string, offsetDays int) (int, error) { if _, err := p.client.Channel.Get(channelID); err != nil { return 0, pkgerrors.Wrap(err, "failed to get channel") } cutoff := time.Now().AddDate(0, 0, -offsetDays).UnixMilli() return deleteChannelPosts(&p.client.Post, channelID, cutoff, &p.client.Log) } func cutoffMillis(offsetDays int, now time.Time) int64 { return now.AddDate(0, 0, -offsetDays).UnixMilli() }