feat(backend): hide drive uuid and return root dir

This commit is contained in:
2026-01-04 17:48:51 +00:00
parent 21a751c10b
commit 86e90af5c2
9 changed files with 87 additions and 37 deletions

View File

@@ -147,7 +147,7 @@ func includeParam(c *fiber.Ctx) []string {
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @Param request body createDirectoryRequest true "Directory details"
// @Param include query string false "Include additional fields" Enums(path)
// @Success 200 {object} DirectoryInfo "Created directory"
@@ -230,7 +230,7 @@ func (h *HTTPHandler) createDirectory(c *fiber.Ctx) error {
// @Tags directories
// @Produce json
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @Param directoryID path string true "Directory ID"
// @Param include query string false "Include additional fields" Enums(path)
// @Success 200 {object} DirectoryInfo "Directory metadata"
@@ -274,7 +274,7 @@ func (h *HTTPHandler) fetchDirectory(c *fiber.Ctx) error {
// @Tags directories
// @Produce json
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @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)
@@ -405,7 +405,7 @@ func (h *HTTPHandler) listDirectory(c *fiber.Ctx) error {
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @Param directoryID path string true "Directory ID"
// @Param request body patchDirectoryRequest true "Directory update"
// @Success 200 {object} DirectoryInfo "Updated directory metadata"
@@ -464,7 +464,7 @@ func (h *HTTPHandler) patchDirectory(c *fiber.Ctx) error {
// @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 driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @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)"
@@ -524,7 +524,7 @@ func (h *HTTPHandler) deleteDirectory(c *fiber.Ctx) error {
// @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 driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @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)"
@@ -619,7 +619,7 @@ func (h *HTTPHandler) deleteDirectories(c *fiber.Ctx) error {
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @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"
@@ -769,7 +769,7 @@ func decodeListChildrenCursor(s string) (*decodedListChildrenCursor, error) {
// @Description Get all share links that include this directory
// @Tags directories
// @Produce json
// @Param driveID path string true "Drive ID" format(uuid)
// @Param driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @Param directoryID path string true "Directory ID"
// @Success 200 {array} sharing.Share "Array of shares"
// @Failure 401 {string} string "Not authenticated"

View File

@@ -64,7 +64,7 @@ func (h *HTTPHandler) currentFileMiddleware(c *fiber.Ctx) error {
// @Tags files
// @Produce json
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @Param fileID path string true "File ID"
// @Success 200 {object} FileInfo "File metadata"
// @Failure 401 {string} string "Not authenticated"
@@ -91,7 +91,7 @@ func (h *HTTPHandler) fetchFile(c *fiber.Ctx) error {
// @Tags files
// @Produce application/octet-stream
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @Param fileID path string true "File ID"
// @Success 200 {file} binary "File content stream"
// @Success 307 {string} string "Redirect to download URL"
@@ -140,7 +140,7 @@ func (h *HTTPHandler) downloadFile(c *fiber.Ctx) error {
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @Param fileID path string true "File ID"
// @Param request body patchFileRequest true "File update"
// @Success 200 {object} FileInfo "Updated file metadata"
@@ -201,7 +201,7 @@ func (h *HTTPHandler) patchFile(c *fiber.Ctx) error {
// @Tags files
// @Produce json
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @Param fileID path string true "File ID"
// @Param trash query bool false "Move to trash instead of permanent delete" default(false)
// @Success 200 {object} FileInfo "Trashed file info (when trash=true)"
@@ -264,7 +264,7 @@ func (h *HTTPHandler) deleteFile(c *fiber.Ctx) error {
// @Description Delete multiple files permanently or move them to trash. All items must be files.
// @Tags files
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @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)"
@@ -352,7 +352,7 @@ func (h *HTTPHandler) deleteFiles(c *fiber.Ctx) error {
// @Description Get all share links that include this file
// @Tags files
// @Produce json
// @Param driveID path string true "Drive ID" format(uuid)
// @Param driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @Param fileID path string true "File ID"
// @Success 200 {array} sharing.Share "Array of shares"
// @Failure 401 {string} string "Not authenticated"

View File

@@ -164,6 +164,20 @@ func TestRegistrationFlow(t *testing.T) {
}
})
t.Run("drive info", func(t *testing.T) {
var info struct {
ID string `json:"id"`
RootDirID string `json:"rootDirId"`
}
doJSON(t, s.App(), http.MethodGet, fmt.Sprintf("/api/my/drives/%s", reg.Drive.ID), reg.AccessToken, nil, http.StatusOK, &info)
if info.ID != reg.Drive.ID {
t.Fatalf("unexpected drive id: got %q want %q", info.ID, reg.Drive.ID)
}
if info.RootDirID == "" {
t.Fatalf("expected rootDirId to be set")
}
})
t.Run("users/me", func(t *testing.T) {
var me struct {
ID string `json:"id"`

View File

@@ -10,12 +10,12 @@ import (
type Drive struct {
bun.BaseModel `bun:"drives" swaggerignore:"true"`
ID uuid.UUID `bun:",pk,type:uuid" json:"id"`
PublicID string `bun:"public_id,notnull" json:"publicId"`
OrgID uuid.UUID `bun:"org_id,notnull,type:uuid" json:"orgId"`
ID uuid.UUID `bun:",pk,type:uuid" json:"-"`
PublicID string `bun:"public_id,notnull" json:"id"`
OrgID uuid.UUID `bun:"org_id,notnull,type:uuid" json:"-"`
Name string `bun:"name,notnull" json:"name"`
OwnerAccountID *uuid.UUID `bun:"owner_account_id,type:uuid" json:"ownerAccountId,omitempty"`
OwnerAccountID *uuid.UUID `bun:"owner_account_id,type:uuid" json:"-"`
StorageUsageBytes int64 `bun:"storage_usage_bytes,notnull" json:"storageUsageBytes"`
StorageQuotaBytes int64 `bun:"storage_quota_bytes,notnull" json:"storageQuotaBytes"`

View File

@@ -10,10 +10,14 @@ import (
"github.com/get-drexa/drexa/internal/user"
"github.com/get-drexa/drexa/internal/virtualfs"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"github.com/uptrace/bun"
)
type driveInfoResponse struct {
*Drive
RootDirID string `json:"rootDirId"`
}
type HTTPHandler struct {
driveService *Service
accountService *account.Service
@@ -39,7 +43,7 @@ func (h *HTTPHandler) RegisterRoutes(api fiber.Router) *virtualfs.ScopedRouter {
drive.Use(h.authMiddleware)
drive.Use(h.driveMiddleware)
drive.Get("/", h.getDrive)
drive.Get("/", h.getDriveInfo)
return &virtualfs.ScopedRouter{Router: drive}
}
@@ -69,12 +73,34 @@ func (h *HTTPHandler) listDrives(c *fiber.Ctx) error {
return c.JSON(drives)
}
func (h *HTTPHandler) getDrive(c *fiber.Ctx) error {
func (h *HTTPHandler) getDriveInfo(c *fiber.Ctx) error {
drive, ok := reqctx.CurrentDrive(c).(*Drive)
if !ok || drive == nil {
return c.SendStatus(fiber.StatusNotFound)
}
return c.JSON(drive)
rootDirID, _ := c.Locals("rootDirId").(string)
if rootDirID == "" {
scopeAny := reqctx.VFSAccessScope(c)
scope, ok := scopeAny.(*virtualfs.Scope)
if !ok || scope == nil {
return c.SendStatus(fiber.StatusUnauthorized)
}
root, err := h.vfs.FindNode(c.Context(), h.db, scope.RootNodeID.String(), scope)
if err != nil {
if errors.Is(err, virtualfs.ErrNodeNotFound) || errors.Is(err, virtualfs.ErrAccessDenied) {
return c.SendStatus(fiber.StatusNotFound)
}
return httperr.Internal(err)
}
rootDirID = root.PublicID
}
return c.JSON(driveInfoResponse{
Drive: drive,
RootDirID: rootDirID,
})
}
func (h *HTTPHandler) driveMiddleware(c *fiber.Ctx) error {
@@ -83,12 +109,8 @@ func (h *HTTPHandler) driveMiddleware(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusNotFound)
}
driveID, err := uuid.Parse(c.Params("driveID"))
if err != nil {
return c.SendStatus(fiber.StatusNotFound)
}
drive, err := h.driveService.DriveByID(c.Context(), h.db, driveID)
driveID := c.Params("driveID")
drive, err := h.driveService.DriveByPublicID(c.Context(), h.db, driveID)
if err != nil {
if errors.Is(err, ErrDriveNotFound) {
return c.SendStatus(fiber.StatusNotFound)
@@ -113,6 +135,8 @@ func (h *HTTPHandler) driveMiddleware(c *fiber.Ctx) error {
return httperr.Internal(err)
}
c.Locals("rootDirId", root.PublicID)
scope := &virtualfs.Scope{
DriveID: drive.ID,
RootNodeID: root.ID,

View File

@@ -65,6 +65,18 @@ func (s *Service) DriveByID(ctx context.Context, db bun.IDB, id uuid.UUID) (*Dri
return &drive, nil
}
func (s *Service) DriveByPublicID(ctx context.Context, db bun.IDB, publicID string) (*Drive, error) {
var drive Drive
err := db.NewSelect().Model(&drive).Where("public_id = ?", publicID).Scan(ctx)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrDriveNotFound
}
return nil, err
}
return &drive, nil
}
// ListAccessibleDrives returns drives a principal account can access:
// - personal drives: owner_account_id = account.ID
// - shared drives: owner_account_id IS NULL (future)

View File

@@ -130,7 +130,7 @@ func (h *HTTPHandler) shareMiddleware(c *fiber.Ctx) error {
// @Tags shares
// @Accept json
// @Produce json
// @Param driveID path string true "Drive ID" format(uuid)
// @Param driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @Param shareID path string true "Share ID"
// @Success 200 {object} Share "Share details"
// @Failure 401 {string} string "Not authenticated"
@@ -161,7 +161,7 @@ func (h *HTTPHandler) getShare(c *fiber.Ctx) error {
// @Tags shares
// @Accept json
// @Produce json
// @Param driveID path string true "Drive ID" format(uuid)
// @Param driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @Param request body createShareRequest true "Share details"
// @Success 200 {object} Share "Created share"
// @Failure 400 {object} map[string]string "Invalid request, items not in same directory, or root directory cannot be shared"
@@ -244,7 +244,7 @@ func (h *HTTPHandler) createShare(c *fiber.Ctx) error {
// @Tags shares
// @Accept json
// @Produce json
// @Param driveID path string true "Drive ID" format(uuid)
// @Param driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @Param shareID path string true "Share ID"
// @Param request body patchShareRequest true "Share details"
// @Success 200 {object} Share "Updated share"
@@ -313,7 +313,7 @@ func (h *HTTPHandler) updateShare(c *fiber.Ctx) error {
// @Summary Delete share
// @Description Delete a share link, revoking access for all users
// @Tags shares
// @Param driveID path string true "Drive ID" format(uuid)
// @Param driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @Param shareID path string true "Share ID"
// @Success 204 {string} string "Share deleted"
// @Failure 401 {string} string "Not authenticated"

View File

@@ -51,7 +51,7 @@ func (h *HTTPHandler) RegisterRoutes(api *virtualfs.ScopedRouter) {
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @Param request body createUploadRequest true "Upload details"
// @Success 200 {object} Upload "Upload session created"
// @Failure 400 {object} map[string]string "Parent is not a directory"
@@ -107,7 +107,7 @@ func (h *HTTPHandler) Create(c *fiber.Ctx) error {
// @Tags uploads
// @Accept application/octet-stream
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @Param uploadID path string true "Upload session ID"
// @Param file body []byte true "File content (binary)"
// @Success 204 {string} string "Content received successfully"
@@ -148,7 +148,7 @@ func (h *HTTPHandler) ReceiveContent(c *fiber.Ctx) error {
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param driveID path string true "Drive ID" format(uuid)
// @Param driveID path string true "Drive ID" example:"kRp2XYTq9A55"
// @Param uploadID path string true "Upload session ID"
// @Param request body updateUploadRequest true "Status update"
// @Success 200 {object} Upload "Upload completed"

View File

@@ -25,5 +25,5 @@ type Upload struct {
// Internal target node reference
TargetNode *virtualfs.Node `json:"-" swaggerignore:"true"`
// URL to upload file content to
UploadURL string `json:"uploadUrl" example:"https://api.example.com/api/drives/550e8400-e29b-41d4-a716-446655440000/uploads/xNq5RVBt3K88/content"`
UploadURL string `json:"uploadUrl" example:"https://api.example.com/api/drives/kRp2XYTq9A55/uploads/xNq5RVBt3K88/content"`
}