feat: universal upload

Implementation of a universal upload allowing for multiple storage backends
starting with `database` and `s3`.

Allows clients to put and retrieve files from either client or server using
a blend of client and server actions.
This commit is contained in:
Mythie
2023-09-14 12:46:36 +10:00
parent 72bec7bc34
commit 3afc35c40c
42 changed files with 2372 additions and 308 deletions

View File

@ -1,96 +0,0 @@
import { NextApiRequest, NextApiResponse } from 'next';
import formidable, { type File } from 'formidable';
import { readFileSync } from 'fs';
import { getServerSession } from '@documenso/lib/next-auth/get-server-session';
import { prisma } from '@documenso/prisma';
import { DocumentDataType, DocumentStatus } from '@documenso/prisma/client';
import {
TCreateDocumentRequestSchema,
TCreateDocumentResponseSchema,
} from '~/api/document/create/types';
export const config = {
api: {
bodyParser: false,
},
};
export type TFormidableCreateDocumentRequestSchema = {
file: File;
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<TCreateDocumentResponseSchema>,
) {
const user = await getServerSession({ req, res });
if (!user) {
return res.status(401).json({
error: 'Unauthorized',
});
}
try {
const form = formidable();
const { file } = await new Promise<TFormidableCreateDocumentRequestSchema>(
(resolve, reject) => {
form.parse(req, (err, fields, files) => {
if (err) {
reject(err);
}
// We had intended to do this with Zod but we can only validate it
// as a persistent file which does not include the properties that we
// need.
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
resolve({ ...fields, ...files } as any);
});
},
);
const fileBuffer = readFileSync(file.filepath);
const bytes64 = fileBuffer.toString('base64');
const document = await prisma.document.create({
data: {
title: file.originalFilename ?? file.newFilename,
status: DocumentStatus.DRAFT,
userId: user.id,
documentData: {
create: {
type: DocumentDataType.BYTES_64,
data: bytes64,
initialData: bytes64,
},
},
created: new Date(),
},
});
return res.status(200).json({
id: document.id,
});
} catch (err) {
console.error(err);
return res.status(500).json({
error: 'Internal server error',
});
}
}
/**
* This is a hack to ensure that the types are correct.
*/
type FormidableSatisfiesCreateDocument =
keyof TCreateDocumentRequestSchema extends keyof TFormidableCreateDocumentRequestSchema
? true
: never;
true satisfies FormidableSatisfiesCreateDocument;

View File

@ -1,9 +1,9 @@
import { NextRequest, NextResponse } from 'next/server';
import { nanoid } from 'nanoid';
import { JWT, getToken } from 'next-auth/jwt';
import { LOCAL_FEATURE_FLAGS, extractPostHogConfig } from '@documenso/lib/constants/feature-flags';
import { nanoid } from '@documenso/lib/universal/id';
import PostHogServerClient from '~/helpers/get-post-hog-server-client';

View File

@ -88,19 +88,21 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const bytes64 = readFileSync('./public/documenso-supporter-pledge.pdf').toString('base64');
const { id: documentDataId } = await prisma.documentData.create({
data: {
type: DocumentDataType.BYTES_64,
data: bytes64,
initialData: bytes64,
},
});
const document = await prisma.document.create({
data: {
title: 'Documenso Supporter Pledge.pdf',
status: DocumentStatus.COMPLETED,
userId: user.id,
created: now,
documentData: {
create: {
type: DocumentDataType.BYTES_64,
data: bytes64,
initialData: bytes64,
},
},
documentDataId,
},
include: {
documentData: true,