diff --git a/packages/lib/server-only/document/create-document.ts b/packages/lib/server-only/document/create-document.ts index 1cc76b9bf..facdaa147 100644 --- a/packages/lib/server-only/document/create-document.ts +++ b/packages/lib/server-only/document/create-document.ts @@ -3,6 +3,7 @@ import type { z } from 'zod'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; +import { normalizePdf as makeNormalizedPdf } from '@documenso/lib/server-only/pdf/normalize-pdf'; 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'; @@ -13,6 +14,8 @@ import { TeamMemberRole } from '@documenso/prisma/client'; import { DocumentSchema } from '@documenso/prisma/generated/zod'; import { ZWebhookDocumentSchema } from '../../types/webhook-payload'; +import { getFile } from '../../universal/upload/get-file'; +import { putPdfFile } from '../../universal/upload/put-file'; import { triggerWebhook } from '../webhooks/trigger/trigger-webhook'; export type CreateDocumentOptions = { @@ -22,6 +25,7 @@ export type CreateDocumentOptions = { teamId?: number; documentDataId: string; formValues?: Record; + normalizePdf?: boolean; requestMetadata?: RequestMetadata; }; @@ -35,6 +39,7 @@ export const createDocument = async ({ externalId, documentDataId, teamId, + normalizePdf, formValues, requestMetadata, }: CreateDocumentOptions): Promise => { @@ -104,6 +109,29 @@ export const createDocument = async ({ return DocumentVisibility.EVERYONE; }; + if (normalizePdf) { + const documentData = await prisma.documentData.findFirst({ + where: { + id: documentDataId, + }, + }); + + if (documentData) { + const buffer = await getFile(documentData); + + const normalizedPdf = await makeNormalizedPdf(Buffer.from(buffer)); + + const newDocumentData = await putPdfFile({ + name: title.endsWith('.pdf') ? title : `${title}.pdf`, + type: 'application/pdf', + arrayBuffer: async () => Promise.resolve(normalizedPdf), + }); + + // eslint-disable-next-line require-atomic-updates + documentDataId = newDocumentData.id; + } + } + return await prisma.$transaction(async (tx) => { const document = await tx.document.create({ data: { diff --git a/packages/lib/server-only/pdf/flatten-form.ts b/packages/lib/server-only/pdf/flatten-form.ts index fd2c981ec..0970309e8 100644 --- a/packages/lib/server-only/pdf/flatten-form.ts +++ b/packages/lib/server-only/pdf/flatten-form.ts @@ -16,7 +16,6 @@ import { export const removeOptionalContentGroups = (document: PDFDocument) => { const context = document.context; const catalog = context.lookup(context.trailerInfo.Root); - if (catalog instanceof PDFDict) { catalog.delete(PDFName.of('OCProperties')); } diff --git a/packages/lib/server-only/pdf/normalize-pdf.ts b/packages/lib/server-only/pdf/normalize-pdf.ts new file mode 100644 index 000000000..cb3ba0eeb --- /dev/null +++ b/packages/lib/server-only/pdf/normalize-pdf.ts @@ -0,0 +1,18 @@ +import { PDFDocument } from 'pdf-lib'; + +import { flattenAnnotations } from './flatten-annotations'; +import { flattenForm, removeOptionalContentGroups } from './flatten-form'; + +export const normalizePdf = async (pdf: Buffer) => { + const pdfDoc = await PDFDocument.load(pdf).catch(() => null); + + if (!pdfDoc) { + return pdf; + } + + removeOptionalContentGroups(pdfDoc); + flattenForm(pdfDoc); + flattenAnnotations(pdfDoc); + + return Buffer.from(await pdfDoc.save()); +}; diff --git a/packages/lib/universal/upload/put-file.ts b/packages/lib/universal/upload/put-file.ts index c7bbf9c05..1f2f7fe43 100644 --- a/packages/lib/universal/upload/put-file.ts +++ b/packages/lib/universal/upload/put-file.ts @@ -8,7 +8,6 @@ import { DocumentDataType } from '@documenso/prisma/client'; import { AppError } from '../../errors/app-error'; import { createDocumentData } from '../../server-only/document-data/create-document-data'; -import { removeOptionalContentGroups } from '../../server-only/pdf/flatten-form'; type File = { name: string; @@ -25,7 +24,9 @@ export const putPdfFile = async (file: File) => { () => false, ); - const pdf = await PDFDocument.load(await file.arrayBuffer()).catch((e) => { + const arrayBuffer = await file.arrayBuffer(); + + const pdf = await PDFDocument.load(arrayBuffer).catch((e) => { console.error(`PDF upload parse error: ${e.message}`); throw new AppError('INVALID_DOCUMENT_FILE'); @@ -39,11 +40,7 @@ export const putPdfFile = async (file: File) => { file.name = `${file.name}.pdf`; } - removeOptionalContentGroups(pdf); - - const bytes = await pdf.save(); - - const { type, data } = await putFile(new File([bytes], file.name, { type: 'application/pdf' })); + const { type, data } = await putFile(file); return await createDocumentData({ type, data }); }; diff --git a/packages/trpc/server/document-router/router.ts b/packages/trpc/server/document-router/router.ts index c2e8af48a..16da0100b 100644 --- a/packages/trpc/server/document-router/router.ts +++ b/packages/trpc/server/document-router/router.ts @@ -197,6 +197,7 @@ export const documentRouter = router({ teamId, title, documentDataId, + normalizePdf: true, requestMetadata: extractNextApiRequestMetadata(ctx.req), }); }),