mirror of
https://github.com/documenso/documenso.git
synced 2025-11-14 16:51:38 +10:00
feat: extend use envelope response (#2192)
This commit is contained in:
@ -28,9 +28,13 @@ import {
|
|||||||
seedPendingDocument,
|
seedPendingDocument,
|
||||||
} from '@documenso/prisma/seed/documents';
|
} from '@documenso/prisma/seed/documents';
|
||||||
import { seedBlankFolder } from '@documenso/prisma/seed/folders';
|
import { seedBlankFolder } from '@documenso/prisma/seed/folders';
|
||||||
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
|
import { seedBlankTemplate, seedTemplate } from '@documenso/prisma/seed/templates';
|
||||||
import { seedUser } from '@documenso/prisma/seed/users';
|
import { seedUser } from '@documenso/prisma/seed/users';
|
||||||
import type { TCreateEnvelopeItemsPayload } from '@documenso/trpc/server/envelope-router/create-envelope-items.types';
|
import type { TCreateEnvelopeItemsPayload } from '@documenso/trpc/server/envelope-router/create-envelope-items.types';
|
||||||
|
import type {
|
||||||
|
TUseEnvelopePayload,
|
||||||
|
TUseEnvelopeResponse,
|
||||||
|
} from '@documenso/trpc/server/envelope-router/use-envelope.types';
|
||||||
|
|
||||||
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
|
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
|
||||||
|
|
||||||
@ -3074,6 +3078,82 @@ test.describe('Document API V2', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe('Envelope use endpoint', () => {
|
||||||
|
test('should block unauthorized access to envelope use endpoint', async ({ request }) => {
|
||||||
|
const doc = await seedTemplate({
|
||||||
|
title: 'Team template 1',
|
||||||
|
userId: userA.id,
|
||||||
|
teamId: teamA.id,
|
||||||
|
internalVersion: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
const payload: TUseEnvelopePayload = {
|
||||||
|
envelopeId: doc.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('payload', JSON.stringify(payload));
|
||||||
|
|
||||||
|
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/use`, {
|
||||||
|
headers: { Authorization: `Bearer ${tokenB}` },
|
||||||
|
multipart: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.ok()).toBeFalsy();
|
||||||
|
expect(res.status()).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should allow authorized access to envelope use endpoint', async ({ page, request }) => {
|
||||||
|
const doc = await seedTemplate({
|
||||||
|
title: 'Team template 1',
|
||||||
|
userId: userA.id,
|
||||||
|
teamId: teamA.id,
|
||||||
|
internalVersion: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
const payload: TUseEnvelopePayload = {
|
||||||
|
envelopeId: doc.id,
|
||||||
|
distributeDocument: true,
|
||||||
|
recipients: [
|
||||||
|
{
|
||||||
|
id: doc.recipients[0].id,
|
||||||
|
email: doc.recipients[0].email,
|
||||||
|
name: 'New Name',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('payload', JSON.stringify(payload));
|
||||||
|
|
||||||
|
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/use`, {
|
||||||
|
headers: { Authorization: `Bearer ${tokenA}` },
|
||||||
|
multipart: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.ok()).toBeTruthy();
|
||||||
|
expect(res.status()).toBe(200);
|
||||||
|
|
||||||
|
const data: TUseEnvelopeResponse = await res.json();
|
||||||
|
|
||||||
|
const createdEnvelope = await prisma.envelope.findFirst({
|
||||||
|
where: {
|
||||||
|
id: data.id,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
recipients: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(createdEnvelope).toBeDefined();
|
||||||
|
expect(createdEnvelope?.recipients.length).toBe(1);
|
||||||
|
expect(createdEnvelope?.recipients[0].email).toBe(doc.recipients[0].email);
|
||||||
|
expect(createdEnvelope?.recipients[0].name).toBe('New Name');
|
||||||
|
expect(createdEnvelope?.recipients[0].token).toBe(data.recipients[0].token);
|
||||||
|
expect(createdEnvelope?.recipients[0].token).not.toBe(doc.recipients[0].token);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test.describe('Envelope distribute endpoint', () => {
|
test.describe('Envelope distribute endpoint', () => {
|
||||||
test('should block unauthorized access to envelope distribute endpoint', async ({
|
test('should block unauthorized access to envelope distribute endpoint', async ({
|
||||||
request,
|
request,
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export const resendDocument = async ({
|
|||||||
recipients,
|
recipients,
|
||||||
teamId,
|
teamId,
|
||||||
requestMetadata,
|
requestMetadata,
|
||||||
}: ResendDocumentOptions): Promise<void> => {
|
}: ResendDocumentOptions) => {
|
||||||
const user = await prisma.user.findFirstOrThrow({
|
const user = await prisma.user.findFirstOrThrow({
|
||||||
where: {
|
where: {
|
||||||
id: userId,
|
id: userId,
|
||||||
@ -103,7 +103,7 @@ export const resendDocument = async ({
|
|||||||
).recipientSigningRequest;
|
).recipientSigningRequest;
|
||||||
|
|
||||||
if (!isRecipientSigningRequestEmailEnabled) {
|
if (!isRecipientSigningRequestEmailEnabled) {
|
||||||
return;
|
return envelope;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { branding, emailLanguage, organisationType, senderEmail, replyToEmail } =
|
const { branding, emailLanguage, organisationType, senderEmail, replyToEmail } =
|
||||||
@ -230,4 +230,6 @@ export const resendDocument = async ({
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return envelope;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -110,7 +110,7 @@ export const seedTemplate = async (options: SeedTemplateOptions) => {
|
|||||||
data: {
|
data: {
|
||||||
id: prefixedId('envelope'),
|
id: prefixedId('envelope'),
|
||||||
secondaryId: templateId.formattedTemplateId,
|
secondaryId: templateId.formattedTemplateId,
|
||||||
internalVersion: 1,
|
internalVersion: options.internalVersion ?? 1,
|
||||||
type: EnvelopeType.TEMPLATE,
|
type: EnvelopeType.TEMPLATE,
|
||||||
title,
|
title,
|
||||||
envelopeItems: {
|
envelopeItems: {
|
||||||
@ -143,6 +143,7 @@ export const seedTemplate = async (options: SeedTemplateOptions) => {
|
|||||||
documentData: true,
|
documentData: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
recipients: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { updateDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
import { updateDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
||||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||||
|
import { formatSigningLink } from '@documenso/lib/utils/recipients';
|
||||||
|
|
||||||
import { ZGenericSuccessResponse } from '../schema';
|
|
||||||
import { authenticatedProcedure } from '../trpc';
|
import { authenticatedProcedure } from '../trpc';
|
||||||
import {
|
import {
|
||||||
ZDistributeEnvelopeRequestSchema,
|
ZDistributeEnvelopeRequestSchema,
|
||||||
@ -45,7 +45,7 @@ export const distributeEnvelopeRoute = authenticatedProcedure
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await sendDocument({
|
const envelope = await sendDocument({
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
id: {
|
id: {
|
||||||
type: 'envelopeId',
|
type: 'envelopeId',
|
||||||
@ -55,5 +55,17 @@ export const distributeEnvelopeRoute = authenticatedProcedure
|
|||||||
requestMetadata: ctx.metadata,
|
requestMetadata: ctx.metadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
return ZGenericSuccessResponse;
|
return {
|
||||||
|
success: true,
|
||||||
|
id: envelope.id,
|
||||||
|
recipients: envelope.recipients.map((recipient) => ({
|
||||||
|
id: recipient.id,
|
||||||
|
name: recipient.name,
|
||||||
|
email: recipient.email,
|
||||||
|
token: recipient.token,
|
||||||
|
role: recipient.role,
|
||||||
|
signingOrder: recipient.signingOrder,
|
||||||
|
signingUrl: formatSigningLink(recipient.token),
|
||||||
|
})),
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { ZDocumentMetaUpdateSchema } from '@documenso/lib/types/document-meta';
|
|||||||
|
|
||||||
import { ZSuccessResponseSchema } from '../schema';
|
import { ZSuccessResponseSchema } from '../schema';
|
||||||
import type { TrpcRouteMeta } from '../trpc';
|
import type { TrpcRouteMeta } from '../trpc';
|
||||||
|
import { ZRecipientWithSigningUrlSchema } from './schema';
|
||||||
|
|
||||||
export const distributeEnvelopeMeta: TrpcRouteMeta = {
|
export const distributeEnvelopeMeta: TrpcRouteMeta = {
|
||||||
openapi: {
|
openapi: {
|
||||||
@ -31,7 +32,10 @@ export const ZDistributeEnvelopeRequestSchema = z.object({
|
|||||||
}).optional(),
|
}).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZDistributeEnvelopeResponseSchema = ZSuccessResponseSchema;
|
export const ZDistributeEnvelopeResponseSchema = ZSuccessResponseSchema.extend({
|
||||||
|
id: z.string().describe('The ID of the envelope that was sent.'),
|
||||||
|
recipients: ZRecipientWithSigningUrlSchema.array(),
|
||||||
|
});
|
||||||
|
|
||||||
export type TDistributeEnvelopeRequest = z.infer<typeof ZDistributeEnvelopeRequestSchema>;
|
export type TDistributeEnvelopeRequest = z.infer<typeof ZDistributeEnvelopeRequestSchema>;
|
||||||
export type TDistributeEnvelopeResponse = z.infer<typeof ZDistributeEnvelopeResponseSchema>;
|
export type TDistributeEnvelopeResponse = z.infer<typeof ZDistributeEnvelopeResponseSchema>;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
||||||
|
import { formatSigningLink } from '@documenso/lib/utils/recipients';
|
||||||
|
|
||||||
import { ZGenericSuccessResponse } from '../schema';
|
|
||||||
import { authenticatedProcedure } from '../trpc';
|
import { authenticatedProcedure } from '../trpc';
|
||||||
import {
|
import {
|
||||||
ZRedistributeEnvelopeRequestSchema,
|
ZRedistributeEnvelopeRequestSchema,
|
||||||
@ -23,7 +23,7 @@ export const redistributeEnvelopeRoute = authenticatedProcedure
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await resendDocument({
|
const envelope = await resendDocument({
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
teamId,
|
teamId,
|
||||||
id: {
|
id: {
|
||||||
@ -34,5 +34,17 @@ export const redistributeEnvelopeRoute = authenticatedProcedure
|
|||||||
requestMetadata: ctx.metadata,
|
requestMetadata: ctx.metadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
return ZGenericSuccessResponse;
|
return {
|
||||||
|
success: true,
|
||||||
|
id: envelope.id,
|
||||||
|
recipients: envelope.recipients.map((recipient) => ({
|
||||||
|
id: recipient.id,
|
||||||
|
name: recipient.name,
|
||||||
|
email: recipient.email,
|
||||||
|
token: recipient.token,
|
||||||
|
role: recipient.role,
|
||||||
|
signingOrder: recipient.signingOrder,
|
||||||
|
signingUrl: formatSigningLink(recipient.token),
|
||||||
|
})),
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
import { ZSuccessResponseSchema } from '../schema';
|
import { ZSuccessResponseSchema } from '../schema';
|
||||||
import type { TrpcRouteMeta } from '../trpc';
|
import type { TrpcRouteMeta } from '../trpc';
|
||||||
|
import { ZRecipientWithSigningUrlSchema } from './schema';
|
||||||
|
|
||||||
export const redistributeEnvelopeMeta: TrpcRouteMeta = {
|
export const redistributeEnvelopeMeta: TrpcRouteMeta = {
|
||||||
openapi: {
|
openapi: {
|
||||||
@ -22,7 +23,10 @@ export const ZRedistributeEnvelopeRequestSchema = z.object({
|
|||||||
.describe('The IDs of the recipients to redistribute the envelope to.'),
|
.describe('The IDs of the recipients to redistribute the envelope to.'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZRedistributeEnvelopeResponseSchema = ZSuccessResponseSchema;
|
export const ZRedistributeEnvelopeResponseSchema = ZSuccessResponseSchema.extend({
|
||||||
|
id: z.string().describe('The ID of the envelope that was redistributed.'),
|
||||||
|
recipients: ZRecipientWithSigningUrlSchema.array(),
|
||||||
|
});
|
||||||
|
|
||||||
export type TRedistributeEnvelopeRequest = z.infer<typeof ZRedistributeEnvelopeRequestSchema>;
|
export type TRedistributeEnvelopeRequest = z.infer<typeof ZRedistributeEnvelopeRequestSchema>;
|
||||||
export type TRedistributeEnvelopeResponse = z.infer<typeof ZRedistributeEnvelopeResponseSchema>;
|
export type TRedistributeEnvelopeResponse = z.infer<typeof ZRedistributeEnvelopeResponseSchema>;
|
||||||
|
|||||||
16
packages/trpc/server/envelope-router/schema.ts
Normal file
16
packages/trpc/server/envelope-router/schema.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import RecipientSchema from '@documenso/prisma/generated/zod/modelSchema/RecipientSchema';
|
||||||
|
|
||||||
|
// Common schemas between envelope routes.
|
||||||
|
|
||||||
|
export const ZRecipientWithSigningUrlSchema = RecipientSchema.pick({
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
email: true,
|
||||||
|
token: true,
|
||||||
|
role: true,
|
||||||
|
signingOrder: true,
|
||||||
|
}).extend({
|
||||||
|
signingUrl: z.string().describe('The URL which the recipient uses to sign the document.'),
|
||||||
|
});
|
||||||
@ -6,6 +6,7 @@ import { sendDocument } from '@documenso/lib/server-only/document/send-document'
|
|||||||
import { getEnvelopeById } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
import { getEnvelopeById } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
||||||
import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template';
|
import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template';
|
||||||
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||||
|
import { formatSigningLink } from '@documenso/lib/utils/recipients';
|
||||||
|
|
||||||
import { authenticatedProcedure } from '../trpc';
|
import { authenticatedProcedure } from '../trpc';
|
||||||
import {
|
import {
|
||||||
@ -26,7 +27,7 @@ export const useEnvelopeRoute = authenticatedProcedure
|
|||||||
const {
|
const {
|
||||||
envelopeId,
|
envelopeId,
|
||||||
externalId,
|
externalId,
|
||||||
recipients,
|
recipients = [],
|
||||||
distributeDocument,
|
distributeDocument,
|
||||||
customDocumentData = [],
|
customDocumentData = [],
|
||||||
folderId,
|
folderId,
|
||||||
@ -166,5 +167,14 @@ export const useEnvelopeRoute = authenticatedProcedure
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
id: createdEnvelope.id,
|
id: createdEnvelope.id,
|
||||||
|
recipients: createdEnvelope.recipients.map((recipient) => ({
|
||||||
|
id: recipient.id,
|
||||||
|
name: recipient.name,
|
||||||
|
email: recipient.email,
|
||||||
|
token: recipient.token,
|
||||||
|
role: recipient.role,
|
||||||
|
signingOrder: recipient.signingOrder,
|
||||||
|
signingUrl: formatSigningLink(recipient.token),
|
||||||
|
})),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import { ZFieldMetaPrefillFieldsSchema } from '@documenso/lib/types/field-meta';
|
|||||||
|
|
||||||
import { zodFormData } from '../../utils/zod-form-data';
|
import { zodFormData } from '../../utils/zod-form-data';
|
||||||
import type { TrpcRouteMeta } from '../trpc';
|
import type { TrpcRouteMeta } from '../trpc';
|
||||||
|
import { ZRecipientWithSigningUrlSchema } from './schema';
|
||||||
|
|
||||||
export const useEnvelopeMeta: TrpcRouteMeta = {
|
export const useEnvelopeMeta: TrpcRouteMeta = {
|
||||||
openapi: {
|
openapi: {
|
||||||
@ -44,7 +45,8 @@ export const ZUseEnvelopePayloadSchema = z.object({
|
|||||||
signingOrder: z.number().optional(),
|
signingOrder: z.number().optional(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.describe('The information of the recipients to create the document with.'),
|
.describe('The information of the recipients to create the document with.')
|
||||||
|
.optional(),
|
||||||
distributeDocument: z
|
distributeDocument: z
|
||||||
.boolean()
|
.boolean()
|
||||||
.describe('Whether to create the document as pending and distribute it to recipients.')
|
.describe('Whether to create the document as pending and distribute it to recipients.')
|
||||||
@ -114,6 +116,7 @@ export const ZUseEnvelopeRequestSchema = zodFormData({
|
|||||||
|
|
||||||
export const ZUseEnvelopeResponseSchema = z.object({
|
export const ZUseEnvelopeResponseSchema = z.object({
|
||||||
id: z.string().describe('The ID of the created envelope.'),
|
id: z.string().describe('The ID of the created envelope.'),
|
||||||
|
recipients: ZRecipientWithSigningUrlSchema.array(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TUseEnvelopePayload = z.infer<typeof ZUseEnvelopePayloadSchema>;
|
export type TUseEnvelopePayload = z.infer<typeof ZUseEnvelopePayloadSchema>;
|
||||||
|
|||||||
Reference in New Issue
Block a user