Per-channel retention config, daily scheduled cleanup, slash command and REST API support, plus a single Channel cleanup menu that opens a plugin modal for configure, run-now, and disable actions. Co-authored-by: Cursor <cursoragent@cursor.com>
243 lines
7.2 KiB
Go
243 lines
7.2 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
|
|
"github.com/gorilla/mux"
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
)
|
|
|
|
type cleanupRequest struct {
|
|
ChannelID string `json:"channel_id"`
|
|
}
|
|
|
|
type cleanupResponse struct {
|
|
Deleted int `json:"deleted"`
|
|
}
|
|
|
|
type autoCleanupRequest struct {
|
|
ChannelID string `json:"channel_id"`
|
|
OffsetDays int `json:"offset_days"`
|
|
}
|
|
|
|
type autoCleanupResponse struct {
|
|
ChannelID string `json:"channel_id"`
|
|
OffsetDays int `json:"offset_days"`
|
|
Enabled bool `json:"enabled"`
|
|
}
|
|
|
|
func (p *Plugin) initRouter() *mux.Router {
|
|
router := mux.NewRouter()
|
|
router.Use(p.requireLoggedInUser)
|
|
|
|
apiRouter := router.PathPrefix("/api/v1").Subrouter()
|
|
apiRouter.HandleFunc("/cleanup", p.handleCleanup).Methods(http.MethodPost)
|
|
apiRouter.HandleFunc("/auto-cleanup/run", p.handleRunAutoCleanup).Methods(http.MethodPost)
|
|
apiRouter.HandleFunc("/auto-cleanup", p.handleSetAutoCleanup).Methods(http.MethodPut)
|
|
apiRouter.HandleFunc("/auto-cleanup/{channel_id}", p.handleGetAutoCleanup).Methods(http.MethodGet)
|
|
apiRouter.HandleFunc("/auto-cleanup/{channel_id}", p.handleDeleteAutoCleanup).Methods(http.MethodDelete)
|
|
|
|
return router
|
|
}
|
|
|
|
func (p *Plugin) ServeHTTP(_ *plugin.Context, w http.ResponseWriter, r *http.Request) {
|
|
p.router.ServeHTTP(w, r)
|
|
}
|
|
|
|
func (p *Plugin) requireLoggedInUser(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Header.Get("Mattermost-User-ID") == "" {
|
|
http.Error(w, "Not authorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func (p *Plugin) handleCleanup(w http.ResponseWriter, r *http.Request) {
|
|
userID := r.Header.Get("Mattermost-User-ID")
|
|
|
|
var req cleanupRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if req.ChannelID == "" {
|
|
http.Error(w, "channel_id is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
deleted, err := p.cleanupChannel(userID, req.ChannelID)
|
|
if err != nil {
|
|
if errors.Is(err, errPermissionDenied) {
|
|
http.Error(w, err.Error(), http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
p.client.Log.Error("Failed to cleanup channel", "channel_id", req.ChannelID, "error", err)
|
|
http.Error(w, "Failed to cleanup channel", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(cleanupResponse{Deleted: deleted}); err != nil {
|
|
p.client.Log.Error("Failed to encode cleanup response", "error", err)
|
|
}
|
|
}
|
|
|
|
type autoCleanupRunRequest struct {
|
|
ChannelID string `json:"channel_id"`
|
|
OffsetDays int `json:"offset_days,omitempty"`
|
|
}
|
|
|
|
func (p *Plugin) handleRunAutoCleanup(w http.ResponseWriter, r *http.Request) {
|
|
userID := r.Header.Get("Mattermost-User-ID")
|
|
if !p.isSystemAdmin(userID) {
|
|
http.Error(w, errPermissionDenied.Error(), http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
var req autoCleanupRunRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if req.ChannelID == "" {
|
|
http.Error(w, "channel_id is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
offsetDays := req.OffsetDays
|
|
if offsetDays <= 0 {
|
|
config, err := p.autoCleanupStore.Get(req.ChannelID)
|
|
if err != nil {
|
|
p.client.Log.Error("Failed to get auto cleanup config", "channel_id", req.ChannelID, "error", err)
|
|
http.Error(w, "Failed to get auto cleanup config", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if config == nil {
|
|
http.Error(w, "automatic cleanup is not configured for this channel", http.StatusBadRequest)
|
|
return
|
|
}
|
|
offsetDays = config.OffsetDays
|
|
}
|
|
|
|
deleted, err := p.autoCleanupChannel(req.ChannelID, offsetDays)
|
|
if err != nil {
|
|
p.client.Log.Error("Failed to run auto cleanup", "channel_id", req.ChannelID, "error", err)
|
|
http.Error(w, "Failed to run auto cleanup", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(cleanupResponse{Deleted: deleted}); err != nil {
|
|
p.client.Log.Error("Failed to encode auto cleanup run response", "error", err)
|
|
}
|
|
}
|
|
|
|
func (p *Plugin) handleGetAutoCleanup(w http.ResponseWriter, r *http.Request) {
|
|
userID := r.Header.Get("Mattermost-User-ID")
|
|
if !p.isSystemAdmin(userID) {
|
|
http.Error(w, errPermissionDenied.Error(), http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
channelID := mux.Vars(r)["channel_id"]
|
|
if channelID == "" {
|
|
http.Error(w, "channel_id is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
config, err := p.autoCleanupStore.Get(channelID)
|
|
if err != nil {
|
|
p.client.Log.Error("Failed to get auto cleanup config", "channel_id", channelID, "error", err)
|
|
http.Error(w, "Failed to get auto cleanup config", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
response := autoCleanupResponse{
|
|
ChannelID: channelID,
|
|
Enabled: config != nil,
|
|
}
|
|
if config != nil {
|
|
response.OffsetDays = config.OffsetDays
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(response); err != nil {
|
|
p.client.Log.Error("Failed to encode auto cleanup response", "error", err)
|
|
}
|
|
}
|
|
|
|
func (p *Plugin) handleSetAutoCleanup(w http.ResponseWriter, r *http.Request) {
|
|
userID := r.Header.Get("Mattermost-User-ID")
|
|
if !p.isSystemAdmin(userID) {
|
|
http.Error(w, errPermissionDenied.Error(), http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
var req autoCleanupRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if req.ChannelID == "" {
|
|
http.Error(w, "channel_id is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if req.OffsetDays <= 0 {
|
|
http.Error(w, "offset_days must be greater than zero", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if _, err := p.client.Channel.Get(req.ChannelID); err != nil {
|
|
http.Error(w, "channel not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
if err := p.autoCleanupStore.Set(req.ChannelID, req.OffsetDays); err != nil {
|
|
p.client.Log.Error("Failed to set auto cleanup config", "channel_id", req.ChannelID, "error", err)
|
|
http.Error(w, "Failed to set auto cleanup config", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(autoCleanupResponse{
|
|
ChannelID: req.ChannelID,
|
|
OffsetDays: req.OffsetDays,
|
|
Enabled: true,
|
|
}); err != nil {
|
|
p.client.Log.Error("Failed to encode auto cleanup response", "error", err)
|
|
}
|
|
}
|
|
|
|
func (p *Plugin) handleDeleteAutoCleanup(w http.ResponseWriter, r *http.Request) {
|
|
userID := r.Header.Get("Mattermost-User-ID")
|
|
if !p.isSystemAdmin(userID) {
|
|
http.Error(w, errPermissionDenied.Error(), http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
channelID := mux.Vars(r)["channel_id"]
|
|
if channelID == "" {
|
|
http.Error(w, "channel_id is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if err := p.autoCleanupStore.Delete(channelID); err != nil {
|
|
p.client.Log.Error("Failed to delete auto cleanup config", "channel_id", channelID, "error", err)
|
|
http.Error(w, "Failed to delete auto cleanup config", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(autoCleanupResponse{
|
|
ChannelID: channelID,
|
|
Enabled: false,
|
|
}); err != nil {
|
|
p.client.Log.Error("Failed to encode auto cleanup response", "error", err)
|
|
}
|
|
}
|