mirror of
https://github.com/get-drexa/drive.git
synced 2026-02-02 14:51:18 +00:00
feat: initial sharing impl
This commit is contained in:
@@ -9,8 +9,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/get-drexa/drexa/internal/account"
|
||||
"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"
|
||||
)
|
||||
@@ -99,8 +99,8 @@ type decodedListChildrenCursor struct {
|
||||
}
|
||||
|
||||
func (h *HTTPHandler) currentDirectoryMiddleware(c *fiber.Ctx) error {
|
||||
account := account.CurrentAccount(c)
|
||||
if account == nil {
|
||||
scope, ok := scopeFromCtx(c)
|
||||
if !ok {
|
||||
return c.SendStatus(fiber.StatusUnauthorized)
|
||||
}
|
||||
|
||||
@@ -108,13 +108,16 @@ func (h *HTTPHandler) currentDirectoryMiddleware(c *fiber.Ctx) error {
|
||||
|
||||
var node *virtualfs.Node
|
||||
if directoryID == "root" {
|
||||
n, err := h.vfs.FindRootDirectory(c.Context(), h.db, account.ID)
|
||||
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, account.ID, directoryID)
|
||||
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)
|
||||
@@ -153,8 +156,8 @@ func includeParam(c *fiber.Ctx) []string {
|
||||
// @Failure 409 {object} map[string]string "Directory already exists"
|
||||
// @Router /accounts/{accountID}/directories [post]
|
||||
func (h *HTTPHandler) createDirectory(c *fiber.Ctx) error {
|
||||
account := account.CurrentAccount(c)
|
||||
if account == nil {
|
||||
scope, ok := scopeFromCtx(c)
|
||||
if !ok {
|
||||
return c.SendStatus(fiber.StatusUnauthorized)
|
||||
}
|
||||
|
||||
@@ -169,7 +172,7 @@ func (h *HTTPHandler) createDirectory(c *fiber.Ctx) error {
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
parent, err := h.vfs.FindNodeByPublicID(c.Context(), tx, account.ID, req.ParentID)
|
||||
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"})
|
||||
@@ -181,11 +184,14 @@ func (h *HTTPHandler) createDirectory(c *fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Parent is not a directory"})
|
||||
}
|
||||
|
||||
node, err := h.vfs.CreateDirectory(c.Context(), tx, account.ID, parent.ID, req.Name)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -200,8 +206,11 @@ func (h *HTTPHandler) createDirectory(c *fiber.Ctx) error {
|
||||
|
||||
include := includeParam(c)
|
||||
if slices.Contains(include, "path") {
|
||||
p, err := h.vfs.RealPath(c.Context(), tx, node)
|
||||
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
|
||||
@@ -230,6 +239,10 @@ func (h *HTTPHandler) createDirectory(c *fiber.Ctx) error {
|
||||
// @Router /accounts/{accountID}/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,
|
||||
@@ -242,8 +255,11 @@ func (h *HTTPHandler) fetchDirectory(c *fiber.Ctx) error {
|
||||
|
||||
include := includeParam(c)
|
||||
if slices.Contains(include, "path") {
|
||||
p, err := h.vfs.RealPath(c.Context(), h.db, node)
|
||||
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
|
||||
@@ -254,7 +270,7 @@ func (h *HTTPHandler) fetchDirectory(c *fiber.Ctx) error {
|
||||
|
||||
// listDirectory returns directory contents
|
||||
// @Summary List directory contents
|
||||
// @Description Get all files and subdirectories within a directory with optional pagination, sorting, and filtering
|
||||
// @Description Get all files and subdirectories within a directory with optional pagination and sorting
|
||||
// @Tags directories
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
@@ -271,6 +287,10 @@ func (h *HTTPHandler) fetchDirectory(c *fiber.Ctx) error {
|
||||
// @Router /accounts/{accountID}/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{}
|
||||
|
||||
@@ -312,7 +332,7 @@ func (h *HTTPHandler) listDirectory(c *fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid cursor"})
|
||||
}
|
||||
|
||||
n, err := h.vfs.FindNodeByPublicID(c.Context(), h.db, node.AccountID, dc.nodeID)
|
||||
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"})
|
||||
@@ -327,11 +347,14 @@ func (h *HTTPHandler) listDirectory(c *fiber.Ctx) error {
|
||||
}
|
||||
}
|
||||
|
||||
children, cursor, err := h.vfs.ListChildren(c.Context(), h.db, node, opts)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -392,6 +415,10 @@ func (h *HTTPHandler) listDirectory(c *fiber.Ctx) error {
|
||||
// @Router /accounts/{accountID}/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 {
|
||||
@@ -405,11 +432,14 @@ func (h *HTTPHandler) patchDirectory(c *fiber.Ctx) error {
|
||||
defer tx.Rollback()
|
||||
|
||||
if patch.Name != "" {
|
||||
err := h.vfs.RenameNode(c.Context(), tx, node, 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)
|
||||
}
|
||||
}
|
||||
@@ -437,12 +467,17 @@ func (h *HTTPHandler) patchDirectory(c *fiber.Ctx) error {
|
||||
// @Param accountID path string true "Account 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 /accounts/{accountID}/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 {
|
||||
@@ -452,8 +487,11 @@ func (h *HTTPHandler) deleteDirectory(c *fiber.Ctx) error {
|
||||
|
||||
shouldTrash := c.Query("trash") == "true"
|
||||
if shouldTrash {
|
||||
_, err := h.vfs.SoftDeleteNode(c.Context(), tx, node)
|
||||
_, 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)
|
||||
}
|
||||
|
||||
@@ -464,8 +502,11 @@ func (h *HTTPHandler) deleteDirectory(c *fiber.Ctx) error {
|
||||
|
||||
return c.JSON(directoryInfoFromNode(node))
|
||||
} else {
|
||||
err = h.vfs.PermanentlyDeleteNode(c.Context(), tx, node)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -486,13 +527,14 @@ func (h *HTTPHandler) deleteDirectory(c *fiber.Ctx) error {
|
||||
// @Param accountID path string true "Account 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 /accounts/{accountID}/directories [delete]
|
||||
func (h *HTTPHandler) deleteDirectories(c *fiber.Ctx) error {
|
||||
account := account.CurrentAccount(c)
|
||||
if account == nil {
|
||||
scope, ok := scopeFromCtx(c)
|
||||
if !ok {
|
||||
return c.SendStatus(fiber.StatusUnauthorized)
|
||||
}
|
||||
|
||||
@@ -514,7 +556,7 @@ func (h *HTTPHandler) deleteDirectories(c *fiber.Ctx) error {
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
nodes, err := h.vfs.FindNodesByPublicID(c.Context(), tx, account.ID, ids)
|
||||
nodes, err := h.vfs.FindNodesByPublicID(c.Context(), tx, ids, scope)
|
||||
if err != nil {
|
||||
return httperr.Internal(err)
|
||||
}
|
||||
@@ -530,8 +572,11 @@ func (h *HTTPHandler) deleteDirectories(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
if shouldTrash {
|
||||
deleted, err := h.vfs.SoftDeleteNodes(c.Context(), tx, nodes)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -545,11 +590,14 @@ func (h *HTTPHandler) deleteDirectories(c *fiber.Ctx) error {
|
||||
res = append(res, directoryInfoFromNode(node))
|
||||
}
|
||||
|
||||
return c.JSON(deleted)
|
||||
return c.JSON(res)
|
||||
} else {
|
||||
for _, node := range nodes {
|
||||
err = h.vfs.PermanentlyDeleteNode(c.Context(), tx, node)
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -580,8 +628,8 @@ func (h *HTTPHandler) deleteDirectories(c *fiber.Ctx) error {
|
||||
// @Failure 404 {object} map[string]string "One or more items not found"
|
||||
// @Router /accounts/{accountID}/directories/{directoryID}/content [post]
|
||||
func (h *HTTPHandler) moveItemsToDirectory(c *fiber.Ctx) error {
|
||||
acc := account.CurrentAccount(c)
|
||||
if acc == nil {
|
||||
scope, ok := scopeFromCtx(c)
|
||||
if !ok {
|
||||
return c.SendStatus(fiber.StatusUnauthorized)
|
||||
}
|
||||
|
||||
@@ -602,7 +650,7 @@ func (h *HTTPHandler) moveItemsToDirectory(c *fiber.Ctx) error {
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
nodes, err := h.vfs.FindNodesByPublicID(c.Context(), tx, acc.ID, req.Items)
|
||||
nodes, err := h.vfs.FindNodesByPublicID(c.Context(), tx, req.Items, scope)
|
||||
if err != nil {
|
||||
return httperr.Internal(err)
|
||||
}
|
||||
@@ -611,7 +659,7 @@ func (h *HTTPHandler) moveItemsToDirectory(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// Move all nodes to the target directory
|
||||
result, err := h.vfs.MoveNodesInSameDirectory(c.Context(), tx, nodes, targetDir.ID)
|
||||
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"})
|
||||
@@ -619,6 +667,9 @@ func (h *HTTPHandler) moveItemsToDirectory(c *fiber.Ctx) error {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -712,3 +763,28 @@ func decodeListChildrenCursor(s string) (*decodedListChildrenCursor, error) {
|
||||
|
||||
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 accountID path string true "Account 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 /accounts/{accountID}/directories/{directoryID}/shares [get]
|
||||
func (h *HTTPHandler) listDirectoryShares(c *fiber.Ctx) error {
|
||||
node := mustCurrentDirectoryNode(c)
|
||||
|
||||
shares, err := h.sharingService.ListShares(c.Context(), h.db, node.AccountID, sharing.ListSharesOptions{
|
||||
Items: []*virtualfs.Node{node},
|
||||
})
|
||||
if err != nil {
|
||||
return httperr.Internal(err)
|
||||
}
|
||||
|
||||
return c.JSON(shares)
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/get-drexa/drexa/internal/account"
|
||||
"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"
|
||||
)
|
||||
@@ -39,13 +39,13 @@ func mustCurrentFileNode(c *fiber.Ctx) *virtualfs.Node {
|
||||
}
|
||||
|
||||
func (h *HTTPHandler) currentFileMiddleware(c *fiber.Ctx) error {
|
||||
account := account.CurrentAccount(c)
|
||||
if account == nil {
|
||||
scope, ok := scopeFromCtx(c)
|
||||
if !ok {
|
||||
return c.SendStatus(fiber.StatusUnauthorized)
|
||||
}
|
||||
|
||||
fileID := c.Params("fileID")
|
||||
node, err := h.vfs.FindNodeByPublicID(c.Context(), h.db, account.ID, fileID)
|
||||
node, err := h.vfs.FindNodeByPublicID(c.Context(), h.db, fileID, scope)
|
||||
if err != nil {
|
||||
if errors.Is(err, virtualfs.ErrNodeNotFound) {
|
||||
return c.SendStatus(fiber.StatusNotFound)
|
||||
@@ -100,9 +100,16 @@ func (h *HTTPHandler) fetchFile(c *fiber.Ctx) error {
|
||||
// @Router /accounts/{accountID}/files/{fileID}/content [get]
|
||||
func (h *HTTPHandler) downloadFile(c *fiber.Ctx) error {
|
||||
node := mustCurrentFileNode(c)
|
||||
scope, ok := scopeFromCtx(c)
|
||||
if !ok {
|
||||
return c.SendStatus(fiber.StatusUnauthorized)
|
||||
}
|
||||
|
||||
content, err := h.vfs.ReadFile(c.Context(), h.db, node)
|
||||
content, err := h.vfs.ReadFile(c.Context(), h.db, node, scope)
|
||||
if err != nil {
|
||||
if errors.Is(err, virtualfs.ErrAccessDenied) {
|
||||
return c.SendStatus(fiber.StatusNotFound)
|
||||
}
|
||||
if errors.Is(err, virtualfs.ErrUnsupportedOperation) {
|
||||
return c.SendStatus(fiber.StatusNotFound)
|
||||
}
|
||||
@@ -143,6 +150,10 @@ func (h *HTTPHandler) downloadFile(c *fiber.Ctx) error {
|
||||
// @Router /accounts/{accountID}/files/{fileID} [patch]
|
||||
func (h *HTTPHandler) patchFile(c *fiber.Ctx) error {
|
||||
node := mustCurrentFileNode(c)
|
||||
scope, ok := scopeFromCtx(c)
|
||||
if !ok {
|
||||
return c.SendStatus(fiber.StatusUnauthorized)
|
||||
}
|
||||
|
||||
patch := new(patchFileRequest)
|
||||
if err := c.BodyParser(patch); err != nil {
|
||||
@@ -156,11 +167,14 @@ func (h *HTTPHandler) patchFile(c *fiber.Ctx) error {
|
||||
defer tx.Rollback()
|
||||
|
||||
if patch.Name != "" {
|
||||
err := h.vfs.RenameNode(c.Context(), tx, node, 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)
|
||||
}
|
||||
}
|
||||
@@ -197,6 +211,10 @@ func (h *HTTPHandler) patchFile(c *fiber.Ctx) error {
|
||||
// @Router /accounts/{accountID}/files/{fileID} [delete]
|
||||
func (h *HTTPHandler) deleteFile(c *fiber.Ctx) error {
|
||||
node := mustCurrentFileNode(c)
|
||||
scope, ok := scopeFromCtx(c)
|
||||
if !ok {
|
||||
return c.SendStatus(fiber.StatusUnauthorized)
|
||||
}
|
||||
|
||||
tx, err := h.db.BeginTx(c.Context(), nil)
|
||||
if err != nil {
|
||||
@@ -206,11 +224,14 @@ func (h *HTTPHandler) deleteFile(c *fiber.Ctx) error {
|
||||
|
||||
shouldTrash := c.Query("trash") == "true"
|
||||
if shouldTrash {
|
||||
deleted, err := h.vfs.SoftDeleteNode(c.Context(), tx, node)
|
||||
deleted, err := h.vfs.SoftDeleteNode(c.Context(), tx, node, 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)
|
||||
}
|
||||
|
||||
@@ -221,8 +242,11 @@ func (h *HTTPHandler) deleteFile(c *fiber.Ctx) error {
|
||||
|
||||
return c.JSON(fileInfoFromNode(deleted))
|
||||
} else {
|
||||
err = h.vfs.PermanentlyDeleteNode(c.Context(), tx, node)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -243,13 +267,14 @@ func (h *HTTPHandler) deleteFile(c *fiber.Ctx) error {
|
||||
// @Param accountID path string true "Account ID" format(uuid)
|
||||
// @Param id query string true "Comma-separated list of file IDs to delete" example:"mElnUNCm8F22,kRp2XYTq9A55"
|
||||
// @Param trash query bool false "Move to trash instead of permanent delete" default(false)
|
||||
// @Success 200 {array} FileInfo "Trashed files (when trash=true)"
|
||||
// @Success 204 {string} string "Files deleted"
|
||||
// @Failure 400 {object} map[string]string "All items must be files"
|
||||
// @Failure 401 {string} string "Not authenticated"
|
||||
// @Router /accounts/{accountID}/files [delete]
|
||||
func (h *HTTPHandler) deleteFiles(c *fiber.Ctx) error {
|
||||
account := account.CurrentAccount(c)
|
||||
if account == nil {
|
||||
scope, ok := scopeFromCtx(c)
|
||||
if !ok {
|
||||
return c.SendStatus(fiber.StatusUnauthorized)
|
||||
}
|
||||
|
||||
@@ -271,7 +296,7 @@ func (h *HTTPHandler) deleteFiles(c *fiber.Ctx) error {
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
nodes, err := h.vfs.FindNodesByPublicID(c.Context(), tx, account.ID, ids)
|
||||
nodes, err := h.vfs.FindNodesByPublicID(c.Context(), tx, ids, scope)
|
||||
if err != nil {
|
||||
return httperr.Internal(err)
|
||||
}
|
||||
@@ -281,8 +306,11 @@ func (h *HTTPHandler) deleteFiles(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
if shouldTrash {
|
||||
deleted, err := h.vfs.SoftDeleteNodes(c.Context(), tx, nodes)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -298,11 +326,14 @@ func (h *HTTPHandler) deleteFiles(c *fiber.Ctx) error {
|
||||
|
||||
return c.JSON(res)
|
||||
} else {
|
||||
err = h.vfs.PermanentlyDeleteFiles(c.Context(), tx, nodes)
|
||||
err = h.vfs.PermanentlyDeleteFiles(c.Context(), tx, nodes, scope)
|
||||
if err != nil {
|
||||
if errors.Is(err, virtualfs.ErrUnsupportedOperation) {
|
||||
return httperr.NewHTTPError(fiber.StatusBadRequest, "all items must be files", err)
|
||||
}
|
||||
if errors.Is(err, virtualfs.ErrAccessDenied) {
|
||||
return c.SendStatus(fiber.StatusNotFound)
|
||||
}
|
||||
return httperr.Internal(err)
|
||||
}
|
||||
|
||||
@@ -315,3 +346,28 @@ func (h *HTTPHandler) deleteFiles(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// listFileShares returns all shares that include this file
|
||||
// @Summary List file shares
|
||||
// @Description Get all share links that include this file
|
||||
// @Tags files
|
||||
// @Produce json
|
||||
// @Param accountID path string true "Account ID" format(uuid)
|
||||
// @Param fileID path string true "File ID"
|
||||
// @Success 200 {array} sharing.Share "Array of shares"
|
||||
// @Failure 401 {string} string "Not authenticated"
|
||||
// @Failure 404 {string} string "File not found"
|
||||
// @Security BearerAuth
|
||||
// @Router /accounts/{accountID}/files/{fileID}/shares [get]
|
||||
func (h *HTTPHandler) listFileShares(c *fiber.Ctx) error {
|
||||
node := mustCurrentFileNode(c)
|
||||
|
||||
shares, err := h.sharingService.ListShares(c.Context(), h.db, node.AccountID, sharing.ListSharesOptions{
|
||||
Items: []*virtualfs.Node{node},
|
||||
})
|
||||
if err != nil {
|
||||
return httperr.Internal(err)
|
||||
}
|
||||
|
||||
return c.JSON(shares)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package catalog
|
||||
|
||||
import (
|
||||
"github.com/get-drexa/drexa/internal/reqctx"
|
||||
"github.com/get-drexa/drexa/internal/sharing"
|
||||
"github.com/get-drexa/drexa/internal/virtualfs"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type HTTPHandler struct {
|
||||
vfs *virtualfs.VirtualFS
|
||||
db *bun.DB
|
||||
sharingService *sharing.Service
|
||||
vfs *virtualfs.VirtualFS
|
||||
db *bun.DB
|
||||
}
|
||||
|
||||
// patchFileRequest represents a file update request
|
||||
@@ -25,11 +28,11 @@ type patchDirectoryRequest struct {
|
||||
Name string `json:"name" example:"My Documents"`
|
||||
}
|
||||
|
||||
func NewHTTPHandler(vfs *virtualfs.VirtualFS, db *bun.DB) *HTTPHandler {
|
||||
return &HTTPHandler{vfs: vfs, db: db}
|
||||
func NewHTTPHandler(sharingService *sharing.Service, vfs *virtualfs.VirtualFS, db *bun.DB) *HTTPHandler {
|
||||
return &HTTPHandler{sharingService: sharingService, vfs: vfs, db: db}
|
||||
}
|
||||
|
||||
func (h *HTTPHandler) RegisterRoutes(api fiber.Router) {
|
||||
func (h *HTTPHandler) RegisterRoutes(api *virtualfs.ScopedRouter) {
|
||||
api.Delete("/files", h.deleteFiles)
|
||||
|
||||
fg := api.Group("/files/:fileID")
|
||||
@@ -38,6 +41,7 @@ func (h *HTTPHandler) RegisterRoutes(api fiber.Router) {
|
||||
fg.Get("/content", h.downloadFile)
|
||||
fg.Patch("/", h.patchFile)
|
||||
fg.Delete("/", h.deleteFile)
|
||||
fg.Get("/shares", h.listFileShares)
|
||||
|
||||
api.Post("/directories", h.createDirectory)
|
||||
api.Delete("/directories", h.deleteDirectories)
|
||||
@@ -49,6 +53,7 @@ func (h *HTTPHandler) RegisterRoutes(api fiber.Router) {
|
||||
dg.Get("/content", h.listDirectory)
|
||||
dg.Patch("/", h.patchDirectory)
|
||||
dg.Delete("/", h.deleteDirectory)
|
||||
dg.Get("/shares", h.listDirectoryShares)
|
||||
}
|
||||
|
||||
func fileInfoFromNode(node *virtualfs.Node) FileInfo {
|
||||
@@ -82,3 +87,15 @@ func toDirectoryItem(node *virtualfs.Node) any {
|
||||
return fileInfoFromNode(node)
|
||||
}
|
||||
}
|
||||
|
||||
func scopeFromCtx(c *fiber.Ctx) (*virtualfs.Scope, bool) {
|
||||
scopeAny := reqctx.VFSAccessScope(c)
|
||||
if scopeAny == nil {
|
||||
return nil, false
|
||||
}
|
||||
scope, ok := scopeAny.(*virtualfs.Scope)
|
||||
if !ok || scope == nil {
|
||||
return nil, false
|
||||
}
|
||||
return scope, true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user