Batch archive/restore/delete/approve/private validate every UID against the PhotoPrism DB in one query. Per-photo PUT/approve/like/stack-file ops are ownership-checked. Admin-role sessions pass through fully so settings/users/index dialogs keep working. Paths are unescaped+cleaned before classification so encoded dot-segments can't smuggle past the allowlist. Full httptest coverage of the routing decisions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
71 lines
1.8 KiB
Go
71 lines
1.8 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// handleFoldersProxy proxies PhotoPrism's /api/v1/folders/originals and
|
|
// post-filters by the caller's BasePath so the folder tree only shows
|
|
// folders under the user's library root.
|
|
//
|
|
// Route: GET /api/sidecar/folders (behind requireSession)
|
|
func handleFoldersProxy(pp *ppClient) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
token := ctxToken(c)
|
|
basePath := ctxBasePath(c)
|
|
|
|
// Forward query params to PhotoPrism.
|
|
query := c.Request.URL.RawQuery
|
|
if query == "" {
|
|
query = "recursive=true&uncached=true&files=false"
|
|
}
|
|
|
|
resp, err := pp.call(c.Request.Context(), http.MethodGet, "/api/v1/folders/originals?"+query, token, nil)
|
|
if err != nil || !resp.OK {
|
|
c.JSON(http.StatusBadGateway, gin.H{"error": "upstream folders request failed"})
|
|
return
|
|
}
|
|
|
|
// Decode the response — PhotoPrism returns { folders: [...] }.
|
|
var payload struct {
|
|
Folders []map[string]any `json:"folders"`
|
|
}
|
|
if err := json.Unmarshal(resp.Body, &payload); err != nil {
|
|
c.Data(resp.Status, "application/json", resp.Body)
|
|
return
|
|
}
|
|
|
|
// If the user has no BasePath (admin/empty), return as-is.
|
|
if basePath == "" {
|
|
c.JSON(http.StatusOK, payload)
|
|
return
|
|
}
|
|
|
|
prefix := basePath + "/"
|
|
|
|
// Post-filter folders by Path field only — frontend handles BasePath
|
|
// prefix stripping via toUserPath().
|
|
filtered := make([]map[string]any, 0, len(payload.Folders))
|
|
for _, f := range payload.Folders {
|
|
rawPath, ok := f["Path"]
|
|
if !ok {
|
|
continue
|
|
}
|
|
pathStr, ok := rawPath.(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
// Keep only folders under the user's base path.
|
|
if pathStr == basePath || strings.HasPrefix(pathStr, prefix) {
|
|
filtered = append(filtered, f)
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"folders": filtered})
|
|
}
|
|
}
|