mirror of
https://github.com/get-drexa/drive.git
synced 2025-11-30 21:41:39 +00:00
feat: impl multi file deletion support
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
import type { Id } from "@fileone/convex/_generated/dataModel"
|
||||
import { v } from "convex/values"
|
||||
import type { Id } from "./_generated/dataModel"
|
||||
import { authenticatedMutation, authenticatedQuery } from "./functions"
|
||||
import type { DirectoryItem } from "./model/directories"
|
||||
import * as Directories from "./model/directories"
|
||||
import * as Err from "./model/error"
|
||||
import * as Files from "./model/files"
|
||||
import type { FileSystemItem } from "./model/filesystem"
|
||||
|
||||
export const generateUploadUrl = authenticatedMutation({
|
||||
handler: async (ctx) => {
|
||||
@@ -55,7 +54,7 @@ export const fetchDirectoryContent = authenticatedQuery({
|
||||
args: {
|
||||
directoryId: v.optional(v.id("directories")),
|
||||
},
|
||||
handler: async (ctx, { directoryId }): Promise<DirectoryItem[]> => {
|
||||
handler: async (ctx, { directoryId }): Promise<FileSystemItem[]> => {
|
||||
return await Directories.fetchContent(ctx, { directoryId })
|
||||
},
|
||||
})
|
||||
@@ -107,37 +106,3 @@ export const renameFile = authenticatedMutation({
|
||||
await Files.renameFile(ctx, { directoryId, itemId, newName })
|
||||
},
|
||||
})
|
||||
|
||||
export const moveToTrash = authenticatedMutation({
|
||||
args: {
|
||||
kind: v.union(v.literal("file"), v.literal("directory")),
|
||||
itemId: v.union(v.id("files"), v.id("directories")),
|
||||
},
|
||||
handler: async (ctx, { itemId, kind }) => {
|
||||
switch (kind) {
|
||||
case "file": {
|
||||
const file = await ctx.db.get(itemId as Id<"files">)
|
||||
if (!file || file.userId !== ctx.user._id) {
|
||||
throw new Error("File not found or access denied")
|
||||
}
|
||||
await ctx.db.patch(itemId, {
|
||||
deletedAt: new Date().toISOString(),
|
||||
})
|
||||
break
|
||||
}
|
||||
case "directory": {
|
||||
const directory = await ctx.db.get(itemId as Id<"directories">)
|
||||
if (!directory || directory.userId !== ctx.user._id) {
|
||||
throw new Error("Directory not found or access denied")
|
||||
}
|
||||
await Directories.moveToTrashRecursive(
|
||||
ctx,
|
||||
itemId as Id<"directories">,
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return itemId
|
||||
},
|
||||
})
|
||||
|
||||
@@ -4,18 +4,12 @@ import * as Directories from "./model/directories"
|
||||
import * as Err from "./model/error"
|
||||
import * as Files from "./model/files"
|
||||
import type { DirectoryHandle, FileHandle } from "./model/filesystem"
|
||||
|
||||
const VDirectoryHandle = v.object({
|
||||
kind: v.literal("directory"),
|
||||
id: v.id("directories"),
|
||||
})
|
||||
|
||||
const VFileHandle = v.object({
|
||||
kind: v.literal("file"),
|
||||
id: v.id("files"),
|
||||
})
|
||||
|
||||
const VFileSystemHandle = v.union(VFileHandle, VDirectoryHandle)
|
||||
import {
|
||||
type FileSystemHandle,
|
||||
FileType,
|
||||
VDirectoryHandle,
|
||||
VFileSystemHandle,
|
||||
} from "./model/filesystem"
|
||||
|
||||
export const moveItems = authenticatedMutation({
|
||||
args: {
|
||||
@@ -38,10 +32,10 @@ export const moveItems = authenticatedMutation({
|
||||
const fileHandles: FileHandle[] = []
|
||||
for (const item of items) {
|
||||
switch (item.kind) {
|
||||
case "directory":
|
||||
case FileType.Directory:
|
||||
directoryHandles.push(item)
|
||||
break
|
||||
case "file":
|
||||
case FileType.File:
|
||||
fileHandles.push(item)
|
||||
break
|
||||
}
|
||||
@@ -64,3 +58,45 @@ export const moveItems = authenticatedMutation({
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const moveToTrash = authenticatedMutation({
|
||||
args: {
|
||||
handles: v.array(VFileSystemHandle),
|
||||
},
|
||||
handler: async (ctx, { handles }) => {
|
||||
// biome-ignore lint/suspicious/useIterableCallbackReturn: switch statement is exhaustive
|
||||
const promises = handles.map((handle) => {
|
||||
switch (handle.kind) {
|
||||
case FileType.File:
|
||||
return ctx.db
|
||||
.patch(handle.id, {
|
||||
deletedAt: new Date().toISOString(),
|
||||
})
|
||||
.then(() => handle)
|
||||
case FileType.Directory:
|
||||
return Directories.moveToTrashRecursive(ctx, handle).then(
|
||||
() => handle,
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const results = await Promise.allSettled(promises)
|
||||
const errors: Err.ApplicationErrorData[] = []
|
||||
const okHandles: FileSystemHandle[] = []
|
||||
for (const result of results) {
|
||||
switch (result.status) {
|
||||
case "fulfilled":
|
||||
okHandles.push(result.value)
|
||||
break
|
||||
case "rejected":
|
||||
errors.push(Err.createJson(Err.Code.Internal))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
deleted: okHandles,
|
||||
errors,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -4,21 +4,14 @@ import type {
|
||||
AuthenticatedQueryCtx,
|
||||
} from "../functions"
|
||||
import * as Err from "./error"
|
||||
import type { DirectoryHandle, FilePath, ReverseFilePath } from "./filesystem"
|
||||
import { newDirectoryHandle } from "./filesystem"
|
||||
|
||||
type Directory = {
|
||||
kind: "directory"
|
||||
doc: Doc<"directories">
|
||||
}
|
||||
|
||||
type File = {
|
||||
kind: "file"
|
||||
doc: Doc<"files">
|
||||
}
|
||||
|
||||
export type DirectoryItem = Directory | File
|
||||
export type DirectoryItemKind = DirectoryItem["kind"]
|
||||
import {
|
||||
type DirectoryHandle,
|
||||
type FilePath,
|
||||
type FileSystemItem,
|
||||
FileType,
|
||||
newDirectoryHandle,
|
||||
type ReverseFilePath,
|
||||
} from "./filesystem"
|
||||
|
||||
export type DirectoryInfo = Doc<"directories"> & { path: FilePath }
|
||||
|
||||
@@ -83,7 +76,7 @@ export async function fetch(
|
||||
export async function fetchContent(
|
||||
ctx: AuthenticatedQueryCtx,
|
||||
{ directoryId }: { directoryId?: Id<"directories"> } = {},
|
||||
): Promise<DirectoryItem[]> {
|
||||
): Promise<FileSystemItem[]> {
|
||||
let dirId: Id<"directories"> | undefined
|
||||
if (directoryId) {
|
||||
dirId = directoryId
|
||||
@@ -110,12 +103,12 @@ export async function fetchContent(
|
||||
.collect(),
|
||||
])
|
||||
|
||||
const items: DirectoryItem[] = []
|
||||
const items: FileSystemItem[] = []
|
||||
for (const directory of directories) {
|
||||
items.push({ kind: "directory", doc: directory })
|
||||
items.push({ kind: FileType.Directory, doc: directory })
|
||||
}
|
||||
for (const file of files) {
|
||||
items.push({ kind: "file", doc: file })
|
||||
items.push({ kind: FileType.File, doc: file })
|
||||
}
|
||||
|
||||
return items
|
||||
@@ -206,7 +199,7 @@ export async function move(
|
||||
),
|
||||
)
|
||||
} else {
|
||||
okDirectories.push(sourceDirectories[i])
|
||||
okDirectories.push(sourceDirectories[i]!)
|
||||
}
|
||||
} else if (result.status === "rejected") {
|
||||
errors.push(Err.createJson(Err.Code.Internal))
|
||||
@@ -244,14 +237,14 @@ export async function move(
|
||||
|
||||
export async function moveToTrashRecursive(
|
||||
ctx: AuthenticatedMutationCtx,
|
||||
directoryId: Id<"directories">,
|
||||
handle: DirectoryHandle,
|
||||
): Promise<void> {
|
||||
const now = new Date().toISOString()
|
||||
|
||||
const filesToDelete: Id<"files">[] = []
|
||||
const directoriesToDelete: Id<"directories">[] = []
|
||||
|
||||
const directoryQueue: Id<"directories">[] = [directoryId]
|
||||
const directoryQueue: Id<"directories">[] = [handle.id]
|
||||
|
||||
while (directoryQueue.length > 0) {
|
||||
const currentDirectoryId = directoryQueue.shift()!
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
import type { Id } from "../_generated/dataModel"
|
||||
import { v } from "convex/values"
|
||||
import type { Doc, Id } from "../_generated/dataModel"
|
||||
|
||||
export enum FileType {
|
||||
File = "File",
|
||||
Directory = "Directory",
|
||||
}
|
||||
|
||||
export type Directory = {
|
||||
kind: FileType.Directory
|
||||
doc: Doc<"directories">
|
||||
}
|
||||
export type File = {
|
||||
kind: FileType.File
|
||||
doc: Doc<"files">
|
||||
}
|
||||
export type FileSystemItem = Directory | File
|
||||
|
||||
export type DirectoryPathComponent = {
|
||||
handle: DirectoryHandle
|
||||
name: string
|
||||
@@ -14,31 +25,28 @@ export type FilePathComponent = {
|
||||
handle: FileHandle
|
||||
name: string
|
||||
}
|
||||
|
||||
export type PathComponent = FilePathComponent | DirectoryPathComponent
|
||||
|
||||
export type FilePath = [...DirectoryPathComponent[], PathComponent]
|
||||
|
||||
export type ReverseFilePath = [PathComponent, ...DirectoryPathComponent[]]
|
||||
|
||||
export type FileHandle = {
|
||||
kind: "file"
|
||||
id: Id<"files">
|
||||
}
|
||||
|
||||
export type DirectoryHandle = {
|
||||
kind: "directory"
|
||||
kind: FileType.Directory
|
||||
id: Id<"directories">
|
||||
}
|
||||
|
||||
export type FileHandle = {
|
||||
kind: FileType.File
|
||||
id: Id<"files">
|
||||
}
|
||||
export type FileSystemHandle = DirectoryHandle | FileHandle
|
||||
|
||||
export function newDirectoryHandle(id: Id<"directories">): DirectoryHandle {
|
||||
return { kind: "directory", id }
|
||||
}
|
||||
|
||||
export function newFileHandle(id: Id<"files">): FileHandle {
|
||||
return { kind: "file", id }
|
||||
export function newFileSystemHandle(item: FileSystemItem): FileSystemHandle {
|
||||
console.log("item", item)
|
||||
switch (item.kind) {
|
||||
case FileType.File:
|
||||
return { kind: item.kind, id: item.doc._id }
|
||||
case FileType.Directory:
|
||||
return { kind: item.kind, id: item.doc._id }
|
||||
}
|
||||
}
|
||||
|
||||
export function isSameHandle(
|
||||
@@ -47,3 +55,21 @@ export function isSameHandle(
|
||||
): boolean {
|
||||
return handle1.kind === handle2.kind && handle1.id === handle2.id
|
||||
}
|
||||
|
||||
export function newDirectoryHandle(id: Id<"directories">): DirectoryHandle {
|
||||
return { kind: FileType.Directory, id }
|
||||
}
|
||||
|
||||
export function newFileHandle(id: Id<"files">): FileHandle {
|
||||
return { kind: FileType.File, id }
|
||||
}
|
||||
|
||||
export const VDirectoryHandle = v.object({
|
||||
kind: v.literal(FileType.Directory),
|
||||
id: v.id("directories"),
|
||||
})
|
||||
export const VFileHandle = v.object({
|
||||
kind: v.literal(FileType.File),
|
||||
id: v.id("files"),
|
||||
})
|
||||
export const VFileSystemHandle = v.union(VFileHandle, VDirectoryHandle)
|
||||
|
||||
Reference in New Issue
Block a user