Remove unused recycle bin and add optional local file delete (#26)

Soft-delete restore/purge/list and the recycle_purge job are gone.
Admin delete now removes library index rows, with an optional
delete_files query that also deletes local media + sidecar NFO.
UI copy is renamed from 移入回收站 to 删除, with a confirm checkbox.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: truewhile <truewhile@users.noreply.github.com>
This commit is contained in:
truewhile
2026-09-03 14:12:52 +08:00
committed by GitHub
parent 205c71b0ad
commit a79bbb80a8
28 changed files with 362 additions and 338 deletions
+35
View File
@@ -0,0 +1,35 @@
// Package handler — media delete endpoint.
package handler
import (
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/truewhile/MeBox/internal/service"
)
func deleteMediaHandler(svc *service.Container) gin.HandlerFunc {
return func(c *gin.Context) {
deleteFiles := parseDeleteFilesQuery(c.Query("delete_files"))
if err := svc.Media.DeleteMedia(c.Request.Context(), c.Param("id"), deleteFiles); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusNoContent)
}
}
func parseDeleteFilesQuery(raw string) bool {
raw = strings.TrimSpace(strings.ToLower(raw))
if raw == "" {
return false
}
if raw == "1" || raw == "true" || raw == "yes" {
return true
}
v, err := strconv.ParseBool(raw)
return err == nil && v
}
+23
View File
@@ -0,0 +1,23 @@
package handler
import "testing"
func TestParseDeleteFilesQuery(t *testing.T) {
cases := []struct {
raw string
want bool
}{
{"", false},
{"0", false},
{"false", false},
{"1", true},
{"true", true},
{"TRUE", true},
{"yes", true},
}
for _, tc := range cases {
if got := parseDeleteFilesQuery(tc.raw); got != tc.want {
t.Fatalf("parseDeleteFilesQuery(%q)=%v, want %v", tc.raw, got, tc.want)
}
}
}
-40
View File
@@ -1,40 +0,0 @@
// Package handler — recycle bin endpoints.
package handler
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/truewhile/MeBox/internal/service"
)
func deleteMediaHandler(svc *service.Container) gin.HandlerFunc {
return func(c *gin.Context) {
if err := svc.Media.SoftDelete(c.Request.Context(), c.Param("id")); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusNoContent)
}
}
func restoreMediaHandler(svc *service.Container) gin.HandlerFunc {
return func(c *gin.Context) {
if err := svc.Media.RestoreDeleted(c.Request.Context(), c.Param("id")); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusNoContent)
}
}
func purgeMediaHandler(svc *service.Container) gin.HandlerFunc {
return func(c *gin.Context) {
if err := svc.Media.PurgeDeleted(c.Request.Context(), c.Param("id")); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusNoContent)
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ func registerAuthenticatedRoutes(api *gin.RouterGroup, cfg *config.Config, svc *
registerAuthedCollectionRoutes(authed, svc)
registerAuthedFileRoutes(authed, svc)
registerAuthedDLNARoutes(authed, svc)
registerAuthedRecycleAndRealtimeRoutes(authed, svc)
registerAuthedRealtimeRoutes(authed, svc)
registerAuthedUISurfaceRoutes(authed, svc)
registerAuthedSearchRoutes(authed, svc)
registerAuthedSystemExtraRoutes(authed, svc)
@@ -50,8 +50,6 @@ func registerAuthedMediaRoutes(authed *gin.RouterGroup, svc *service.Container)
authed.POST("/media/scrape/apply", middleware.AdminRequired(), manualScrapeApplyBatchHandler(svc))
authed.POST("/media/:id/probe", middleware.AdminRequired(), reprobeHandler(svc))
authed.DELETE("/media/:id", middleware.AdminRequired(), deleteMediaHandler(svc))
authed.POST("/media/:id/restore", middleware.AdminRequired(), restoreMediaHandler(svc))
authed.DELETE("/media/:id/purge", middleware.AdminRequired(), purgeMediaHandler(svc))
authed.GET("/media/:id/subtitles", listSubtitlesHandler(svc))
authed.GET("/subtitles/:id", serveSubtitleHandler(svc))
authed.POST("/media/:id/nfo", middleware.AdminRequired(), exportNFOHandler(svc))
@@ -20,7 +20,7 @@ func registerAuthedDLNARoutes(authed *gin.RouterGroup, svc *service.Container) {
authed.POST("/dlna/cast", dlnaCastHandler(svc))
}
func registerAuthedRecycleAndRealtimeRoutes(authed *gin.RouterGroup, svc *service.Container) {
func registerAuthedRealtimeRoutes(authed *gin.RouterGroup, svc *service.Container) {
authed.GET("/ws", wsHandler(svc))
authed.GET("/events", sseHandler(svc))
}