feat(backend): add server-side directory browsing API
- Add BrowseDirectories() to library service with path traversal protection - Add BrowseDirectories handler with proper error handling - Register GET /api/libraries/browse endpoint (admin-only) - Returns current path, parent path, and list of subdirectories - Security: blocks "..", validates path exists, checks is directory Fixes: Issue 2 (backend)
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"bookhoard/internal/database"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -207,3 +208,49 @@ func (s *LibraryService) HasFolders(ctx context.Context, libraryID pgtype.UUID)
|
||||
}
|
||||
return len(folders) > 0, nil
|
||||
}
|
||||
|
||||
// BrowseDirectories lists directories at a given path for folder browser UI
|
||||
// Returns: (directories, currentPath, parentPath, error)
|
||||
func (s *LibraryService) BrowseDirectories(ctx context.Context, path string) ([]string, string, string, error) {
|
||||
// Security: path traversal protection
|
||||
if strings.Contains(path, "..") {
|
||||
return nil, "", "", fmt.Errorf("path traversal not allowed")
|
||||
}
|
||||
|
||||
cleanPath := filepath.Clean(path)
|
||||
|
||||
// Check if path exists and is accessible
|
||||
fileInfo, err := os.Stat(cleanPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, "", "", fmt.Errorf("path does not exist")
|
||||
}
|
||||
return nil, "", "", fmt.Errorf("path not accessible: %w", err)
|
||||
}
|
||||
|
||||
if !fileInfo.IsDir() {
|
||||
return nil, "", "", fmt.Errorf("not a directory")
|
||||
}
|
||||
|
||||
// Read directory contents
|
||||
entries, err := os.ReadDir(cleanPath)
|
||||
if err != nil {
|
||||
return nil, "", "", fmt.Errorf("failed to read directory: %w", err)
|
||||
}
|
||||
|
||||
// Filter only directories
|
||||
var dirs []string
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
dirs = append(dirs, entry.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// Get parent path for navigation
|
||||
parentPath := filepath.Dir(cleanPath)
|
||||
if parentPath == cleanPath {
|
||||
parentPath = "" // At root
|
||||
}
|
||||
|
||||
return dirs, cleanPath, parentPath, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user