Compare commits

..

1 Commits

Author SHA1 Message Date
David Nguyen
9067351f96 fix: demo 2024-04-27 17:19:58 +07:00
17 changed files with 74 additions and 164 deletions

View File

@@ -8,7 +8,7 @@ import { useRouter } from 'next/navigation';
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { base64 } from '@documenso/lib/universal/base64';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import { putFile } from '@documenso/lib/universal/upload/put-file';
import type { Field, Recipient } from '@documenso/prisma/client';
import { DocumentDataType, Prisma } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
@@ -115,7 +115,7 @@ export const SinglePlayerClient = () => {
}
try {
const putFileData = await putPdfFile(uploadedFile.file);
const putFileData = await putFile(uploadedFile.file);
const documentToken = await createSinglePlayerDocument({
documentData: {

View File

@@ -10,9 +10,8 @@ import { useSession } from 'next-auth/react';
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
import { AppError } from '@documenso/lib/errors/app-error';
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import { putFile } from '@documenso/lib/universal/upload/put-file';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import { TRPCClientError } from '@documenso/trpc/client';
import { trpc } from '@documenso/trpc/react';
@@ -58,7 +57,7 @@ export const UploadDocument = ({ className, team }: UploadDocumentProps) => {
try {
setIsLoading(true);
const { type, data } = await putPdfFile(file);
const { type, data } = await putFile(file);
const { id: documentDataId } = await createDocumentData({
type,
@@ -84,21 +83,13 @@ export const UploadDocument = ({ className, team }: UploadDocumentProps) => {
});
router.push(`${formatDocumentsPath(team?.url)}/${id}/edit`);
} catch (err) {
const error = AppError.parseError(err);
} catch (error) {
console.error(error);
console.error(err);
if (error.code === 'INVALID_DOCUMENT_FILE') {
toast({
title: 'Invalid file',
description: 'You cannot upload encrypted PDFs',
variant: 'destructive',
});
} else if (err instanceof TRPCClientError) {
if (error instanceof TRPCClientError) {
toast({
title: 'Error',
description: err.message,
description: error.message,
variant: 'destructive',
});
} else {

View File

@@ -12,7 +12,7 @@ import * as z from 'zod';
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
import { base64 } from '@documenso/lib/universal/base64';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import { putFile } from '@documenso/lib/universal/upload/put-file';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import { Card, CardContent } from '@documenso/ui/primitives/card';
@@ -98,7 +98,7 @@ export const NewTemplateDialog = ({ teamId, templateRootPath }: NewTemplateDialo
const file: File = uploadedFile.file;
try {
const { type, data } = await putPdfFile(file);
const { type, data } = await putFile(file);
const { id: templateDocumentDataId } = await createDocumentData({
type,

View File

@@ -5,7 +5,7 @@ import { Button } from '@documenso/ui/primitives/button';
export default function SignatureDisclosure() {
return (
<div>
<article className="prose dark:prose-invert">
<article className="prose">
<h1>Electronic Signature Disclosure</h1>
<h2>Welcome</h2>

View File

@@ -2,8 +2,6 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import NextAuth from 'next-auth';
import { getStripeCustomerByUser } from '@documenso/ee/server-only/stripe/get-customer';
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { NEXT_AUTH_OPTIONS } from '@documenso/lib/next-auth/auth-options';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { prisma } from '@documenso/prisma';
@@ -20,29 +18,15 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) {
error: '/signin',
},
events: {
signIn: async ({ user: { id: userId } }) => {
const [user] = await Promise.all([
await prisma.user.findFirstOrThrow({
where: {
id: userId,
},
}),
await prisma.userSecurityAuditLog.create({
data: {
userId,
ipAddress,
userAgent,
type: UserSecurityAuditLogType.SIGN_IN,
},
}),
]);
// Create the Stripe customer and attach it to the user if it doesn't exist.
if (user.customerId === null && IS_BILLING_ENABLED()) {
await getStripeCustomerByUser(user).catch((err) => {
console.error(err);
});
}
signIn: async ({ user }) => {
await prisma.userSecurityAuditLog.create({
data: {
userId: user.id,
ipAddress,
userAgent,
type: UserSecurityAuditLogType.SIGN_IN,
},
});
},
signOut: async ({ token }) => {
const userId = typeof token.id === 'string' ? parseInt(token.id) : token.id;

View File

@@ -3,7 +3,7 @@ import { createTrpcContext } from '@documenso/trpc/server/context';
import { appRouter } from '@documenso/trpc/server/router';
export const config = {
maxDuration: 120,
maxDuration: 90,
api: {
bodyParser: {
sizeLimit: '50mb',

View File

@@ -22,7 +22,7 @@ import { updateRecipient } from '@documenso/lib/server-only/recipient/update-rec
import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { getFile } from '@documenso/lib/universal/upload/get-file';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import { putFile } from '@documenso/lib/universal/upload/put-file';
import {
getPresignGetUrl,
getPresignPostUrl,
@@ -229,13 +229,6 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
requestMetadata: extractNextApiRequestMetadata(args.req),
});
await upsertDocumentMeta({
documentId: document.id,
userId: user.id,
...body.meta,
requestMetadata: extractNextApiRequestMetadata(args.req),
});
const recipients = await setRecipientsForDocument({
userId: user.id,
teamId: team?.id,
@@ -303,7 +296,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
formValues: body.formValues,
});
const newDocumentData = await putPdfFile({
const newDocumentData = await putFile({
name: fileName,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(prefilled),
@@ -331,7 +324,10 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
await upsertDocumentMeta({
documentId: document.id,
userId: user.id,
...body.meta,
subject: body.meta.subject,
message: body.meta.message,
dateFormat: body.meta.dateFormat,
timezone: body.meta.timezone,
requestMetadata: extractNextApiRequestMetadata(args.req),
});
}

View File

@@ -5,7 +5,7 @@ import { sealDocument } from '@documenso/lib/server-only/document/seal-document'
import { redis } from '@documenso/lib/server-only/redis';
import { stripe } from '@documenso/lib/server-only/stripe';
import { alphaid, nanoid } from '@documenso/lib/universal/id';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import { putFile } from '@documenso/lib/universal/upload/put-file';
import { prisma } from '@documenso/prisma';
import {
DocumentStatus,
@@ -74,7 +74,7 @@ export const onEarlyAdoptersCheckout = async ({ session }: OnEarlyAdoptersChecko
new URL('@documenso/assets/documenso-supporter-pledge.pdf', import.meta.url),
).then(async (res) => res.arrayBuffer());
const { id: documentDataId } = await putPdfFile({
const { id: documentDataId } = await putFile({
name: 'Documenso Supporter Pledge.pdf',
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(documentBuffer),

View File

@@ -21,7 +21,6 @@ export const FEATURE_FLAG_POLL_INTERVAL = 30000;
* Does not take any person or group properties into account.
*/
export const LOCAL_FEATURE_FLAGS: Record<string, boolean> = {
app_allow_encrypted_documents: false,
app_billing: NEXT_PUBLIC_FEATURE_BILLING_ENABLED() === 'true',
app_document_page_view_history_sheet: false,
app_passkey: WEBAPP_BASE_URL === 'http://localhost:3000', // Temp feature flag.

View File

@@ -74,6 +74,13 @@ export const completeDocumentWithToken = async ({
throw new Error(`Recipient ${recipient.id} has unsigned fields`);
}
// eslint-disable-next-line no-promise-executor-return
await new Promise<void>((resolve) => setTimeout(() => resolve(), 70000));
if (Date.now() > 0) {
throw new Error('Test');
}
// Document reauth for completing documents is currently not required.
// const { derivedRecipientActionAuth } = extractDocumentAuthMethods({
@@ -137,7 +144,7 @@ export const completeDocumentWithToken = async ({
await sendPendingEmail({ documentId, recipientId: recipient.id });
}
const haveAllRecipientsSigned = await prisma.document.findFirst({
const documents = await prisma.document.updateMany({
where: {
id: document.id,
Recipient: {
@@ -146,9 +153,13 @@ export const completeDocumentWithToken = async ({
},
},
},
data: {
status: DocumentStatus.COMPLETED,
completedAt: new Date(),
},
});
if (haveAllRecipientsSigned) {
if (documents.count > 0) {
await sealDocument({ documentId: document.id, requestMetadata });
}

View File

@@ -75,20 +75,18 @@ export const deleteDocument = async ({
}
// Continue to hide the document from the user if they are a recipient.
// Dirty way of doing this but it's faster than refetching the document.
if (userRecipient?.documentDeletedAt === null) {
await prisma.recipient
.update({
where: {
id: userRecipient.id,
await prisma.recipient.update({
where: {
documentId_email: {
documentId: document.id,
email: user.email,
},
data: {
documentDeletedAt: new Date().toISOString(),
},
})
.catch(() => {
// Do nothing.
});
},
data: {
documentDeletedAt: new Date().toISOString(),
},
});
}
// Return partial document for API v1 response.

View File

@@ -14,7 +14,7 @@ import { signPdf } from '@documenso/signing';
import type { RequestMetadata } from '../../universal/extract-request-metadata';
import { getFile } from '../../universal/upload/get-file';
import { putPdfFile } from '../../universal/upload/put-file';
import { putFile } from '../../universal/upload/put-file';
import { getCertificatePdf } from '../htmltopdf/get-certificate-pdf';
import { flattenAnnotations } from '../pdf/flatten-annotations';
import { insertFieldInPDF } from '../pdf/insert-field-in-pdf';
@@ -40,11 +40,6 @@ export const sealDocument = async ({
const document = await prisma.document.findFirstOrThrow({
where: {
id: documentId,
Recipient: {
every: {
signingStatus: SigningStatus.SIGNED,
},
},
},
include: {
documentData: true,
@@ -58,6 +53,10 @@ export const sealDocument = async ({
throw new Error(`Document ${document.id} has no document data`);
}
if (document.status !== DocumentStatus.COMPLETED) {
throw new Error(`Document ${document.id} has not been completed`);
}
const recipients = await prisma.recipient.findMany({
where: {
documentId: document.id,
@@ -93,9 +92,9 @@ export const sealDocument = async ({
// !: Need to write the fields onto the document as a hard copy
const pdfData = await getFile(documentData);
const certificate = await getCertificatePdf({ documentId })
.then(async (doc) => PDFDocument.load(doc))
.catch(() => null);
const certificate = await getCertificatePdf({ documentId }).then(async (doc) =>
PDFDocument.load(doc),
);
const doc = await PDFDocument.load(pdfData);
@@ -104,13 +103,11 @@ export const sealDocument = async ({
doc.getForm().flatten();
flattenAnnotations(doc);
if (certificate) {
const certificatePages = await doc.copyPages(certificate, certificate.getPageIndices());
const certificatePages = await doc.copyPages(certificate, certificate.getPageIndices());
certificatePages.forEach((page) => {
doc.addPage(page);
});
}
certificatePages.forEach((page) => {
doc.addPage(page);
});
for (const field of fields) {
await insertFieldInPDF(doc, field);
@@ -122,7 +119,7 @@ export const sealDocument = async ({
const { name, ext } = path.parse(document.title);
const { data: newData } = await putPdfFile({
const { data: newData } = await putFile({
name: `${name}_signed${ext}`,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(pdfBuffer),
@@ -141,16 +138,6 @@ export const sealDocument = async ({
}
await prisma.$transaction(async (tx) => {
await tx.document.update({
where: {
id: document.id,
},
data: {
status: DocumentStatus.COMPLETED,
completedAt: new Date(),
},
});
await tx.documentData.update({
where: {
id: documentData.id,

View File

@@ -4,15 +4,12 @@ import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render';
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
import { sealDocument } from '@documenso/lib/server-only/document/seal-document';
import { updateDocument } from '@documenso/lib/server-only/document/update-document';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
import { renderCustomEmailTemplate } from '@documenso/lib/utils/render-custom-email-template';
import { prisma } from '@documenso/prisma';
import { DocumentStatus, RecipientRole, SendStatus, SigningStatus } from '@documenso/prisma/client';
import { DocumentStatus, RecipientRole, SendStatus } from '@documenso/prisma/client';
import { WebhookTriggerEvents } from '@documenso/prisma/client';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
@@ -21,6 +18,7 @@ import {
RECIPIENT_ROLE_TO_EMAIL_TYPE,
} from '../../constants/recipient-roles';
import { getFile } from '../../universal/upload/get-file';
import { putFile } from '../../universal/upload/put-file';
import { insertFormValuesInPdf } from '../pdf/insert-form-values-in-pdf';
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
@@ -102,7 +100,7 @@ export const sendDocument = async ({
formValues: document.formValues as Record<string, string | number | boolean>,
});
const newDocumentData = await putPdfFile({
const newDocumentData = await putFile({
name: document.title,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(prefilled),
@@ -122,7 +120,7 @@ export const sendDocument = async ({
await Promise.all(
document.Recipient.map(async (recipient) => {
if (recipient.signingStatus === SigningStatus.SIGNED || recipient.role === RecipientRole.CC) {
if (recipient.sendStatus === SendStatus.SENT || recipient.role === RecipientRole.CC) {
return;
}
@@ -213,31 +211,6 @@ export const sendDocument = async ({
}),
);
const allRecipientsHaveNoActionToTake = document.Recipient.every(
(recipient) => recipient.role === RecipientRole.CC,
);
if (allRecipientsHaveNoActionToTake) {
const updatedDocument = await updateDocument({
documentId,
userId,
teamId,
data: { status: DocumentStatus.COMPLETED },
});
await sealDocument({ documentId: updatedDocument.id, requestMetadata });
// Keep the return type the same for the `sendDocument` method
return await prisma.document.findFirstOrThrow({
where: {
id: documentId,
},
include: {
Recipient: true,
},
});
}
const updatedDocument = await prisma.$transaction(async (tx) => {
if (document.status === DocumentStatus.DRAFT) {
await tx.documentAuditLog.create({

View File

@@ -35,7 +35,6 @@ export const getCertificatePdf = async ({ documentId }: GetCertificatePdfOptions
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encryptedId}`, {
waitUntil: 'networkidle',
timeout: 10_000,
});
const result = await page.pdf({

View File

@@ -1,12 +1,9 @@
import { base64 } from '@scure/base';
import { env } from 'next-runtime-env';
import { PDFDocument } from 'pdf-lib';
import { match } from 'ts-pattern';
import { getFlag } from '@documenso/lib/universal/get-feature-flag';
import { DocumentDataType } from '@documenso/prisma/client';
import { AppError } from '../../errors/app-error';
import { createDocumentData } from '../../server-only/document-data/create-document-data';
type File = {
@@ -15,38 +12,14 @@ type File = {
arrayBuffer: () => Promise<ArrayBuffer>;
};
/**
* Uploads a document file to the appropriate storage location and creates
* a document data record.
*/
export const putPdfFile = async (file: File) => {
const isEncryptedDocumentsAllowed = await getFlag('app_allow_encrypted_documents').catch(
() => false,
);
// This will prevent uploading encrypted PDFs or anything that can't be opened.
if (!isEncryptedDocumentsAllowed) {
await PDFDocument.load(await file.arrayBuffer()).catch((e) => {
console.error(`PDF upload parse error: ${e.message}`);
throw new AppError('INVALID_DOCUMENT_FILE');
});
}
const { type, data } = await putFile(file);
return await createDocumentData({ type, data });
};
/**
* Uploads a file to the appropriate storage location.
*/
export const putFile = async (file: File) => {
const NEXT_PUBLIC_UPLOAD_TRANSPORT = env('NEXT_PUBLIC_UPLOAD_TRANSPORT');
return await match(NEXT_PUBLIC_UPLOAD_TRANSPORT)
const { type, data } = await match(NEXT_PUBLIC_UPLOAD_TRANSPORT)
.with('s3', async () => putFileInS3(file))
.otherwise(async () => putFileInDatabase(file));
return await createDocumentData({ type, data });
};
const putFileInDatabase = async (file: File) => {

View File

@@ -10,7 +10,7 @@ import { FROM_ADDRESS, FROM_NAME, SERVICE_USER_EMAIL } from '@documenso/lib/cons
import { insertFieldInPDF } from '@documenso/lib/server-only/pdf/insert-field-in-pdf';
import { alphaid } from '@documenso/lib/universal/id';
import { getFile } from '@documenso/lib/universal/upload/get-file';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import { putFile } from '@documenso/lib/universal/upload/put-file';
import { prisma } from '@documenso/prisma';
import {
DocumentStatus,
@@ -86,7 +86,7 @@ export const singleplayerRouter = router({
},
});
const { id: documentDataId } = await putPdfFile({
const { id: documentDataId } = await putFile({
name: `${documentName}.pdf`,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(signedPdfBuffer),

View File

@@ -282,7 +282,6 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
<Button
type="button"
className="dark:bg-muted dark:hover:bg-muted/80 bg-black/5 hover:bg-black/10"
variant="secondary"
disabled={
isSubmitting || getValues('signers').some((signer) => signer.email === user?.email)
}