mirror of
https://github.com/get-drexa/drive.git
synced 2025-11-30 21:41:39 +00:00
feat: impl file/dir restoration from trash
This commit is contained in:
@@ -127,3 +127,12 @@ export const permanentlyDeleteItems = authenticatedMutation({
|
||||
return await FileSystem.deleteItemsPermanently(ctx, { handles })
|
||||
},
|
||||
})
|
||||
|
||||
export const restoreItems = authenticatedMutation({
|
||||
args: {
|
||||
handles: v.array(VFileSystemHandle),
|
||||
},
|
||||
handler: async (ctx, { handles }) => {
|
||||
return await FileSystem.restoreItems(ctx, { handles })
|
||||
},
|
||||
})
|
||||
|
||||
@@ -347,3 +347,46 @@ export async function deletePermanently(
|
||||
|
||||
return { deleted: successfulDeletions, errors }
|
||||
}
|
||||
|
||||
export async function restore(
|
||||
ctx: AuthenticatedMutationCtx,
|
||||
{
|
||||
items,
|
||||
}: {
|
||||
items: DirectoryHandle[]
|
||||
},
|
||||
) {
|
||||
if (items.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const itemsToBeRestored = await Promise.allSettled(
|
||||
items.map((item) => ctx.db.get(item.id)),
|
||||
).then((results) =>
|
||||
results.filter(
|
||||
(result): result is PromiseFulfilledResult<Doc<"directories">> =>
|
||||
result.status === "fulfilled" && result.value !== null,
|
||||
),
|
||||
)
|
||||
|
||||
const restoreDirectoryPromises = itemsToBeRestored.map((item) =>
|
||||
ctx.db.patch(item.value._id, {
|
||||
deletedAt: undefined,
|
||||
updatedAt: Date.now(),
|
||||
}),
|
||||
)
|
||||
|
||||
const restoreResults = await Promise.allSettled(restoreDirectoryPromises)
|
||||
|
||||
const errors: Err.ApplicationErrorData[] = []
|
||||
let successfulRestorations = 0
|
||||
for (const result of restoreResults) {
|
||||
if (result.status === "rejected") {
|
||||
errors.push(Err.createJson(Err.Code.Internal))
|
||||
} else {
|
||||
successfulRestorations += 1
|
||||
}
|
||||
}
|
||||
|
||||
return { restored: successfulRestorations, errors }
|
||||
}
|
||||
|
||||
@@ -148,3 +148,46 @@ export async function deletePermanently(
|
||||
|
||||
return { deleted: successfulDeletions, errors }
|
||||
}
|
||||
|
||||
export async function restore(
|
||||
ctx: AuthenticatedMutationCtx,
|
||||
{
|
||||
items,
|
||||
}: {
|
||||
items: FileHandle[]
|
||||
},
|
||||
) {
|
||||
if (items.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const itemsToBeRestored = await Promise.allSettled(
|
||||
items.map((item) => ctx.db.get(item.id)),
|
||||
).then((results) =>
|
||||
results.filter(
|
||||
(result): result is PromiseFulfilledResult<Doc<"files">> =>
|
||||
result.status === "fulfilled" && result.value !== null,
|
||||
),
|
||||
)
|
||||
|
||||
const restoreFilePromises = itemsToBeRestored.map((item) =>
|
||||
ctx.db.patch(item.value._id, {
|
||||
deletedAt: undefined,
|
||||
updatedAt: Date.now(),
|
||||
}),
|
||||
)
|
||||
|
||||
const restoreResults = await Promise.allSettled(restoreFilePromises)
|
||||
|
||||
const errors: Err.ApplicationErrorData[] = []
|
||||
let successfulRestorations = 0
|
||||
for (const result of restoreResults) {
|
||||
if (result.status === "rejected") {
|
||||
errors.push(Err.createJson(Err.Code.Internal))
|
||||
} else {
|
||||
successfulRestorations += 1
|
||||
}
|
||||
}
|
||||
|
||||
return { restored: successfulRestorations, errors }
|
||||
}
|
||||
|
||||
@@ -77,13 +77,16 @@ export const VFileHandle = v.object({
|
||||
})
|
||||
export const VFileSystemHandle = v.union(VFileHandle, VDirectoryHandle)
|
||||
|
||||
export async function deleteItemsPermanently(
|
||||
/**
|
||||
* Recursively collects all file and directory handles from the given handles,
|
||||
* including all nested items. Only includes items that are in trash (deletedAt >= 0).
|
||||
*/
|
||||
async function collectAllHandlesRecursively(
|
||||
ctx: AuthenticatedMutationCtx,
|
||||
{ handles }: { handles: FileSystemHandle[] },
|
||||
) {
|
||||
// Collect all items to delete (including nested items)
|
||||
const fileHandlesToDelete: FileHandle[] = []
|
||||
const directoryHandlesToDelete: DirectoryHandle[] = []
|
||||
): Promise<{ fileHandles: FileHandle[]; directoryHandles: DirectoryHandle[] }> {
|
||||
const fileHandles: FileHandle[] = []
|
||||
const directoryHandles: DirectoryHandle[] = []
|
||||
|
||||
// Process each handle to collect files and directories
|
||||
for (const handle of handles) {
|
||||
@@ -93,16 +96,16 @@ export async function deleteItemsPermanently(
|
||||
while (queue.length > 0) {
|
||||
const currentHandle = queue.shift()!
|
||||
|
||||
// Add current item to appropriate deletion collection
|
||||
// Add current item to appropriate collection
|
||||
if (currentHandle.kind === FileType.File) {
|
||||
fileHandlesToDelete.push(currentHandle)
|
||||
fileHandles.push(currentHandle)
|
||||
} else {
|
||||
directoryHandlesToDelete.push(currentHandle)
|
||||
directoryHandles.push(currentHandle)
|
||||
}
|
||||
|
||||
// If it's a directory, collect all children and add them to the queue
|
||||
if (currentHandle.kind === FileType.Directory) {
|
||||
// Get all child directories that are in trash (deletedAt > 0)
|
||||
// Get all child directories that are in trash (deletedAt >= 0)
|
||||
const childDirectories = await ctx.db
|
||||
.query("directories")
|
||||
.withIndex("byParentId", (q) =>
|
||||
@@ -113,7 +116,7 @@ export async function deleteItemsPermanently(
|
||||
)
|
||||
.collect()
|
||||
|
||||
// Get all child files that are in trash (deletedAt > 0)
|
||||
// Get all child files that are in trash (deletedAt >= 0)
|
||||
const childFiles = await ctx.db
|
||||
.query("files")
|
||||
.withIndex("byDirectoryId", (q) =>
|
||||
@@ -133,12 +136,56 @@ export async function deleteItemsPermanently(
|
||||
// Add child files to file handles collection
|
||||
for (const childFile of childFiles) {
|
||||
const childFileHandle = newFileHandle(childFile._id)
|
||||
fileHandlesToDelete.push(childFileHandle)
|
||||
fileHandles.push(childFileHandle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { fileHandles, directoryHandles }
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores deleted items by unsetting the deletedAt field recursively.
|
||||
* This includes all nested files and directories within the given handles.
|
||||
*/
|
||||
export async function restoreItems(
|
||||
ctx: AuthenticatedMutationCtx,
|
||||
{ handles }: { handles: FileSystemHandle[] },
|
||||
) {
|
||||
// Collect all items to restore (including nested items)
|
||||
const { fileHandles, directoryHandles } = await collectAllHandlesRecursively(
|
||||
ctx,
|
||||
{ handles },
|
||||
)
|
||||
|
||||
// Restore files and directories by unsetting deletedAt
|
||||
const [filesResult, directoriesResult] = await Promise.all([
|
||||
Files.restore(ctx, { items: fileHandles }),
|
||||
Directories.restore(ctx, { items: directoryHandles }),
|
||||
])
|
||||
|
||||
// Combine results, handling null responses
|
||||
return {
|
||||
restored: {
|
||||
files: filesResult?.restored || 0,
|
||||
directories: directoriesResult?.restored || 0,
|
||||
},
|
||||
errors: [
|
||||
...(filesResult?.errors || []),
|
||||
...(directoriesResult?.errors || []),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteItemsPermanently(
|
||||
ctx: AuthenticatedMutationCtx,
|
||||
{ handles }: { handles: FileSystemHandle[] },
|
||||
) {
|
||||
// Collect all items to delete (including nested items)
|
||||
const { fileHandles: fileHandlesToDelete, directoryHandles: directoryHandlesToDelete } =
|
||||
await collectAllHandlesRecursively(ctx, { handles })
|
||||
|
||||
// Delete files and directories using their respective models
|
||||
const [filesResult, directoriesResult] = await Promise.all([
|
||||
Files.deletePermanently(ctx, { items: fileHandlesToDelete }),
|
||||
|
||||
Reference in New Issue
Block a user