;
};
-export const createPublicEnv = () =>
- Object.fromEntries(Object.entries(process.env).filter(([key]) => key.startsWith('NEXT_PUBLIC_')));
+export const createPublicEnv = () => ({
+ ...Object.fromEntries(Object.entries(process.env).filter(([key]) => key.startsWith('NEXT_PUBLIC_'))),
+ // Derived from the private URL so the public flag cannot drift from the
+ // real server-side configuration. Placed last so it wins over any literal
+ // env var with the same name.
+ // The `? 'true' : 'false'` might seem dumb but it's because we're expecting env var strings.
+ NEXT_PUBLIC_DOCUMENT_CONVERSION_ENABLED: process.env.NEXT_PRIVATE_DOCUMENT_CONVERSION_URL ? 'true' : 'false',
+});
diff --git a/packages/trpc/server/document-router/create-document.ts b/packages/trpc/server/document-router/create-document.ts
index 04dd9a22d..f2e84364e 100644
--- a/packages/trpc/server/document-router/create-document.ts
+++ b/packages/trpc/server/document-router/create-document.ts
@@ -1,5 +1,6 @@
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
+import { convertToPdf } from '@documenso/lib/server-only/document-conversion';
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
import { insertFormValuesInPdf } from '@documenso/lib/server-only/pdf/insert-form-values-in-pdf';
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
@@ -35,7 +36,7 @@ export const createDocumentRoute = authenticatedProcedure
attachments,
} = payload;
- let pdf = Buffer.from(await file.arrayBuffer());
+ let pdf = await convertToPdf(file, ctx.logger);
if (formValues) {
// eslint-disable-next-line require-atomic-updates
diff --git a/packages/trpc/server/embedding-router/create-embedding-envelope.ts b/packages/trpc/server/embedding-router/create-embedding-envelope.ts
index b4ae1709f..5123f6f50 100644
--- a/packages/trpc/server/embedding-router/create-embedding-envelope.ts
+++ b/packages/trpc/server/embedding-router/create-embedding-envelope.ts
@@ -37,5 +37,6 @@ export const createEmbeddingEnvelopeRoute = procedure
bypassDefaultRecipients: true,
},
apiRequestMetadata: ctx.metadata,
+ logger: ctx.logger,
});
});
diff --git a/packages/trpc/server/envelope-router/create-envelope.ts b/packages/trpc/server/envelope-router/create-envelope.ts
index 87f5168e9..c721ad31e 100644
--- a/packages/trpc/server/envelope-router/create-envelope.ts
+++ b/packages/trpc/server/envelope-router/create-envelope.ts
@@ -1,11 +1,13 @@
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
+import { convertToPdf } from '@documenso/lib/server-only/document-conversion';
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
import { extractPdfPlaceholders } from '@documenso/lib/server-only/pdf/auto-place-fields';
import { normalizePdf } from '@documenso/lib/server-only/pdf/normalize-pdf';
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { putPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
import { EnvelopeType } from '@prisma/client';
+import type { Logger } from 'pino';
import { insertFormValuesInPdf } from '../../../lib/server-only/pdf/insert-form-values-in-pdf';
import { authenticatedProcedure } from '../trpc';
@@ -32,6 +34,7 @@ export const createEnvelopeRoute = authenticatedProcedure
teamId: ctx.teamId,
input,
apiRequestMetadata: ctx.metadata,
+ logger: ctx.logger,
});
});
@@ -48,6 +51,12 @@ type CreateEnvelopeRouteOptions = {
input: TCreateEnvelopeRequest;
apiRequestMetadata: ApiRequestMetadata;
+ /**
+ * Optional pino logger threaded from the calling tRPC context. Passed to
+ * downstream helpers (e.g. `convertToPdf`) for structured logging.
+ */
+ logger?: Logger;
+
options?: {
bypassDefaultRecipients?: boolean;
};
@@ -58,6 +67,7 @@ export const createEnvelopeRouteCaller = async ({
teamId,
input,
apiRequestMetadata,
+ logger,
options = {},
}: CreateEnvelopeRouteOptions) => {
const { payload, files } = input;
@@ -96,17 +106,10 @@ export const createEnvelopeRouteCaller = async ({
});
}
- if (files.some((file) => !file.type.startsWith('application/pdf'))) {
- throw new AppError('INVALID_DOCUMENT_FILE', {
- message: 'You cannot upload non-PDF files',
- statusCode: 400,
- });
- }
-
- // For each file: normalize, extract & clean placeholders, then upload.
+ // For each file: convert to PDF if needed, normalize, extract & clean placeholders, then upload.
const envelopeItems = await Promise.all(
files.map(async (file) => {
- let pdf = Buffer.from(await file.arrayBuffer());
+ let pdf = await convertToPdf(file, logger);
if (formValues) {
// eslint-disable-next-line require-atomic-updates
diff --git a/packages/ui/primitives/document-dropzone.tsx b/packages/ui/primitives/document-dropzone.tsx
index e9a111d0b..848751042 100644
--- a/packages/ui/primitives/document-dropzone.tsx
+++ b/packages/ui/primitives/document-dropzone.tsx
@@ -1,5 +1,6 @@
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT, IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
+import { getAllowedUploadMimeTypes } from '@documenso/lib/constants/document-conversion';
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
@@ -54,9 +55,7 @@ export const DocumentDropzone = ({
const organisation = useCurrentOrganisation();
const { getRootProps, getInputProps } = useDropzone({
- accept: {
- 'application/pdf': ['.pdf'],
- },
+ accept: getAllowedUploadMimeTypes(),
multiple: allowMultiple,
disabled,
onDrop: (acceptedFiles) => {
@@ -151,7 +150,7 @@ export const DocumentDropzone = ({
{_(heading[type])}
- {_(disabled ? disabledMessage : msg`Drag & drop your PDF here.`)}
+ {_(disabled ? disabledMessage : msg`Drag & drop your document here.`)}
{disabled && IS_BILLING_ENABLED() && (
diff --git a/packages/ui/primitives/document-upload-button.tsx b/packages/ui/primitives/document-upload-button.tsx
index ff8459c48..1d9d635c3 100644
--- a/packages/ui/primitives/document-upload-button.tsx
+++ b/packages/ui/primitives/document-upload-button.tsx
@@ -1,6 +1,7 @@
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT, IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
+import { getAllowedUploadMimeTypes } from '@documenso/lib/constants/document-conversion';
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
import { isPersonalLayout } from '@documenso/lib/utils/organisations';
import type { MessageDescriptor } from '@lingui/core';
@@ -51,9 +52,7 @@ export const DocumentUploadButton = ({
const isPersonalLayoutMode = isPersonalLayout(organisations);
const { getRootProps, getInputProps } = useDropzone({
- accept: {
- 'application/pdf': ['.pdf'],
- },
+ accept: getAllowedUploadMimeTypes(),
multiple: internalVersion === '2',
disabled,
maxFiles,