Files
drive/packages/convex/filesystem.ts

211 lines
4.9 KiB
TypeScript
Raw Normal View History

import { ConvexError, v } from "convex/values"
import {
2025-10-21 23:45:04 +00:00
apiKeyAuthenticatedQuery,
authenticatedMutation,
authenticatedQuery,
authorizedGet,
} from "./functions"
import * as Directories from "./model/directories"
import * as Files from "./model/files"
2025-10-21 23:45:04 +00:00
import * as FileSystem from "./model/filesystem"
2025-09-28 15:45:49 +00:00
import {
deleteItemsPermanently,
emptyTrash as emptyTrashImpl,
fetchFileUrl as fetchFileUrlImpl,
restoreItems as restoreItemsImpl,
2025-09-28 15:45:49 +00:00
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) {
2025-09-28 15:45:49 +00:00
case FileType.Directory:
directoryHandles.push(item)
break
2025-09-28 15:45:49 +00:00
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],
}
},
})
2025-09-28 15:45:49 +00:00
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`,
})
}
}
2025-09-28 15:45:49 +00:00
// 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(),
2025-09-28 15:45:49 +00:00
})
.then(() => handle)
case FileType.Directory:
return Directories.moveToTrashRecursive(ctx, handle).then(
() => handle,
)
}
})
const results = await Promise.allSettled(promises)
const errors = []
2025-09-28 15:45:49 +00:00
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))
2025-09-28 15:45:49 +00:00
break
}
}
return {
deleted: okHandles,
errors,
}
},
})
2025-10-03 23:23:05 +00:00
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 })
},
})
2025-10-12 14:31:02 +00:00
export const emptyTrash = authenticatedMutation({
handler: async (ctx) => {
return await emptyTrashImpl(ctx)
2025-10-12 14:31:02 +00:00
},
})
export const restoreItems = authenticatedMutation({
args: {
handles: v.array(VFileSystemHandle),
},
handler: async (ctx, { handles }) => {
return await restoreItemsImpl(ctx, { handles })
},
})
2025-10-21 23:45:04 +00:00
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 })
},
})
2025-10-21 23:45:04 +00:00
export const openFile = authenticatedMutation({
args: {
fileId: v.id("files"),
},
handler: async (ctx, { fileId }) => {
return await FileSystem.openFile(ctx, { fileId })
},
})
2025-10-28 20:26:12 +00:00
2025-11-02 18:12:33 +00:00
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 })
},
})
2025-10-28 20:26:12 +00:00
export const fetchRecentFiles = authenticatedQuery({
args: {
limit: v.number(),
},
handler: async (ctx, { limit }) => {
return await FileSystem.fetchRecentFiles(ctx, { limit })
},
})