mirror of
https://github.com/documenso/documenso.git
synced 2025-11-15 17:21:41 +10:00
Merge branch 'main' into fix/refactor-use-template
This commit is contained in:
@ -5,7 +5,7 @@ import { Button } from '@documenso/ui/primitives/button';
|
|||||||
export default function SignatureDisclosure() {
|
export default function SignatureDisclosure() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<article className="prose">
|
<article className="prose dark:prose-invert">
|
||||||
<h1>Electronic Signature Disclosure</h1>
|
<h1>Electronic Signature Disclosure</h1>
|
||||||
|
|
||||||
<h2>Welcome</h2>
|
<h2>Welcome</h2>
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
|||||||
|
|
||||||
import NextAuth from 'next-auth';
|
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 { NEXT_AUTH_OPTIONS } from '@documenso/lib/next-auth/auth-options';
|
||||||
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
@ -18,15 +20,29 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
error: '/signin',
|
error: '/signin',
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
signIn: async ({ user }) => {
|
signIn: async ({ user: { id: userId } }) => {
|
||||||
await prisma.userSecurityAuditLog.create({
|
const [user] = await Promise.all([
|
||||||
data: {
|
await prisma.user.findFirstOrThrow({
|
||||||
userId: user.id,
|
where: {
|
||||||
ipAddress,
|
id: userId,
|
||||||
userAgent,
|
},
|
||||||
type: UserSecurityAuditLogType.SIGN_IN,
|
}),
|
||||||
},
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
signOut: async ({ token }) => {
|
signOut: async ({ token }) => {
|
||||||
const userId = typeof token.id === 'string' ? parseInt(token.id) : token.id;
|
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';
|
import { appRouter } from '@documenso/trpc/server/router';
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
maxDuration: 60,
|
maxDuration: 120,
|
||||||
api: {
|
api: {
|
||||||
bodyParser: {
|
bodyParser: {
|
||||||
sizeLimit: '50mb',
|
sizeLimit: '50mb',
|
||||||
|
|||||||
@ -229,6 +229,13 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await upsertDocumentMeta({
|
||||||
|
documentId: document.id,
|
||||||
|
userId: user.id,
|
||||||
|
...body.meta,
|
||||||
|
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||||
|
});
|
||||||
|
|
||||||
const recipients = await setRecipientsForDocument({
|
const recipients = await setRecipientsForDocument({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
@ -324,10 +331,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
await upsertDocumentMeta({
|
await upsertDocumentMeta({
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
subject: body.meta.subject,
|
...body.meta,
|
||||||
message: body.meta.message,
|
|
||||||
dateFormat: body.meta.dateFormat,
|
|
||||||
timezone: body.meta.timezone,
|
|
||||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -137,7 +137,7 @@ export const completeDocumentWithToken = async ({
|
|||||||
await sendPendingEmail({ documentId, recipientId: recipient.id });
|
await sendPendingEmail({ documentId, recipientId: recipient.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
const documents = await prisma.document.updateMany({
|
const haveAllRecipientsSigned = await prisma.document.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: document.id,
|
id: document.id,
|
||||||
Recipient: {
|
Recipient: {
|
||||||
@ -146,13 +146,9 @@ export const completeDocumentWithToken = async ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: {
|
|
||||||
status: DocumentStatus.COMPLETED,
|
|
||||||
completedAt: new Date(),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (documents.count > 0) {
|
if (haveAllRecipientsSigned) {
|
||||||
await sealDocument({ documentId: document.id, requestMetadata });
|
await sealDocument({ documentId: document.id, requestMetadata });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -75,18 +75,20 @@ export const deleteDocument = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Continue to hide the document from the user if they are a recipient.
|
// 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) {
|
if (userRecipient?.documentDeletedAt === null) {
|
||||||
await prisma.recipient.update({
|
await prisma.recipient
|
||||||
where: {
|
.update({
|
||||||
documentId_email: {
|
where: {
|
||||||
documentId: document.id,
|
id: userRecipient.id,
|
||||||
email: user.email,
|
|
||||||
},
|
},
|
||||||
},
|
data: {
|
||||||
data: {
|
documentDeletedAt: new Date().toISOString(),
|
||||||
documentDeletedAt: new Date().toISOString(),
|
},
|
||||||
},
|
})
|
||||||
});
|
.catch(() => {
|
||||||
|
// Do nothing.
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return partial document for API v1 response.
|
// Return partial document for API v1 response.
|
||||||
|
|||||||
@ -40,6 +40,11 @@ export const sealDocument = async ({
|
|||||||
const document = await prisma.document.findFirstOrThrow({
|
const document = await prisma.document.findFirstOrThrow({
|
||||||
where: {
|
where: {
|
||||||
id: documentId,
|
id: documentId,
|
||||||
|
Recipient: {
|
||||||
|
every: {
|
||||||
|
signingStatus: SigningStatus.SIGNED,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
documentData: true,
|
documentData: true,
|
||||||
@ -53,10 +58,6 @@ export const sealDocument = async ({
|
|||||||
throw new Error(`Document ${document.id} has no document data`);
|
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({
|
const recipients = await prisma.recipient.findMany({
|
||||||
where: {
|
where: {
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
@ -92,9 +93,9 @@ export const sealDocument = async ({
|
|||||||
// !: Need to write the fields onto the document as a hard copy
|
// !: Need to write the fields onto the document as a hard copy
|
||||||
const pdfData = await getFile(documentData);
|
const pdfData = await getFile(documentData);
|
||||||
|
|
||||||
const certificate = await getCertificatePdf({ documentId }).then(async (doc) =>
|
const certificate = await getCertificatePdf({ documentId })
|
||||||
PDFDocument.load(doc),
|
.then(async (doc) => PDFDocument.load(doc))
|
||||||
);
|
.catch(() => null);
|
||||||
|
|
||||||
const doc = await PDFDocument.load(pdfData);
|
const doc = await PDFDocument.load(pdfData);
|
||||||
|
|
||||||
@ -103,11 +104,13 @@ export const sealDocument = async ({
|
|||||||
doc.getForm().flatten();
|
doc.getForm().flatten();
|
||||||
flattenAnnotations(doc);
|
flattenAnnotations(doc);
|
||||||
|
|
||||||
const certificatePages = await doc.copyPages(certificate, certificate.getPageIndices());
|
if (certificate) {
|
||||||
|
const certificatePages = await doc.copyPages(certificate, certificate.getPageIndices());
|
||||||
|
|
||||||
certificatePages.forEach((page) => {
|
certificatePages.forEach((page) => {
|
||||||
doc.addPage(page);
|
doc.addPage(page);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
await insertFieldInPDF(doc, field);
|
await insertFieldInPDF(doc, field);
|
||||||
@ -138,6 +141,16 @@ export const sealDocument = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
await prisma.$transaction(async (tx) => {
|
await prisma.$transaction(async (tx) => {
|
||||||
|
await tx.document.update({
|
||||||
|
where: {
|
||||||
|
id: document.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
status: DocumentStatus.COMPLETED,
|
||||||
|
completedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await tx.documentData.update({
|
await tx.documentData.update({
|
||||||
where: {
|
where: {
|
||||||
id: documentData.id,
|
id: documentData.id,
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import { mailer } from '@documenso/email/mailer';
|
|||||||
import { render } from '@documenso/email/render';
|
import { render } from '@documenso/email/render';
|
||||||
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
|
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
|
||||||
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
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 { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||||
@ -211,6 +213,31 @@ 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) => {
|
const updatedDocument = await prisma.$transaction(async (tx) => {
|
||||||
if (document.status === DocumentStatus.DRAFT) {
|
if (document.status === DocumentStatus.DRAFT) {
|
||||||
await tx.documentAuditLog.create({
|
await tx.documentAuditLog.create({
|
||||||
|
|||||||
@ -35,6 +35,7 @@ export const getCertificatePdf = async ({ documentId }: GetCertificatePdfOptions
|
|||||||
|
|
||||||
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encryptedId}`, {
|
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encryptedId}`, {
|
||||||
waitUntil: 'networkidle',
|
waitUntil: 'networkidle',
|
||||||
|
timeout: 10_000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await page.pdf({
|
const result = await page.pdf({
|
||||||
|
|||||||
@ -17,6 +17,7 @@ export const getFlag = async (
|
|||||||
options?: GetFlagOptions,
|
options?: GetFlagOptions,
|
||||||
): Promise<TFeatureFlagValue> => {
|
): Promise<TFeatureFlagValue> => {
|
||||||
const requestHeaders = options?.requestHeaders ?? {};
|
const requestHeaders = options?.requestHeaders ?? {};
|
||||||
|
delete requestHeaders['content-length'];
|
||||||
|
|
||||||
if (!isFeatureFlagEnabled()) {
|
if (!isFeatureFlagEnabled()) {
|
||||||
return LOCAL_FEATURE_FLAGS[flag] ?? true;
|
return LOCAL_FEATURE_FLAGS[flag] ?? true;
|
||||||
@ -25,7 +26,7 @@ export const getFlag = async (
|
|||||||
const url = new URL(`${APP_BASE_URL()}/api/feature-flag/get`);
|
const url = new URL(`${APP_BASE_URL()}/api/feature-flag/get`);
|
||||||
url.searchParams.set('flag', flag);
|
url.searchParams.set('flag', flag);
|
||||||
|
|
||||||
const response = await fetch(url, {
|
return await fetch(url, {
|
||||||
headers: {
|
headers: {
|
||||||
...requestHeaders,
|
...requestHeaders,
|
||||||
},
|
},
|
||||||
@ -35,9 +36,10 @@ export const getFlag = async (
|
|||||||
})
|
})
|
||||||
.then(async (res) => res.json())
|
.then(async (res) => res.json())
|
||||||
.then((res) => ZFeatureFlagValueSchema.parse(res))
|
.then((res) => ZFeatureFlagValueSchema.parse(res))
|
||||||
.catch(() => false);
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
return response;
|
return LOCAL_FEATURE_FLAGS[flag] ?? false;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,6 +52,7 @@ export const getAllFlags = async (
|
|||||||
options?: GetFlagOptions,
|
options?: GetFlagOptions,
|
||||||
): Promise<Record<string, TFeatureFlagValue>> => {
|
): Promise<Record<string, TFeatureFlagValue>> => {
|
||||||
const requestHeaders = options?.requestHeaders ?? {};
|
const requestHeaders = options?.requestHeaders ?? {};
|
||||||
|
delete requestHeaders['content-length'];
|
||||||
|
|
||||||
if (!isFeatureFlagEnabled()) {
|
if (!isFeatureFlagEnabled()) {
|
||||||
return LOCAL_FEATURE_FLAGS;
|
return LOCAL_FEATURE_FLAGS;
|
||||||
@ -67,7 +70,10 @@ export const getAllFlags = async (
|
|||||||
})
|
})
|
||||||
.then(async (res) => res.json())
|
.then(async (res) => res.json())
|
||||||
.then((res) => z.record(z.string(), ZFeatureFlagValueSchema).parse(res))
|
.then((res) => z.record(z.string(), ZFeatureFlagValueSchema).parse(res))
|
||||||
.catch(() => LOCAL_FEATURE_FLAGS);
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
return LOCAL_FEATURE_FLAGS;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,7 +95,10 @@ export const getAllAnonymousFlags = async (): Promise<Record<string, TFeatureFla
|
|||||||
})
|
})
|
||||||
.then(async (res) => res.json())
|
.then(async (res) => res.json())
|
||||||
.then((res) => z.record(z.string(), ZFeatureFlagValueSchema).parse(res))
|
.then((res) => z.record(z.string(), ZFeatureFlagValueSchema).parse(res))
|
||||||
.catch(() => LOCAL_FEATURE_FLAGS);
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
return LOCAL_FEATURE_FLAGS;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
interface GetFlagOptions {
|
interface GetFlagOptions {
|
||||||
|
|||||||
@ -282,6 +282,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
className="dark:bg-muted dark:hover:bg-muted/80 bg-black/5 hover:bg-black/10"
|
className="dark:bg-muted dark:hover:bg-muted/80 bg-black/5 hover:bg-black/10"
|
||||||
|
variant="secondary"
|
||||||
disabled={
|
disabled={
|
||||||
isSubmitting || getValues('signers').some((signer) => signer.email === user?.email)
|
isSubmitting || getValues('signers').some((signer) => signer.email === user?.email)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user