mirror of
https://github.com/documenso/documenso.git
synced 2026-07-08 12:04:59 +10:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ea93c1f508 | |||
| 2dd3e4440f |
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createTrpcContext } from '@documenso/trpc/server/context';
|
||||
import { appRouter } from '@documenso/trpc/server/router';
|
||||
|
||||
export const config = {
|
||||
maxDuration: 120,
|
||||
maxDuration: 60,
|
||||
api: {
|
||||
bodyParser: {
|
||||
sizeLimit: '50mb',
|
||||
|
||||
@@ -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,
|
||||
@@ -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),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -47,7 +47,13 @@ export const completeDocumentWithToken = async ({
|
||||
}: CompleteDocumentWithTokenOptions) => {
|
||||
'use server';
|
||||
|
||||
const startTime = Date.now();
|
||||
console.log('Start:' + startTime);
|
||||
|
||||
console.log('getDocumentStart:' + startTime);
|
||||
const document = await getDocument({ token, documentId });
|
||||
console.log('getDocumentEnd:' + (Date.now() - startTime));
|
||||
console.log('Acc:' + (Date.now() - startTime));
|
||||
|
||||
if (document.status !== DocumentStatus.PENDING) {
|
||||
throw new Error(`Document ${document.id} must be pending`);
|
||||
@@ -63,12 +69,16 @@ export const completeDocumentWithToken = async ({
|
||||
throw new Error(`Recipient ${recipient.id} has already signed`);
|
||||
}
|
||||
|
||||
const fieldStartTime = Date.now();
|
||||
console.log('fieldStart:' + fieldStartTime);
|
||||
const fields = await prisma.field.findMany({
|
||||
where: {
|
||||
documentId: document.id,
|
||||
recipientId: recipient.id,
|
||||
},
|
||||
});
|
||||
console.log('fieldEnd:' + (Date.now() - fieldStartTime));
|
||||
console.log('Acc:' + (Date.now() - startTime));
|
||||
|
||||
if (fields.some((field) => !field.inserted)) {
|
||||
throw new Error(`Recipient ${recipient.id} has unsigned fields`);
|
||||
@@ -93,6 +103,9 @@ export const completeDocumentWithToken = async ({
|
||||
// throw new AppError(AppErrorCode.UNAUTHORIZED, 'Invalid authentication values');
|
||||
// }
|
||||
|
||||
const recipientUpdateStartTime = Date.now();
|
||||
console.log('recipientUpdateStart:' + recipientUpdateStartTime);
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.recipient.update({
|
||||
where: {
|
||||
@@ -124,6 +137,12 @@ export const completeDocumentWithToken = async ({
|
||||
});
|
||||
});
|
||||
|
||||
console.log('recipientUpdateEnd:' + (Date.now() - recipientUpdateStartTime));
|
||||
console.log('Acc:' + (Date.now() - startTime));
|
||||
|
||||
const pendingRecipientsStartTime = Date.now();
|
||||
console.log('pendingRecipientsStart:' + pendingRecipientsStartTime);
|
||||
|
||||
const pendingRecipients = await prisma.recipient.count({
|
||||
where: {
|
||||
documentId: document.id,
|
||||
@@ -132,12 +151,17 @@ export const completeDocumentWithToken = async ({
|
||||
},
|
||||
},
|
||||
});
|
||||
console.log('pendingRecipientsEnd:' + (Date.now() - pendingRecipientsStartTime));
|
||||
console.log('Acc:' + (Date.now() - startTime));
|
||||
|
||||
if (pendingRecipients > 0) {
|
||||
await sendPendingEmail({ documentId, recipientId: recipient.id });
|
||||
}
|
||||
|
||||
const haveAllRecipientsSigned = await prisma.document.findFirst({
|
||||
const updateDocumentStartTime = Date.now();
|
||||
console.log('updateDocumentStart:' + updateDocumentStartTime);
|
||||
|
||||
const documents = await prisma.document.updateMany({
|
||||
where: {
|
||||
id: document.id,
|
||||
Recipient: {
|
||||
@@ -146,13 +170,31 @@ export const completeDocumentWithToken = async ({
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
status: DocumentStatus.COMPLETED,
|
||||
completedAt: new Date(),
|
||||
},
|
||||
});
|
||||
console.log('updateDocumentEnd:' + (Date.now() - updateDocumentStartTime));
|
||||
console.log('Acc:' + (Date.now() - startTime));
|
||||
|
||||
if (haveAllRecipientsSigned) {
|
||||
if (documents.count > 0) {
|
||||
const sealDocumentStartTime = Date.now();
|
||||
console.log('sealDocumentStart:' + sealDocumentStartTime);
|
||||
await sealDocument({ documentId: document.id, requestMetadata });
|
||||
console.log('sealDocumentEnd:' + (Date.now() - sealDocumentStartTime));
|
||||
console.log('Acc:' + (Date.now() - startTime));
|
||||
}
|
||||
|
||||
const updateDocumentStartTime2 = Date.now();
|
||||
console.log('updateDocumentStart2:' + updateDocumentStartTime2);
|
||||
|
||||
const updatedDocument = await getDocument({ token, documentId });
|
||||
console.log('updateDocumentEnd2:' + (Date.now() - updateDocumentStartTime2));
|
||||
console.log('Acc:' + (Date.now() - startTime));
|
||||
|
||||
const triggerWebhookStartTime = Date.now();
|
||||
console.log('triggerWebhookStart:' + triggerWebhookStartTime);
|
||||
|
||||
await triggerWebhook({
|
||||
event: WebhookTriggerEvents.DOCUMENT_SIGNED,
|
||||
@@ -160,4 +202,6 @@ export const completeDocumentWithToken = async ({
|
||||
userId: updatedDocument.userId,
|
||||
teamId: updatedDocument.teamId ?? undefined,
|
||||
});
|
||||
console.log('triggerWebhookEnd:' + (Date.now() - triggerWebhookStartTime));
|
||||
console.log('Acc:' + (Date.now() - startTime));
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
@@ -91,43 +90,69 @@ export const sealDocument = async ({
|
||||
}
|
||||
|
||||
// !: Need to write the fields onto the document as a hard copy
|
||||
const getFileTime = Date.now();
|
||||
console.log('getFileStart:' + getFileTime);
|
||||
const pdfData = await getFile(documentData);
|
||||
console.log('getFileEnd:' + (Date.now() - getFileTime));
|
||||
|
||||
const certificate = await getCertificatePdf({ documentId })
|
||||
.then(async (doc) => PDFDocument.load(doc))
|
||||
.catch(() => null);
|
||||
const getCertificatePdfTime = Date.now();
|
||||
console.log('getCertificatePdfStart:' + getCertificatePdfTime);
|
||||
const certificate = await getCertificatePdf({ documentId }).then(async (doc) =>
|
||||
PDFDocument.load(doc),
|
||||
);
|
||||
console.log('getCertificatePdfEnd:' + (Date.now() - getCertificatePdfTime));
|
||||
|
||||
const loadDoc = Date.now();
|
||||
console.log('loadDocStart:' + loadDoc);
|
||||
const doc = await PDFDocument.load(pdfData);
|
||||
console.log('loadDocEnd:' + (Date.now() - loadDoc));
|
||||
|
||||
// Normalize and flatten layers that could cause issues with the signature
|
||||
normalizeSignatureAppearances(doc);
|
||||
doc.getForm().flatten();
|
||||
flattenAnnotations(doc);
|
||||
|
||||
if (certificate) {
|
||||
const certificatePages = await doc.copyPages(certificate, certificate.getPageIndices());
|
||||
const certificatePageTime = Date.now();
|
||||
console.log('certificatePageStart:' + certificatePageTime);
|
||||
|
||||
certificatePages.forEach((page) => {
|
||||
doc.addPage(page);
|
||||
});
|
||||
}
|
||||
const certificatePages = await doc.copyPages(certificate, certificate.getPageIndices());
|
||||
console.log('certificatePageEnd:' + (Date.now() - certificatePageTime));
|
||||
|
||||
certificatePages.forEach((page) => {
|
||||
doc.addPage(page);
|
||||
});
|
||||
|
||||
for (const field of fields) {
|
||||
const insertFIeldTime = Date.now();
|
||||
console.log('insertFieldStart:' + insertFIeldTime);
|
||||
await insertFieldInPDF(doc, field);
|
||||
console.log('insertFieldEnd:' + (Date.now() - insertFIeldTime));
|
||||
}
|
||||
|
||||
const pdfBytes = await doc.save();
|
||||
const docSaveTime = Date.now();
|
||||
console.log('docSaveStart:' + docSaveTime);
|
||||
|
||||
const pdfBytes = await doc.save();
|
||||
console.log('docSaveEnd:' + (Date.now() - docSaveTime));
|
||||
|
||||
const pdfBufferTIme = Date.now();
|
||||
console.log('pdfBufferStart:' + pdfBufferTIme);
|
||||
const pdfBuffer = await signPdf({ pdf: Buffer.from(pdfBytes) });
|
||||
console.log('pdfBufferEnd:' + (Date.now() - pdfBufferTIme));
|
||||
|
||||
const { name, ext } = path.parse(document.title);
|
||||
|
||||
const putFIleTIme = Date.now();
|
||||
console.log('putFileStart:' + putFIleTIme);
|
||||
|
||||
const { data: newData } = await putFile({
|
||||
name: `${name}_signed${ext}`,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(pdfBuffer),
|
||||
});
|
||||
|
||||
console.log('putFileEnd:' + (Date.now() - putFIleTIme));
|
||||
|
||||
const postHog = PostHogServerClient();
|
||||
|
||||
if (postHog) {
|
||||
@@ -140,17 +165,10 @@ export const sealDocument = async ({
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.document.update({
|
||||
where: {
|
||||
id: document.id,
|
||||
},
|
||||
data: {
|
||||
status: DocumentStatus.COMPLETED,
|
||||
completedAt: new Date(),
|
||||
},
|
||||
});
|
||||
const updateDocumentTime = Date.now();
|
||||
console.log('updateDocumentStart:' + updateDocumentTime);
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.documentData.update({
|
||||
where: {
|
||||
id: documentData.id,
|
||||
@@ -173,10 +191,18 @@ export const sealDocument = async ({
|
||||
});
|
||||
});
|
||||
|
||||
console.log('updateDocumentEnd:' + (Date.now() - updateDocumentTime));
|
||||
|
||||
if (sendEmail && !isResealing) {
|
||||
const sendCompleteEmailTime = Date.now();
|
||||
console.log('sendCompleteEmailStart:' + sendCompleteEmailTime);
|
||||
await sendCompletedEmail({ documentId, requestMetadata });
|
||||
console.log('sendCompleteEmailEnd:' + (Date.now() - sendCompleteEmailTime));
|
||||
}
|
||||
|
||||
const asdfasdfasdf = Date.now();
|
||||
console.log('updateDocumentStart:' + asdfasdfasdf);
|
||||
|
||||
const updatedDocument = await prisma.document.findFirstOrThrow({
|
||||
where: {
|
||||
id: document.id,
|
||||
@@ -186,6 +212,10 @@ export const sealDocument = async ({
|
||||
Recipient: true,
|
||||
},
|
||||
});
|
||||
console.log('updateDocumentEnd:' + (Date.now() - asdfasdfasdf));
|
||||
|
||||
const triggerWebhookTime = Date.now();
|
||||
console.log('triggerWebhookStart:' + triggerWebhookTime);
|
||||
|
||||
await triggerWebhook({
|
||||
event: WebhookTriggerEvents.DOCUMENT_COMPLETED,
|
||||
@@ -193,4 +223,5 @@ export const sealDocument = async ({
|
||||
userId: document.userId,
|
||||
teamId: document.teamId ?? undefined,
|
||||
});
|
||||
console.log('triggerWebhookEnd:' + (Date.now() - triggerWebhookTime));
|
||||
};
|
||||
|
||||
@@ -4,8 +4,6 @@ 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 { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
@@ -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({
|
||||
|
||||
@@ -22,7 +22,9 @@ export const getCertificatePdf = async ({ documentId }: GetCertificatePdfOptions
|
||||
// !: Previously we would have to keep the playwright version in sync with the browserless version to avoid errors.
|
||||
browser = await chromium.connectOverCDP(process.env.NEXT_PRIVATE_BROWSERLESS_URL);
|
||||
} else {
|
||||
console.log('here');
|
||||
browser = await chromium.launch();
|
||||
console.log('here2');
|
||||
}
|
||||
|
||||
if (!browser) {
|
||||
@@ -31,18 +33,41 @@ export const getCertificatePdf = async ({ documentId }: GetCertificatePdfOptions
|
||||
);
|
||||
}
|
||||
|
||||
console.log('1');
|
||||
const page = await browser.newPage();
|
||||
console.log('2');
|
||||
|
||||
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encryptedId}`, {
|
||||
waitUntil: 'networkidle',
|
||||
timeout: 10_000,
|
||||
});
|
||||
const domcontentloadedTime = Date.now();
|
||||
console.log('domcontentloadedTime:' + domcontentloadedTime);
|
||||
|
||||
await page
|
||||
.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encryptedId}`, {
|
||||
waitUntil: 'domcontentloaded',
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
console.log('domcontentloadedEnd:' + (Date.now() - domcontentloadedTime));
|
||||
|
||||
const networkidleTime = Date.now();
|
||||
console.log('networkidleTime:' + networkidleTime);
|
||||
|
||||
await page
|
||||
.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encryptedId}`, {
|
||||
waitUntil: 'networkidle',
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
console.log('networkidleEnd:' + (Date.now() - networkidleTime));
|
||||
|
||||
const result = await page.pdf({
|
||||
format: 'A4',
|
||||
});
|
||||
console.log(4);
|
||||
|
||||
void browser.close();
|
||||
|
||||
console.log(5);
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user