From 494a76249a4790ed8784379aad75444710a51c90 Mon Sep 17 00:00:00 2001 From: Snigdha Tadi Date: Wed, 23 Sep 2026 00:52:29 -0500 Subject: [PATCH 1/4] Replaced ownedFileProcedure with slightly modified version to avoid empty db rows should note creation fail --- src/server/api/routers/storage.ts | 45 ++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/server/api/routers/storage.ts b/src/server/api/routers/storage.ts index 8961087..f27d2f8 100644 --- a/src/server/api/routers/storage.ts +++ b/src/server/api/routers/storage.ts @@ -13,21 +13,36 @@ const createUploadSchema = z.object({ mime: z.literal(NOTE_MIME_TYPE), }); -const ownedFileProcedure = protectedProcedure - .input(getDeleteSchema) - .use(async ({ ctx, input, next }) => { - const file = await ctx.db.query.file.findFirst({ - where: (file, { eq }) => eq(file.id, input.objectId), - columns: { authorId: true }, - }); - if (!file || file.authorId !== ctx.session.user.id) { - throw new TRPCError({ - code: 'FORBIDDEN', - message: 'You do not own this note.', + +// Trying to rewrite ownedFileProcedure in a way that preserves its old function for +// everything except for creating a new entry/note page +function createOwnedFileProcedure(options: { allowMissing: boolean }) { + return protectedProcedure + .input(getDeleteSchema) + .use(async ({ ctx, input, next }) => { + const file = await ctx.db.query.file.findFirst({ + where: (file, { eq }) => eq(file.id, input.objectId), + columns: { authorId: true }, }); - } - return next(); - }); + + const missing = !file; + const notYours = file && file.authorId !== ctx.session.user.id; + + if (notYours || (missing && !options.allowMissing)) { + throw new TRPCError({ + code: 'FORBIDDEN', + message: 'You do not own this note.', + }); + } + return next(); + }); +} + +// These two separate variables are to avoid changing the usage of the +// original function below +const ownedFileProcedure = createOwnedFileProcedure({ allowMissing: false }); +const ownedOrNewFileProcedure = createOwnedFileProcedure({ allowMissing: true }); +// End of replacement export const storageRouter = createTRPCRouter({ get: publicProcedure.input(getDeleteSchema).query(async ({ input }) => { @@ -52,7 +67,7 @@ export const storageRouter = createTRPCRouter({ } return data; }), - createUpload: ownedFileProcedure + createUpload: ownedOrNewFileProcedure // Instead of ownedFileProcedure .input(createUploadSchema) .mutation(async ({ input }) => { const data = await getUploadURL(input.objectId, input.mime); From d11e275a1209a3399eae9f6b5c4a8ec400003843 Mon Sep 17 00:00:00 2001 From: Snigdha Tadi Date: Wed, 23 Sep 2026 00:53:18 -0500 Subject: [PATCH 2/4] Replaced ownedFileProcedure with slightly modified version to avoid empty db rows should note creation fail --- src/server/api/routers/storage.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/server/api/routers/storage.ts b/src/server/api/routers/storage.ts index f27d2f8..2816779 100644 --- a/src/server/api/routers/storage.ts +++ b/src/server/api/routers/storage.ts @@ -13,9 +13,6 @@ const createUploadSchema = z.object({ mime: z.literal(NOTE_MIME_TYPE), }); - -// Trying to rewrite ownedFileProcedure in a way that preserves its old function for -// everything except for creating a new entry/note page function createOwnedFileProcedure(options: { allowMissing: boolean }) { return protectedProcedure .input(getDeleteSchema) @@ -38,11 +35,8 @@ function createOwnedFileProcedure(options: { allowMissing: boolean }) { }); } -// These two separate variables are to avoid changing the usage of the -// original function below const ownedFileProcedure = createOwnedFileProcedure({ allowMissing: false }); const ownedOrNewFileProcedure = createOwnedFileProcedure({ allowMissing: true }); -// End of replacement export const storageRouter = createTRPCRouter({ get: publicProcedure.input(getDeleteSchema).query(async ({ input }) => { @@ -67,7 +61,7 @@ export const storageRouter = createTRPCRouter({ } return data; }), - createUpload: ownedOrNewFileProcedure // Instead of ownedFileProcedure + createUpload: ownedOrNewFileProcedure .input(createUploadSchema) .mutation(async ({ input }) => { const data = await getUploadURL(input.objectId, input.mime); @@ -80,4 +74,4 @@ export const storageRouter = createTRPCRouter({ } return data; }), -}); +}); \ No newline at end of file From 3b5887cdece3bac7bc643c6065d974e0f6a43789 Mon Sep 17 00:00:00 2001 From: Snigdha Tadi Date: Wed, 23 Sep 2026 01:57:37 -0500 Subject: [PATCH 3/4] Used an id to chain claiming a storage slot before a DB row is created, then uploading a file, followed by creating the DB entry. --- src/lib/schemas/note.ts | 1 + src/server/api/routers/file.ts | 2 ++ src/systems/notes/forms/CreateNoteForm.tsx | 38 +++++++++------------- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/lib/schemas/note.ts b/src/lib/schemas/note.ts index 73e81f9..1007651 100644 --- a/src/lib/schemas/note.ts +++ b/src/lib/schemas/note.ts @@ -35,6 +35,7 @@ export const createFileFormSchema = z.object({ }); export const createFileSchema = z.object({ + id: noteIdSchema.optional(), name: z .string() .min(3, 'Name must be at least 3 characters') diff --git a/src/server/api/routers/file.ts b/src/server/api/routers/file.ts index e2da5ee..0538734 100644 --- a/src/server/api/routers/file.ts +++ b/src/server/api/routers/file.ts @@ -103,6 +103,7 @@ export const fileRouter = createTRPCRouter({ year, profFirst, profLast, + id, ...fileData } = input; @@ -144,6 +145,7 @@ export const fileRouter = createTRPCRouter({ .insert(files) .values({ ...fileData, + ...(id ? { id } : {}), // Providing id should prevent publicURL from having '' placeholder authorId: userId, sectionId: section.id, publicUrl: '', // This must be filled in with an update call right after the create call diff --git a/src/systems/notes/forms/CreateNoteForm.tsx b/src/systems/notes/forms/CreateNoteForm.tsx index 8cf578a..ff01635 100644 --- a/src/systems/notes/forms/CreateNoteForm.tsx +++ b/src/systems/notes/forms/CreateNoteForm.tsx @@ -8,6 +8,7 @@ import FormFile from '@src/lib/components/form/FormFile'; import { createFileFormSchema } from '@src/lib/schemas/note'; import { useTRPC } from '@src/lib/trpc/react'; import { useUploadToUploadURL } from '@src/systems/notes/hooks/useUploadToUploadURL'; +import { nanoid } from 'nanoid'; export type FileDetails = { file?: File | null; @@ -42,7 +43,6 @@ const defaultValues: FileDetails = { export default function CreateNoteForm() { const api = useTRPC(); const createMutation = useMutation(api.file.create.mutationOptions()); - const updateMutation = useMutation(api.file.update.mutationOptions()); const uploadFile = useUploadToUploadURL(); const router = useRouter(); const searchParams = useSearchParams(); @@ -52,10 +52,22 @@ export default function CreateNoteForm() { defaultValues, onSubmit: async ({ value, formApi }) => { const selectedFile = value.file ?? null; + const isFileDirty = !formApi.getFieldMeta('file')?.isDefaultValue; + let fileId: string | undefined; + + if (isFileDirty) { + fileId = nanoid(20); + await uploadFile.mutateAsync({ + file: selectedFile, + fileName: fileId, + }); + } + // Create return createMutation.mutateAsync( { + id: fileId, name: value.name, description: value.description, handwritten: value.handwritten, @@ -69,28 +81,8 @@ export default function CreateNoteForm() { }, { onSuccess: async (newId) => { - const isFileDirty = !formApi.getFieldMeta('file')?.isDefaultValue; - if (!isFileDirty) { - router.push(`/notes/${newId}`); - return; - } - - await uploadFile.mutateAsync({ - file: selectedFile, - fileName: newId, - }); - updateMutation.mutate( - { - id: newId, - name: value.name, - description: value.description, - handwritten: value.handwritten, - }, - { - onSuccess: () => router.push(`/notes/${newId}`), - }, - ); - }, + router.push(`/notes/${newId}`); + } }, ); }, From 62a1c070098ef55b5b58379a147840870d177677 Mon Sep 17 00:00:00 2001 From: Snigdha Tadi Date: Wed, 23 Sep 2026 01:59:01 -0500 Subject: [PATCH 4/4] Forgot to run prettier --- src/server/api/routers/storage.ts | 6 ++++-- src/systems/notes/forms/CreateNoteForm.tsx | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/server/api/routers/storage.ts b/src/server/api/routers/storage.ts index 2816779..86ca8ef 100644 --- a/src/server/api/routers/storage.ts +++ b/src/server/api/routers/storage.ts @@ -36,7 +36,9 @@ function createOwnedFileProcedure(options: { allowMissing: boolean }) { } const ownedFileProcedure = createOwnedFileProcedure({ allowMissing: false }); -const ownedOrNewFileProcedure = createOwnedFileProcedure({ allowMissing: true }); +const ownedOrNewFileProcedure = createOwnedFileProcedure({ + allowMissing: true, +}); export const storageRouter = createTRPCRouter({ get: publicProcedure.input(getDeleteSchema).query(async ({ input }) => { @@ -74,4 +76,4 @@ export const storageRouter = createTRPCRouter({ } return data; }), -}); \ No newline at end of file +}); diff --git a/src/systems/notes/forms/CreateNoteForm.tsx b/src/systems/notes/forms/CreateNoteForm.tsx index ff01635..6379b30 100644 --- a/src/systems/notes/forms/CreateNoteForm.tsx +++ b/src/systems/notes/forms/CreateNoteForm.tsx @@ -1,6 +1,7 @@ 'use client'; import { useMutation } from '@tanstack/react-query'; +import { nanoid } from 'nanoid'; import { useRouter, useSearchParams } from 'next/navigation'; import Panel, { PanelSkeleton } from '@nebula-library/components/Panel'; import { useAppForm } from '@src/lib/components/form/form'; @@ -8,7 +9,6 @@ import FormFile from '@src/lib/components/form/FormFile'; import { createFileFormSchema } from '@src/lib/schemas/note'; import { useTRPC } from '@src/lib/trpc/react'; import { useUploadToUploadURL } from '@src/systems/notes/hooks/useUploadToUploadURL'; -import { nanoid } from 'nanoid'; export type FileDetails = { file?: File | null; @@ -63,7 +63,7 @@ export default function CreateNoteForm() { fileName: fileId, }); } - + // Create return createMutation.mutateAsync( { @@ -82,7 +82,7 @@ export default function CreateNoteForm() { { onSuccess: async (newId) => { router.push(`/notes/${newId}`); - } + }, }, ); },