mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 02:15:57 +00:00
Replace flat paginated document list with collapsible tree view. Backend: two-query-per-level approach (files + DISTINCT deeper paths for virtual folder derivation), path parsing in Go for PG+SQLite compatibility, text_pattern_ops index for prefix range scans. Frontend: VaultTree component with doc_type colored icons, compact single-line nodes, truncate-middle filenames, scope dots, hover tooltip for date. Sidebar expanded to 384px on large screens. Also fixes pre-existing SQLite scanVaultDocRow missing path_basename.
57 lines
1.6 KiB
Go
57 lines
1.6 KiB
Go
package http
|
|
|
|
import (
|
|
"log/slog"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
)
|
|
|
|
// handleVaultTree returns immediate children (files + virtual folders) under
|
|
// a given path prefix for lazy-loading the vault sidebar tree.
|
|
func (h *VaultHandler) handleVaultTree(w http.ResponseWriter, r *http.Request) {
|
|
tenantID := store.TenantIDFromContext(r.Context())
|
|
path := r.URL.Query().Get("path")
|
|
|
|
if strings.Contains(path, "..") || strings.HasPrefix(path, "/") {
|
|
slog.Warn("security.vault_tree_traversal", "path", path)
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid path"})
|
|
return
|
|
}
|
|
|
|
opts := store.VaultTreeOptions{
|
|
Path: path,
|
|
Scope: r.URL.Query().Get("scope"),
|
|
DocTypes: splitCSV(r.URL.Query().Get("doc_type")),
|
|
}
|
|
if teamID := r.URL.Query().Get("team_id"); teamID != "" {
|
|
opts.TeamID = &teamID
|
|
}
|
|
|
|
if opts.TeamID == nil && !store.IsOwnerRole(r.Context()) {
|
|
if ids := h.userAccessibleTeamIDs(r.Context()); len(ids) > 0 {
|
|
opts.TeamIDs = ids
|
|
} else {
|
|
empty := ""
|
|
opts.TeamID = &empty
|
|
}
|
|
}
|
|
if opts.TeamID != nil && *opts.TeamID != "" {
|
|
if !h.validateTeamMembership(r.Context(), w, *opts.TeamID) {
|
|
return
|
|
}
|
|
}
|
|
|
|
entries, err := h.store.ListTreeEntries(r.Context(), tenantID.String(), opts)
|
|
if err != nil {
|
|
slog.Warn("vault.tree failed", "error", err)
|
|
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to load tree"})
|
|
return
|
|
}
|
|
if entries == nil {
|
|
entries = []store.VaultTreeEntry{}
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"entries": entries})
|
|
}
|