mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-01-04 02:31:14 -06:00
[server][core] Restore deleted
This commit is contained in:
@@ -23,6 +23,7 @@ func SetupCommand() *cobra.Command {
|
||||
cmd.AddCommand(
|
||||
setupListCommand(),
|
||||
setupSummaryCommand(),
|
||||
setupRestoreCommand(),
|
||||
)
|
||||
|
||||
return cmd
|
||||
|
||||
31
server/internal/command/trash/restore.go
Normal file
31
server/internal/command/trash/restore.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package trash
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/shroff/phylum/server/internal/command/common"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func setupRestoreCommand() *cobra.Command {
|
||||
cmd := cobra.Command{
|
||||
Use: "restore uuid... ",
|
||||
Short: "Restore Deleted Resource",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
f := common.UserFileSystem(cmd)
|
||||
for _, uuid := range args {
|
||||
r, err := f.ResourceByPathOrUUID(uuid)
|
||||
items, err := r.Restore()
|
||||
if err != nil {
|
||||
fmt.Println("cannot restore '" + uuid + "': " + err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Items restored: %d\n", items)
|
||||
}
|
||||
},
|
||||
}
|
||||
return &cmd
|
||||
}
|
||||
@@ -96,56 +96,6 @@ type CreateResourcesParams struct {
|
||||
ContentSha256 string
|
||||
}
|
||||
|
||||
const deleteRecursive = `-- name: DeleteRecursive :many
|
||||
WITH RECURSIVE nodes(id, parent, dir) AS (
|
||||
SELECT r.id, r.parent, r.dir
|
||||
FROM resources r
|
||||
WHERE
|
||||
CASE $1::boolean
|
||||
WHEN TRUE THEN r.parent = $2::uuid
|
||||
ELSE r.id = $2::uuid
|
||||
END
|
||||
UNION ALL
|
||||
SELECT r.id, r.parent, r.dir
|
||||
FROM resources r JOIN nodes n on r.parent = n.id
|
||||
WHERE deleted IS NULL
|
||||
)
|
||||
UPDATE resources
|
||||
SET modified = NOW(), deleted = NOW()
|
||||
WHERE id in (SELECT id FROM nodes)
|
||||
RETURNING id, dir
|
||||
`
|
||||
|
||||
type DeleteRecursiveParams struct {
|
||||
PreserveRoot bool
|
||||
ID uuid.UUID
|
||||
}
|
||||
|
||||
type DeleteRecursiveRow struct {
|
||||
ID uuid.UUID
|
||||
Dir bool
|
||||
}
|
||||
|
||||
func (q *Queries) DeleteRecursive(ctx context.Context, arg DeleteRecursiveParams) ([]DeleteRecursiveRow, error) {
|
||||
rows, err := q.db.Query(ctx, deleteRecursive, arg.PreserveRoot, arg.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []DeleteRecursiveRow
|
||||
for rows.Next() {
|
||||
var i DeleteRecursiveRow
|
||||
if err := rows.Scan(&i.ID, &i.Dir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const diskUsage = `-- name: DiskUsage :one
|
||||
WITH RECURSIVE nodes(id, parent, content_length, dir) AS (
|
||||
SELECT r.id, r.parent, r.content_length, r.dir
|
||||
|
||||
@@ -12,6 +12,102 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const deleteRecursive = `-- name: DeleteRecursive :many
|
||||
WITH RECURSIVE nodes(id, parent, dir) AS (
|
||||
SELECT r.id, r.parent, r.dir
|
||||
FROM resources r
|
||||
WHERE
|
||||
CASE $1::boolean
|
||||
WHEN TRUE THEN r.parent = $2::uuid
|
||||
ELSE r.id = $2::uuid
|
||||
END
|
||||
UNION ALL
|
||||
SELECT r.id, r.parent, r.dir
|
||||
FROM resources r JOIN nodes n on r.parent = n.id
|
||||
WHERE deleted IS NULL
|
||||
)
|
||||
UPDATE resources
|
||||
SET modified = NOW(), deleted = NOW()
|
||||
WHERE id in (SELECT id FROM nodes)
|
||||
RETURNING id, dir
|
||||
`
|
||||
|
||||
type DeleteRecursiveParams struct {
|
||||
PreserveRoot bool
|
||||
ID uuid.UUID
|
||||
}
|
||||
|
||||
type DeleteRecursiveRow struct {
|
||||
ID uuid.UUID
|
||||
Dir bool
|
||||
}
|
||||
|
||||
func (q *Queries) DeleteRecursive(ctx context.Context, arg DeleteRecursiveParams) ([]DeleteRecursiveRow, error) {
|
||||
rows, err := q.db.Query(ctx, deleteRecursive, arg.PreserveRoot, arg.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []DeleteRecursiveRow
|
||||
for rows.Next() {
|
||||
var i DeleteRecursiveRow
|
||||
if err := rows.Scan(&i.ID, &i.Dir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const restoreDeleted = `-- name: RestoreDeleted :many
|
||||
WITH RECURSIVE nodes(id, parent, ts) AS (
|
||||
SELECT r.id, r.parent, r.deleted
|
||||
FROM resources r
|
||||
WHERE r.id = $1::UUID
|
||||
AND CASE
|
||||
WHEN $2::TEXT IS NULL THEN TRUE
|
||||
ELSE permissions[$2::TEXT]::integer <> 0 END
|
||||
UNION ALL
|
||||
SELECT r.id, r.parent, n.ts
|
||||
FROM resources r
|
||||
JOIN nodes n ON r.parent = n.id
|
||||
WHERE r.deleted = n.ts
|
||||
)
|
||||
UPDATE resources
|
||||
SET modified = NOW(), deleted = NULL
|
||||
FROM nodes
|
||||
WHERE resources.id = nodes.id
|
||||
RETURNING 1
|
||||
`
|
||||
|
||||
type RestoreDeletedParams struct {
|
||||
ResourceID uuid.UUID
|
||||
Username pgtype.Text
|
||||
}
|
||||
|
||||
func (q *Queries) RestoreDeleted(ctx context.Context, arg RestoreDeletedParams) ([]int32, error) {
|
||||
rows, err := q.db.Query(ctx, restoreDeleted, arg.ResourceID, arg.Username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []int32
|
||||
for rows.Next() {
|
||||
var column_1 int32
|
||||
if err := rows.Scan(&column_1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, column_1)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const trashList = `-- name: TrashList :many
|
||||
SELECT id, name, parent, dir, created, modified, deleted, content_length, content_type, content_sha256, permissions, grants FROM resources
|
||||
WHERE CASE
|
||||
|
||||
@@ -18,4 +18,5 @@ var (
|
||||
ErrResourceMoveTargetSubdirectory = errors.NewError(http.StatusConflict, "move_target_subdirectory", "Cannot move a resource to its own subdirectory")
|
||||
ErrResourceCopyTargetSelf = errors.NewError(http.StatusConflict, "copy_target_self", "Cannot overwrite a resource with itself")
|
||||
ErrParentNotFound = errors.NewError(http.StatusConflict, "parent_not_found", "Parent not found")
|
||||
ErrParentDeleted = errors.NewError(http.StatusConflict, "parent_deleted", "Parent deleted")
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"github.com/shroff/phylum/server/internal/core/db"
|
||||
)
|
||||
@@ -71,3 +72,41 @@ func (f filesystem) TrashSummary() (int, int, error) {
|
||||
return int(res.Items), int(res.Size), nil
|
||||
}
|
||||
}
|
||||
|
||||
// RestoreDeleted restores a previously deleted resources
|
||||
// Checks:
|
||||
// - Parent must not be deleted
|
||||
// - Parent must have write permission
|
||||
// - No name conflict with exiting resource
|
||||
func (r Resource) Restore() (int, error) {
|
||||
p, err := r.f.ResourceByID(*r.parentID)
|
||||
if err == ErrResourceNotFound {
|
||||
err = ErrParentNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if p.deleted != nil {
|
||||
return 0, ErrParentDeleted
|
||||
}
|
||||
if !p.hasPermission(PermissionWrite) {
|
||||
return 0, ErrInsufficientPermissions
|
||||
}
|
||||
|
||||
_, err = r.f.db.ChildResourceByName(r.f.ctx, db.ChildResourceByNameParams{Parent: p.id, Name: r.name})
|
||||
if err != pgx.ErrNoRows {
|
||||
if err != nil {
|
||||
err = ErrResourceNameConflict
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var username pgtype.Text
|
||||
if !r.f.fullAccess {
|
||||
username.String = r.f.username
|
||||
username.Valid = true
|
||||
}
|
||||
res, err := r.f.db.RestoreDeleted(r.f.ctx, db.RestoreDeletedParams{Username: username, ResourceID: r.id})
|
||||
return len(res), err
|
||||
}
|
||||
|
||||
@@ -107,25 +107,6 @@ SELECT r.*
|
||||
ON n.id = r.id
|
||||
WHERE n.depth >= @min_depth::INTEGER;
|
||||
|
||||
-- name: DeleteRecursive :many
|
||||
WITH RECURSIVE nodes(id, parent, dir) AS (
|
||||
SELECT r.id, r.parent, r.dir
|
||||
FROM resources r
|
||||
WHERE
|
||||
CASE @preserve_root::boolean
|
||||
WHEN TRUE THEN r.parent = @id::uuid
|
||||
ELSE r.id = @id::uuid
|
||||
END
|
||||
UNION ALL
|
||||
SELECT r.id, r.parent, r.dir
|
||||
FROM resources r JOIN nodes n on r.parent = n.id
|
||||
WHERE deleted IS NULL
|
||||
)
|
||||
UPDATE resources
|
||||
SET modified = NOW(), deleted = NOW()
|
||||
WHERE id in (SELECT id FROM nodes)
|
||||
RETURNING id, dir;
|
||||
|
||||
-- name: DiskUsage :one
|
||||
WITH RECURSIVE nodes(id, parent, content_length, dir) AS (
|
||||
SELECT r.id, r.parent, r.content_length, r.dir
|
||||
|
||||
@@ -1,3 +1,48 @@
|
||||
-- name: DeleteRecursive :many
|
||||
WITH RECURSIVE nodes(id, parent, dir) AS (
|
||||
SELECT r.id, r.parent, r.dir
|
||||
FROM resources r
|
||||
WHERE
|
||||
CASE @preserve_root::boolean
|
||||
WHEN TRUE THEN r.parent = @id::uuid
|
||||
ELSE r.id = @id::uuid
|
||||
END
|
||||
UNION ALL
|
||||
SELECT r.id, r.parent, r.dir
|
||||
FROM resources r JOIN nodes n on r.parent = n.id
|
||||
WHERE deleted IS NULL
|
||||
)
|
||||
UPDATE resources
|
||||
SET modified = NOW(), deleted = NOW()
|
||||
WHERE id in (SELECT id FROM nodes)
|
||||
RETURNING id, dir;
|
||||
|
||||
-- name: RestoreDeleted :many
|
||||
WITH RECURSIVE nodes(id, parent, ts) AS (
|
||||
SELECT r.id, r.parent, r.deleted
|
||||
FROM resources r
|
||||
WHERE r.id = @resource_id::UUID
|
||||
AND CASE
|
||||
WHEN sqlc.narg('username')::TEXT IS NULL THEN TRUE
|
||||
ELSE permissions[@username::TEXT]::integer <> 0 END
|
||||
UNION ALL
|
||||
SELECT r.id, r.parent, n.ts
|
||||
FROM resources r
|
||||
JOIN nodes n ON r.parent = n.id
|
||||
WHERE r.deleted = n.ts
|
||||
)
|
||||
UPDATE resources
|
||||
SET modified = NOW(), deleted = NULL
|
||||
FROM nodes
|
||||
WHERE resources.id = nodes.id
|
||||
RETURNING 1;
|
||||
|
||||
SELECT r.*
|
||||
FROM nodes n
|
||||
JOIN resources r
|
||||
ON n.id = r.id
|
||||
WHERE n.depth >= @min_depth::INTEGER;
|
||||
|
||||
-- name: TrashList :many
|
||||
SELECT * FROM resources
|
||||
WHERE CASE
|
||||
|
||||
Reference in New Issue
Block a user