Files
drive/apps/backend/internal/catalog/directory.go

794 lines
24 KiB
Go

package catalog
import (
"encoding/base64"
"errors"
"fmt"
"slices"
"strconv"
"strings"
"time"
"github.com/get-drexa/drexa/internal/httperr"
"github.com/get-drexa/drexa/internal/sharing"
"github.com/get-drexa/drexa/internal/virtualfs"
"github.com/gofiber/fiber/v2"
)
const (
DirItemKindDirectory = "directory"
DirItemKindFile = "file"
)
// DirectoryInfo represents directory metadata
// @Description Directory information including path and timestamps
type DirectoryInfo struct {
// Item type, always "directory"
Kind string `json:"kind" example:"directory"`
// Unique directory identifier
ID string `json:"id" example:"kRp2XYTq9A55"`
// ParentID is the public ID of the directory this directory is in
ParentID string `json:"parentId,omitempty" example:"kRp2XYTq9A55"`
// Full path from root (included when ?include=path)
Path virtualfs.Path `json:"path,omitempty"`
// Directory name
Name string `json:"name" example:"My Documents"`
// When the directory was created (ISO 8601)
CreatedAt time.Time `json:"createdAt" example:"2024-12-13T15:04:05Z"`
// When the directory was last updated (ISO 8601)
UpdatedAt time.Time `json:"updatedAt" example:"2024-12-13T16:30:00Z"`
// When the directory was trashed, null if not trashed (ISO 8601)
DeletedAt *time.Time `json:"deletedAt,omitempty" example:"2024-12-14T10:00:00Z"`
}
// createDirectoryRequest represents a new directory creation request
// @Description Request to create a new directory
type createDirectoryRequest struct {
// ID of the parent directory
ParentID string `json:"parentID" example:"kRp2XYTq9A55"`
// Name for the new directory
Name string `json:"name" example:"New Folder"`
}
// postDirectoryContentRequest represents a move items request
// @Description Request to move items into this directory
type postDirectoryContentRequest struct {
// Array of file/directory IDs to move
Items []string `json:"items" example:"mElnUNCm8F22,kRp2XYTq9A55"`
}
// listDirectoryResponse represents the response to a request to list the contents of a directory
// @Description Response to a request to list the contents of a directory
type listDirectoryResponse struct {
// Items is the list of items in the directory, limited to the limit specified in the request
Items []any `json:"items"`
// NextCursor is the cursor to use to get the next page of results
NextCursor string `json:"nextCursor,omitempty"`
}
// moveItemsToDirectoryResponse represents the response to a request
// to move items into a directory.
// @Description Response from moving items to a directory with status for each item
type moveItemsToDirectoryResponse struct {
// Array of items included in the request (FileInfo or DirectoryInfo objects)
Items []any `json:"items"`
// Array of IDs of successfully moved items
Moved []string `json:"moved" example:"mElnUNCm8F22,kRp2XYTq9A55"`
// Array of IDs of items that conflicted with existing items in the target directory
Conflicts []string `json:"conflicts" example:"xYz123AbC456"`
// Array of errors that occurred during the move operation
Errors []moveItemError `json:"errors"`
}
// moveItemError represents an error that occurred while moving a specific item
// @Description Error details for a failed item move
type moveItemError struct {
// ID of the item that failed to move
ID string `json:"id" example:"mElnUNCm8F22"`
// Error message describing what went wrong
Error string `json:"error" example:"permission denied"`
}
type decodedListChildrenCursor struct {
orderBy virtualfs.ListChildrenOrder
orderDirection virtualfs.ListChildrenDirection
nodeID string
}
func (h *HTTPHandler) currentDirectoryMiddleware(c *fiber.Ctx) error {
scope, ok := scopeFromCtx(c)
if !ok {
return c.SendStatus(fiber.StatusUnauthorized)
}
directoryID := c.Params("directoryID")
var node *virtualfs.Node
if directoryID == "root" {
n, err := h.vfs.FindNode(c.Context(), h.db, scope.RootNodeID.String(), scope)
if err != nil {
if errors.Is(err, virtualfs.ErrNodeNotFound) {
return c.SendStatus(fiber.StatusNotFound)
}
return httperr.Internal(err)
}
node = n
} else {
n, err := h.vfs.FindNodeByPublicID(c.Context(), h.db, directoryID, scope)
if err != nil {
if errors.Is(err, virtualfs.ErrNodeNotFound) {
return c.SendStatus(fiber.StatusNotFound)
}
return httperr.Internal(err)
}
node = n
}
c.Locals("directory", node)
return c.Next()
}
func mustCurrentDirectoryNode(c *fiber.Ctx) *virtualfs.Node {
return c.Locals("directory").(*virtualfs.Node)
}
func includeParam(c *fiber.Ctx) []string {
return strings.Split(c.Query("include"), ",")
}
// createDirectory creates a new directory
// @Summary Create directory
// @Description Create a new directory within a parent directory
// @Tags directories
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param request body createDirectoryRequest true "Directory details"
// @Param include query string false "Include additional fields" Enums(path)
// @Success 200 {object} DirectoryInfo "Created directory"
// @Failure 400 {object} map[string]string "Parent not found or not a directory"
// @Failure 401 {string} string "Not authenticated"
// @Failure 409 {object} map[string]string "Directory already exists"
// @Router /drives/{driveID}/directories [post]
func (h *HTTPHandler) createDirectory(c *fiber.Ctx) error {
scope, ok := scopeFromCtx(c)
if !ok {
return c.SendStatus(fiber.StatusUnauthorized)
}
req := new(createDirectoryRequest)
if err := c.BodyParser(req); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
tx, err := h.db.BeginTx(c.Context(), nil)
if err != nil {
return httperr.Internal(err)
}
defer tx.Rollback()
parent, err := h.vfs.FindNodeByPublicID(c.Context(), tx, req.ParentID, scope)
if err != nil {
if errors.Is(err, virtualfs.ErrNodeNotFound) {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Parent not found"})
}
return httperr.Internal(err)
}
if parent.Kind != virtualfs.NodeKindDirectory {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Parent is not a directory"})
}
node, err := h.vfs.CreateDirectory(c.Context(), tx, parent.ID, req.Name, scope)
if err != nil {
if errors.Is(err, virtualfs.ErrNodeConflict) {
return c.Status(fiber.StatusConflict).JSON(fiber.Map{"error": "Directory already exists"})
}
if errors.Is(err, virtualfs.ErrAccessDenied) {
return c.SendStatus(fiber.StatusNotFound)
}
return httperr.Internal(err)
}
i := DirectoryInfo{
Kind: DirItemKindDirectory,
ID: node.PublicID,
Name: node.Name,
CreatedAt: node.CreatedAt,
UpdatedAt: node.UpdatedAt,
DeletedAt: node.DeletedAt,
}
include := includeParam(c)
if slices.Contains(include, "path") {
p, err := h.vfs.RealPath(c.Context(), tx, node, scope)
if err != nil {
if errors.Is(err, virtualfs.ErrAccessDenied) {
return c.SendStatus(fiber.StatusNotFound)
}
return httperr.Internal(err)
}
i.Path = p
}
err = tx.Commit()
if err != nil {
return httperr.Internal(err)
}
return c.JSON(i)
}
// fetchDirectory returns directory metadata
// @Summary Get directory info
// @Description Retrieve metadata for a specific directory
// @Tags directories
// @Produce json
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param directoryID path string true "Directory ID"
// @Param include query string false "Include additional fields" Enums(path)
// @Success 200 {object} DirectoryInfo "Directory metadata"
// @Failure 401 {string} string "Not authenticated"
// @Failure 404 {string} string "Directory not found"
// @Router /drives/{driveID}/directories/{directoryID} [get]
func (h *HTTPHandler) fetchDirectory(c *fiber.Ctx) error {
node := mustCurrentDirectoryNode(c)
scope, ok := scopeFromCtx(c)
if !ok {
return c.SendStatus(fiber.StatusUnauthorized)
}
i := DirectoryInfo{
Kind: DirItemKindDirectory,
ID: node.PublicID,
Name: node.Name,
CreatedAt: node.CreatedAt,
UpdatedAt: node.UpdatedAt,
DeletedAt: node.DeletedAt,
}
include := includeParam(c)
if slices.Contains(include, "path") {
p, err := h.vfs.RealPath(c.Context(), h.db, node, scope)
if err != nil {
if errors.Is(err, virtualfs.ErrAccessDenied) {
return c.SendStatus(fiber.StatusNotFound)
}
return httperr.Internal(err)
}
i.Path = p
}
return c.JSON(i)
}
// listDirectory returns directory contents
// @Summary List directory contents
// @Description Get all files and subdirectories within a directory with optional pagination and sorting
// @Tags directories
// @Produce json
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param directoryID path string true "Directory ID (use 'root' for the root directory)"
// @Param orderBy query string false "Sort field: name, createdAt, or updatedAt" Enums(name,createdAt,updatedAt)
// @Param dir query string false "Sort direction: asc or desc" Enums(asc,desc)
// @Param limit query integer false "Maximum number of items to return (default: 100, min: 1)"
// @Param cursor query string false "Cursor for pagination (base64-encoded cursor from previous response)"
// @Success 200 {object} listDirectoryResponse "Paginated list of FileInfo and DirectoryInfo objects"
// @Failure 400 {object} map[string]string "Invalid limit or cursor"
// @Failure 401 {string} string "Not authenticated"
// @Failure 404 {string} string "Directory not found"
// @Router /drives/{driveID}/directories/{directoryID}/content [get]
func (h *HTTPHandler) listDirectory(c *fiber.Ctx) error {
node := mustCurrentDirectoryNode(c)
scope, ok := scopeFromCtx(c)
if !ok {
return c.SendStatus(fiber.StatusUnauthorized)
}
opts := virtualfs.ListChildrenOptions{}
if by := c.Query("orderBy"); by != "" {
switch by {
case "name":
opts.OrderBy = virtualfs.ListChildrenOrderByName
case "createdAt":
opts.OrderBy = virtualfs.ListChildrenOrderByCreatedAt
case "updatedAt":
opts.OrderBy = virtualfs.ListChildrenOrderByUpdatedAt
}
}
if dir := c.Query("dir"); dir != "" {
switch dir {
case "asc":
opts.OrderDirection = virtualfs.ListChildrenDirectionAsc
case "desc":
opts.OrderDirection = virtualfs.ListChildrenDirectionDesc
}
}
if limit := c.Query("limit"); limit != "" {
limit, err := strconv.Atoi(limit)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid limit"})
}
if limit < 1 {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Limit must be at least 1"})
}
opts.Limit = limit
}
if cursor := c.Query("cursor"); cursor != "" {
dc, err := decodeListChildrenCursor(cursor)
fmt.Printf("dc: %v\n", dc)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid cursor"})
}
n, err := h.vfs.FindNodeByPublicID(c.Context(), h.db, dc.nodeID, scope)
if err != nil {
if errors.Is(err, virtualfs.ErrNodeNotFound) {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid cursor"})
}
return httperr.Internal(err)
}
opts.Cursor = &virtualfs.ListChildrenCursor{
Node: n,
OrderBy: dc.orderBy,
OrderDirection: dc.orderDirection,
}
}
children, cursor, err := h.vfs.ListChildren(c.Context(), h.db, node, opts, scope)
if err != nil {
if errors.Is(err, virtualfs.ErrNodeNotFound) {
return c.SendStatus(fiber.StatusNotFound)
}
if errors.Is(err, virtualfs.ErrAccessDenied) {
return c.SendStatus(fiber.StatusNotFound)
}
return httperr.Internal(err)
}
items := make([]any, len(children))
for i, child := range children {
switch child.Kind {
case virtualfs.NodeKindDirectory:
items[i] = DirectoryInfo{
Kind: DirItemKindDirectory,
ID: child.PublicID,
Name: child.Name,
CreatedAt: child.CreatedAt,
UpdatedAt: child.UpdatedAt,
DeletedAt: child.DeletedAt,
}
case virtualfs.NodeKindFile:
items[i] = FileInfo{
Kind: DirItemKindFile,
ID: child.PublicID,
ParentID: node.PublicID,
Name: child.Name,
Size: child.Size,
MimeType: child.MimeType,
CreatedAt: child.CreatedAt,
UpdatedAt: child.UpdatedAt,
DeletedAt: child.DeletedAt,
}
}
}
if cursor != nil {
return c.JSON(listDirectoryResponse{
Items: items,
NextCursor: encodeListChildrenCursor(cursor),
})
}
return c.JSON(listDirectoryResponse{
Items: items,
})
}
// patchDirectory updates directory properties
// @Summary Update directory
// @Description Update directory properties such as name (rename)
// @Tags directories
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param directoryID path string true "Directory ID"
// @Param request body patchDirectoryRequest true "Directory update"
// @Success 200 {object} DirectoryInfo "Updated directory metadata"
// @Failure 400 {object} map[string]string "Invalid request"
// @Failure 401 {string} string "Not authenticated"
// @Failure 404 {string} string "Directory not found"
// @Router /drives/{driveID}/directories/{directoryID} [patch]
func (h *HTTPHandler) patchDirectory(c *fiber.Ctx) error {
node := mustCurrentDirectoryNode(c)
scope, ok := scopeFromCtx(c)
if !ok {
return c.SendStatus(fiber.StatusUnauthorized)
}
patch := new(patchDirectoryRequest)
if err := c.BodyParser(patch); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request"})
}
tx, err := h.db.BeginTx(c.Context(), nil)
if err != nil {
return httperr.Internal(err)
}
defer tx.Rollback()
if patch.Name != "" {
err := h.vfs.RenameNode(c.Context(), tx, node, patch.Name, scope)
if err != nil {
if errors.Is(err, virtualfs.ErrNodeNotFound) {
return c.SendStatus(fiber.StatusNotFound)
}
if errors.Is(err, virtualfs.ErrAccessDenied) {
return c.SendStatus(fiber.StatusNotFound)
}
return httperr.Internal(err)
}
}
err = tx.Commit()
if err != nil {
return httperr.Internal(err)
}
return c.JSON(DirectoryInfo{
Kind: DirItemKindDirectory,
ID: node.PublicID,
Name: node.Name,
CreatedAt: node.CreatedAt,
UpdatedAt: node.UpdatedAt,
DeletedAt: node.DeletedAt,
})
}
// deleteDirectory removes a directory
// @Summary Delete directory
// @Description Delete a directory permanently or move it to trash. Deleting a directory also affects all its contents.
// @Tags directories
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param directoryID path string true "Directory ID"
// @Param trash query bool false "Move to trash instead of permanent delete" default(false)
// @Success 200 {object} DirectoryInfo "Trashed directory info (when trash=true)"
// @Success 204 {string} string "Directory deleted"
// @Failure 401 {string} string "Not authenticated"
// @Failure 404 {string} string "Directory not found"
// @Router /drives/{driveID}/directories/{directoryID} [delete]
func (h *HTTPHandler) deleteDirectory(c *fiber.Ctx) error {
node := mustCurrentDirectoryNode(c)
scope, ok := scopeFromCtx(c)
if !ok {
return c.SendStatus(fiber.StatusUnauthorized)
}
tx, err := h.db.BeginTx(c.Context(), nil)
if err != nil {
return httperr.Internal(err)
}
defer tx.Rollback()
shouldTrash := c.Query("trash") == "true"
if shouldTrash {
_, err := h.vfs.SoftDeleteNode(c.Context(), tx, node, scope)
if err != nil {
if errors.Is(err, virtualfs.ErrAccessDenied) {
return c.SendStatus(fiber.StatusNotFound)
}
return httperr.Internal(err)
}
err = tx.Commit()
if err != nil {
return httperr.Internal(err)
}
return c.JSON(directoryInfoFromNode(node))
} else {
err = h.vfs.PermanentlyDeleteNode(c.Context(), tx, node, scope)
if err != nil {
if errors.Is(err, virtualfs.ErrAccessDenied) {
return c.SendStatus(fiber.StatusNotFound)
}
return httperr.Internal(err)
}
err = tx.Commit()
if err != nil {
return httperr.Internal(err)
}
return c.SendStatus(fiber.StatusNoContent)
}
}
// deleteDirectories removes multiple directories
// @Summary Bulk delete directories
// @Description Delete multiple directories permanently or move them to trash. Deleting directories also affects all their contents. All items must be directories.
// @Tags directories
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param id query string true "Comma-separated list of directory IDs to delete" example:"kRp2XYTq9A55,xYz123AbC456"
// @Param trash query bool false "Move to trash instead of permanent delete" default(false)
// @Success 200 {array} DirectoryInfo "Trashed directories (when trash=true)"
// @Success 204 {string} string "Directories deleted"
// @Failure 400 {object} map[string]string "All items must be directories"
// @Failure 401 {string} string "Not authenticated"
// @Router /drives/{driveID}/directories [delete]
func (h *HTTPHandler) deleteDirectories(c *fiber.Ctx) error {
scope, ok := scopeFromCtx(c)
if !ok {
return c.SendStatus(fiber.StatusUnauthorized)
}
idq := c.Query("id", "")
if idq == "" {
return c.SendStatus(fiber.StatusNoContent)
}
ids := strings.Split(idq, ",")
if len(ids) == 0 {
return c.SendStatus(fiber.StatusNoContent)
}
shouldTrash := c.Query("trash") == "true"
tx, err := h.db.BeginTx(c.Context(), nil)
if err != nil {
return httperr.Internal(err)
}
defer tx.Rollback()
nodes, err := h.vfs.FindNodesByPublicID(c.Context(), tx, ids, scope)
if err != nil {
return httperr.Internal(err)
}
if len(nodes) == 0 {
return c.SendStatus(fiber.StatusNoContent)
}
for _, node := range nodes {
if node.Kind != virtualfs.NodeKindDirectory {
return httperr.NewHTTPError(fiber.StatusBadRequest, "all items must be directories", nil)
}
}
if shouldTrash {
deleted, err := h.vfs.SoftDeleteNodes(c.Context(), tx, nodes, scope)
if err != nil {
if errors.Is(err, virtualfs.ErrAccessDenied) {
return c.SendStatus(fiber.StatusNotFound)
}
return httperr.Internal(err)
}
err = tx.Commit()
if err != nil {
return httperr.Internal(err)
}
res := make([]DirectoryInfo, 0, len(deleted))
for _, node := range deleted {
res = append(res, directoryInfoFromNode(node))
}
return c.JSON(res)
} else {
for _, node := range nodes {
err = h.vfs.PermanentlyDeleteNode(c.Context(), tx, node, scope)
if err != nil {
if errors.Is(err, virtualfs.ErrAccessDenied) {
return c.SendStatus(fiber.StatusNotFound)
}
return httperr.Internal(err)
}
}
err = tx.Commit()
if err != nil {
return httperr.Internal(err)
}
return c.SendStatus(fiber.StatusNoContent)
}
}
// moveItemsToDirectory moves files and directories into this directory
// @Summary Move items to directory
// @Description Move one or more files or directories into this directory. Returns detailed status for each item including which were successfully moved, which had conflicts, and which encountered errors.
// @Tags directories
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param directoryID path string true "Target directory ID"
// @Param request body postDirectoryContentRequest true "Items to move"
// @Success 200 {object} moveItemsToDirectoryResponse "Move operation results with moved, conflict, and error states"
// @Failure 400 {object} map[string]string "Invalid request or items not in same directory"
// @Failure 401 {string} string "Not authenticated"
// @Failure 404 {object} map[string]string "One or more items not found"
// @Router /drives/{driveID}/directories/{directoryID}/content [post]
func (h *HTTPHandler) moveItemsToDirectory(c *fiber.Ctx) error {
scope, ok := scopeFromCtx(c)
if !ok {
return c.SendStatus(fiber.StatusUnauthorized)
}
targetDir := mustCurrentDirectoryNode(c)
req := new(postDirectoryContentRequest)
if err := c.BodyParser(req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request"})
}
if len(req.Items) == 0 {
return c.SendStatus(fiber.StatusNoContent)
}
tx, err := h.db.BeginTx(c.Context(), nil)
if err != nil {
return httperr.Internal(err)
}
defer tx.Rollback()
nodes, err := h.vfs.FindNodesByPublicID(c.Context(), tx, req.Items, scope)
if err != nil {
return httperr.Internal(err)
}
if len(nodes) != len(req.Items) {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "One or more items not found"})
}
// Move all nodes to the target directory
result, err := h.vfs.MoveNodesInSameDirectory(c.Context(), tx, nodes, targetDir.ID, scope)
if err != nil {
if errors.Is(err, virtualfs.ErrUnsupportedOperation) {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "All items must be in the same directory"})
}
if errors.Is(err, virtualfs.ErrNodeConflict) {
return c.Status(fiber.StatusConflict).JSON(fiber.Map{"error": "Name conflict in target directory"})
}
if errors.Is(err, virtualfs.ErrAccessDenied) {
return c.SendStatus(fiber.StatusNotFound)
}
return httperr.Internal(err)
}
err = tx.Commit()
if err != nil {
return httperr.Internal(err)
}
res := moveItemsToDirectoryResponse{
Items: make([]any, 0),
Moved: make([]string, 0),
Conflicts: make([]string, 0),
Errors: make([]moveItemError, 0),
}
for _, node := range result.Moved {
res.Items = append(res.Items, toDirectoryItem(node))
res.Moved = append(res.Moved, node.PublicID)
}
for _, node := range result.Conflicts {
res.Items = append(res.Items, toDirectoryItem(node))
res.Conflicts = append(res.Conflicts, node.PublicID)
}
for _, err := range result.Errors {
res.Errors = append(res.Errors, moveItemError{
ID: err.Node.PublicID,
Error: err.Error.Error(),
})
}
return c.JSON(res)
}
func encodeListChildrenCursor(cursor *virtualfs.ListChildrenCursor) string {
var by int
switch cursor.OrderBy {
case virtualfs.ListChildrenOrderByName:
by = 0
case virtualfs.ListChildrenOrderByCreatedAt:
by = 1
case virtualfs.ListChildrenOrderByUpdatedAt:
by = 2
}
var d int
switch cursor.OrderDirection {
case virtualfs.ListChildrenDirectionAsc:
d = 0
case virtualfs.ListChildrenDirectionDesc:
d = 1
}
s := fmt.Sprintf("%d:%d:%s", by, d, cursor.Node.PublicID)
return base64.URLEncoding.EncodeToString([]byte(s))
}
func decodeListChildrenCursor(s string) (*decodedListChildrenCursor, error) {
bs, err := base64.URLEncoding.DecodeString(s)
if err != nil {
return nil, err
}
parts := strings.Split(string(bs), ":")
if len(parts) != 3 {
return nil, errors.New("invalid cursor")
}
c := new(decodedListChildrenCursor)
switch parts[0] {
case "0":
c.orderBy = virtualfs.ListChildrenOrderByName
case "1":
c.orderBy = virtualfs.ListChildrenOrderByCreatedAt
case "2":
c.orderBy = virtualfs.ListChildrenOrderByUpdatedAt
default:
return nil, errors.New("invalid cursor")
}
switch parts[1] {
case "0":
c.orderDirection = virtualfs.ListChildrenDirectionAsc
case "1":
c.orderDirection = virtualfs.ListChildrenDirectionDesc
default:
return nil, errors.New("invalid cursor")
}
c.nodeID = parts[2]
return c, nil
}
// listDirectoryShares returns all shares that include this directory
// @Summary List directory shares
// @Description Get all share links that include this directory
// @Tags directories
// @Produce json
// @Param driveID path string true "Drive ID" format(uuid)
// @Param directoryID path string true "Directory ID"
// @Success 200 {array} sharing.Share "Array of shares"
// @Failure 401 {string} string "Not authenticated"
// @Failure 404 {string} string "Directory not found"
// @Security BearerAuth
// @Router /drives/{driveID}/directories/{directoryID}/shares [get]
func (h *HTTPHandler) listDirectoryShares(c *fiber.Ctx) error {
node := mustCurrentDirectoryNode(c)
includesExpired := c.Query("includesExpired") == "true"
shares, err := h.sharingService.ListShares(c.Context(), h.db, node.DriveID, sharing.ListSharesOptions{
Items: []*virtualfs.Node{node},
IncludesExpired: includesExpired,
})
if err != nil {
return httperr.Internal(err)
}
return c.JSON(shares)
}