mirror of
https://github.com/documenso/documenso.git
synced 2026-07-26 01:45:08 +10:00
feat: add envelopes api (#2105)
This commit is contained in:
@@ -4,18 +4,11 @@ import { authenticatedProcedure } from '../../trpc';
|
||||
import {
|
||||
ZCreateAttachmentRequestSchema,
|
||||
ZCreateAttachmentResponseSchema,
|
||||
createAttachmentMeta,
|
||||
} from './create-attachment.types';
|
||||
|
||||
export const createAttachmentRoute = authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/attachment/create',
|
||||
summary: 'Create attachment',
|
||||
description: 'Create a new attachment for an envelope',
|
||||
tags: ['Envelope'],
|
||||
},
|
||||
})
|
||||
.meta(createAttachmentMeta)
|
||||
.input(ZCreateAttachmentRequestSchema)
|
||||
.output(ZCreateAttachmentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TrpcRouteMeta } from '../../trpc';
|
||||
|
||||
export const createAttachmentMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/attachment/create',
|
||||
summary: 'Create attachment',
|
||||
description: 'Create a new attachment for an envelope',
|
||||
tags: ['Envelope Attachments'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZCreateAttachmentRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
data: z.object({
|
||||
|
||||
@@ -4,18 +4,11 @@ import { authenticatedProcedure } from '../../trpc';
|
||||
import {
|
||||
ZDeleteAttachmentRequestSchema,
|
||||
ZDeleteAttachmentResponseSchema,
|
||||
deleteAttachmentMeta,
|
||||
} from './delete-attachment.types';
|
||||
|
||||
export const deleteAttachmentRoute = authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/attachment/delete',
|
||||
summary: 'Delete attachment',
|
||||
description: 'Delete an attachment from an envelope',
|
||||
tags: ['Envelope'],
|
||||
},
|
||||
})
|
||||
.meta(deleteAttachmentMeta)
|
||||
.input(ZDeleteAttachmentRequestSchema)
|
||||
.output(ZDeleteAttachmentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TrpcRouteMeta } from '../../trpc';
|
||||
|
||||
export const deleteAttachmentMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/attachment/delete',
|
||||
summary: 'Delete attachment',
|
||||
description: 'Delete an attachment from an envelope',
|
||||
tags: ['Envelope Attachments'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZDeleteAttachmentRequestSchema = z.object({
|
||||
id: z.string(),
|
||||
});
|
||||
|
||||
@@ -2,22 +2,15 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { findAttachmentsByEnvelopeId } from '@documenso/lib/server-only/envelope-attachment/find-attachments-by-envelope-id';
|
||||
import { findAttachmentsByToken } from '@documenso/lib/server-only/envelope-attachment/find-attachments-by-token';
|
||||
|
||||
import { procedure } from '../../trpc';
|
||||
import { maybeAuthenticatedProcedure } from '../../trpc';
|
||||
import {
|
||||
ZFindAttachmentsRequestSchema,
|
||||
ZFindAttachmentsResponseSchema,
|
||||
findAttachmentsMeta,
|
||||
} from './find-attachments.types';
|
||||
|
||||
export const findAttachmentsRoute = procedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/envelope/attachment',
|
||||
summary: 'Find attachments',
|
||||
description: 'Find all attachments for an envelope',
|
||||
tags: ['Envelope'],
|
||||
},
|
||||
})
|
||||
export const findAttachmentsRoute = maybeAuthenticatedProcedure
|
||||
.meta(findAttachmentsMeta)
|
||||
.input(ZFindAttachmentsRequestSchema)
|
||||
.output(ZFindAttachmentsResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
|
||||
@@ -2,6 +2,18 @@ import { z } from 'zod';
|
||||
|
||||
import { ZEnvelopeAttachmentTypeSchema } from '@documenso/lib/types/envelope-attachment';
|
||||
|
||||
import type { TrpcRouteMeta } from '../../trpc';
|
||||
|
||||
export const findAttachmentsMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/envelope/attachment',
|
||||
summary: 'Find attachments',
|
||||
description: 'Find all attachments for an envelope',
|
||||
tags: ['Envelope Attachments'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZFindAttachmentsRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
token: z.string().optional(),
|
||||
|
||||
@@ -4,18 +4,11 @@ import { authenticatedProcedure } from '../../trpc';
|
||||
import {
|
||||
ZUpdateAttachmentRequestSchema,
|
||||
ZUpdateAttachmentResponseSchema,
|
||||
updateAttachmentMeta,
|
||||
} from './update-attachment.types';
|
||||
|
||||
export const updateAttachmentRoute = authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/attachment/update',
|
||||
summary: 'Update attachment',
|
||||
description: 'Update an existing attachment',
|
||||
tags: ['Envelope'],
|
||||
},
|
||||
})
|
||||
.meta(updateAttachmentMeta)
|
||||
.input(ZUpdateAttachmentRequestSchema)
|
||||
.output(ZUpdateAttachmentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TrpcRouteMeta } from '../../trpc';
|
||||
|
||||
export const updateAttachmentMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/attachment/update',
|
||||
summary: 'Update attachment',
|
||||
description: 'Update an existing attachment',
|
||||
tags: ['Envelope Attachments'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZUpdateAttachmentRequestSchema = z.object({
|
||||
id: z.string(),
|
||||
data: z.object({
|
||||
|
||||
@@ -2,6 +2,7 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getEnvelopeWhereInput } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import { prefixedId } from '@documenso/lib/universal/id';
|
||||
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { canEnvelopeItemsBeModified } from '@documenso/lib/utils/envelope';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
@@ -10,14 +11,17 @@ import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZCreateEnvelopeItemsRequestSchema,
|
||||
ZCreateEnvelopeItemsResponseSchema,
|
||||
createEnvelopeItemsMeta,
|
||||
} from './create-envelope-items.types';
|
||||
|
||||
export const createEnvelopeItemsRoute = authenticatedProcedure
|
||||
.meta(createEnvelopeItemsMeta)
|
||||
.input(ZCreateEnvelopeItemsRequestSchema)
|
||||
.output(ZCreateEnvelopeItemsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { user, teamId, metadata } = ctx;
|
||||
const { envelopeId, items } = input;
|
||||
const { payload, files } = input;
|
||||
const { envelopeId } = payload;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
@@ -71,7 +75,7 @@ export const createEnvelopeItemsRoute = authenticatedProcedure
|
||||
const organisationClaim = envelope.team.organisation.organisationClaim;
|
||||
|
||||
const remainingEnvelopeItems =
|
||||
organisationClaim.envelopeItemCount - envelope.envelopeItems.length - items.length;
|
||||
organisationClaim.envelopeItemCount - envelope.envelopeItems.length - files.length;
|
||||
|
||||
if (remainingEnvelopeItems < 0) {
|
||||
throw new AppError('ENVELOPE_ITEM_LIMIT_EXCEEDED', {
|
||||
@@ -80,41 +84,24 @@ export const createEnvelopeItemsRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
const foundDocumentData = await prisma.documentData.findMany({
|
||||
where: {
|
||||
id: {
|
||||
in: items.map((item) => item.documentDataId),
|
||||
},
|
||||
},
|
||||
select: {
|
||||
envelopeItem: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
// For each file, stream to s3 and create the document data.
|
||||
const envelopeItems = await Promise.all(
|
||||
files.map(async (file) => {
|
||||
const { id: documentDataId } = await putNormalizedPdfFileServerSide(file);
|
||||
|
||||
// Check that all the document data was found.
|
||||
if (foundDocumentData.length !== items.length) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Document data not found',
|
||||
});
|
||||
}
|
||||
|
||||
// Check that it doesn't already have an envelope item.
|
||||
if (foundDocumentData.some((documentData) => documentData.envelopeItem?.id)) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Document data not found',
|
||||
});
|
||||
}
|
||||
return {
|
||||
title: file.name,
|
||||
documentDataId,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const currentHighestOrderValue =
|
||||
envelope.envelopeItems[envelope.envelopeItems.length - 1]?.order ?? 1;
|
||||
|
||||
const result = await prisma.$transaction(async (tx) => {
|
||||
const createdItems = await tx.envelopeItem.createManyAndReturn({
|
||||
data: items.map((item) => ({
|
||||
data: envelopeItems.map((item) => ({
|
||||
id: prefixedId('envelope_item'),
|
||||
envelopeId,
|
||||
title: item.title,
|
||||
@@ -148,6 +135,6 @@ export const createEnvelopeItemsRoute = authenticatedProcedure
|
||||
});
|
||||
|
||||
return {
|
||||
createdEnvelopeItems: result,
|
||||
data: result,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,38 +1,40 @@
|
||||
import { z } from 'zod';
|
||||
import { zfd } from 'zod-form-data';
|
||||
|
||||
import DocumentDataSchema from '@documenso/prisma/generated/zod/modelSchema/DocumentDataSchema';
|
||||
import EnvelopeItemSchema from '@documenso/prisma/generated/zod/modelSchema/EnvelopeItemSchema';
|
||||
|
||||
import { ZDocumentTitleSchema } from '../document-router/schema';
|
||||
import { zodFormData } from '../../utils/zod-form-data';
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const ZCreateEnvelopeItemsRequestSchema = z.object({
|
||||
export const createEnvelopeItemsMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/item/create-many',
|
||||
summary: 'Create envelope items',
|
||||
description: 'Create multiple envelope items for an envelope',
|
||||
tags: ['Envelope Items'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZCreateEnvelopeItemsPayloadSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
items: z
|
||||
.object({
|
||||
title: ZDocumentTitleSchema,
|
||||
documentDataId: z.string(),
|
||||
})
|
||||
.array(),
|
||||
// data: z.object() // Currently not used.
|
||||
});
|
||||
|
||||
export const ZCreateEnvelopeItemsRequestSchema = zodFormData({
|
||||
payload: zfd.json(ZCreateEnvelopeItemsPayloadSchema),
|
||||
files: zfd.repeatableOfType(zfd.file()),
|
||||
});
|
||||
|
||||
export const ZCreateEnvelopeItemsResponseSchema = z.object({
|
||||
createdEnvelopeItems: EnvelopeItemSchema.pick({
|
||||
data: EnvelopeItemSchema.pick({
|
||||
id: true,
|
||||
title: true,
|
||||
documentDataId: true,
|
||||
envelopeId: true,
|
||||
order: true,
|
||||
})
|
||||
.extend({
|
||||
documentData: DocumentDataSchema.pick({
|
||||
type: true,
|
||||
id: true,
|
||||
data: true,
|
||||
initialData: true,
|
||||
}),
|
||||
})
|
||||
.array(),
|
||||
}).array(),
|
||||
});
|
||||
|
||||
export type TCreateEnvelopeItemsPayload = z.infer<typeof ZCreateEnvelopeItemsPayloadSchema>;
|
||||
export type TCreateEnvelopeItemsRequest = z.infer<typeof ZCreateEnvelopeItemsRequestSchema>;
|
||||
export type TCreateEnvelopeItemsResponse = z.infer<typeof ZCreateEnvelopeItemsResponseSchema>;
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
|
||||
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZCreateEnvelopeRequestSchema,
|
||||
ZCreateEnvelopeResponseSchema,
|
||||
createEnvelopeMeta,
|
||||
} from './create-envelope.types';
|
||||
|
||||
export const createEnvelopeRoute = authenticatedProcedure
|
||||
.meta(createEnvelopeMeta)
|
||||
.input(ZCreateEnvelopeRequestSchema)
|
||||
.output(ZCreateEnvelopeResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
|
||||
const { payload, files } = input;
|
||||
|
||||
const {
|
||||
title,
|
||||
type,
|
||||
@@ -20,12 +26,12 @@ export const createEnvelopeRoute = authenticatedProcedure
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
formValues,
|
||||
recipients,
|
||||
folderId,
|
||||
items,
|
||||
meta,
|
||||
attachments,
|
||||
} = input;
|
||||
} = payload;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
@@ -45,13 +51,62 @@ export const createEnvelopeRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
if (items.length > maximumEnvelopeItemCount) {
|
||||
if (files.length > maximumEnvelopeItemCount) {
|
||||
throw new AppError('ENVELOPE_ITEM_LIMIT_EXCEEDED', {
|
||||
message: `You cannot upload more than ${maximumEnvelopeItemCount} envelope items per envelope`,
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
// For each file, stream to s3 and create the document data.
|
||||
const envelopeItems = await Promise.all(
|
||||
files.map(async (file) => {
|
||||
const { id: documentDataId } = await putNormalizedPdfFileServerSide(file);
|
||||
|
||||
return {
|
||||
title: file.name,
|
||||
documentDataId,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const recipientsToCreate = recipients?.map((recipient) => ({
|
||||
email: recipient.email,
|
||||
name: recipient.name,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
accessAuth: recipient.accessAuth,
|
||||
actionAuth: recipient.actionAuth,
|
||||
fields: recipient.fields?.map((field) => {
|
||||
let documentDataId: string | undefined = undefined;
|
||||
|
||||
if (typeof field.identifier === 'string') {
|
||||
documentDataId = envelopeItems.find(
|
||||
(item) => item.title === field.identifier,
|
||||
)?.documentDataId;
|
||||
}
|
||||
|
||||
if (typeof field.identifier === 'number') {
|
||||
documentDataId = envelopeItems.at(field.identifier)?.documentDataId;
|
||||
}
|
||||
|
||||
if (field.identifier === undefined) {
|
||||
documentDataId = envelopeItems.at(0)?.documentDataId;
|
||||
}
|
||||
|
||||
if (!documentDataId) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Document data not found',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...field,
|
||||
documentDataId,
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
const envelope = await createEnvelope({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
@@ -60,16 +115,16 @@ export const createEnvelopeRoute = authenticatedProcedure
|
||||
type,
|
||||
title,
|
||||
externalId,
|
||||
formValues,
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
recipients,
|
||||
recipients: recipientsToCreate,
|
||||
folderId,
|
||||
envelopeItems: items,
|
||||
envelopeItems,
|
||||
},
|
||||
attachments,
|
||||
meta,
|
||||
normalizePdf: true,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
import { zfd } from 'zod-form-data';
|
||||
|
||||
import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
@@ -9,32 +10,35 @@ import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-va
|
||||
import { ZDocumentMetaCreateSchema } from '@documenso/lib/types/document-meta';
|
||||
import { ZEnvelopeAttachmentTypeSchema } from '@documenso/lib/types/envelope-attachment';
|
||||
import {
|
||||
ZFieldHeightSchema,
|
||||
ZClampedFieldHeightSchema,
|
||||
ZClampedFieldPositionXSchema,
|
||||
ZClampedFieldPositionYSchema,
|
||||
ZClampedFieldWidthSchema,
|
||||
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 {
|
||||
ZDocumentExternalIdSchema,
|
||||
ZDocumentTitleSchema,
|
||||
ZDocumentVisibilitySchema,
|
||||
} from '../document-router/schema';
|
||||
import { ZCreateRecipientSchema } from '../recipient-router/schema';
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
// Currently not in use until we allow passthrough documents on create.
|
||||
// export const createEnvelopeMeta: TrpcRouteMeta = {
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/envelope/create',
|
||||
// summary: 'Create envelope',
|
||||
// tags: ['Envelope'],
|
||||
// },
|
||||
// };
|
||||
export const createEnvelopeMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/create',
|
||||
contentTypes: ['multipart/form-data'],
|
||||
summary: 'Create envelope',
|
||||
description: 'Create an envelope using form data.',
|
||||
tags: ['Envelope'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZCreateEnvelopeRequestSchema = z.object({
|
||||
export const ZCreateEnvelopePayloadSchema = z.object({
|
||||
title: ZDocumentTitleSchema,
|
||||
type: z.nativeEnum(EnvelopeType),
|
||||
externalId: ZDocumentExternalIdSchema.optional(),
|
||||
@@ -42,12 +46,6 @@ export const ZCreateEnvelopeRequestSchema = z.object({
|
||||
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional(),
|
||||
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional(),
|
||||
formValues: ZDocumentFormValuesSchema.optional(),
|
||||
items: z
|
||||
.object({
|
||||
title: ZDocumentTitleSchema.optional(),
|
||||
documentDataId: z.string(),
|
||||
})
|
||||
.array(),
|
||||
folderId: z
|
||||
.string()
|
||||
.describe(
|
||||
@@ -59,16 +57,17 @@ export const ZCreateEnvelopeRequestSchema = z.object({
|
||||
ZCreateRecipientSchema.extend({
|
||||
fields: ZFieldAndMetaSchema.and(
|
||||
z.object({
|
||||
documentDataId: z
|
||||
.string()
|
||||
identifier: z
|
||||
.union([z.string(), z.number()])
|
||||
.describe(
|
||||
'The ID of the document data to create the field on. If empty, the first document data will be used.',
|
||||
),
|
||||
'Either the filename or the index of the file that was uploaded to attach the field to.',
|
||||
)
|
||||
.optional(),
|
||||
page: ZFieldPageNumberSchema,
|
||||
positionX: ZFieldPageXSchema,
|
||||
positionY: ZFieldPageYSchema,
|
||||
width: ZFieldWidthSchema,
|
||||
height: ZFieldHeightSchema,
|
||||
positionX: ZClampedFieldPositionXSchema,
|
||||
positionY: ZClampedFieldPositionYSchema,
|
||||
width: ZClampedFieldWidthSchema,
|
||||
height: ZClampedFieldHeightSchema,
|
||||
}),
|
||||
)
|
||||
.array()
|
||||
@@ -88,9 +87,15 @@ export const ZCreateEnvelopeRequestSchema = z.object({
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const ZCreateEnvelopeRequestSchema = zodFormData({
|
||||
payload: zfd.json(ZCreateEnvelopePayloadSchema),
|
||||
files: zfd.repeatableOfType(zfd.file()),
|
||||
});
|
||||
|
||||
export const ZCreateEnvelopeResponseSchema = z.object({
|
||||
id: z.string(),
|
||||
});
|
||||
|
||||
export type TCreateEnvelopePayload = z.infer<typeof ZCreateEnvelopePayloadSchema>;
|
||||
export type TCreateEnvelopeRequest = z.infer<typeof ZCreateEnvelopeRequestSchema>;
|
||||
export type TCreateEnvelopeResponse = z.infer<typeof ZCreateEnvelopeResponseSchema>;
|
||||
|
||||
@@ -9,9 +9,11 @@ import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDeleteEnvelopeItemRequestSchema,
|
||||
ZDeleteEnvelopeItemResponseSchema,
|
||||
deleteEnvelopeItemMeta,
|
||||
} from './delete-envelope-item.types';
|
||||
|
||||
export const deleteEnvelopeItemRoute = authenticatedProcedure
|
||||
.meta(deleteEnvelopeItemMeta)
|
||||
.input(ZDeleteEnvelopeItemRequestSchema)
|
||||
.output(ZDeleteEnvelopeItemResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const deleteEnvelopeItemMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/item/delete',
|
||||
summary: 'Delete envelope item',
|
||||
description: 'Delete an envelope item from an envelope',
|
||||
tags: ['Envelope Items'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZDeleteEnvelopeItemRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
envelopeItemId: z.string(),
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { deleteDocument } from '@documenso/lib/server-only/document/delete-document';
|
||||
import { deleteTemplate } from '@documenso/lib/server-only/template/delete-template';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDeleteEnvelopeRequestSchema,
|
||||
ZDeleteEnvelopeResponseSchema,
|
||||
deleteEnvelopeMeta,
|
||||
} from './delete-envelope.types';
|
||||
|
||||
export const deleteEnvelopeRoute = authenticatedProcedure
|
||||
// .meta(deleteEnvelopeMeta)
|
||||
.meta(deleteEnvelopeMeta)
|
||||
.input(ZDeleteEnvelopeRequestSchema)
|
||||
.output(ZDeleteEnvelopeResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { envelopeId, envelopeType } = input;
|
||||
const { envelopeId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
@@ -24,7 +27,22 @@ export const deleteEnvelopeRoute = authenticatedProcedure
|
||||
},
|
||||
});
|
||||
|
||||
await match(envelopeType)
|
||||
const unsafeEnvelope = await prisma.envelope.findUnique({
|
||||
where: {
|
||||
id: envelopeId,
|
||||
},
|
||||
select: {
|
||||
type: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!unsafeEnvelope) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Envelope not found',
|
||||
});
|
||||
}
|
||||
|
||||
await match(unsafeEnvelope.type)
|
||||
.with(EnvelopeType.DOCUMENT, async () =>
|
||||
deleteDocument({
|
||||
userId: ctx.user.id,
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
// export const deleteEnvelopeMeta: TrpcRouteMeta = {
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/envelope/delete',
|
||||
// summary: 'Delete envelope',
|
||||
// tags: ['Envelope'],
|
||||
// },
|
||||
// };
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const deleteEnvelopeMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/delete',
|
||||
summary: 'Delete envelope',
|
||||
tags: ['Envelope'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZDeleteEnvelopeRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
envelopeType: z.nativeEnum(EnvelopeType),
|
||||
});
|
||||
|
||||
export const ZDeleteEnvelopeResponseSchema = z.void();
|
||||
|
||||
@@ -5,10 +5,11 @@ import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDistributeEnvelopeRequestSchema,
|
||||
ZDistributeEnvelopeResponseSchema,
|
||||
distributeEnvelopeMeta,
|
||||
} from './distribute-envelope.types';
|
||||
|
||||
export const distributeEnvelopeRoute = authenticatedProcedure
|
||||
// .meta(distributeEnvelopeMeta)
|
||||
.meta(distributeEnvelopeMeta)
|
||||
.input(ZDistributeEnvelopeRequestSchema)
|
||||
.output(ZDistributeEnvelopeResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
|
||||
@@ -2,15 +2,17 @@ import { z } from 'zod';
|
||||
|
||||
import { ZDocumentMetaUpdateSchema } from '@documenso/lib/types/document-meta';
|
||||
|
||||
// export const distributeEnvelopeMeta: TrpcRouteMeta = {
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/envelope/distribute',
|
||||
// summary: 'Distribute envelope',
|
||||
// description: 'Send the document out to recipients based on your distribution method',
|
||||
// tags: ['Envelope'],
|
||||
// },
|
||||
// };
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const distributeEnvelopeMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/distribute',
|
||||
summary: 'Distribute envelope',
|
||||
description: 'Send the envelope to recipients based on your distribution method',
|
||||
tags: ['Envelope'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZDistributeEnvelopeRequestSchema = z.object({
|
||||
envelopeId: z.string().describe('The ID of the envelope to send.'),
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDownloadEnvelopeItemRequestSchema,
|
||||
ZDownloadEnvelopeItemResponseSchema,
|
||||
downloadEnvelopeItemMeta,
|
||||
} from './download-envelope-item.types';
|
||||
|
||||
export const downloadEnvelopeItemRoute = authenticatedProcedure
|
||||
.meta(downloadEnvelopeItemMeta)
|
||||
.input(ZDownloadEnvelopeItemRequestSchema)
|
||||
.output(ZDownloadEnvelopeItemResponseSchema)
|
||||
.query(({ input, ctx }) => {
|
||||
const { envelopeItemId, version } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
envelopeItemId,
|
||||
version,
|
||||
},
|
||||
});
|
||||
|
||||
throw new Error('NOT_IMPLEMENTED');
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const downloadEnvelopeItemMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/envelopeItem/{envelopeItemId}/download',
|
||||
summary: 'Download an envelope item',
|
||||
description: 'Download an envelope item by its ID',
|
||||
tags: ['Envelope Items'],
|
||||
responseHeaders: z.object({
|
||||
'Content-Type': z.literal('application/pdf'),
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
export const ZDownloadEnvelopeItemRequestSchema = z.object({
|
||||
envelopeItemId: z.string().describe('The ID of the envelope item to download.'),
|
||||
version: z
|
||||
.enum(['original', 'signed'])
|
||||
.describe(
|
||||
'The version of the envelope item to download. "signed" returns the completed document with signatures, "original" returns the original uploaded document.',
|
||||
)
|
||||
.default('signed'),
|
||||
});
|
||||
|
||||
export const ZDownloadEnvelopeItemResponseSchema = z.instanceof(Uint8Array);
|
||||
|
||||
export type TDownloadEnvelopeItemRequest = z.infer<typeof ZDownloadEnvelopeItemRequestSchema>;
|
||||
export type TDownloadEnvelopeItemResponse = z.infer<typeof ZDownloadEnvelopeItemResponseSchema>;
|
||||
@@ -4,9 +4,11 @@ import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDuplicateEnvelopeRequestSchema,
|
||||
ZDuplicateEnvelopeResponseSchema,
|
||||
duplicateEnvelopeMeta,
|
||||
} from './duplicate-envelope.types';
|
||||
|
||||
export const duplicateEnvelopeRoute = authenticatedProcedure
|
||||
.meta(duplicateEnvelopeMeta)
|
||||
.input(ZDuplicateEnvelopeRequestSchema)
|
||||
.output(ZDuplicateEnvelopeResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
@@ -29,6 +31,6 @@ export const duplicateEnvelopeRoute = authenticatedProcedure
|
||||
});
|
||||
|
||||
return {
|
||||
duplicatedEnvelopeId: duplicatedEnvelope.id,
|
||||
id: duplicatedEnvelope.id,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const duplicateEnvelopeMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/duplicate',
|
||||
summary: 'Duplicate envelope',
|
||||
description: 'Duplicate an envelope with all its settings',
|
||||
tags: ['Envelope'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZDuplicateEnvelopeRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
});
|
||||
|
||||
export const ZDuplicateEnvelopeResponseSchema = z.object({
|
||||
duplicatedEnvelopeId: z.string(),
|
||||
id: z.string().describe('The ID of the newly created envelope.'),
|
||||
});
|
||||
|
||||
export type TDuplicateEnvelopeRequest = z.infer<typeof ZDuplicateEnvelopeRequestSchema>;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { createEnvelopeFields } from '@documenso/lib/server-only/field/create-envelope-fields';
|
||||
|
||||
import { authenticatedProcedure } from '../../trpc';
|
||||
import {
|
||||
ZCreateEnvelopeFieldsRequestSchema,
|
||||
ZCreateEnvelopeFieldsResponseSchema,
|
||||
createEnvelopeFieldsMeta,
|
||||
} from './create-envelope-fields.types';
|
||||
|
||||
export const createEnvelopeFieldsRoute = authenticatedProcedure
|
||||
.meta(createEnvelopeFieldsMeta)
|
||||
.input(ZCreateEnvelopeFieldsRequestSchema)
|
||||
.output(ZCreateEnvelopeFieldsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { user, teamId, metadata } = ctx;
|
||||
const { envelopeId, data: fields } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
envelopeId,
|
||||
},
|
||||
});
|
||||
|
||||
const { fields: data } = await createEnvelopeFields({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
fields,
|
||||
requestMetadata: metadata,
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
ZClampedFieldHeightSchema,
|
||||
ZClampedFieldPositionXSchema,
|
||||
ZClampedFieldPositionYSchema,
|
||||
ZClampedFieldWidthSchema,
|
||||
ZFieldPageNumberSchema,
|
||||
ZFieldSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
|
||||
import type { TrpcRouteMeta } from '../../trpc';
|
||||
|
||||
export const createEnvelopeFieldsMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/field/create-many',
|
||||
contentTypes: ['multipart/form-data'],
|
||||
summary: 'Create envelope fields',
|
||||
description: 'Create multiple fields for an envelope',
|
||||
tags: ['Envelope Fields'],
|
||||
},
|
||||
};
|
||||
|
||||
const ZCreateFieldSchema = ZFieldAndMetaSchema.and(
|
||||
z.object({
|
||||
recipientId: z.number().describe('The ID of the recipient to create the field for'),
|
||||
envelopeItemId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'The ID of the envelope item to put the field on. If not provided, field will be placed on the first item.',
|
||||
),
|
||||
page: ZFieldPageNumberSchema,
|
||||
positionX: ZClampedFieldPositionXSchema,
|
||||
positionY: ZClampedFieldPositionYSchema,
|
||||
width: ZClampedFieldWidthSchema,
|
||||
height: ZClampedFieldHeightSchema,
|
||||
}),
|
||||
);
|
||||
|
||||
export const ZCreateEnvelopeFieldsRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
data: ZCreateFieldSchema.array(),
|
||||
});
|
||||
|
||||
export const ZCreateEnvelopeFieldsResponseSchema = z.object({
|
||||
data: z.array(ZFieldSchema),
|
||||
});
|
||||
|
||||
export type TCreateEnvelopeFieldsRequest = z.infer<typeof ZCreateEnvelopeFieldsRequestSchema>;
|
||||
export type TCreateEnvelopeFieldsResponse = z.infer<typeof ZCreateEnvelopeFieldsResponseSchema>;
|
||||
@@ -0,0 +1,118 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getEnvelopeWhereInput } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { canRecipientFieldsBeModified } from '@documenso/lib/utils/recipients';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../../trpc';
|
||||
import {
|
||||
ZDeleteEnvelopeFieldRequestSchema,
|
||||
ZDeleteEnvelopeFieldResponseSchema,
|
||||
deleteEnvelopeFieldMeta,
|
||||
} from './delete-envelope-field.types';
|
||||
|
||||
export const deleteEnvelopeFieldRoute = authenticatedProcedure
|
||||
.meta(deleteEnvelopeFieldMeta)
|
||||
.input(ZDeleteEnvelopeFieldRequestSchema)
|
||||
.output(ZDeleteEnvelopeFieldResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { user, teamId, metadata } = ctx;
|
||||
const { fieldId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
fieldId,
|
||||
},
|
||||
});
|
||||
|
||||
const unsafeField = await prisma.field.findUnique({
|
||||
where: {
|
||||
id: fieldId,
|
||||
},
|
||||
select: {
|
||||
envelopeId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!unsafeField) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Field not found',
|
||||
});
|
||||
}
|
||||
|
||||
const { envelopeWhereInput } = await getEnvelopeWhereInput({
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: unsafeField.envelopeId,
|
||||
},
|
||||
type: null,
|
||||
userId: user.id,
|
||||
teamId,
|
||||
});
|
||||
|
||||
const envelope = await prisma.envelope.findUnique({
|
||||
where: envelopeWhereInput,
|
||||
include: {
|
||||
recipients: {
|
||||
include: {
|
||||
fields: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const recipientWithFields = envelope?.recipients.find((recipient) =>
|
||||
recipient.fields.some((field) => field.id === fieldId),
|
||||
);
|
||||
const fieldToDelete = recipientWithFields?.fields.find((field) => field.id === fieldId);
|
||||
|
||||
if (!envelope || !recipientWithFields || !fieldToDelete) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Field not found',
|
||||
});
|
||||
}
|
||||
|
||||
if (envelope.completedAt) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Envelope already complete',
|
||||
});
|
||||
}
|
||||
|
||||
// Check whether the recipient associated with the field can have new fields created.
|
||||
if (!canRecipientFieldsBeModified(recipientWithFields, recipientWithFields.fields)) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Recipient has already interacted with the document.',
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
const deletedField = await tx.field.delete({
|
||||
where: {
|
||||
id: fieldToDelete.id,
|
||||
envelopeId: envelope.id,
|
||||
},
|
||||
});
|
||||
|
||||
// Handle field deleted audit log.
|
||||
if (envelope.type === EnvelopeType.DOCUMENT) {
|
||||
await tx.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_DELETED,
|
||||
envelopeId: envelope.id,
|
||||
metadata,
|
||||
data: {
|
||||
fieldId: deletedField.secondaryId,
|
||||
fieldRecipientEmail: recipientWithFields.email,
|
||||
fieldRecipientId: deletedField.recipientId,
|
||||
fieldType: deletedField.type,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
return deletedField;
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TrpcRouteMeta } from '../../trpc';
|
||||
|
||||
export const deleteEnvelopeFieldMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/field/delete',
|
||||
summary: 'Delete envelope field',
|
||||
description: 'Delete an envelope field',
|
||||
tags: ['Envelope Fields'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZDeleteEnvelopeFieldRequestSchema = z.object({
|
||||
fieldId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDeleteEnvelopeFieldResponseSchema = z.void();
|
||||
|
||||
export type TDeleteEnvelopeFieldRequest = z.infer<typeof ZDeleteEnvelopeFieldRequestSchema>;
|
||||
export type TDeleteEnvelopeFieldResponse = z.infer<typeof ZDeleteEnvelopeFieldResponseSchema>;
|
||||
@@ -0,0 +1,29 @@
|
||||
import { getFieldById } from '@documenso/lib/server-only/field/get-field-by-id';
|
||||
|
||||
import { authenticatedProcedure } from '../../trpc';
|
||||
import {
|
||||
ZGetEnvelopeFieldRequestSchema,
|
||||
ZGetEnvelopeFieldResponseSchema,
|
||||
getEnvelopeFieldMeta,
|
||||
} from './get-envelope-field.types';
|
||||
|
||||
export const getEnvelopeFieldRoute = authenticatedProcedure
|
||||
.meta(getEnvelopeFieldMeta)
|
||||
.input(ZGetEnvelopeFieldRequestSchema)
|
||||
.output(ZGetEnvelopeFieldResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
const { fieldId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
fieldId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getFieldById({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
fieldId,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZEnvelopeFieldSchema } from '@documenso/lib/types/field';
|
||||
|
||||
import type { TrpcRouteMeta } from '../../trpc';
|
||||
|
||||
export const getEnvelopeFieldMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/envelope/field/{fieldId}',
|
||||
summary: 'Get envelope field',
|
||||
description: 'Returns an envelope field given an ID',
|
||||
tags: ['Envelope Fields'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZGetEnvelopeFieldRequestSchema = z.object({
|
||||
fieldId: z.number(),
|
||||
});
|
||||
|
||||
export const ZGetEnvelopeFieldResponseSchema = ZEnvelopeFieldSchema;
|
||||
|
||||
export type TGetEnvelopeFieldRequest = z.infer<typeof ZGetEnvelopeFieldRequestSchema>;
|
||||
export type TGetEnvelopeFieldResponse = z.infer<typeof ZGetEnvelopeFieldResponseSchema>;
|
||||
@@ -0,0 +1,39 @@
|
||||
import { updateEnvelopeFields } from '@documenso/lib/server-only/field/update-envelope-fields';
|
||||
|
||||
import { authenticatedProcedure } from '../../trpc';
|
||||
import {
|
||||
ZUpdateEnvelopeFieldsRequestSchema,
|
||||
ZUpdateEnvelopeFieldsResponseSchema,
|
||||
updateEnvelopeFieldsMeta,
|
||||
} from './update-envelope-fields.types';
|
||||
|
||||
export const updateEnvelopeFieldsRoute = authenticatedProcedure
|
||||
.meta(updateEnvelopeFieldsMeta)
|
||||
.input(ZUpdateEnvelopeFieldsRequestSchema)
|
||||
.output(ZUpdateEnvelopeFieldsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
const { envelopeId, data: fields } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
envelopeId,
|
||||
},
|
||||
});
|
||||
|
||||
const { fields: data } = await updateEnvelopeFields({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
type: null,
|
||||
fields,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
ZClampedFieldHeightSchema,
|
||||
ZClampedFieldPositionXSchema,
|
||||
ZClampedFieldPositionYSchema,
|
||||
ZClampedFieldWidthSchema,
|
||||
ZFieldPageNumberSchema,
|
||||
ZFieldSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
|
||||
import type { TrpcRouteMeta } from '../../trpc';
|
||||
|
||||
export const updateEnvelopeFieldsMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/field/update-many',
|
||||
summary: 'Update envelope fields',
|
||||
description: 'Update multiple envelope fields for an envelope',
|
||||
tags: ['Envelope Fields'],
|
||||
},
|
||||
};
|
||||
|
||||
const ZUpdateFieldSchema = ZFieldAndMetaSchema.and(
|
||||
z.object({
|
||||
id: z.number().describe('The ID of the field to update.'),
|
||||
envelopeItemId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'The ID of the envelope item to put the field on. If not provided, field will be placed on the first item.',
|
||||
),
|
||||
page: ZFieldPageNumberSchema.optional(),
|
||||
positionX: ZClampedFieldPositionXSchema.optional(),
|
||||
positionY: ZClampedFieldPositionYSchema.optional(),
|
||||
width: ZClampedFieldWidthSchema.optional(),
|
||||
height: ZClampedFieldHeightSchema.optional(),
|
||||
}),
|
||||
);
|
||||
|
||||
export const ZUpdateEnvelopeFieldsRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
data: ZUpdateFieldSchema.array(),
|
||||
});
|
||||
|
||||
export const ZUpdateEnvelopeFieldsResponseSchema = z.object({
|
||||
data: z.array(ZFieldSchema),
|
||||
});
|
||||
|
||||
export type TUpdateEnvelopeFieldsRequest = z.infer<typeof ZUpdateEnvelopeFieldsRequestSchema>;
|
||||
export type TUpdateEnvelopeFieldsResponse = z.infer<typeof ZUpdateEnvelopeFieldsResponseSchema>;
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { createEnvelopeRecipients } from '@documenso/lib/server-only/recipient/create-envelope-recipients';
|
||||
|
||||
import { authenticatedProcedure } from '../../trpc';
|
||||
import {
|
||||
ZCreateEnvelopeRecipientsRequestSchema,
|
||||
ZCreateEnvelopeRecipientsResponseSchema,
|
||||
createEnvelopeRecipientsMeta,
|
||||
} from './create-envelope-recipients.types';
|
||||
|
||||
export const createEnvelopeRecipientsRoute = authenticatedProcedure
|
||||
.meta(createEnvelopeRecipientsMeta)
|
||||
.input(ZCreateEnvelopeRecipientsRequestSchema)
|
||||
.output(ZCreateEnvelopeRecipientsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { user, teamId, metadata } = ctx;
|
||||
const { envelopeId, data: recipients } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
envelopeId,
|
||||
},
|
||||
});
|
||||
|
||||
const { recipients: data } = await createEnvelopeRecipients({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
recipients,
|
||||
requestMetadata: metadata,
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
};
|
||||
});
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZEnvelopeRecipientLiteSchema } from '@documenso/lib/types/recipient';
|
||||
|
||||
import { ZCreateRecipientSchema } from '../../recipient-router/schema';
|
||||
import type { TrpcRouteMeta } from '../../trpc';
|
||||
|
||||
export const createEnvelopeRecipientsMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/recipient/create-many',
|
||||
summary: 'Create envelope recipients',
|
||||
description: 'Create multiple recipients for an envelope',
|
||||
tags: ['Envelope Recipients'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZCreateEnvelopeRecipientsRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
data: ZCreateRecipientSchema.array(),
|
||||
});
|
||||
|
||||
export const ZCreateEnvelopeRecipientsResponseSchema = z.object({
|
||||
data: ZEnvelopeRecipientLiteSchema.array(),
|
||||
});
|
||||
|
||||
export type TCreateEnvelopeRecipientsRequest = z.infer<
|
||||
typeof ZCreateEnvelopeRecipientsRequestSchema
|
||||
>;
|
||||
export type TCreateEnvelopeRecipientsResponse = z.infer<
|
||||
typeof ZCreateEnvelopeRecipientsResponseSchema
|
||||
>;
|
||||
@@ -0,0 +1,30 @@
|
||||
import { deleteEnvelopeRecipient } from '@documenso/lib/server-only/recipient/delete-envelope-recipient';
|
||||
|
||||
import { authenticatedProcedure } from '../../trpc';
|
||||
import {
|
||||
ZDeleteEnvelopeRecipientRequestSchema,
|
||||
ZDeleteEnvelopeRecipientResponseSchema,
|
||||
deleteEnvelopeRecipientMeta,
|
||||
} from './delete-envelope-recipient.types';
|
||||
|
||||
export const deleteEnvelopeRecipientRoute = authenticatedProcedure
|
||||
.meta(deleteEnvelopeRecipientMeta)
|
||||
.input(ZDeleteEnvelopeRecipientRequestSchema)
|
||||
.output(ZDeleteEnvelopeRecipientResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { user, teamId, metadata } = ctx;
|
||||
const { recipientId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
recipientId,
|
||||
},
|
||||
});
|
||||
|
||||
await deleteEnvelopeRecipient({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
recipientId,
|
||||
requestMetadata: metadata,
|
||||
});
|
||||
});
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TrpcRouteMeta } from '../../trpc';
|
||||
|
||||
export const deleteEnvelopeRecipientMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/recipient/delete',
|
||||
summary: 'Delete envelope recipient',
|
||||
description: 'Delete an envelope recipient',
|
||||
tags: ['Envelope Recipients'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZDeleteEnvelopeRecipientRequestSchema = z.object({
|
||||
recipientId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDeleteEnvelopeRecipientResponseSchema = z.void();
|
||||
|
||||
export type TDeleteEnvelopeRecipientRequest = z.infer<typeof ZDeleteEnvelopeRecipientRequestSchema>;
|
||||
export type TDeleteEnvelopeRecipientResponse = z.infer<
|
||||
typeof ZDeleteEnvelopeRecipientResponseSchema
|
||||
>;
|
||||
@@ -0,0 +1,45 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { buildTeamWhereQuery } from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../../trpc';
|
||||
import {
|
||||
ZGetEnvelopeRecipientRequestSchema,
|
||||
ZGetEnvelopeRecipientResponseSchema,
|
||||
getEnvelopeRecipientMeta,
|
||||
} from './get-envelope-recipient.types';
|
||||
|
||||
export const getEnvelopeRecipientRoute = authenticatedProcedure
|
||||
.meta(getEnvelopeRecipientMeta)
|
||||
.input(ZGetEnvelopeRecipientRequestSchema)
|
||||
.output(ZGetEnvelopeRecipientResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
const { recipientId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
recipientId,
|
||||
},
|
||||
});
|
||||
|
||||
const recipient = await prisma.recipient.findFirst({
|
||||
where: {
|
||||
id: recipientId,
|
||||
envelope: {
|
||||
team: buildTeamWhereQuery({ teamId, userId: user.id }),
|
||||
},
|
||||
},
|
||||
include: {
|
||||
fields: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!recipient) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Recipient not found',
|
||||
});
|
||||
}
|
||||
|
||||
return recipient;
|
||||
});
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZEnvelopeRecipientSchema } from '@documenso/lib/types/recipient';
|
||||
|
||||
import type { TrpcRouteMeta } from '../../trpc';
|
||||
|
||||
export const getEnvelopeRecipientMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/envelope/recipient/{recipientId}',
|
||||
summary: 'Get envelope recipient',
|
||||
description: 'Returns an envelope recipient given an ID',
|
||||
tags: ['Envelope Recipients'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZGetEnvelopeRecipientRequestSchema = z.object({
|
||||
recipientId: z.number(),
|
||||
});
|
||||
|
||||
export const ZGetEnvelopeRecipientResponseSchema = ZEnvelopeRecipientSchema;
|
||||
|
||||
export type TGetEnvelopeRecipientRequest = z.infer<typeof ZGetEnvelopeRecipientRequestSchema>;
|
||||
export type TGetEnvelopeRecipientResponse = z.infer<typeof ZGetEnvelopeRecipientResponseSchema>;
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { updateEnvelopeRecipients } from '@documenso/lib/server-only/recipient/update-envelope-recipients';
|
||||
|
||||
import { authenticatedProcedure } from '../../trpc';
|
||||
import {
|
||||
ZUpdateEnvelopeRecipientsRequestSchema,
|
||||
ZUpdateEnvelopeRecipientsResponseSchema,
|
||||
updateEnvelopeRecipientsMeta,
|
||||
} from './update-envelope-recipients.types';
|
||||
|
||||
export const updateEnvelopeRecipientsRoute = authenticatedProcedure
|
||||
.meta(updateEnvelopeRecipientsMeta)
|
||||
.input(ZUpdateEnvelopeRecipientsRequestSchema)
|
||||
.output(ZUpdateEnvelopeRecipientsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
const { envelopeId, data: recipients } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
envelopeId,
|
||||
},
|
||||
});
|
||||
|
||||
const { recipients: data } = await updateEnvelopeRecipients({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
recipients,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
};
|
||||
});
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZRecipientLiteSchema } from '@documenso/lib/types/recipient';
|
||||
|
||||
import { ZUpdateRecipientSchema } from '../../recipient-router/schema';
|
||||
import type { TrpcRouteMeta } from '../../trpc';
|
||||
|
||||
export const updateEnvelopeRecipientsMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/recipient/update-many',
|
||||
summary: 'Update envelope recipients',
|
||||
description: 'Update multiple recipients for an envelope',
|
||||
tags: ['Envelope Recipients'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZUpdateEnvelopeRecipientsRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
data: ZUpdateRecipientSchema.array(),
|
||||
});
|
||||
|
||||
export const ZUpdateEnvelopeRecipientsResponseSchema = z.object({
|
||||
data: ZRecipientLiteSchema.array(),
|
||||
});
|
||||
|
||||
export type TUpdateEnvelopeRecipientsRequest = z.infer<
|
||||
typeof ZUpdateEnvelopeRecipientsRequestSchema
|
||||
>;
|
||||
export type TUpdateEnvelopeRecipientsResponse = z.infer<
|
||||
typeof ZUpdateEnvelopeRecipientsResponseSchema
|
||||
>;
|
||||
@@ -34,10 +34,25 @@ export const getEnvelopeItemsByTokenRoute = maybeAuthenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
return await handleGetEnvelopeItemsByUser({ envelopeId, userId: user.id, teamId });
|
||||
const { envelopeItems: data } = await handleGetEnvelopeItemsByUser({
|
||||
envelopeId,
|
||||
userId: user.id,
|
||||
teamId,
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
return await handleGetEnvelopeItemsByToken({ envelopeId, token: access.token });
|
||||
const { envelopeItems: data } = await handleGetEnvelopeItemsByToken({
|
||||
envelopeId,
|
||||
token: access.token,
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
};
|
||||
});
|
||||
|
||||
const handleGetEnvelopeItemsByToken = async ({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import DocumentDataSchema from '@documenso/prisma/generated/zod/modelSchema/DocumentDataSchema';
|
||||
import EnvelopeItemSchema from '@documenso/prisma/generated/zod/modelSchema/EnvelopeItemSchema';
|
||||
|
||||
export const ZGetEnvelopeItemsByTokenRequestSchema = z.object({
|
||||
@@ -17,18 +16,10 @@ export const ZGetEnvelopeItemsByTokenRequestSchema = z.object({
|
||||
});
|
||||
|
||||
export const ZGetEnvelopeItemsByTokenResponseSchema = z.object({
|
||||
envelopeItems: EnvelopeItemSchema.pick({
|
||||
data: EnvelopeItemSchema.pick({
|
||||
id: true,
|
||||
envelopeId: true,
|
||||
title: true,
|
||||
order: true,
|
||||
})
|
||||
.extend({
|
||||
documentData: DocumentDataSchema.pick({
|
||||
type: true,
|
||||
id: true,
|
||||
data: true,
|
||||
initialData: true,
|
||||
}),
|
||||
})
|
||||
.array(),
|
||||
}).array(),
|
||||
});
|
||||
|
||||
@@ -50,6 +50,6 @@ export const getEnvelopeItemsRoute = authenticatedProcedure
|
||||
}
|
||||
|
||||
return {
|
||||
envelopeItems: envelope.envelopeItems,
|
||||
data: envelope.envelopeItems,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ export const ZGetEnvelopeItemsRequestSchema = z.object({
|
||||
});
|
||||
|
||||
export const ZGetEnvelopeItemsResponseSchema = z.object({
|
||||
envelopeItems: EnvelopeItemSchema.pick({
|
||||
data: EnvelopeItemSchema.pick({
|
||||
id: true,
|
||||
title: true,
|
||||
order: true,
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { getEnvelopeById } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZGetEnvelopeRequestSchema, ZGetEnvelopeResponseSchema } from './get-envelope.types';
|
||||
import {
|
||||
ZGetEnvelopeRequestSchema,
|
||||
ZGetEnvelopeResponseSchema,
|
||||
getEnvelopeMeta,
|
||||
} from './get-envelope.types';
|
||||
|
||||
export const getEnvelopeRoute = authenticatedProcedure
|
||||
// .meta(getEnvelopeMeta)
|
||||
.meta(getEnvelopeMeta)
|
||||
.input(ZGetEnvelopeRequestSchema)
|
||||
.output(ZGetEnvelopeResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
|
||||
@@ -2,15 +2,17 @@ import { z } from 'zod';
|
||||
|
||||
import { ZEnvelopeSchema } from '@documenso/lib/types/envelope';
|
||||
|
||||
// export const getEnvelopeMeta: TrpcRouteMeta = {
|
||||
// openapi: {
|
||||
// method: 'GET',
|
||||
// path: '/envelope/{envelopeId}',
|
||||
// summary: 'Get envelope',
|
||||
// description: 'Returns a envelope given an ID',
|
||||
// tags: ['Envelope'],
|
||||
// },
|
||||
// };
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const getEnvelopeMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/envelope/{envelopeId}',
|
||||
summary: 'Get envelope',
|
||||
description: 'Returns an envelope given an ID',
|
||||
tags: ['Envelope'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZGetEnvelopeRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
|
||||
@@ -4,10 +4,11 @@ import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZRedistributeEnvelopeRequestSchema,
|
||||
ZRedistributeEnvelopeResponseSchema,
|
||||
redistributeEnvelopeMeta,
|
||||
} from './redistribute-envelope.types';
|
||||
|
||||
export const redistributeEnvelopeRoute = authenticatedProcedure
|
||||
// .meta(redistributeEnvelopeMeta)
|
||||
.meta(redistributeEnvelopeMeta)
|
||||
.input(ZRedistributeEnvelopeRequestSchema)
|
||||
.output(ZRedistributeEnvelopeResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
// export const redistributeEnvelopeMeta: TrpcRouteMeta = {
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/envelope/redistribute',
|
||||
// summary: 'Redistribute document',
|
||||
// description:
|
||||
// 'Redistribute the document to the provided recipients who have not actioned the document. Will use the distribution method set in the document',
|
||||
// tags: ['Envelope'],
|
||||
// },
|
||||
// };
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const redistributeEnvelopeMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/redistribute',
|
||||
summary: 'Redistribute envelope',
|
||||
description:
|
||||
'Redistribute the envelope to the provided recipients who have not actioned the envelope. Will use the distribution method set in the envelope',
|
||||
tags: ['Envelope'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZRedistributeEnvelopeRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
|
||||
@@ -9,6 +9,14 @@ import { deleteEnvelopeRoute } from './delete-envelope';
|
||||
import { deleteEnvelopeItemRoute } from './delete-envelope-item';
|
||||
import { distributeEnvelopeRoute } from './distribute-envelope';
|
||||
import { duplicateEnvelopeRoute } from './duplicate-envelope';
|
||||
import { createEnvelopeFieldsRoute } from './envelope-fields/create-envelope-fields';
|
||||
import { deleteEnvelopeFieldRoute } from './envelope-fields/delete-envelope-field';
|
||||
import { getEnvelopeFieldRoute } from './envelope-fields/get-envelope-field';
|
||||
import { updateEnvelopeFieldsRoute } from './envelope-fields/update-envelope-fields';
|
||||
import { createEnvelopeRecipientsRoute } from './envelope-recipients/create-envelope-recipients';
|
||||
import { deleteEnvelopeRecipientRoute } from './envelope-recipients/delete-envelope-recipient';
|
||||
import { getEnvelopeRecipientRoute } from './envelope-recipients/get-envelope-recipient';
|
||||
import { updateEnvelopeRecipientsRoute } from './envelope-recipients/update-envelope-recipients';
|
||||
import { getEnvelopeRoute } from './get-envelope';
|
||||
import { getEnvelopeItemsRoute } from './get-envelope-items';
|
||||
import { getEnvelopeItemsByTokenRoute } from './get-envelope-items-by-token';
|
||||
@@ -18,17 +26,20 @@ import { setEnvelopeRecipientsRoute } from './set-envelope-recipients';
|
||||
import { signEnvelopeFieldRoute } from './sign-envelope-field';
|
||||
import { updateEnvelopeRoute } from './update-envelope';
|
||||
import { updateEnvelopeItemsRoute } from './update-envelope-items';
|
||||
import { useEnvelopeRoute } from './use-envelope';
|
||||
|
||||
/**
|
||||
* Note: The order of the routes is important for public API routes.
|
||||
*
|
||||
* Example: GET /envelope/attachment must appear before GET /envelope/:id
|
||||
*/
|
||||
export const envelopeRouter = router({
|
||||
get: getEnvelopeRoute,
|
||||
create: createEnvelopeRoute,
|
||||
update: updateEnvelopeRoute,
|
||||
delete: deleteEnvelopeRoute,
|
||||
duplicate: duplicateEnvelopeRoute,
|
||||
distribute: distributeEnvelopeRoute,
|
||||
redistribute: redistributeEnvelopeRoute,
|
||||
// share: shareEnvelopeRoute,
|
||||
|
||||
attachment: {
|
||||
find: findAttachmentsRoute,
|
||||
create: createAttachmentRoute,
|
||||
update: updateAttachmentRoute,
|
||||
delete: deleteAttachmentRoute,
|
||||
},
|
||||
item: {
|
||||
getMany: getEnvelopeItemsRoute,
|
||||
getManyByToken: getEnvelopeItemsByTokenRoute,
|
||||
@@ -37,16 +48,26 @@ export const envelopeRouter = router({
|
||||
delete: deleteEnvelopeItemRoute,
|
||||
},
|
||||
recipient: {
|
||||
get: getEnvelopeRecipientRoute,
|
||||
createMany: createEnvelopeRecipientsRoute,
|
||||
updateMany: updateEnvelopeRecipientsRoute,
|
||||
delete: deleteEnvelopeRecipientRoute,
|
||||
set: setEnvelopeRecipientsRoute,
|
||||
},
|
||||
field: {
|
||||
get: getEnvelopeFieldRoute,
|
||||
createMany: createEnvelopeFieldsRoute,
|
||||
updateMany: updateEnvelopeFieldsRoute,
|
||||
delete: deleteEnvelopeFieldRoute,
|
||||
set: setEnvelopeFieldsRoute,
|
||||
sign: signEnvelopeFieldRoute,
|
||||
},
|
||||
attachment: {
|
||||
find: findAttachmentsRoute,
|
||||
create: createAttachmentRoute,
|
||||
update: updateAttachmentRoute,
|
||||
delete: deleteAttachmentRoute,
|
||||
},
|
||||
get: getEnvelopeRoute,
|
||||
create: createEnvelopeRoute,
|
||||
use: useEnvelopeRoute,
|
||||
update: updateEnvelopeRoute,
|
||||
delete: deleteEnvelopeRoute,
|
||||
duplicate: duplicateEnvelopeRoute,
|
||||
distribute: distributeEnvelopeRoute,
|
||||
redistribute: redistributeEnvelopeRoute,
|
||||
});
|
||||
|
||||
@@ -65,7 +65,7 @@ export const setEnvelopeFieldsRoute = authenticatedProcedure
|
||||
.exhaustive();
|
||||
|
||||
return {
|
||||
fields: result.fields.map((field) => ({
|
||||
data: result.fields.map((field) => ({
|
||||
id: field.id,
|
||||
formId: field.formId,
|
||||
})),
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import { EnvelopeType, FieldType } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
ZClampedFieldHeightSchema,
|
||||
ZClampedFieldPositionXSchema,
|
||||
ZClampedFieldPositionYSchema,
|
||||
ZClampedFieldWidthSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
|
||||
export const ZSetEnvelopeFieldsRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
envelopeType: z.nativeEnum(EnvelopeType),
|
||||
fields: z.array(
|
||||
// Todo: Envelopes - Use strict schema for types + field meta.
|
||||
z.object({
|
||||
id: z
|
||||
.number()
|
||||
@@ -20,34 +27,17 @@ export const ZSetEnvelopeFieldsRequestSchema = z.object({
|
||||
.number()
|
||||
.min(1)
|
||||
.describe('The page number of the field on the envelope. Starts from 1.'),
|
||||
// Todo: Envelopes - Extract these 0-100 schemas with better descriptions.
|
||||
positionX: z
|
||||
.number()
|
||||
.min(0)
|
||||
.max(100)
|
||||
.describe('The percentage based X position of the field on the envelope.'),
|
||||
positionY: z
|
||||
.number()
|
||||
.min(0)
|
||||
.max(100)
|
||||
.describe('The percentage based Y position of the field on the envelope.'),
|
||||
width: z
|
||||
.number()
|
||||
.min(0)
|
||||
.max(100)
|
||||
.describe('The percentage based width of the field on the envelope.'),
|
||||
height: z
|
||||
.number()
|
||||
.min(0)
|
||||
.max(100)
|
||||
.describe('The percentage based height of the field on the envelope.'),
|
||||
fieldMeta: ZFieldMetaSchema, // Todo: Envelopes - Use a more strict form?
|
||||
positionX: ZClampedFieldPositionXSchema,
|
||||
positionY: ZClampedFieldPositionYSchema,
|
||||
width: ZClampedFieldWidthSchema,
|
||||
height: ZClampedFieldHeightSchema,
|
||||
fieldMeta: ZFieldMetaSchema,
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export const ZSetEnvelopeFieldsResponseSchema = z.object({
|
||||
fields: z
|
||||
data: z
|
||||
.object({
|
||||
id: z.number(),
|
||||
formId: z.string().optional(),
|
||||
|
||||
@@ -23,7 +23,7 @@ export const setEnvelopeRecipientsRoute = authenticatedProcedure
|
||||
},
|
||||
});
|
||||
|
||||
return await match(envelopeType)
|
||||
const { recipients: data } = await match(envelopeType)
|
||||
.with(EnvelopeType.DOCUMENT, async () =>
|
||||
setDocumentRecipients({
|
||||
userId: ctx.user.id,
|
||||
@@ -48,4 +48,8 @@ export const setEnvelopeRecipientsRoute = authenticatedProcedure
|
||||
}),
|
||||
)
|
||||
.exhaustive();
|
||||
|
||||
return {
|
||||
data,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ export const ZSetEnvelopeRecipientsRequestSchema = z.object({
|
||||
});
|
||||
|
||||
export const ZSetEnvelopeRecipientsResponseSchema = z.object({
|
||||
recipients: ZRecipientLiteSchema.omit({
|
||||
data: ZRecipientLiteSchema.omit({
|
||||
documentId: true,
|
||||
templateId: true,
|
||||
}).array(),
|
||||
|
||||
@@ -133,6 +133,49 @@ export const signEnvelopeFieldRoute = procedure
|
||||
|
||||
const insertionValues = extractFieldInsertionValues({ fieldValue, field, documentMeta });
|
||||
|
||||
// Early return for uninserting fields.
|
||||
if (!insertionValues.inserted) {
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
const updatedField = await tx.field.update({
|
||||
where: {
|
||||
id: field.id,
|
||||
},
|
||||
data: {
|
||||
customText: '',
|
||||
inserted: false,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.signature.deleteMany({
|
||||
where: {
|
||||
fieldId: field.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (recipient.role !== RecipientRole.ASSISTANT) {
|
||||
await tx.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_UNINSERTED,
|
||||
envelopeId: envelope.id,
|
||||
user: {
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
},
|
||||
requestMetadata: metadata.requestMetadata,
|
||||
data: {
|
||||
field: field.type,
|
||||
fieldId: field.secondaryId,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
signedField: updatedField,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const derivedRecipientActionAuth = await validateFieldAuth({
|
||||
documentAuthOptions: envelope.authOptions,
|
||||
recipient,
|
||||
|
||||
@@ -7,9 +7,11 @@ import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZUpdateEnvelopeItemsRequestSchema,
|
||||
ZUpdateEnvelopeItemsResponseSchema,
|
||||
updateEnvelopeItemsMeta,
|
||||
} from './update-envelope-items.types';
|
||||
|
||||
export const updateEnvelopeItemsRoute = authenticatedProcedure
|
||||
.meta(updateEnvelopeItemsMeta)
|
||||
.input(ZUpdateEnvelopeItemsRequestSchema)
|
||||
.output(ZUpdateEnvelopeItemsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
@@ -93,6 +95,6 @@ export const updateEnvelopeItemsRoute = authenticatedProcedure
|
||||
// Todo: Envelope [AUDIT_LOGS]
|
||||
|
||||
return {
|
||||
updatedEnvelopeItems,
|
||||
data: updatedEnvelopeItems,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -3,6 +3,17 @@ import { z } from 'zod';
|
||||
import EnvelopeItemSchema from '@documenso/prisma/generated/zod/modelSchema/EnvelopeItemSchema';
|
||||
|
||||
import { ZDocumentTitleSchema } from '../document-router/schema';
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const updateEnvelopeItemsMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/item/update-many',
|
||||
summary: 'Update envelope items',
|
||||
description: 'Update multiple envelope items for an envelope',
|
||||
tags: ['Envelope Items'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZUpdateEnvelopeItemsRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
@@ -17,7 +28,7 @@ export const ZUpdateEnvelopeItemsRequestSchema = z.object({
|
||||
});
|
||||
|
||||
export const ZUpdateEnvelopeItemsResponseSchema = z.object({
|
||||
updatedEnvelopeItems: EnvelopeItemSchema.pick({
|
||||
data: EnvelopeItemSchema.pick({
|
||||
id: true,
|
||||
order: true,
|
||||
title: true,
|
||||
|
||||
@@ -4,10 +4,11 @@ import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZUpdateEnvelopeRequestSchema,
|
||||
ZUpdateEnvelopeResponseSchema,
|
||||
updateEnvelopeMeta,
|
||||
} from './update-envelope.types';
|
||||
|
||||
export const updateEnvelopeRoute = authenticatedProcedure
|
||||
// .meta(updateEnvelopeTrpcMeta)
|
||||
.meta(updateEnvelopeMeta)
|
||||
.input(ZUpdateEnvelopeRequestSchema)
|
||||
.output(ZUpdateEnvelopeResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
// import type { OpenApiMeta } from 'trpc-to-openapi';
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
@@ -14,19 +12,19 @@ import {
|
||||
ZDocumentTitleSchema,
|
||||
ZDocumentVisibilitySchema,
|
||||
} from '../document-router/schema';
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
// export const updateEnvelopeMeta: TrpcRouteMeta = {
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/envelope/update',
|
||||
// summary: 'Update envelope',
|
||||
// tags: ['Envelope'],
|
||||
// },
|
||||
// };
|
||||
export const updateEnvelopeMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/update',
|
||||
summary: 'Update envelope',
|
||||
tags: ['Envelope'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZUpdateEnvelopeRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
envelopeType: z.nativeEnum(EnvelopeType),
|
||||
data: z
|
||||
.object({
|
||||
title: ZDocumentTitleSchema.optional(),
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||
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 { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZUseEnvelopeRequestSchema,
|
||||
ZUseEnvelopeResponseSchema,
|
||||
useEnvelopeMeta,
|
||||
} from './use-envelope.types';
|
||||
|
||||
export const useEnvelopeRoute = authenticatedProcedure
|
||||
.meta(useEnvelopeMeta)
|
||||
.input(ZUseEnvelopeRequestSchema)
|
||||
.output(ZUseEnvelopeResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
|
||||
const { payload, files = [] } = input;
|
||||
|
||||
const {
|
||||
envelopeId,
|
||||
externalId,
|
||||
recipients,
|
||||
distributeDocument,
|
||||
customDocumentData = [],
|
||||
folderId,
|
||||
prefillFields,
|
||||
override,
|
||||
attachments,
|
||||
} = payload;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
envelopeId,
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
|
||||
const limits = await getServerLimits({ userId: user.id, teamId });
|
||||
|
||||
if (limits.remaining.documents === 0) {
|
||||
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
|
||||
message: 'You have reached your document limit.',
|
||||
});
|
||||
}
|
||||
|
||||
// Verify the template exists and get envelope items
|
||||
const envelope = await getEnvelopeById({
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
userId: user.id,
|
||||
teamId,
|
||||
});
|
||||
|
||||
if (files.length > envelope.envelopeItems.length) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: `You cannot upload more than ${envelope.envelopeItems.length} envelope items per envelope`,
|
||||
});
|
||||
}
|
||||
|
||||
const filesToUpload = files.filter(
|
||||
(file, index) =>
|
||||
payload.customDocumentData &&
|
||||
payload.customDocumentData.some(
|
||||
(mapping) => mapping.identifier === file.name || mapping.identifier === index,
|
||||
),
|
||||
);
|
||||
|
||||
// Process uploaded files and create document data for them
|
||||
const uploadedFiles = await Promise.all(
|
||||
filesToUpload.map(async (file) => {
|
||||
const { id: documentDataId } = await putNormalizedPdfFileServerSide(file);
|
||||
|
||||
return {
|
||||
name: file.name,
|
||||
documentDataId,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
// Map custom document data using identifiers
|
||||
const customDocumentDataMapped = customDocumentData?.map((mapping) => {
|
||||
let documentDataId: string | undefined;
|
||||
|
||||
// Find the uploaded file by identifier
|
||||
if (typeof mapping.identifier === 'string') {
|
||||
documentDataId = uploadedFiles.find(
|
||||
(file) => file.name === mapping.identifier,
|
||||
)?.documentDataId;
|
||||
}
|
||||
|
||||
if (typeof mapping.identifier === 'number') {
|
||||
documentDataId = uploadedFiles.at(mapping.identifier)?.documentDataId;
|
||||
}
|
||||
|
||||
if (mapping.identifier === undefined) {
|
||||
documentDataId = uploadedFiles.at(0)?.documentDataId;
|
||||
}
|
||||
|
||||
if (!documentDataId) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: `File with identifier "${mapping.identifier}" not found in uploaded files`,
|
||||
});
|
||||
}
|
||||
|
||||
// Verify that the envelopeItemId exists in the template
|
||||
const envelopeItem = envelope.envelopeItems.find(
|
||||
(item) => item.id === mapping.envelopeItemId,
|
||||
);
|
||||
|
||||
if (!envelopeItem) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: `Envelope item with ID "${mapping.envelopeItemId}" not found in template`,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
documentDataId,
|
||||
envelopeItemId: mapping.envelopeItemId,
|
||||
};
|
||||
});
|
||||
|
||||
// Create document from template
|
||||
const createdEnvelope = await createDocumentFromTemplate({
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
externalId,
|
||||
teamId,
|
||||
userId: user.id,
|
||||
recipients,
|
||||
customDocumentData: customDocumentDataMapped,
|
||||
requestMetadata: ctx.metadata,
|
||||
folderId,
|
||||
prefillFields,
|
||||
override,
|
||||
attachments,
|
||||
});
|
||||
|
||||
// Distribute document if requested
|
||||
if (distributeDocument) {
|
||||
await sendDocument({
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: createdEnvelope.id,
|
||||
},
|
||||
userId: user.id,
|
||||
teamId,
|
||||
requestMetadata: ctx.metadata,
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
|
||||
throw new AppError('DOCUMENT_SEND_FAILED');
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
id: createdEnvelope.id,
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,121 @@
|
||||
import { z } from 'zod';
|
||||
import { zfd } from 'zod-form-data';
|
||||
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
ZDocumentMetaDrawSignatureEnabledSchema,
|
||||
ZDocumentMetaLanguageSchema,
|
||||
ZDocumentMetaMessageSchema,
|
||||
ZDocumentMetaRedirectUrlSchema,
|
||||
ZDocumentMetaSubjectSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
ZDocumentMetaTypedSignatureEnabledSchema,
|
||||
ZDocumentMetaUploadSignatureEnabledSchema,
|
||||
} from '@documenso/lib/types/document-meta';
|
||||
import { ZEnvelopeAttachmentTypeSchema } from '@documenso/lib/types/envelope-attachment';
|
||||
import { ZFieldMetaPrefillFieldsSchema } from '@documenso/lib/types/field-meta';
|
||||
|
||||
import { zodFormData } from '../../utils/zod-form-data';
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const useEnvelopeMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/use',
|
||||
contentTypes: ['multipart/form-data'],
|
||||
summary: 'Use envelope',
|
||||
description:
|
||||
'Create a document envelope from a template envelope. Upload custom files to replace the template PDFs and map them to specific envelope items using identifiers.',
|
||||
tags: ['Envelope'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZUseEnvelopePayloadSchema = z.object({
|
||||
envelopeId: z.string().describe('The ID of the template envelope to use.'),
|
||||
externalId: z.string().optional().describe('External ID for the created document.'),
|
||||
recipients: z
|
||||
.array(
|
||||
z.object({
|
||||
id: z.number().describe('The ID of the recipient in the template.'),
|
||||
email: z.string().email().max(254),
|
||||
name: z.string().max(255).optional(),
|
||||
signingOrder: z.number().optional(),
|
||||
}),
|
||||
)
|
||||
.describe('The information of the recipients to create the document with.'),
|
||||
distributeDocument: z
|
||||
.boolean()
|
||||
.describe('Whether to create the document as pending and distribute it to recipients.')
|
||||
.optional(),
|
||||
customDocumentData: z
|
||||
.array(
|
||||
z.object({
|
||||
identifier: z
|
||||
.union([z.string(), z.number()])
|
||||
.describe(
|
||||
'Either the filename or the index of the file that was uploaded. This maps to which envelope item in the template should use this file.',
|
||||
),
|
||||
envelopeItemId: z
|
||||
.string()
|
||||
.describe('The envelope item ID from the template to replace with the uploaded file.'),
|
||||
}),
|
||||
)
|
||||
.describe(
|
||||
'Map uploaded files to specific envelope items in the template. If not provided, files will be ignored.',
|
||||
)
|
||||
.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(),
|
||||
prefillFields: z
|
||||
.array(ZFieldMetaPrefillFieldsSchema)
|
||||
.describe(
|
||||
'The fields to prefill on the document before sending it out. Useful when you want to create a document from an existing template and pre-fill the fields with specific values.',
|
||||
)
|
||||
.optional(),
|
||||
override: z
|
||||
.object({
|
||||
title: z.string().min(1).max(255).optional(),
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
message: ZDocumentMetaMessageSchema.optional(),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
|
||||
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
|
||||
allowDictateNextSigner: z.boolean().optional(),
|
||||
})
|
||||
.describe('Override values from the template for the created document.')
|
||||
.optional(),
|
||||
attachments: z
|
||||
.array(
|
||||
z.object({
|
||||
label: z.string().min(1, 'Label is required'),
|
||||
data: z.string().url('Must be a valid URL'),
|
||||
type: ZEnvelopeAttachmentTypeSchema.optional().default('link'),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const ZUseEnvelopeRequestSchema = zodFormData({
|
||||
payload: zfd.json(ZUseEnvelopePayloadSchema),
|
||||
files: zfd.repeatableOfType(zfd.file()).optional(),
|
||||
});
|
||||
|
||||
export const ZUseEnvelopeResponseSchema = z.object({
|
||||
id: z.string().describe('The ID of the created envelope.'),
|
||||
});
|
||||
|
||||
export type TUseEnvelopePayload = z.infer<typeof ZUseEnvelopePayloadSchema>;
|
||||
export type TUseEnvelopeRequest = z.infer<typeof ZUseEnvelopeRequestSchema>;
|
||||
export type TUseEnvelopeResponse = z.infer<typeof ZUseEnvelopeResponseSchema>;
|
||||
Reference in New Issue
Block a user