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 => { 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 }) }, })