Files
drive/apps/backend/internal/virtualfs/path.go
Kenneth eca3b95c3e fix: resolving path for shared item leaks parent info
when an item is shared, their path should stop at the closest directory
that is shared. eg if file c is in shared dir b which is in private dir
a, its public facing path should be b/c, not a/b/c. internally, the real
path is still a/b/c.
2025-12-27 20:25:54 +00:00

82 lines
2.1 KiB
Go

package virtualfs
import (
"context"
"database/sql"
"errors"
"strings"
"github.com/google/uuid"
"github.com/uptrace/bun"
)
const absolutePathQuery = `WITH RECURSIVE path AS (
SELECT id, parent_id, name, 1 as depth
FROM vfs_nodes WHERE id = ? AND deleted_at IS NULL
UNION ALL
SELECT n.id, n.parent_id, n.name, p.depth + 1
FROM vfs_nodes n
JOIN path p ON n.id = p.parent_id
)
SELECT name FROM path
WHERE EXISTS (SELECT 1 FROM path WHERE parent_id IS NULL)
ORDER BY depth DESC;`
const absolutePathWithPublicIDQuery = `WITH RECURSIVE path AS (
SELECT id, parent_id, name, public_id, 1 as depth
FROM vfs_nodes WHERE id = ? AND deleted_at IS NULL
UNION ALL
SELECT n.id, n.parent_id, n.name, n.public_id, p.depth + 1
FROM vfs_nodes n
JOIN path p ON n.id = p.parent_id
WHERE p.id != ?
)
SELECT name, public_id FROM path
WHERE EXISTS (SELECT 1 FROM path WHERE id = ?)
ORDER BY depth DESC;`
// Path represents a path to a node.
type Path []PathSegment
// PathSegment represents a segment of a path, containing the name and public ID of the node.
type PathSegment struct {
Name string `bun:"name" json:"name"`
PublicID string `bun:"public_id" json:"id"`
}
func JoinPath(parts ...string) string {
return strings.Join(parts, "/")
}
func buildNodeAbsolutePathString(ctx context.Context, db bun.IDB, node *Node) (string, error) {
return buildPathFromNodeID(ctx, db, node.ID)
}
func buildPathFromNodeID(ctx context.Context, db bun.IDB, nodeID uuid.UUID) (string, error) {
var path []string
err := db.NewRaw(absolutePathQuery, nodeID).Scan(ctx, &path)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return "", ErrNodeNotFound
}
return "", err
}
return JoinPath(path...), nil
}
func buildNodeAbsolutePath(ctx context.Context, db bun.IDB, node *Node, rootID uuid.UUID) (Path, error) {
var segments []PathSegment
err := db.NewRaw(absolutePathWithPublicIDQuery, node.ID, rootID, rootID).Scan(ctx, &segments)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNodeNotFound
}
return nil, err
}
return segments, nil
}