import type { Doc, Id } from "@fileone/convex/dataModel" import { type AuthenticatedMutationCtx, authorizedGet } from "../functions" import type { ApplicationErrorData } from "../shared/error" import { createErrorData, ErrorCode, error } from "../shared/error" import type { DirectoryHandle, FileHandle } from "../shared/filesystem" export async function renameFile( ctx: AuthenticatedMutationCtx, { directoryId, itemId, newName, }: { directoryId?: Id<"directories"> itemId: Id<"files"> newName: string }, ) { const existing = await ctx.db .query("files") .withIndex("uniqueFileInDirectory", (q) => q .eq("userId", ctx.user._id) .eq("directoryId", directoryId) .eq("name", newName) .eq("deletedAt", undefined), ) .first() if (existing) { error({ code: ErrorCode.FileExists, message: `File with name ${newName} already exists in ${directoryId ? `directory ${directoryId}` : "root"}`, }) } await ctx.db.patch(itemId, { name: newName, updatedAt: Date.now() }) } export async function move( ctx: AuthenticatedMutationCtx, { targetDirectory: targetDirectoryHandle, items, }: { targetDirectory: DirectoryHandle items: FileHandle[] }, ) { const conflictCheckResults = await Promise.allSettled( items.map((fileHandle) => authorizedGet(ctx, fileHandle.id).then((f) => { if (!f) { error({ code: ErrorCode.NotFound, message: `File ${fileHandle.id} not found`, }) } return ctx.db .query("files") .withIndex("uniqueFileInDirectory", (q) => q .eq("userId", ctx.user._id) .eq("directoryId", targetDirectoryHandle.id) .eq("name", f.name) .eq("deletedAt", undefined), ) .first() }), ), ) const errors: ApplicationErrorData[] = [] const okFiles: FileHandle[] = [] conflictCheckResults.forEach((result, i) => { if (result.status === "fulfilled") { if (result.value) { errors.push( createErrorData( ErrorCode.Conflict, `Directory ${targetDirectoryHandle.id} already contains a file with name ${result.value.name}`, ), ) } else { okFiles.push(items[i]) } } else if (result.status === "rejected") { errors.push(createErrorData(ErrorCode.Internal)) } }) const results = await Promise.allSettled( okFiles.map((handle) => ctx.db.patch(handle.id, { directoryId: targetDirectoryHandle.id, updatedAt: Date.now(), }), ), ) for (const updateResult of results) { if (updateResult.status === "rejected") { errors.push(createErrorData(ErrorCode.Internal)) } } return { moved: okFiles, errors } } export async function deletePermanently( ctx: AuthenticatedMutationCtx, { items, }: { items: FileHandle[] }, ) { if (items.length === 0) { return null } const itemsToBeDeleted = await Promise.allSettled( items.map((item) => ctx.db.get(item.id)), ).then((results) => results.filter( (result): result is PromiseFulfilledResult> => result.status === "fulfilled" && result.value !== null, ), ) const deleteFilePromises = itemsToBeDeleted.map((item) => Promise.all([ ctx.db.delete(item.value._id), ctx.storage.delete(item.value.storageId), ]), ) const deleteResults = await Promise.allSettled(deleteFilePromises) const errors: ApplicationErrorData[] = [] let successfulDeletions = 0 for (const result of deleteResults) { if (result.status === "rejected") { errors.push(createErrorData(ErrorCode.Internal)) } else { successfulDeletions += 1 } } 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> => 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: ApplicationErrorData[] = [] let successfulRestorations = 0 for (const result of restoreResults) { if (result.status === "rejected") { errors.push(createErrorData(ErrorCode.Internal)) } else { successfulRestorations += 1 } } return { restored: successfulRestorations, errors } }