feat(scanner): add debounced file watching with polling fallback

- Implement event queue with 3-second debouncing for file system events
- Add configurable polling fallback (default 3 min) via SCAN_POLL_INTERVAL_MINUTES
- Add SyncFilesystemWithDatabase to detect orphaned DB entries and new files
- Integrate utils.ResolveMediaURL for consistent media file path resolution
- Add COOKIE_SECURE env var with SameSite=LaxMode for session cookies
- Update media handler to properly decode URL paths for file serving
- Refactor scanner initialization to accept poll interval configuration
This commit is contained in:
2026-02-28 01:16:27 -05:00
parent 594c630b99
commit 037e7c1189
14 changed files with 485 additions and 113 deletions
+13 -10
View File
@@ -9,6 +9,7 @@ import (
"io"
"mime"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
@@ -1490,21 +1491,23 @@ func (mh *MediaHandler) getFullFilePath(ctx context.Context, libraryID pgtype.UU
// Requires JWT authentication
func (mh *MediaHandler) ServeFile(c echo.Context) error {
// URL format: /uploads/library-{libraryID}/{relativePath}
path := c.Param("*") // Gets everything after /uploads/library-{id}/
// Extract library ID from path
parts := strings.SplitN(path, "/", 2)
if len(parts) < 2 {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid path"})
}
libraryIDStr := strings.TrimPrefix(parts[0], "library-")
// Get library ID directly from route parameter
libraryIDStr := c.Param("id")
libraryUUID, err := uuid.Parse(libraryIDStr)
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library ID"})
}
relativePath := parts[1]
// Get remaining path from URL
rawPath := c.Param("*")
relativePath, err := url.QueryUnescape(rawPath)
if err != nil {
relativePath = rawPath
}
if relativePath == "" {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid path"})
}
// Resolve using service
fullPath, err := mh.getFullFilePath(c.Request().Context(), pgtype.UUID{Bytes: libraryUUID, Valid: true}, relativePath)