diff --git a/packages/convex/files.ts b/packages/convex/files.ts index 8b3485a..fc9436a 100644 --- a/packages/convex/files.ts +++ b/packages/convex/files.ts @@ -1,5 +1,5 @@ import type { Id } from "@fileone/convex/dataModel" -import { v } from "convex/values" +import { ConvexError, v } from "convex/values" import { authenticatedMutation, authenticatedQuery, @@ -8,19 +8,19 @@ import { import * as Directories from "./model/directories" import * as Files from "./model/files" import * as User from "./model/user" -import * as Err from "./shared/error" +import { ErrorCode, error } from "./shared/error" export const generateUploadUrl = authenticatedMutation({ handler: async (ctx) => { - const usageStatistics = await User.queryCachedUsageStatistics(ctx) - if (!usageStatistics) { - throw Err.create(Err.Code.Internal, "Internal server error") + const userInfo = await User.queryInfo(ctx) + if (!userInfo) { + throw new ConvexError({ message: "Internal server error" }) } - if ( - usageStatistics.storageUsageBytes >= - usageStatistics.storageQuotaBytes - ) { - throw Err.create(Err.Code.Forbidden, "Storage quota exceeded") + if (userInfo.storageUsageBytes >= userInfo.storageQuotaBytes) { + throw new ConvexError({ + code: ErrorCode.StorageQuotaExceeded, + message: "Storage quota exceeded", + }) } return await ctx.storage.generateUploadUrl() }, @@ -53,7 +53,10 @@ export const fetchDirectory = authenticatedQuery({ handler: async (ctx, { directoryId }) => { const directory = await authorizedGet(ctx, directoryId) if (!directory) { - throw new Error("Directory not found") + error({ + code: ErrorCode.NotFound, + message: "Directory not found", + }) } return await Directories.fetch(ctx, { directoryId }) }, @@ -67,7 +70,10 @@ export const createDirectory = authenticatedMutation({ handler: async (ctx, { name, directoryId }): Promise> => { const parentDirectory = await authorizedGet(ctx, directoryId) if (!parentDirectory) { - throw new Error("Parent directory not found") + error({ + code: ErrorCode.NotFound, + message: "Parent directory not found", + }) } return await Directories.create(ctx, { @@ -76,56 +82,3 @@ export const createDirectory = authenticatedMutation({ }) }, }) - -export const saveFile = authenticatedMutation({ - args: { - name: v.string(), - directoryId: v.id("directories"), - storageId: v.id("_storage"), - }, - handler: async (ctx, { name, storageId, directoryId }) => { - const directory = await authorizedGet(ctx, directoryId) - if (!directory) { - throw new Error("Directory not found") - } - - const now = Date.now() - - const fileMetadata = await Promise.all([ - ctx.db.system.get(storageId), - ctx.user.queryUsageStatistics(), - ]) - if (!fileMetadata) { - throw Err.create(Err.Code.Internal, "Internal server error") - } - - await Promise.all([ - ctx.db.insert("files", { - name, - size: fileMetadata.size, - storageId, - directoryId, - userId: ctx.user._id, - mimeType, - createdAt: now, - updatedAt: now, - }), - ]) - }, -}) - -export const renameFile = authenticatedMutation({ - args: { - directoryId: v.optional(v.id("directories")), - itemId: v.id("files"), - newName: v.string(), - }, - handler: async (ctx, { directoryId, itemId, newName }) => { - const file = await authorizedGet(ctx, itemId) - if (!file) { - throw new Error("File not found") - } - - await Files.renameFile(ctx, { directoryId, itemId, newName }) - }, -}) diff --git a/packages/convex/filesystem.ts b/packages/convex/filesystem.ts index a340d3f..3361bb7 100644 --- a/packages/convex/filesystem.ts +++ b/packages/convex/filesystem.ts @@ -1,4 +1,4 @@ -import { v } from "convex/values" +import { ConvexError, v } from "convex/values" import { apiKeyAuthenticatedQuery, authenticatedMutation, @@ -16,7 +16,7 @@ import { VDirectoryHandle, VFileSystemHandle, } from "./model/filesystem" -import * as Err from "./shared/error" +import { createErrorData, ErrorCode, error } from "./shared/error" import type { DirectoryHandle, FileHandle, @@ -36,10 +36,10 @@ export const moveItems = authenticatedMutation({ targetDirectoryHandle.id, ) if (!targetDirectory) { - throw Err.create( - Err.Code.DirectoryNotFound, - `Directory ${targetDirectoryHandle.id} not found`, - ) + error({ + code: ErrorCode.NotFound, + message: `Directory ${targetDirectoryHandle.id} not found`, + }) } const directoryHandles: DirectoryHandle[] = [] @@ -81,10 +81,10 @@ export const moveToTrash = authenticatedMutation({ for (const handle of handles) { const item = await authorizedGet(ctx, handle.id) if (!item) { - throw Err.create( - Err.Code.NotFound, - `Item ${handle.id} not found`, - ) + error({ + code: ErrorCode.NotFound, + message: `Item ${handle.id} not found`, + }) } } @@ -105,7 +105,7 @@ export const moveToTrash = authenticatedMutation({ }) const results = await Promise.allSettled(promises) - const errors: Err.ApplicationErrorData[] = [] + const errors = [] const okHandles: FileSystemHandle[] = [] for (const result of results) { switch (result.status) { @@ -113,7 +113,7 @@ export const moveToTrash = authenticatedMutation({ okHandles.push(result.value) break case "rejected": - errors.push(Err.createJson(Err.Code.Internal)) + errors.push(createErrorData(ErrorCode.Internal)) break } } diff --git a/packages/convex/functions.ts b/packages/convex/functions.ts index 1fb2127..6112d91 100644 --- a/packages/convex/functions.ts +++ b/packages/convex/functions.ts @@ -14,7 +14,7 @@ import { } from "convex-helpers/server/customFunctions" import * as ApiKey from "./model/apikey" import { type AuthUser, userIdentityOrThrow, userOrThrow } from "./model/user" -import * as Err from "./shared/error" +import { ErrorCode, error } from "./shared/error" export type AuthenticatedQueryCtx = QueryCtx & { user: AuthUser @@ -65,7 +65,10 @@ export const apiKeyAuthenticatedQuery = customQuery(query, { }, input: async (ctx, args) => { if (!(await ApiKey.verifyApiKey(ctx, args.apiKey))) { - throw Err.create(Err.Code.Unauthenticated, "Invalid API key") + error({ + code: ErrorCode.Unauthenticated, + message: "Invalid API key", + }) } return { ctx: ctx as ApiKeyAuthenticatedQueryCtx, args } }, @@ -80,7 +83,10 @@ export const apiKeyAuthenticatedMutation = customMutation(mutation, { }, input: async (ctx, args) => { if (!(await ApiKey.verifyApiKey(ctx, args.apiKey))) { - throw Err.create(Err.Code.Unauthenticated, "Invalid API key") + error({ + code: ErrorCode.Unauthenticated, + message: "Invalid API key", + }) } return { ctx, args } }, diff --git a/packages/convex/model/directories.ts b/packages/convex/model/directories.ts index 9121e6c..d396bec 100644 --- a/packages/convex/model/directories.ts +++ b/packages/convex/model/directories.ts @@ -4,7 +4,8 @@ import type { AuthenticatedQueryCtx, } from "../functions" import { authorizedGet } from "../functions" -import * as Err from "../shared/error" +import type { ApplicationErrorData } from "../shared/error" +import { createErrorData, ErrorCode, error } from "../shared/error" import { type DirectoryHandle, type DirectoryPath, @@ -30,10 +31,10 @@ export async function fetchHandle( ): Promise> { const directory = await authorizedGet(ctx, handle.id) if (!directory) { - throw Err.create( - Err.Code.DirectoryNotFound, - `Directory ${handle.id} not found`, - ) + error({ + code: ErrorCode.NotFound, + message: `Directory ${handle.id} not found`, + }) } return directory } @@ -44,10 +45,10 @@ export async function fetch( ): Promise { const directory = await authorizedGet(ctx, directoryId) if (!directory) { - throw Err.create( - Err.Code.DirectoryNotFound, - `Directory ${directoryId} not found`, - ) + error({ + code: ErrorCode.NotFound, + message: `Directory ${directoryId} not found`, + }) } const path: DirectoryPath = [ @@ -66,7 +67,10 @@ export async function fetch( }) parentDirId = parentDir.parentId } else { - throw Err.create(Err.Code.DirectoryNotFound, "Parent directory not found") + error({ + code: ErrorCode.NotFound, + message: "Parent directory not found", + }) } } @@ -135,10 +139,10 @@ export async function create( ): Promise> { const parentDir = await authorizedGet(ctx, parentId) if (!parentDir) { - throw Err.create( - Err.Code.DirectoryNotFound, - `Parent directory ${parentId} not found`, - ) + error({ + code: ErrorCode.NotFound, + message: `Parent directory ${parentId} not found`, + }) } const existing = await ctx.db @@ -153,10 +157,10 @@ export async function create( .first() if (existing) { - throw Err.create( - Err.Code.DirectoryExists, - `Directory with name ${name} already exists in ${parentId ? `directory ${parentId}` : "root"}`, - ) + error({ + code: ErrorCode.DirectoryExists, + message: `Directory with name ${name} already exists in ${parentId ? `directory ${parentId}` : "root"}`, + }) } const now = Date.now() @@ -183,10 +187,10 @@ export async function move( sourceDirectories.map((directory) => authorizedGet(ctx, directory.id).then((d) => { if (!d) { - throw Err.create( - Err.Code.DirectoryNotFound, - `Directory ${directory.id} not found`, - ) + error({ + code: ErrorCode.NotFound, + message: `Directory ${directory.id} not found`, + }) } return ctx.db .query("directories") @@ -202,14 +206,14 @@ export async function move( ), ) - const errors: Err.ApplicationErrorData[] = [] + const errors: ApplicationErrorData[] = [] const okDirectories: DirectoryHandle[] = [] conflictCheckResults.forEach((result, i) => { if (result.status === "fulfilled") { if (result.value) { errors.push( - Err.createJson( - Err.Code.Conflict, + createErrorData( + ErrorCode.Conflict, `Directory ${targetDirectory.id} already contains a directory with name ${result.value.name}`, ), ) @@ -217,7 +221,7 @@ export async function move( okDirectories.push(sourceDirectories[i]!) } } else if (result.status === "rejected") { - errors.push(Err.createJson(Err.Code.Internal)) + errors.push(createErrorData(ErrorCode.Internal)) } }) @@ -243,7 +247,7 @@ export async function move( for (const updateResult of results) { if (updateResult.status === "rejected") { - errors.push(Err.createJson(Err.Code.Internal)) + errors.push(createErrorData(ErrorCode.Internal)) } } @@ -335,11 +339,11 @@ export async function deletePermanently( const deleteResults = await Promise.allSettled(deleteDirectoryPromises) - const errors: Err.ApplicationErrorData[] = [] + const errors: ApplicationErrorData[] = [] let successfulDeletions = 0 for (const result of deleteResults) { if (result.status === "rejected") { - errors.push(Err.createJson(Err.Code.Internal)) + errors.push(createErrorData(ErrorCode.Internal)) } else { successfulDeletions += 1 } @@ -378,11 +382,11 @@ export async function restore( const restoreResults = await Promise.allSettled(restoreDirectoryPromises) - const errors: Err.ApplicationErrorData[] = [] + const errors: ApplicationErrorData[] = [] let successfulRestorations = 0 for (const result of restoreResults) { if (result.status === "rejected") { - errors.push(Err.createJson(Err.Code.Internal)) + errors.push(createErrorData(ErrorCode.Internal)) } else { successfulRestorations += 1 } diff --git a/packages/convex/model/files.ts b/packages/convex/model/files.ts index c55ce05..f23b477 100644 --- a/packages/convex/model/files.ts +++ b/packages/convex/model/files.ts @@ -1,6 +1,7 @@ import type { Doc, Id } from "@fileone/convex/dataModel" import { type AuthenticatedMutationCtx, authorizedGet } from "../functions" -import * as Err from "../shared/error" +import type { ApplicationErrorData } from "../shared/error" +import { createErrorData, ErrorCode, error } from "../shared/error" import type { DirectoryHandle, FileHandle } from "../shared/filesystem" export async function renameFile( @@ -27,10 +28,10 @@ export async function renameFile( .first() if (existing) { - throw Err.create( - Err.Code.FileExists, - `File with name ${newName} already exists in ${directoryId ? `directory ${directoryId}` : "root"}`, - ) + 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() }) @@ -50,10 +51,10 @@ export async function move( items.map((fileHandle) => authorizedGet(ctx, fileHandle.id).then((f) => { if (!f) { - throw Err.create( - Err.Code.FileNotFound, - `File ${fileHandle.id} not found`, - ) + error({ + code: ErrorCode.NotFound, + message: `File ${fileHandle.id} not found`, + }) } return ctx.db .query("files") @@ -69,14 +70,14 @@ export async function move( ), ) - const errors: Err.ApplicationErrorData[] = [] + const errors: ApplicationErrorData[] = [] const okFiles: FileHandle[] = [] conflictCheckResults.forEach((result, i) => { if (result.status === "fulfilled") { if (result.value) { errors.push( - Err.createJson( - Err.Code.Conflict, + createErrorData( + ErrorCode.Conflict, `Directory ${targetDirectoryHandle.id} already contains a file with name ${result.value.name}`, ), ) @@ -84,7 +85,7 @@ export async function move( okFiles.push(items[i]) } } else if (result.status === "rejected") { - errors.push(Err.createJson(Err.Code.Internal)) + errors.push(createErrorData(ErrorCode.Internal)) } }) @@ -99,7 +100,7 @@ export async function move( for (const updateResult of results) { if (updateResult.status === "rejected") { - errors.push(Err.createJson(Err.Code.Internal)) + errors.push(createErrorData(ErrorCode.Internal)) } } @@ -136,11 +137,11 @@ export async function deletePermanently( const deleteResults = await Promise.allSettled(deleteFilePromises) - const errors: Err.ApplicationErrorData[] = [] + const errors: ApplicationErrorData[] = [] let successfulDeletions = 0 for (const result of deleteResults) { if (result.status === "rejected") { - errors.push(Err.createJson(Err.Code.Internal)) + errors.push(createErrorData(ErrorCode.Internal)) } else { successfulDeletions += 1 } @@ -179,11 +180,11 @@ export async function restore( const restoreResults = await Promise.allSettled(restoreFilePromises) - const errors: Err.ApplicationErrorData[] = [] + const errors: ApplicationErrorData[] = [] let successfulRestorations = 0 for (const result of restoreResults) { if (result.status === "rejected") { - errors.push(Err.createJson(Err.Code.Internal)) + errors.push(createErrorData(ErrorCode.Internal)) } else { successfulRestorations += 1 } diff --git a/packages/convex/model/fileshare.ts b/packages/convex/model/fileshare.ts index c54b606..ef97d13 100644 --- a/packages/convex/model/fileshare.ts +++ b/packages/convex/model/fileshare.ts @@ -1,3 +1,4 @@ +import { ConvexError } from "convex/values" import type { Doc, Id } from "../_generated/dataModel" import type { MutationCtx } from "../_generated/server" import type { @@ -5,7 +6,7 @@ import type { AuthenticatedMutationCtx, AuthenticatedQueryCtx, } from "../functions" -import * as Err from "../shared/error" +import { ErrorCode, error } from "../shared/error" export async function create( ctx: MutationCtx, @@ -22,7 +23,7 @@ export async function create( }) const doc = await ctx.db.get(id) if (!doc) { - throw Err.create(Err.Code.Internal, "Failed to create file share") + throw new ConvexError({ message: "Failed to create file share" }) } return doc } @@ -46,11 +47,17 @@ export async function find( .withIndex("byShareToken", (q) => q.eq("shareToken", shareToken)) .first() if (!doc) { - throw Err.create(Err.Code.NotFound, "File share not found") + error({ + code: ErrorCode.NotFound, + message: "File share not found", + }) } if (hasExpired(doc)) { - throw Err.create(Err.Code.NotFound, "File share not found") + error({ + code: ErrorCode.NotFound, + message: "File share not found", + }) } return doc diff --git a/packages/convex/model/filesystem.ts b/packages/convex/model/filesystem.ts index 0793c95..7121181 100644 --- a/packages/convex/model/filesystem.ts +++ b/packages/convex/model/filesystem.ts @@ -1,11 +1,11 @@ -import { v } from "convex/values" +import { ConvexError, v } from "convex/values" import type { Doc, Id } from "../_generated/dataModel" import { type AuthenticatedMutationCtx, type AuthenticatedQueryCtx, authorizedGet, } from "../functions" -import * as Err from "../shared/error" +import { ErrorCode, error } from "../shared/error" import type { DirectoryHandle, FileHandle, @@ -174,7 +174,10 @@ export async function deleteItemsPermanently( export async function emptyTrash(ctx: AuthenticatedMutationCtx) { const rootDir = await queryRootDirectory(ctx) if (!rootDir) { - throw Err.create(Err.Code.NotFound, "user root directory not found") + error({ + code: ErrorCode.NotFound, + message: "user root directory not found", + }) } const dirs = await ctx.db @@ -221,12 +224,18 @@ export async function fetchFileUrl( ): Promise { const file = await authorizedGet(ctx, fileId) if (!file) { - throw Err.create(Err.Code.NotFound, "file not found") + error({ + code: ErrorCode.NotFound, + message: "file not found", + }) } const url = await ctx.storage.getUrl(file.storageId) if (!url) { - throw Err.create(Err.Code.NotFound, "file not found") + error({ + code: ErrorCode.NotFound, + message: "file not found", + }) } return url @@ -238,7 +247,10 @@ export async function openFile( ) { const file = await authorizedGet(ctx, fileId) if (!file) { - throw Err.create(Err.Code.NotFound, "file not found") + error({ + code: ErrorCode.NotFound, + message: "file not found", + }) } const fileShare = await FilePreview.find(ctx, { @@ -281,7 +293,10 @@ export async function saveFile( ) { const directory = await authorizedGet(ctx, directoryId) if (!directory) { - throw Err.create(Err.Code.NotFound, "directory not found") + error({ + code: ErrorCode.NotFound, + message: "directory not found", + }) } const [fileMetadata, userInfo] = await Promise.all([ @@ -289,7 +304,7 @@ export async function saveFile( User.queryInfo(ctx), ]) if (!fileMetadata || !userInfo) { - throw Err.create(Err.Code.Internal, "Internal server error") + throw new ConvexError({ message: "Internal server error" }) } if ( @@ -297,7 +312,10 @@ export async function saveFile( userInfo.storageQuotaBytes ) { await ctx.storage.delete(storageId) - throw Err.create(Err.Code.StorageQuotaExceeded, "Storage quota exceeded") + error({ + code: ErrorCode.StorageQuotaExceeded, + message: "Storage quota exceeded", + }) } const now = Date.now() diff --git a/packages/convex/model/user.ts b/packages/convex/model/user.ts index 53a2869..14413d6 100644 --- a/packages/convex/model/user.ts +++ b/packages/convex/model/user.ts @@ -2,7 +2,7 @@ import type { MutationCtx, QueryCtx } from "@fileone/convex/server" import type { Doc } from "../_generated/dataModel" import { authComponent } from "../auth" import { type AuthenticatedQueryCtx, authorizedGet } from "../functions" -import * as Err from "../shared/error" +import { ErrorCode, error } from "../shared/error" export type AuthUser = Awaited> @@ -12,7 +12,10 @@ export type AuthUser = Awaited> export async function userIdentityOrThrow(ctx: QueryCtx | MutationCtx) { const identity = await ctx.auth.getUserIdentity() if (!identity) { - throw Err.create(Err.Code.Unauthenticated, "Not authenticated") + error({ + code: ErrorCode.Unauthenticated, + message: "Not authenticated", + }) } return identity }