mirror of
https://github.com/get-drexa/drive.git
synced 2025-11-30 21:41:39 +00:00
refactor: node deletion
This commit is contained in:
@@ -111,6 +111,15 @@ func (s *FSStore) Delete(ctx context.Context, key Key) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FSStore) DeletePrefix(ctx context.Context, prefix Key) error {
|
||||
prefixPath := filepath.Join(s.config.Root, string(prefix))
|
||||
err := os.RemoveAll(prefixPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FSStore) Update(ctx context.Context, key Key, opts UpdateOptions) error {
|
||||
// Update is a no-op for FSStore
|
||||
return nil
|
||||
|
||||
@@ -20,6 +20,7 @@ type Store interface {
|
||||
Put(ctx context.Context, key Key, reader io.Reader) error
|
||||
Update(ctx context.Context, key Key, opts UpdateOptions) error
|
||||
Delete(ctx context.Context, key Key) error
|
||||
DeletePrefix(ctx context.Context, prefix Key) error
|
||||
Move(ctx context.Context, srcKey, dstKey Key) error
|
||||
Read(ctx context.Context, key Key) (io.ReadCloser, error)
|
||||
ReadRange(ctx context.Context, key Key, offset, length int64) (io.ReadCloser, error)
|
||||
|
||||
@@ -62,9 +62,11 @@ func NewServer(c Config) (*fiber.App, error) {
|
||||
})
|
||||
uploadService := upload.NewService(vfs, blobStore)
|
||||
|
||||
authMiddleware := auth.NewBearerAuthMiddleware(authService, db)
|
||||
|
||||
api := app.Group("/api")
|
||||
auth.NewHTTPHandler(authService, db).RegisterRoutes(api)
|
||||
upload.NewHTTPHandler(uploadService).RegisterRoutes(api)
|
||||
upload.NewHTTPHandler(uploadService, authMiddleware).RegisterRoutes(api, authMiddleware)
|
||||
|
||||
return app, nil
|
||||
}
|
||||
|
||||
@@ -18,14 +18,16 @@ type updateUploadRequest struct {
|
||||
|
||||
type HTTPHandler struct {
|
||||
service *Service
|
||||
authMiddleware fiber.Handler
|
||||
}
|
||||
|
||||
func NewHTTPHandler(s *Service) *HTTPHandler {
|
||||
return &HTTPHandler{service: s}
|
||||
func NewHTTPHandler(s *Service, authMiddleware fiber.Handler) *HTTPHandler {
|
||||
return &HTTPHandler{service: s, authMiddleware: authMiddleware}
|
||||
}
|
||||
|
||||
func (h *HTTPHandler) RegisterRoutes(api fiber.Router) {
|
||||
func (h *HTTPHandler) RegisterRoutes(api fiber.Router, authMiddleware fiber.Handler) {
|
||||
upload := api.Group("/uploads")
|
||||
upload.Use(authMiddleware)
|
||||
|
||||
upload.Post("/", h.Create)
|
||||
upload.Put("/:uploadID/content", h.ReceiveContent)
|
||||
|
||||
@@ -61,6 +61,7 @@ func (s *Service) CreateUpload(ctx context.Context, userID uuid.UUID, opts Creat
|
||||
Duration: 1 * time.Hour,
|
||||
})
|
||||
if err != nil {
|
||||
_ = s.vfs.PermanentlyDeleteNode(ctx, node)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -5,4 +5,5 @@ import "errors"
|
||||
var (
|
||||
ErrNodeNotFound = errors.New("node not found")
|
||||
ErrNodeConflict = errors.New("node conflict")
|
||||
ErrUnsupportedOperation = errors.New("unsupported operation")
|
||||
)
|
||||
|
||||
35
apps/backend/internal/virtualfs/flat_key_resolver.go
Normal file
35
apps/backend/internal/virtualfs/flat_key_resolver.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package virtualfs
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/get-drexa/drexa/internal/blob"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type FlatKeyResolver struct{}
|
||||
|
||||
var _ BlobKeyResolver = &FlatKeyResolver{}
|
||||
|
||||
func NewFlatKeyResolver() *FlatKeyResolver {
|
||||
return &FlatKeyResolver{}
|
||||
}
|
||||
|
||||
func (r *FlatKeyResolver) KeyMode() blob.KeyMode {
|
||||
return blob.KeyModeStable
|
||||
}
|
||||
|
||||
func (r *FlatKeyResolver) Resolve(ctx context.Context, node *Node) (blob.Key, error) {
|
||||
if node.BlobKey == "" {
|
||||
id, err := uuid.NewV7()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return blob.Key(id.String()), nil
|
||||
}
|
||||
return node.BlobKey, nil
|
||||
}
|
||||
|
||||
func (r *FlatKeyResolver) ResolveDeletionKeys(ctx context.Context, node *Node, allKeys []blob.Key) (*DeletionPlan, error) {
|
||||
return &DeletionPlan{Keys: allKeys}, nil
|
||||
}
|
||||
39
apps/backend/internal/virtualfs/hierarchical_key_resolver.go
Normal file
39
apps/backend/internal/virtualfs/hierarchical_key_resolver.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package virtualfs
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/get-drexa/drexa/internal/blob"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type HierarchicalKeyResolver struct {
|
||||
db *bun.DB
|
||||
}
|
||||
|
||||
var _ BlobKeyResolver = &HierarchicalKeyResolver{}
|
||||
|
||||
func NewHierarchicalKeyResolver(db *bun.DB) *HierarchicalKeyResolver {
|
||||
return &HierarchicalKeyResolver{db: db}
|
||||
}
|
||||
|
||||
func (r *HierarchicalKeyResolver) KeyMode() blob.KeyMode {
|
||||
return blob.KeyModeDerived
|
||||
}
|
||||
|
||||
func (r *HierarchicalKeyResolver) Resolve(ctx context.Context, node *Node) (blob.Key, error) {
|
||||
path, err := buildNodeAbsolutePath(ctx, r.db, node.ID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return blob.Key(path), nil
|
||||
}
|
||||
|
||||
func (r *HierarchicalKeyResolver) ResolveDeletionKeys(ctx context.Context, node *Node, allKeys []blob.Key) (*DeletionPlan, error) {
|
||||
path, err := buildNodeAbsolutePath(ctx, r.db, node.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DeletionPlan{Prefix: blob.Key(path)}, nil
|
||||
}
|
||||
@@ -4,53 +4,15 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/get-drexa/drexa/internal/blob"
|
||||
"github.com/google/uuid"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type BlobKeyResolver interface {
|
||||
KeyMode() blob.KeyMode
|
||||
Resolve(ctx context.Context, node *Node) (blob.Key, error)
|
||||
ResolveDeletionKeys(ctx context.Context, node *Node, allKeys []blob.Key) (*DeletionPlan, error)
|
||||
}
|
||||
|
||||
type FlatKeyResolver struct{}
|
||||
|
||||
func NewFlatKeyResolver() *FlatKeyResolver {
|
||||
return &FlatKeyResolver{}
|
||||
}
|
||||
|
||||
func (r *FlatKeyResolver) KeyMode() blob.KeyMode {
|
||||
return blob.KeyModeStable
|
||||
}
|
||||
|
||||
func (r *FlatKeyResolver) Resolve(ctx context.Context, node *Node) (blob.Key, error) {
|
||||
if node.BlobKey == "" {
|
||||
id, err := uuid.NewV7()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return blob.Key(id.String()), nil
|
||||
}
|
||||
return node.BlobKey, nil
|
||||
}
|
||||
|
||||
type HierarchicalKeyResolver struct {
|
||||
db *bun.DB
|
||||
}
|
||||
|
||||
func NewHierarchicalKeyResolver(db *bun.DB) *HierarchicalKeyResolver {
|
||||
return &HierarchicalKeyResolver{db: db}
|
||||
}
|
||||
|
||||
func (r *HierarchicalKeyResolver) KeyMode() blob.KeyMode {
|
||||
return blob.KeyModeDerived
|
||||
}
|
||||
|
||||
func (r *HierarchicalKeyResolver) Resolve(ctx context.Context, node *Node) (blob.Key, error) {
|
||||
path, err := buildNodeAbsolutePath(ctx, r.db, node.ID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return blob.Key(path), nil
|
||||
type DeletionPlan struct {
|
||||
Prefix blob.Key
|
||||
Keys []blob.Key
|
||||
}
|
||||
|
||||
@@ -385,7 +385,31 @@ func (vfs *VirtualFS) PermanentlyDeleteNode(ctx context.Context, node *Node) err
|
||||
if !node.IsAccessible() {
|
||||
return ErrNodeNotFound
|
||||
}
|
||||
switch node.Kind {
|
||||
case NodeKindFile:
|
||||
return vfs.permanentlyDeleteFileNode(ctx, node)
|
||||
case NodeKindDirectory:
|
||||
return vfs.permanentlyDeleteDirectoryNode(ctx, node)
|
||||
default:
|
||||
return ErrUnsupportedOperation
|
||||
}
|
||||
}
|
||||
|
||||
func (vfs *VirtualFS) permanentlyDeleteFileNode(ctx context.Context, node *Node) error {
|
||||
err := vfs.blobStore.Delete(ctx, node.BlobKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = vfs.db.NewDelete().Model(node).WherePK().Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vfs *VirtualFS) permanentlyDeleteDirectoryNode(ctx context.Context, node *Node) error {
|
||||
const descendantsQuery = `WITH RECURSIVE descendants AS (
|
||||
SELECT id, blob_key FROM vfs_nodes WHERE id = ?
|
||||
UNION ALL
|
||||
@@ -399,11 +423,14 @@ func (vfs *VirtualFS) PermanentlyDeleteNode(ctx context.Context, node *Node) err
|
||||
BlobKey blob.Key `bun:"blob_key"`
|
||||
}
|
||||
|
||||
var blobKeys []blob.Key
|
||||
tx, err := vfs.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
err := vfs.db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
var records []nodeRecord
|
||||
err := tx.NewRaw(descendantsQuery, node.ID).Scan(ctx, &records)
|
||||
err = tx.NewRaw(descendantsQuery, node.ID).Scan(ctx, &records)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return ErrNodeNotFound
|
||||
@@ -416,6 +443,7 @@ func (vfs *VirtualFS) PermanentlyDeleteNode(ctx context.Context, node *Node) err
|
||||
}
|
||||
|
||||
nodeIDs := make([]uuid.UUID, 0, len(records))
|
||||
blobKeys := make([]blob.Key, 0, len(records))
|
||||
for _, r := range records {
|
||||
nodeIDs = append(nodeIDs, r.ID)
|
||||
if !r.BlobKey.IsNil() {
|
||||
@@ -423,22 +451,28 @@ func (vfs *VirtualFS) PermanentlyDeleteNode(ctx context.Context, node *Node) err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = tx.NewDelete().
|
||||
Model((*Node)(nil)).
|
||||
Where("id IN (?)", bun.In(nodeIDs)).
|
||||
Exec(ctx)
|
||||
return err
|
||||
})
|
||||
plan, err := vfs.keyResolver.ResolveDeletionKeys(ctx, node, blobKeys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete blobs outside transaction (best effort)
|
||||
for _, key := range blobKeys {
|
||||
_ = vfs.blobStore.Delete(ctx, key)
|
||||
_, err = tx.NewDelete().
|
||||
Model((*Node)(nil)).
|
||||
Where("id IN (?)", bun.In(nodeIDs)).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
if !plan.Prefix.IsNil() {
|
||||
_ = vfs.blobStore.DeletePrefix(ctx, plan.Prefix)
|
||||
} else {
|
||||
for _, key := range plan.Keys {
|
||||
_ = vfs.blobStore.Delete(ctx, key)
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (vfs *VirtualFS) generatePublicID() (string, error) {
|
||||
|
||||
Reference in New Issue
Block a user