Files
drive/packages/convex/filesystem.ts
kenneth 94d6a22ab2 refactor: update remaining error imports to use ErrorCode
- Replace Err.Code with ErrorCode throughout convex model files
- Update error() function calls to use new signature
- Remove unused Err namespace imports

Co-authored-by: Ona <no-reply@ona.com>
2025-11-08 18:03:10 +00:00

211 lines
4.9 KiB
TypeScript

import { ConvexError, v } from "convex/values"
import {
apiKeyAuthenticatedQuery,
authenticatedMutation,
authenticatedQuery,
authorizedGet,
} from "./functions"
import * as Directories from "./model/directories"
import * as Files from "./model/files"
import * as FileSystem from "./model/filesystem"
import {
deleteItemsPermanently,
emptyTrash as emptyTrashImpl,
fetchFileUrl as fetchFileUrlImpl,
restoreItems as restoreItemsImpl,
VDirectoryHandle,
VFileSystemHandle,
} from "./model/filesystem"
import { createErrorData, ErrorCode, error } from "./shared/error"
import type {
DirectoryHandle,
FileHandle,
FileSystemHandle,
FileSystemItem,
} from "./shared/filesystem"
import { FileType } from "./shared/filesystem"
export const moveItems = authenticatedMutation({
args: {
targetDirectory: VDirectoryHandle,
items: v.array(VFileSystemHandle),
},
handler: async (ctx, { targetDirectory: targetDirectoryHandle, items }) => {
const targetDirectory = await authorizedGet(
ctx,
targetDirectoryHandle.id,
)
if (!targetDirectory) {
error({
code: ErrorCode.NotFound,
message: `Directory ${targetDirectoryHandle.id} not found`,
})
}
const directoryHandles: DirectoryHandle[] = []
const fileHandles: FileHandle[] = []
for (const item of items) {
switch (item.kind) {
case FileType.Directory:
directoryHandles.push(item)
break
case FileType.File:
fileHandles.push(item)
break
}
}
const [fileMoveResult, directoryMoveResult] = await Promise.all([
Files.move(ctx, {
targetDirectory: targetDirectoryHandle,
items: fileHandles,
}),
Directories.move(ctx, {
targetDirectory: targetDirectoryHandle,
sourceDirectories: directoryHandles,
}),
])
return {
moved: [...directoryMoveResult.moved, ...fileMoveResult.moved],
errors: [...fileMoveResult.errors, ...directoryMoveResult.errors],
}
},
})
export const moveToTrash = authenticatedMutation({
args: {
handles: v.array(VFileSystemHandle),
},
handler: async (ctx, { handles }) => {
for (const handle of handles) {
const item = await authorizedGet(ctx, handle.id)
if (!item) {
error({
code: ErrorCode.NotFound,
message: `Item ${handle.id} not found`,
})
}
}
// 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: Date.now(),
})
.then(() => handle)
case FileType.Directory:
return Directories.moveToTrashRecursive(ctx, handle).then(
() => handle,
)
}
})
const results = await Promise.allSettled(promises)
const errors = []
const okHandles: FileSystemHandle[] = []
for (const result of results) {
switch (result.status) {
case "fulfilled":
okHandles.push(result.value)
break
case "rejected":
errors.push(createErrorData(ErrorCode.Internal))
break
}
}
return {
deleted: okHandles,
errors,
}
},
})
export const fetchDirectoryContent = authenticatedQuery({
args: {
directoryId: v.optional(v.id("directories")),
trashed: v.boolean(),
},
handler: async (
ctx,
{ directoryId, trashed },
): Promise<FileSystemItem[]> => {
return await Directories.fetchContent(ctx, { directoryId, trashed })
},
})
export const permanentlyDeleteItems = authenticatedMutation({
args: {
handles: v.array(VFileSystemHandle),
},
handler: async (ctx, { handles }) => {
return await deleteItemsPermanently(ctx, { handles })
},
})
export const emptyTrash = authenticatedMutation({
handler: async (ctx) => {
return await emptyTrashImpl(ctx)
},
})
export const restoreItems = authenticatedMutation({
args: {
handles: v.array(VFileSystemHandle),
},
handler: async (ctx, { handles }) => {
return await restoreItemsImpl(ctx, { handles })
},
})
export const getStorageUrl = apiKeyAuthenticatedQuery({
args: {
storageId: v.id("_storage"),
},
handler: async (ctx, { storageId }) => {
return await ctx.storage.getUrl(storageId)
},
})
export const fetchFileUrl = authenticatedQuery({
args: {
fileId: v.id("files"),
},
handler: async (ctx, { fileId }) => {
return await fetchFileUrlImpl(ctx, { fileId })
},
})
export const openFile = authenticatedMutation({
args: {
fileId: v.id("files"),
},
handler: async (ctx, { fileId }) => {
return await FileSystem.openFile(ctx, { fileId })
},
})
export const saveFile = authenticatedMutation({
args: {
name: v.string(),
directoryId: v.id("directories"),
storageId: v.id("_storage"),
},
handler: async (ctx, { name, directoryId, storageId }) => {
return await FileSystem.saveFile(ctx, { name, directoryId, storageId })
},
})
export const fetchRecentFiles = authenticatedQuery({
args: {
limit: v.number(),
},
handler: async (ctx, { limit }) => {
return await FileSystem.fetchRecentFiles(ctx, { limit })
},
})