mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-01-06 11:39:42 -06:00
[server][core] Further streamline resource deletion
This commit is contained in:
@@ -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{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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").
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user