pingdotgg / uploadthing

File uploads for modern web devs
https://uploadthing.com
MIT License
4.19k stars 312 forks source link

File is successfully uploaded to uploadthing but is not saved to my database #284

Closed KUSHAD closed 1 year ago

KUSHAD commented 1 year ago

Problem

I am writing the file route for my document uploading through the uploading, the file is successfully uploaded to uploadthing but that is not updated to my database.But it is showing as success on the client side I am using Prisma and Mongodb

Error

Screenshot 2023-08-28 at 4 47 50 PM

What is expected

The data should be updated to my database after the onUploadComplete

What I tried

I went to the FAQ section of the docs and found out this question Failed to simulate callback for file. Is your webhook configured correctly? but the route is already excluded from the middleware.js

Code

lib/db/uploadthing.js

import { createUploadthing } from 'uploadthing/next';
import { getCurrentUser } from '@/lib/actions/get-current-user';
import prisma from '@/lib/db/prisma';
import { expenditureDocumentValidationSchema } from '@/lib/schema/expenditure-document-schema';

const f = createUploadthing();

export const fileRouter = {
    expenditureDocs: f({
        image: { maxFileCount: 1, maxFileSize: '1MB' },
    })
        .middleware(async ({ req }) => {
            const user = await getCurrentUser();

            if (!user) throw new Error('Unauthorized');

            return {
                expeditureDocDesc: req.cookies.get('expeditureDocDesc').value,
                expenditureID: req.cookies.get('expenditureID').value,
                uploadedBy: user.id,
            };
        })
        .onUploadComplete(async ({ metadata, file }) => {
            try {
                const body = { ...metadata, showUploader: true };
                await expenditureDocumentValidationSchema.validate(body);

                const expenditureExists = await prisma.expenditure.findFirst({
                    where: {
                        id: metadata.expenditureID,
                    },
                });

                if (!expenditureExists)
                    throw new Error(
                        {
                            message: 'Invalid Expenditure ID',
                        },
                        { status: 400 }
                    );
                await prisma.expenditureDocs.create({
                    data: {
                        description: metadata.expeditureDocDesc,
                        docSrc: file.key,
                        addedBy: metadata.uploadedBy,
                        expenditureID: expenditureExists.id,
                    },
                });
            } catch (error) {
                throw new Error({
                    message: error.errors[0] || error.message,
                });
            }
        }),
};

app/api/uploadthing/route.js

import { createNextRouteHandler } from 'uploadthing/next';

import { fileRouter } from '@/lib/db/uploadthing';

// Export routes for Next App Router
export const { GET, POST } = createNextRouteHandler({
    router: fileRouter,
});

components/add-expediture-docs.js

'use client';

import { Button } from '@/components/ui/button';
import {
    DialogTrigger,
    Dialog,
    DialogContent,
    DialogHeader,
} from '@/components/ui/dialog';
import { UploadButton } from '@uploadthing/react';

import {
    Form,
    FormControl,
    FormDescription,
    FormField,
    FormItem,
    FormLabel,
    FormMessage,
} from '@/components/ui/form';
import { Textarea } from '@/components/ui/textarea';
import { yupResolver } from '@hookform/resolvers/yup';
import { useForm } from 'react-hook-form';
import { toast } from '@/components/ui/use-toast';
import { expenditureDocumentValidationSchema } from '@/lib/schema/expenditure-document-schema';
import { Progress } from '@/components/ui/progress';
import { useParams, useRouter } from 'next/navigation';
import { deleteCookie, setCookie } from 'cookies-next';

const AddExpeditureDocs = () => {
    const { id } = useParams();
    const router = useRouter();
    const resolver = yupResolver(expenditureDocumentValidationSchema);
    const form = useForm({
        resolver,
        defaultValues: {
            description: '',
            showUploader: false,
        },
    });

    async function onSubmit(data) {
        setCookie('expeditureDocDesc', data.description, { path: '/' });
        setCookie('expenditureID', id, { path: '/' });
        form.setValue('showUploader', true);
    }

    const watchShowUploader = form.watch('showUploader');
    return (
        <div className='flex flex-row'>
            <div className='mr-auto' />
            <Dialog>
                <DialogTrigger asChild>
                    <Button> Add Expediture Docs</Button>
                </DialogTrigger>
                <DialogContent>
                    <DialogHeader>Add Expediture Docs</DialogHeader>
                    <div className='my-4'>
                        <strong>Progress</strong>
                        <Progress value={watchShowUploader ? 100 : 50} />
                    </div>
                    {watchShowUploader ? (
                        <>
                            <UploadButton
                                endpoint='expenditureDocs'
                                className='w-full ut-button:w-full'
                                onClientUploadComplete={() => {
                                    try {
                                        deleteCookie('expeditureDocDesc', { path: '/' });
                                        deleteCookie('expenditureID', { path: '/' });
                                        form.reset();
                                        toast({
                                            title: 'Added Expenditure Document',
                                        });
                                        router.refresh();
                                    } catch (error) {
                                        toast({
                                            title: error.response
                                                ? error.response.data.message
                                                : error.message,

                                            variant: 'destructive',
                                        });
                                    }
                                }}
                            />
                            <Button
                                variant='destructive'
                                onClick={() => form.setValue('showUploader', false)}>
                                Go Back
                            </Button>
                        </>
                    ) : (
                        <Form {...form}>
                            <form onSubmit={form.handleSubmit(onSubmit)}>
                                <FormField
                                    control={form.control}
                                    name='description'
                                    render={({ field }) => (
                                        <FormItem>
                                            <FormLabel>Description of the document</FormLabel>
                                            <FormControl>
                                                <Textarea
                                                    className='resize-none'
                                                    placeholder='Description of the document'
                                                    type='text'
                                                    disabled={form.formState.isSubmitting}
                                                    {...field}
                                                />
                                            </FormControl>
                                            <FormDescription>
                                                Description of the document
                                            </FormDescription>
                                            <FormMessage />
                                        </FormItem>
                                    )}
                                />
                                <Button
                                    disabled={form.formState.isSubmitting}
                                    className='w-full my-2'
                                    type='submit'>
                                    Add Expenditure
                                </Button>
                            </form>
                        </Form>
                    )}
                </DialogContent>
            </Dialog>
        </div>
    );
};
export default AddExpeditureDocs;

middleware.js

import { NextResponse } from 'next/server';
import * as jose from 'jose';

export async function middleware(req) {
    try {
        const res = NextResponse.next();

        const cookie = req.cookies.get('staffToken');

        if (!cookie.value) {
            if (req.nextUrl.pathname.startsWith('/api/'))
                return new NextResponse(JSON.stringify({ message: 'Unauthorized' }), {
                    status: 401,
                });
            else return NextResponse.redirect(new URL('/auth', req.url));
        }

        await jose.jwtVerify(
            cookie.value,
            new TextEncoder().encode(process.env.AUTH_SECRET)
        );

        return res;
    } catch {
        if (req.nextUrl.pathname.startsWith('/api/'))
            return new NextResponse(JSON.stringify({ message: 'Unauthorized' }), {
                status: 401,
            });
        else return NextResponse.redirect(new URL('/auth', req.url));
    }
}

export const config = {
    matcher: [
        '/((?!auth|api/auth|api/uploadthing|_next/static|_next/image|favicon.ico|manifest.json|icon.png|apple-icon.png|icon-192x192.png|icon-256x256.png|icon-384x384.png|icon-512x512.png|opengraph-image.png|twitter-image.png|opengraph-image.alt.txt|twitter-image.alt.txt).*)',
    ],
};

Video Reproduction

https://github.com/pingdotgg/uploadthing/assets/60665405/46fa28f6-8be9-4fd9-8603-44c45de6ccb2

Mr0Bread commented 1 year ago

Seems like something is throwing an exception when webhook call is simulated. I see that you are catching error and then rethrowing it and message seems to be some kind of an object. Can you try to not rethrow it and simply log original error message and stack trace?

like

try {
  ...
} catch (e) {
  console.log(e.message, e.stack)
}
KUSHAD commented 1 year ago

i am closing this issue as i was not handling error the error correctly, console.log(e.message, e.stack) this really helped getting to know the error was SIMULATING FILE UPLOAD WEBHOOK CALLBACK http://localhost:3000/api/uploadthing?slug=expenditureDocs Document Description Required ValidationError: Document Description Required at createError (webpack-internal:///(rsc)/./node_modules/yup/index.js:264:27) at handleResult (webpack-internal:///(rsc)/./node_modules/yup/index.js:282:45) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) [UT] Successfully simulated callback for file c56b33ae-b105-4a8a-8890-b38cf26059cf_Screenshot 2023-08-28 at 4.47.50 PM.png

which i understood was a validation error so i fixed it thanks for the help @markflorkowski