[server][core] Further streamline resource deletion

This commit is contained in:
Abhishek Shroff
2025-06-10 20:52:06 +05:30
parent 478ec3b67a
commit fbbc2b3732
4 changed files with 110 additions and 52 deletions

View File

@@ -60,10 +60,14 @@ func (f filesystem) Move(r Resource, target string, conflictResolution ResourceB
var deleted = false
return res, deleted, f.runInTx(func(f filesystem) error {
if conflictResolution == ResourceBindConflictResolutionOverwrite || conflictResolution == ResourceBindConflictResolutionDelete {
if _, err := f.softDeleteChild(destParent, destName); err == nil {
deleted = true
} else if !errors.Is(err, ErrResourceNotFound) {
if id, _, err := f.childResourceIDByName(r.ID(), destName); err != nil {
if !errors.Is(err, ErrResourceNotFound) {
return err
}
} else if err := softDelete(f.db, id); err != nil {
return err
} else {
deleted = true
}
}
newParentID := pgtype.UUID{

View File

@@ -156,8 +156,9 @@ func (f filesystem) createResource(
case ResourceBindConflictResolutionError:
err = ErrResourceNameConflict
case ResourceBindConflictResolutionEnsure:
res, err = f.childResourceByName(parent, name)
if err == nil && res.dir != dir {
var rDir bool
_, rDir, err = f.childResourceIDByName(parent, name)
if err == nil && rDir != dir {
err = ErrResourceNameConflict
}
case ResourceBindConflictResolutionRename:
@@ -187,15 +188,21 @@ func (f filesystem) createResource(
}
}
case ResourceBindConflictResolutionOverwrite:
res, err = f.childResourceByName(parent, name)
var rID uuid.UUID
var rDir bool
rID, rDir, err = f.childResourceIDByName(parent, name)
if err == nil {
deleted = true
if res.dir == dir {
if rDir == dir {
if dir {
err = f.softDeleteChildren(res.id, parent)
err = f.softDeleteChildren(rID, parent)
}
if err == nil {
// Repurpose existing resource
res, err = f.ResourceByID(rID)
}
} else {
err = f.deleteRecursive(res.id, parent, true)
err = softDelete(f.db, res.id)
if err == nil {
res, created, _, err = f.createResource(
id,
@@ -209,10 +216,11 @@ func (f filesystem) createResource(
}
}
case ResourceBindConflictResolutionDelete:
res, err = f.childResourceByName(parent, name)
var rID uuid.UUID
rID, _, err = f.childResourceIDByName(parent, name)
if err == nil {
deleted = true
err = f.deleteRecursive(res.id, parent, true)
err = softDelete(f.db, rID)
if err == nil {
res, created, _, err = f.createResource(
id,

View File

@@ -1,11 +1,11 @@
package core
import (
"errors"
"fmt"
"path"
"time"
"codeberg.org/shroff/phylum/server/internal/db"
"codeberg.org/shroff/phylum/server/internal/jobs"
"github.com/doug-martin/goqu/v9"
"github.com/google/uuid"
@@ -13,31 +13,6 @@ import (
"github.com/jackc/pgx/v5/pgtype"
)
func (f filesystem) softDeleteChild(r Resource, name string) (Resource, error) {
if !r.hasPermission(PermissionWrite) {
return Resource{}, ErrInsufficientPermissions
}
if r.deleted.Valid {
return Resource{}, ErrResourceDeleted
}
c, err := f.childResourceByName(r.ID(), name)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
err = ErrResourceNotFound
}
return Resource{}, err
}
if err := f.deleteRecursive(c.ID(), r.ID(), true); err != nil {
return Resource{}, err
}
r.deleted = pgtype.Timestamp{
Valid: true,
Time: time.Now(),
}
return r, nil
}
func (f filesystem) DeleteRecursive(r Resource, softDelete bool) (Resource, error) {
if !r.parentID.Valid {
return Resource{}, ErrInsufficientPermissions
@@ -100,14 +75,14 @@ func (f filesystem) RestoreDeleted(r Resource, parentPathOrUUID string, name str
if name == "" {
name = r.name
}
_, err = f.childResourceByName(p.id, name)
_, _, err = f.childResourceIDByName(p.id, name)
if autoRename && err == nil {
ext := path.Ext(name)
basename := name[:len(name)-len(ext)]
counter := 1
for {
name = fmt.Sprintf("%s (%d)%s", basename, counter, ext)
if _, err = f.childResourceByName(p.id, name); err == nil {
if _, _, err = f.childResourceIDByName(p.id, name); err == nil {
counter++
} else {
break
@@ -177,13 +152,82 @@ func (f filesystem) deleteRecursive(id, parent uuid.UUID, softDelete bool) error
return err
}
func (f filesystem) softDeleteChildren(id, parent uuid.UUID) error {
err := f.runInTx(func(f filesystem) error {
var err error
if _, _, err = f.markDeleted(id, true, true); err != nil {
func softDelete(d db.Handler, id uuid.UUID) error {
return d.RunInTx(func(db db.Handler) error {
// Select all descendants, not including deleted ones
_, _, q := selectResourceTree(id, false, false, false)
// Set modified and deleted
query, params, _ := q.
Update().
Set(
goqu.Record{
"modified": goqu.L("NOW()"),
"deleted": goqu.L("NOW()"),
}).
ToSQL()
if _, err := db.Exec(query, params...); err != nil {
return err
}
// Add to trash
query, params, _ = pg.Insert(goqu.T("trash")).Cols("id").Vals(goqu.Vals{id}).ToSQL()
_, err := db.Exec(query, params...)
return err
})
}
func (f filesystem) hardDelete(id, parent uuid.UUID) error {
return f.runInTx(func(f filesystem) error {
// Select all descendants, including deleted resources
v := goqu.T("resource_versions").As("v")
r, _, q := selectResourceTree(id, false, true, false)
// Delete resources and versions from db, returning version ids
query, params, _ := q.
LeftJoin(v, goqu.On(r.Col("id").Eq(v.Col("resource_id")))).
Delete().
Returning(v.Col("id")).
ToSQL()
if rows, err := f.db.Query(query, params...); err != nil {
return err
} else if ids, err := pgx.CollectRows(rows, scanID); err != nil {
return err
} else if err := f.updateResourceModified(parent); err != nil {
return err
} else {
jobs.DeleteAllVersionContents(ids)
}
return nil
})
}
func scanID(row pgx.CollectableRow) (uuid.UUID, error) {
var id uuid.UUID
err := row.Scan(&id)
return id, err
}
func (f filesystem) softDeleteChildren(id, parent uuid.UUID) error {
err := f.runInTx(func(f filesystem) error {
// Select non-deleted descendents excluding the tree root (id)
_, _, s := selectResourceTree(id, true, false, false)
// Mark deleted
q, params, _ := s.Update().
Set(
goqu.Record{
"modified": goqu.L("NOW()"),
"deleted": goqu.L("NOW()"),
}).
ToSQL()
if _, err := f.db.Exec(q, params...); err != nil {
return err
}
// Add children to trash
insert := pg.
Insert(goqu.T("trash")).
Cols("id").

View File

@@ -120,18 +120,20 @@ func (f filesystem) targetNameParentByPathWithRoot(path string, src Resource) (s
return name, parent, nil
}
func (f filesystem) childResourceByName(parentID uuid.UUID, name string) (Resource, error) {
const query = fullResourceQuery + "WHERE r.parent = @parent::UUID AND r.name = @name::TEXT AND r.deleted IS NULL"
func (f filesystem) childResourceIDByName(parentID uuid.UUID, name string) (uuid.UUID, bool, error) {
const query = "SELECT id, dir FROM resources WHERE parent = @parent::UUID AND name = @name::TEXT AND deleted IS NULL"
args := pgx.NamedArgs{
"user_id": f.userID,
"parent": parentID,
"name": name,
"parent": parentID,
"name": name,
}
if rows, err := f.db.Query(query, args); err != nil {
return Resource{}, err
} else {
return f.collectFullResource(rows)
row := f.db.QueryRow(query, args)
var id uuid.UUID
var dir bool
err := row.Scan(&id, &dir)
if Is(err, pgx.ErrNoRows) {
err = ErrResourceNotFound
}
return id, dir, err
}
func parseUUIDPrefix(path string) (pgtype.UUID, string, error) {