;
diff --git a/packages/trpc/server/admin-router/router.ts b/packages/trpc/server/admin-router/router.ts
index c36ef55bb..7efc6de70 100644
--- a/packages/trpc/server/admin-router/router.ts
+++ b/packages/trpc/server/admin-router/router.ts
@@ -3,6 +3,7 @@ import { createAdminOrganisationRoute } from './create-admin-organisation';
import { createStripeCustomerRoute } from './create-stripe-customer';
import { createSubscriptionClaimRoute } from './create-subscription-claim';
import { deleteDocumentRoute } from './delete-document';
+import { deleteOrganisationRoute } from './delete-organisation';
import { deleteAdminOrganisationMemberRoute } from './delete-organisation-member';
import { deleteSubscriptionClaimRoute } from './delete-subscription-claim';
import { deleteAdminTeamMemberRoute } from './delete-team-member';
@@ -41,6 +42,7 @@ export const adminRouter = router({
get: getAdminOrganisationRoute,
create: createAdminOrganisationRoute,
update: updateAdminOrganisationRoute,
+ delete: deleteOrganisationRoute,
swapSubscription: swapOrganisationSubscriptionRoute,
},
organisationMember: {
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/trpc/server/organisation-router/delete-organisation.ts b/packages/trpc/server/organisation-router/delete-organisation.ts
index 4a048e9b3..11b051ba3 100644
--- a/packages/trpc/server/organisation-router/delete-organisation.ts
+++ b/packages/trpc/server/organisation-router/delete-organisation.ts
@@ -3,6 +3,7 @@ import {
ORGANISATION_USER_ACCOUNT_TYPE,
} from '@documenso/lib/constants/organisations';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
+import { jobs } from '@documenso/lib/jobs/client';
import { orphanEnvelopes } from '@documenso/lib/server-only/envelope/orphan-envelopes';
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
import { prisma } from '@documenso/prisma';
@@ -42,6 +43,11 @@ export const deleteOrganisationRoute = authenticatedProcedure
id: true,
},
},
+ subscription: {
+ select: {
+ planId: true,
+ },
+ },
},
});
@@ -68,4 +74,18 @@ export const deleteOrganisationRoute = authenticatedProcedure
},
});
});
+
+ // If the organisation has a Stripe subscription, schedule it to be
+ // cancelled at the end of the current billing period. The job runs
+ // asynchronously so a Stripe outage doesn't block deletion, and is
+ // retried by the job runner if Stripe is temporarily unavailable.
+ if (organisation.subscription) {
+ await jobs.triggerJob({
+ name: 'internal.cancel-organisation-subscription',
+ payload: {
+ stripeSubscriptionId: organisation.subscription.planId,
+ organisationId: organisation.id,
+ },
+ });
+ }
});
diff --git a/packages/trpc/server/template-router/router.ts b/packages/trpc/server/template-router/router.ts
index e33b09ef3..2ee6c2586 100644
--- a/packages/trpc/server/template-router/router.ts
+++ b/packages/trpc/server/template-router/router.ts
@@ -3,6 +3,7 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { jobs } from '@documenso/lib/jobs/client';
import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
+import { convertToPdf } from '@documenso/lib/server-only/document-conversion';
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
import { duplicateEnvelope } from '@documenso/lib/server-only/envelope/duplicate-envelope';
@@ -269,9 +270,18 @@ export const templateRouter = router({
attachments,
} = payload;
- const { id: templateDocumentDataId } = await putNormalizedPdfFileServerSide(file, {
- flattenForm: false,
- });
+ const pdf = await convertToPdf(file, ctx.logger);
+
+ const { id: templateDocumentDataId } = await putNormalizedPdfFileServerSide(
+ {
+ name: file.name,
+ type: 'application/pdf',
+ arrayBuffer: async () => Promise.resolve(pdf),
+ },
+ {
+ flattenForm: false,
+ },
+ );
ctx.logger.info({
input: {
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,