mirror of
https://github.com/documenso/documenso.git
synced 2025-11-14 00:32:43 +10:00
fix: prefill arcoforms with formdata endpoints (#2169)
This commit is contained in:
@ -76,8 +76,10 @@ export const DocumentUploadButtonLegacy = ({ className }: DocumentUploadButtonLe
|
|||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
title: file.name,
|
title: file.name,
|
||||||
timezone: userTimezone,
|
|
||||||
folderId: folderId ?? undefined,
|
folderId: folderId ?? undefined,
|
||||||
|
meta: {
|
||||||
|
timezone: userTimezone,
|
||||||
|
},
|
||||||
} satisfies TCreateDocumentPayloadSchema;
|
} satisfies TCreateDocumentPayloadSchema;
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { seedTeamMember } from '@documenso/prisma/seed/teams';
|
|||||||
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
|
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
|
||||||
|
|
||||||
import { apiSignin } from '../fixtures/authentication';
|
import { apiSignin } from '../fixtures/authentication';
|
||||||
|
import { expectTextToBeVisible } from '../fixtures/generic';
|
||||||
|
|
||||||
test.describe.configure({ mode: 'parallel' });
|
test.describe.configure({ mode: 'parallel' });
|
||||||
|
|
||||||
@ -81,20 +82,23 @@ test('[TEAMS]: can create a document inside a document folder', async ({ page })
|
|||||||
redirectPath: `/t/${team.url}/documents/f/${teamFolder.id}`,
|
redirectPath: `/t/${team.url}/documents/f/${teamFolder.id}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const fileInput = page.locator('input[type="file"]').nth(2);
|
// Upload document.
|
||||||
await fileInput.waitFor({ state: 'attached' });
|
const [fileChooser] = await Promise.all([
|
||||||
|
page.waitForEvent('filechooser'),
|
||||||
|
page.getByRole('button', { name: 'Document (Legacy)' }).click(),
|
||||||
|
]);
|
||||||
|
|
||||||
await fileInput.setInputFiles(
|
await fileChooser.setFiles(
|
||||||
path.join(__dirname, '../../../assets/documenso-supporter-pledge.pdf'),
|
path.join(__dirname, '../../../assets/documenso-supporter-pledge.pdf'),
|
||||||
);
|
);
|
||||||
|
|
||||||
await page.waitForTimeout(3000);
|
await page.waitForTimeout(3000);
|
||||||
|
|
||||||
await expect(page.getByText('documenso-supporter-pledge.pdf')).toBeVisible();
|
await expectTextToBeVisible(page, 'documenso-supporter-pledge.pdf');
|
||||||
|
|
||||||
await page.goto(`/t/${team.url}/documents/f/${teamFolder.id}`);
|
await page.goto(`/t/${team.url}/documents/f/${teamFolder.id}`);
|
||||||
|
|
||||||
await expect(page.getByText('documenso-supporter-pledge.pdf')).toBeVisible();
|
await expectTextToBeVisible(page, 'documenso-supporter-pledge.pdf');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('[TEAMS]: can pin a document folder', async ({ page }) => {
|
test('[TEAMS]: can pin a document folder', async ({ page }) => {
|
||||||
@ -382,11 +386,11 @@ test('[TEAMS]: can create a template inside a template folder', async ({ page })
|
|||||||
await page.waitForTimeout(3000);
|
await page.waitForTimeout(3000);
|
||||||
|
|
||||||
// Expect redirect.
|
// Expect redirect.
|
||||||
await expect(page.getByText('documenso-supporter-pledge.pdf')).toBeVisible();
|
await expectTextToBeVisible(page, 'documenso-supporter-pledge.pdf');
|
||||||
|
|
||||||
// Return to folder and verify file is visible.
|
// Return to folder and verify file is visible.
|
||||||
await page.goto(`/t/${team.url}/templates/f/${folder.id}`);
|
await page.goto(`/t/${team.url}/templates/f/${folder.id}`);
|
||||||
await expect(page.getByText('documenso-supporter-pledge.pdf')).toBeVisible();
|
await expectTextToBeVisible(page, 'documenso-supporter-pledge.pdf');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('[TEAMS]: can pin a template folder', async ({ page }) => {
|
test('[TEAMS]: can pin a template folder', async ({ page }) => {
|
||||||
@ -851,7 +855,7 @@ test('[TEAMS]: documents inherit folder visibility', async ({ page }) => {
|
|||||||
|
|
||||||
await page.waitForTimeout(3000);
|
await page.waitForTimeout(3000);
|
||||||
|
|
||||||
await expect(page.getByText('documenso-supporter-pledge.pdf')).toBeVisible();
|
await expectTextToBeVisible(page, 'documenso-supporter-pledge.pdf');
|
||||||
|
|
||||||
await expect(page.getByRole('combobox').filter({ hasText: 'Admins only' })).toBeVisible();
|
await expect(page.getByRole('combobox').filter({ hasText: 'Admins only' })).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { EnvelopeType } from '@prisma/client';
|
|||||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
|
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';
|
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||||
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
|
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
|
||||||
|
|
||||||
@ -22,9 +23,34 @@ export const createDocumentRoute = authenticatedProcedure
|
|||||||
|
|
||||||
const { payload, file } = input;
|
const { payload, file } = input;
|
||||||
|
|
||||||
const { title, timezone, folderId, attachments } = payload;
|
const {
|
||||||
|
title,
|
||||||
|
externalId,
|
||||||
|
visibility,
|
||||||
|
globalAccessAuth,
|
||||||
|
globalActionAuth,
|
||||||
|
recipients,
|
||||||
|
meta,
|
||||||
|
folderId,
|
||||||
|
formValues,
|
||||||
|
attachments,
|
||||||
|
} = payload;
|
||||||
|
|
||||||
const { id: documentDataId } = await putNormalizedPdfFileServerSide(file);
|
let pdf = Buffer.from(await file.arrayBuffer());
|
||||||
|
|
||||||
|
if (formValues) {
|
||||||
|
// eslint-disable-next-line require-atomic-updates
|
||||||
|
pdf = await insertFormValuesInPdf({
|
||||||
|
pdf,
|
||||||
|
formValues,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id: documentDataId } = await putNormalizedPdfFileServerSide({
|
||||||
|
name: file.name,
|
||||||
|
type: 'application/pdf',
|
||||||
|
arrayBuffer: async () => Promise.resolve(pdf),
|
||||||
|
});
|
||||||
|
|
||||||
ctx.logger.info({
|
ctx.logger.info({
|
||||||
input: {
|
input: {
|
||||||
@ -48,7 +74,20 @@ export const createDocumentRoute = authenticatedProcedure
|
|||||||
data: {
|
data: {
|
||||||
type: EnvelopeType.DOCUMENT,
|
type: EnvelopeType.DOCUMENT,
|
||||||
title,
|
title,
|
||||||
userTimezone: timezone,
|
externalId,
|
||||||
|
visibility,
|
||||||
|
globalAccessAuth,
|
||||||
|
globalActionAuth,
|
||||||
|
recipients: (recipients || []).map((recipient) => ({
|
||||||
|
...recipient,
|
||||||
|
fields: (recipient.fields || []).map((field) => ({
|
||||||
|
...field,
|
||||||
|
page: field.pageNumber,
|
||||||
|
positionX: field.pageX,
|
||||||
|
positionY: field.pageY,
|
||||||
|
documentDataId,
|
||||||
|
})),
|
||||||
|
})),
|
||||||
folderId,
|
folderId,
|
||||||
envelopeItems: [
|
envelopeItems: [
|
||||||
{
|
{
|
||||||
@ -58,7 +97,10 @@ export const createDocumentRoute = authenticatedProcedure
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
attachments,
|
attachments,
|
||||||
normalizePdf: true,
|
meta: {
|
||||||
|
...meta,
|
||||||
|
emailSettings: meta?.emailSettings ?? undefined,
|
||||||
|
},
|
||||||
requestMetadata: ctx.metadata,
|
requestMetadata: ctx.metadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,27 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { zfd } from 'zod-form-data';
|
import { zfd } from 'zod-form-data';
|
||||||
|
|
||||||
import { ZDocumentMetaTimezoneSchema } from '@documenso/lib/types/document-meta';
|
import {
|
||||||
|
ZDocumentAccessAuthTypesSchema,
|
||||||
|
ZDocumentActionAuthTypesSchema,
|
||||||
|
} from '@documenso/lib/types/document-auth';
|
||||||
|
import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';
|
||||||
|
import { ZDocumentMetaCreateSchema } from '@documenso/lib/types/document-meta';
|
||||||
|
import { ZDocumentVisibilitySchema } from '@documenso/lib/types/document-visibility';
|
||||||
import { ZEnvelopeAttachmentTypeSchema } from '@documenso/lib/types/envelope-attachment';
|
import { ZEnvelopeAttachmentTypeSchema } from '@documenso/lib/types/envelope-attachment';
|
||||||
|
import {
|
||||||
|
ZFieldHeightSchema,
|
||||||
|
ZFieldPageNumberSchema,
|
||||||
|
ZFieldPageXSchema,
|
||||||
|
ZFieldPageYSchema,
|
||||||
|
ZFieldWidthSchema,
|
||||||
|
} from '@documenso/lib/types/field';
|
||||||
|
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||||
|
|
||||||
import { zodFormData } from '../../utils/zod-form-data';
|
import { zodFormData } from '../../utils/zod-form-data';
|
||||||
|
import { ZCreateRecipientSchema } from '../recipient-router/schema';
|
||||||
import type { TrpcRouteMeta } from '../trpc';
|
import type { TrpcRouteMeta } from '../trpc';
|
||||||
import { ZDocumentTitleSchema } from './schema';
|
import { ZDocumentExternalIdSchema, ZDocumentTitleSchema } from './schema';
|
||||||
|
|
||||||
export const createDocumentMeta: TrpcRouteMeta = {
|
export const createDocumentMeta: TrpcRouteMeta = {
|
||||||
openapi: {
|
openapi: {
|
||||||
@ -21,8 +36,35 @@ export const createDocumentMeta: TrpcRouteMeta = {
|
|||||||
|
|
||||||
export const ZCreateDocumentPayloadSchema = z.object({
|
export const ZCreateDocumentPayloadSchema = z.object({
|
||||||
title: ZDocumentTitleSchema,
|
title: ZDocumentTitleSchema,
|
||||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
externalId: ZDocumentExternalIdSchema.optional(),
|
||||||
folderId: z.string().describe('The ID of the folder to create the document in').optional(),
|
visibility: ZDocumentVisibilitySchema.optional(),
|
||||||
|
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional(),
|
||||||
|
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional(),
|
||||||
|
formValues: ZDocumentFormValuesSchema.optional(),
|
||||||
|
folderId: z
|
||||||
|
.string()
|
||||||
|
.describe(
|
||||||
|
'The ID of the folder to create the document in. If not provided, the document will be created in the root folder.',
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
recipients: z
|
||||||
|
.array(
|
||||||
|
ZCreateRecipientSchema.extend({
|
||||||
|
fields: ZFieldAndMetaSchema.and(
|
||||||
|
z.object({
|
||||||
|
pageNumber: ZFieldPageNumberSchema,
|
||||||
|
pageX: ZFieldPageXSchema,
|
||||||
|
pageY: ZFieldPageYSchema,
|
||||||
|
width: ZFieldWidthSchema,
|
||||||
|
height: ZFieldHeightSchema,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.array()
|
||||||
|
.optional(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
.optional(),
|
||||||
attachments: z
|
attachments: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
@ -32,6 +74,7 @@ export const ZCreateDocumentPayloadSchema = z.object({
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
|
meta: ZDocumentMetaCreateSchema.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZCreateDocumentRequestSchema = zodFormData({
|
export const ZCreateDocumentRequestSchema = zodFormData({
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
|||||||
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
|
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
|
||||||
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||||
|
|
||||||
|
import { insertFormValuesInPdf } from '../../../lib/server-only/pdf/insert-form-values-in-pdf';
|
||||||
import { authenticatedProcedure } from '../trpc';
|
import { authenticatedProcedure } from '../trpc';
|
||||||
import {
|
import {
|
||||||
ZCreateEnvelopeRequestSchema,
|
ZCreateEnvelopeRequestSchema,
|
||||||
@ -58,10 +59,31 @@ export const createEnvelopeRoute = authenticatedProcedure
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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, stream to s3 and create the document data.
|
// For each file, stream to s3 and create the document data.
|
||||||
const envelopeItems = await Promise.all(
|
const envelopeItems = await Promise.all(
|
||||||
files.map(async (file) => {
|
files.map(async (file) => {
|
||||||
const { id: documentDataId } = await putNormalizedPdfFileServerSide(file);
|
let pdf = Buffer.from(await file.arrayBuffer());
|
||||||
|
|
||||||
|
if (formValues) {
|
||||||
|
// eslint-disable-next-line require-atomic-updates
|
||||||
|
pdf = await insertFormValuesInPdf({
|
||||||
|
pdf,
|
||||||
|
formValues,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id: documentDataId } = await putNormalizedPdfFileServerSide({
|
||||||
|
name: file.name,
|
||||||
|
type: 'application/pdf',
|
||||||
|
arrayBuffer: async () => Promise.resolve(pdf),
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: file.name,
|
title: file.name,
|
||||||
|
|||||||
Reference in New Issue
Block a user