From 3ea12cf51a0c926bc4ebd03c4d5b5338bba8e981 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Fri, 5 Dec 2025 00:38:05 +0000 Subject: [PATCH] feat: add query param to include dir path in query --- apps/backend/internal/catalog/directory.go | 24 +++++++++--- .../virtualfs/hierarchical_key_resolver.go | 4 +- apps/backend/internal/virtualfs/path.go | 37 ++++++++++++++++++- apps/backend/internal/virtualfs/vfs.go | 9 ++++- 4 files changed, 64 insertions(+), 10 deletions(-) diff --git a/apps/backend/internal/catalog/directory.go b/apps/backend/internal/catalog/directory.go index e8f3855..56d3890 100644 --- a/apps/backend/internal/catalog/directory.go +++ b/apps/backend/internal/catalog/directory.go @@ -16,12 +16,13 @@ const ( ) type DirectoryInfo struct { - Kind string `json:"kind"` - ID string `json:"id"` - Name string `json:"name"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - DeletedAt *time.Time `json:"deletedAt,omitempty"` + Kind string `json:"kind"` + ID string `json:"id"` + Path virtualfs.Path `json:"path,omitempty"` + Name string `json:"name"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt *time.Time `json:"deletedAt,omitempty"` } type createDirectoryRequest struct { @@ -93,6 +94,7 @@ func (h *HTTPHandler) createDirectory(c *fiber.Ctx) error { func (h *HTTPHandler) fetchDirectory(c *fiber.Ctx) error { node := mustCurrentDirectoryNode(c) + i := DirectoryInfo{ Kind: DirItemKindDirectory, ID: node.PublicID, @@ -101,6 +103,16 @@ func (h *HTTPHandler) fetchDirectory(c *fiber.Ctx) error { UpdatedAt: node.UpdatedAt, DeletedAt: node.DeletedAt, } + + include := c.Query("include") + if include == "path" { + p, err := h.vfs.RealPath(c.Context(), h.db, node) + if err != nil { + return httperr.Internal(err) + } + i.Path = p + } + return c.JSON(i) } diff --git a/apps/backend/internal/virtualfs/hierarchical_key_resolver.go b/apps/backend/internal/virtualfs/hierarchical_key_resolver.go index 720dfec..2f6f366 100644 --- a/apps/backend/internal/virtualfs/hierarchical_key_resolver.go +++ b/apps/backend/internal/virtualfs/hierarchical_key_resolver.go @@ -23,7 +23,7 @@ func (r *HierarchicalKeyResolver) ShouldPersistKey() bool { } func (r *HierarchicalKeyResolver) Resolve(ctx context.Context, db bun.IDB, node *Node) (blob.Key, error) { - path, err := buildNodeAbsolutePath(ctx, db, node) + path, err := buildNodeAbsolutePathString(ctx, db, node) if err != nil { return "", err } @@ -32,7 +32,7 @@ func (r *HierarchicalKeyResolver) Resolve(ctx context.Context, db bun.IDB, node } func (r *HierarchicalKeyResolver) ResolveDeletionKeys(ctx context.Context, node *Node, allKeys []blob.Key) (*DeletionPlan, error) { - path, err := buildNodeAbsolutePath(ctx, r.db, node) + path, err := buildNodeAbsolutePathString(ctx, r.db, node) if err != nil { return nil, err } diff --git a/apps/backend/internal/virtualfs/path.go b/apps/backend/internal/virtualfs/path.go index 0fe86d8..ea35ac0 100644 --- a/apps/backend/internal/virtualfs/path.go +++ b/apps/backend/internal/virtualfs/path.go @@ -23,11 +23,34 @@ 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 +) +SELECT name, public_id FROM path +WHERE EXISTS (SELECT 1 FROM path WHERE parent_id IS NULL) +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 buildNodeAbsolutePath(ctx context.Context, db bun.IDB, node *Node) (string, error) { +func buildNodeAbsolutePathString(ctx context.Context, db bun.IDB, node *Node) (string, error) { var path []string err := db.NewRaw(absolutePathQuery, node.ID).Scan(ctx, &path) if err != nil { @@ -38,3 +61,15 @@ func buildNodeAbsolutePath(ctx context.Context, db bun.IDB, node *Node) (string, } return JoinPath(path...), nil } + +func buildNoteAbsolutePath(ctx context.Context, db bun.IDB, node *Node) (Path, error) { + var segments []PathSegment + err := db.NewRaw(absolutePathWithPublicIDQuery, node.ID).Scan(ctx, &segments) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, ErrNodeNotFound + } + return nil, err + } + return segments, nil +} diff --git a/apps/backend/internal/virtualfs/vfs.go b/apps/backend/internal/virtualfs/vfs.go index 6041129..17434ba 100644 --- a/apps/backend/internal/virtualfs/vfs.go +++ b/apps/backend/internal/virtualfs/vfs.go @@ -434,7 +434,7 @@ func (vfs *VirtualFS) AbsolutePath(ctx context.Context, db bun.IDB, node *Node) if !node.IsAccessible() { return "", ErrNodeNotFound } - return buildNodeAbsolutePath(ctx, db, node) + return buildNodeAbsolutePathString(ctx, db, node) } func (vfs *VirtualFS) PermanentlyDeleteNode(ctx context.Context, db bun.IDB, node *Node) error { @@ -564,3 +564,10 @@ func (vfs *VirtualFS) generatePublicID() (string, error) { n := binary.BigEndian.Uint64(b[:]) return vfs.sqid.Encode([]uint64{n}) } + +func (vfs *VirtualFS) RealPath(ctx context.Context, db bun.IDB, node *Node) (Path, error) { + if !node.IsAccessible() { + return nil, ErrNodeNotFound + } + return buildNoteAbsolutePath(ctx, db, node) +}