diff --git a/packages/api/v1/implementation.ts b/packages/api/v1/implementation.ts index 83a4b7dc8..c7ec90f2b 100644 --- a/packages/api/v1/implementation.ts +++ b/packages/api/v1/implementation.ts @@ -20,12 +20,12 @@ import { getEnvelopeWhereInput, } from '@documenso/lib/server-only/envelope/get-envelope-by-id'; import { deleteDocumentField } from '@documenso/lib/server-only/field/delete-document-field'; -import { updateDocumentFields } from '@documenso/lib/server-only/field/update-document-fields'; +import { updateEnvelopeFields } from '@documenso/lib/server-only/field/update-envelope-fields'; import { insertFormValuesInPdf } from '@documenso/lib/server-only/pdf/insert-form-values-in-pdf'; -import { deleteDocumentRecipient } from '@documenso/lib/server-only/recipient/delete-document-recipient'; +import { deleteEnvelopeRecipient } from '@documenso/lib/server-only/recipient/delete-envelope-recipient'; import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/get-recipients-for-document'; import { setDocumentRecipients } from '@documenso/lib/server-only/recipient/set-document-recipients'; -import { updateDocumentRecipients } from '@documenso/lib/server-only/recipient/update-document-recipients'; +import { updateEnvelopeRecipients } from '@documenso/lib/server-only/recipient/update-envelope-recipients'; import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template'; import { deleteTemplate } from '@documenso/lib/server-only/template/delete-template'; import { findTemplates } from '@documenso/lib/server-only/template/find-templates'; @@ -1285,7 +1285,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, { }; } - const updatedRecipient = await updateDocumentRecipients({ + const updatedRecipient = await updateEnvelopeRecipients({ userId: user.id, teamId: team.id, id: { @@ -1336,7 +1336,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, { }, }); - const deletedRecipient = await deleteDocumentRecipient({ + const deletedRecipient = await deleteEnvelopeRecipient({ userId: user.id, teamId: team.id, recipientId: Number(recipientId), @@ -1634,10 +1634,13 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, { }; } - const { fields } = await updateDocumentFields({ + const { fields } = await updateEnvelopeFields({ userId: user.id, teamId: team.id, - documentId: legacyDocumentId, + id: { + type: 'documentId', + id: legacyDocumentId, + }, fields: [ { id: Number(fieldId), diff --git a/packages/lib/server-only/field/get-field-by-id.ts b/packages/lib/server-only/field/get-field-by-id.ts index dee488ff6..5a1437e34 100644 --- a/packages/lib/server-only/field/get-field-by-id.ts +++ b/packages/lib/server-only/field/get-field-by-id.ts @@ -11,7 +11,7 @@ export type GetFieldByIdOptions = { userId: number; teamId: number; fieldId: number; - envelopeType: EnvelopeType; + envelopeType?: EnvelopeType; }; export const getFieldById = async ({ @@ -41,7 +41,7 @@ export const getFieldById = async ({ type: 'envelopeId', id: field.envelopeId, }, - type: envelopeType, + type: envelopeType ?? null, userId, teamId, }); diff --git a/packages/lib/server-only/field/update-document-fields.ts b/packages/lib/server-only/field/update-envelope-fields.ts similarity index 63% rename from packages/lib/server-only/field/update-document-fields.ts rename to packages/lib/server-only/field/update-envelope-fields.ts index 2df65d436..80e18efbd 100644 --- a/packages/lib/server-only/field/update-document-fields.ts +++ b/packages/lib/server-only/field/update-envelope-fields.ts @@ -10,18 +10,21 @@ import { import { prisma } from '@documenso/prisma'; import { AppError, AppErrorCode } from '../../errors/app-error'; +import { type EnvelopeIdOptions } from '../../utils/envelope'; import { mapFieldToLegacyField } from '../../utils/fields'; import { canRecipientFieldsBeModified } from '../../utils/recipients'; import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id'; -export interface UpdateDocumentFieldsOptions { +export interface UpdateEnvelopeFieldsOptions { userId: number; teamId: number; - documentId: number; + id: EnvelopeIdOptions; + type?: EnvelopeType | null; // Only used to enforce the type. fields: { id: number; type?: FieldType; pageNumber?: number; + envelopeItemId?: string; pageX?: number; pageY?: number; width?: number; @@ -31,19 +34,17 @@ export interface UpdateDocumentFieldsOptions { requestMetadata: ApiRequestMetadata; } -export const updateDocumentFields = async ({ +export const updateEnvelopeFields = async ({ userId, teamId, - documentId, + id, + type = null, fields, requestMetadata, -}: UpdateDocumentFieldsOptions) => { +}: UpdateEnvelopeFieldsOptions) => { const { envelopeWhereInput } = await getEnvelopeWhereInput({ - id: { - type: 'documentId', - id: documentId, - }, - type: EnvelopeType.DOCUMENT, + id, + type, userId, teamId, }); @@ -53,18 +54,19 @@ export const updateDocumentFields = async ({ include: { recipients: true, fields: true, + envelopeItems: true, }, }); if (!envelope) { throw new AppError(AppErrorCode.NOT_FOUND, { - message: 'Document not found', + message: 'Envelope not found', }); } if (envelope.completedAt) { throw new AppError(AppErrorCode.INVALID_REQUEST, { - message: 'Document already complete', + message: 'Envelope already complete', }); } @@ -96,6 +98,29 @@ export const updateDocumentFields = async ({ }); } + const fieldType = field.type || originalField.type; + const fieldMetaType = field.fieldMeta?.type || originalField.fieldMeta?.type; + + // Not going to mess with V1 envelopes. + if ( + envelope.internalVersion === 2 && + fieldMetaType && + fieldMetaType.toLowerCase() !== fieldType.toLowerCase() + ) { + throw new AppError(AppErrorCode.INVALID_REQUEST, { + message: 'Field meta type does not match the field type', + }); + } + + if ( + field.envelopeItemId && + !envelope.envelopeItems.some((item) => item.id === field.envelopeItemId) + ) { + throw new AppError(AppErrorCode.INVALID_REQUEST, { + message: 'Envelope item not found', + }); + } + return { originalField, updateData: field, @@ -118,27 +143,30 @@ export const updateDocumentFields = async ({ width: updateData.width, height: updateData.height, fieldMeta: updateData.fieldMeta, + envelopeItemId: updateData.envelopeItemId, }, }); - const changes = diffFieldChanges(originalField, updatedField); - // Handle field updated audit log. - if (changes.length > 0) { - await tx.documentAuditLog.create({ - data: createDocumentAuditLogData({ - type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_UPDATED, - envelopeId: envelope.id, - metadata: requestMetadata, - data: { - fieldId: updatedField.secondaryId, - fieldRecipientEmail: recipientEmail, - fieldRecipientId: updatedField.recipientId, - fieldType: updatedField.type, - changes, - }, - }), - }); + if (envelope.type === EnvelopeType.DOCUMENT) { + const changes = diffFieldChanges(originalField, updatedField); + + if (changes.length > 0) { + await tx.documentAuditLog.create({ + data: createDocumentAuditLogData({ + type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_UPDATED, + envelopeId: envelope.id, + metadata: requestMetadata, + data: { + fieldId: updatedField.secondaryId, + fieldRecipientEmail: recipientEmail, + fieldRecipientId: updatedField.recipientId, + fieldType: updatedField.type, + changes, + }, + }), + }); + } } return updatedField; diff --git a/packages/lib/server-only/field/update-template-fields.ts b/packages/lib/server-only/field/update-template-fields.ts deleted file mode 100644 index cd05ebbd7..000000000 --- a/packages/lib/server-only/field/update-template-fields.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { EnvelopeType, type FieldType } from '@prisma/client'; - -import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta'; -import { prisma } from '@documenso/prisma'; - -import { AppError, AppErrorCode } from '../../errors/app-error'; -import { mapFieldToLegacyField } from '../../utils/fields'; -import { canRecipientFieldsBeModified } from '../../utils/recipients'; -import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id'; - -export interface UpdateTemplateFieldsOptions { - userId: number; - teamId: number; - templateId: number; - fields: { - id: number; - type?: FieldType; - pageNumber?: number; - pageX?: number; - pageY?: number; - width?: number; - height?: number; - fieldMeta?: TFieldMetaSchema; - }[]; -} - -export const updateTemplateFields = async ({ - userId, - teamId, - templateId, - fields, -}: UpdateTemplateFieldsOptions) => { - const { envelopeWhereInput } = await getEnvelopeWhereInput({ - id: { - type: 'templateId', - id: templateId, - }, - type: EnvelopeType.TEMPLATE, - userId, - teamId, - }); - - const envelope = await prisma.envelope.findFirst({ - where: envelopeWhereInput, - include: { - recipients: true, - fields: true, - }, - }); - - if (!envelope) { - throw new AppError(AppErrorCode.NOT_FOUND, { - message: 'Document not found', - }); - } - - const fieldsToUpdate = fields.map((field) => { - const originalField = envelope.fields.find((existingField) => existingField.id === field.id); - - if (!originalField) { - throw new AppError(AppErrorCode.NOT_FOUND, { - message: `Field with id ${field.id} not found`, - }); - } - - const recipient = envelope.recipients.find( - (recipient) => recipient.id === originalField.recipientId, - ); - - // Each field MUST have a recipient associated with it. - if (!recipient) { - throw new AppError(AppErrorCode.INVALID_REQUEST, { - message: `Recipient attached to field ${field.id} not found`, - }); - } - - // Check whether the recipient associated with the field can be modified. - if (!canRecipientFieldsBeModified(recipient, envelope.fields)) { - throw new AppError(AppErrorCode.INVALID_REQUEST, { - message: - 'Cannot modify a field where the recipient has already interacted with the document', - }); - } - - return { - updateData: field, - }; - }); - - const updatedFields = await prisma.$transaction(async (tx) => { - return await Promise.all( - fieldsToUpdate.map(async ({ updateData }) => { - const updatedField = await tx.field.update({ - where: { - id: updateData.id, - }, - data: { - type: updateData.type, - page: updateData.pageNumber, - positionX: updateData.pageX, - positionY: updateData.pageY, - width: updateData.width, - height: updateData.height, - fieldMeta: updateData.fieldMeta, - }, - }); - - return updatedField; - }), - ); - }); - - return { - fields: updatedFields.map((field) => mapFieldToLegacyField(field, envelope)), - }; -}; diff --git a/packages/lib/server-only/recipient/create-document-recipients.ts b/packages/lib/server-only/recipient/create-envelope-recipients.ts similarity index 79% rename from packages/lib/server-only/recipient/create-document-recipients.ts rename to packages/lib/server-only/recipient/create-envelope-recipients.ts index d2b2e0d13..eaa5651f1 100644 --- a/packages/lib/server-only/recipient/create-document-recipients.ts +++ b/packages/lib/server-only/recipient/create-envelope-recipients.ts @@ -15,7 +15,7 @@ import type { EnvelopeIdOptions } from '../../utils/envelope'; import { mapRecipientToLegacyRecipient } from '../../utils/recipients'; import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id'; -export interface CreateDocumentRecipientsOptions { +export interface CreateEnvelopeRecipientsOptions { userId: number; teamId: number; id: EnvelopeIdOptions; @@ -30,16 +30,16 @@ export interface CreateDocumentRecipientsOptions { requestMetadata: ApiRequestMetadata; } -export const createDocumentRecipients = async ({ +export const createEnvelopeRecipients = async ({ userId, teamId, id, recipients: recipientsToCreate, requestMetadata, -}: CreateDocumentRecipientsOptions) => { +}: CreateEnvelopeRecipientsOptions) => { const { envelopeWhereInput } = await getEnvelopeWhereInput({ id, - type: EnvelopeType.DOCUMENT, + type: null, userId, teamId, }); @@ -62,13 +62,13 @@ export const createDocumentRecipients = async ({ if (!envelope) { throw new AppError(AppErrorCode.NOT_FOUND, { - message: 'Document not found', + message: 'Envelope not found', }); } if (envelope.completedAt) { throw new AppError(AppErrorCode.INVALID_REQUEST, { - message: 'Document already complete', + message: 'Envelope already complete', }); } @@ -112,21 +112,23 @@ export const createDocumentRecipients = async ({ }); // Handle recipient created audit log. - await tx.documentAuditLog.create({ - data: createDocumentAuditLogData({ - type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_CREATED, - envelopeId: envelope.id, - metadata: requestMetadata, - data: { - recipientEmail: createdRecipient.email, - recipientName: createdRecipient.name, - recipientId: createdRecipient.id, - recipientRole: createdRecipient.role, - accessAuth: recipient.accessAuth ?? [], - actionAuth: recipient.actionAuth ?? [], - }, - }), - }); + if (envelope.type === EnvelopeType.DOCUMENT) { + await tx.documentAuditLog.create({ + data: createDocumentAuditLogData({ + type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_CREATED, + envelopeId: envelope.id, + metadata: requestMetadata, + data: { + recipientEmail: createdRecipient.email, + recipientName: createdRecipient.name, + recipientId: createdRecipient.id, + recipientRole: createdRecipient.role, + accessAuth: recipient.accessAuth ?? [], + actionAuth: recipient.actionAuth ?? [], + }, + }), + }); + } return createdRecipient; }), diff --git a/packages/lib/server-only/recipient/create-template-recipients.ts b/packages/lib/server-only/recipient/create-template-recipients.ts deleted file mode 100644 index e64e00cfe..000000000 --- a/packages/lib/server-only/recipient/create-template-recipients.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { EnvelopeType, RecipientRole } from '@prisma/client'; -import { SendStatus, SigningStatus } from '@prisma/client'; - -import type { TRecipientAccessAuthTypes } from '@documenso/lib/types/document-auth'; -import { type TRecipientActionAuthTypes } from '@documenso/lib/types/document-auth'; -import { nanoid } from '@documenso/lib/universal/id'; -import { createRecipientAuthOptions } from '@documenso/lib/utils/document-auth'; -import { prisma } from '@documenso/prisma'; - -import { AppError, AppErrorCode } from '../../errors/app-error'; -import { mapRecipientToLegacyRecipient } from '../../utils/recipients'; -import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id'; - -export interface CreateTemplateRecipientsOptions { - userId: number; - teamId: number; - templateId: number; - recipients: { - email: string; - name: string; - role: RecipientRole; - signingOrder?: number | null; - accessAuth?: TRecipientAccessAuthTypes[]; - actionAuth?: TRecipientActionAuthTypes[]; - }[]; -} - -export const createTemplateRecipients = async ({ - userId, - teamId, - templateId, - recipients: recipientsToCreate, -}: CreateTemplateRecipientsOptions) => { - const { envelopeWhereInput } = await getEnvelopeWhereInput({ - id: { - type: 'templateId', - id: templateId, - }, - type: EnvelopeType.TEMPLATE, - userId, - teamId, - }); - - const template = await prisma.envelope.findFirst({ - where: envelopeWhereInput, - include: { - recipients: true, - team: { - select: { - organisation: { - select: { - organisationClaim: true, - }, - }, - }, - }, - }, - }); - - if (!template) { - throw new AppError(AppErrorCode.NOT_FOUND, { - message: 'Template not found', - }); - } - - const recipientsHaveActionAuth = recipientsToCreate.some( - (recipient) => recipient.actionAuth && recipient.actionAuth.length > 0, - ); - - // Check if user has permission to set the global action auth. - if (recipientsHaveActionAuth && !template.team.organisation.organisationClaim.flags.cfr21) { - throw new AppError(AppErrorCode.UNAUTHORIZED, { - message: 'You do not have permission to set the action auth', - }); - } - - const normalizedRecipients = recipientsToCreate.map((recipient) => ({ - ...recipient, - email: recipient.email.toLowerCase(), - })); - - const createdRecipients = await prisma.$transaction(async (tx) => { - return await Promise.all( - normalizedRecipients.map(async (recipient) => { - const authOptions = createRecipientAuthOptions({ - accessAuth: recipient.accessAuth ?? [], - actionAuth: recipient.actionAuth ?? [], - }); - - const createdRecipient = await tx.recipient.create({ - data: { - envelopeId: template.id, - name: recipient.name, - email: recipient.email, - role: recipient.role, - signingOrder: recipient.signingOrder, - token: nanoid(), - sendStatus: recipient.role === RecipientRole.CC ? SendStatus.SENT : SendStatus.NOT_SENT, - signingStatus: - recipient.role === RecipientRole.CC ? SigningStatus.SIGNED : SigningStatus.NOT_SIGNED, - authOptions, - }, - }); - - return createdRecipient; - }), - ); - }); - - return { - recipients: createdRecipients.map((recipient) => - mapRecipientToLegacyRecipient(recipient, template), - ), - }; -}; diff --git a/packages/lib/server-only/recipient/delete-document-recipient.ts b/packages/lib/server-only/recipient/delete-envelope-recipient.ts similarity index 72% rename from packages/lib/server-only/recipient/delete-document-recipient.ts rename to packages/lib/server-only/recipient/delete-envelope-recipient.ts index dfefd1f23..29004c3a6 100644 --- a/packages/lib/server-only/recipient/delete-document-recipient.ts +++ b/packages/lib/server-only/recipient/delete-envelope-recipient.ts @@ -14,26 +14,27 @@ import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app'; import { AppError, AppErrorCode } from '../../errors/app-error'; import { extractDerivedDocumentEmailSettings } from '../../types/document-email'; import { createDocumentAuditLogData } from '../../utils/document-audit-logs'; +import { canRecipientBeModified } from '../../utils/recipients'; import { renderEmailWithI18N } from '../../utils/render-email-with-i18n'; import { buildTeamWhereQuery } from '../../utils/teams'; import { getEmailContext } from '../email/get-email-context'; +import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id'; -export interface DeleteDocumentRecipientOptions { +export interface DeleteEnvelopeRecipientOptions { userId: number; teamId: number; recipientId: number; requestMetadata: ApiRequestMetadata; } -export const deleteDocumentRecipient = async ({ +export const deleteEnvelopeRecipient = async ({ userId, teamId, recipientId, requestMetadata, -}: DeleteDocumentRecipientOptions) => { +}: DeleteEnvelopeRecipientOptions) => { const envelope = await prisma.envelope.findFirst({ where: { - type: EnvelopeType.DOCUMENT, recipients: { some: { id: recipientId, @@ -48,6 +49,9 @@ export const deleteDocumentRecipient = async ({ where: { id: recipientId, }, + include: { + fields: true, + }, }, }, }); @@ -89,24 +93,43 @@ export const deleteDocumentRecipient = async ({ }); } - const deletedRecipient = await prisma.$transaction(async (tx) => { - await tx.documentAuditLog.create({ - data: createDocumentAuditLogData({ - type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_DELETED, - envelopeId: envelope.id, - metadata: requestMetadata, - data: { - recipientEmail: recipientToDelete.email, - recipientName: recipientToDelete.name, - recipientId: recipientToDelete.id, - recipientRole: recipientToDelete.role, - }, - }), + if (!canRecipientBeModified(recipientToDelete, recipientToDelete.fields)) { + throw new AppError(AppErrorCode.INVALID_REQUEST, { + message: 'Recipient has already interacted with the document.', }); + } + + const { envelopeWhereInput } = await getEnvelopeWhereInput({ + id: { + type: 'envelopeId', + id: envelope.id, + }, + type: null, + userId, + teamId, + }); + + const deletedRecipient = await prisma.$transaction(async (tx) => { + if (envelope.type === EnvelopeType.DOCUMENT) { + await tx.documentAuditLog.create({ + data: createDocumentAuditLogData({ + type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_DELETED, + envelopeId: envelope.id, + metadata: requestMetadata, + data: { + recipientEmail: recipientToDelete.email, + recipientName: recipientToDelete.name, + recipientId: recipientToDelete.id, + recipientRole: recipientToDelete.role, + }, + }), + }); + } return await tx.recipient.delete({ where: { id: recipientId, + envelope: envelopeWhereInput, }, }); }); @@ -116,7 +139,11 @@ export const deleteDocumentRecipient = async ({ ).recipientRemoved; // Send email to deleted recipient. - if (recipientToDelete.sendStatus === SendStatus.SENT && isRecipientRemovedEmailEnabled) { + if ( + recipientToDelete.sendStatus === SendStatus.SENT && + isRecipientRemovedEmailEnabled && + envelope.type === EnvelopeType.DOCUMENT + ) { const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000'; const template = createElement(RecipientRemovedFromDocumentTemplate, { diff --git a/packages/lib/server-only/recipient/delete-template-recipient.ts b/packages/lib/server-only/recipient/delete-template-recipient.ts deleted file mode 100644 index 839526f3b..000000000 --- a/packages/lib/server-only/recipient/delete-template-recipient.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { EnvelopeType } from '@prisma/client'; - -import { prisma } from '@documenso/prisma'; - -import { AppError, AppErrorCode } from '../../errors/app-error'; -import { buildTeamWhereQuery } from '../../utils/teams'; -import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id'; - -export interface DeleteTemplateRecipientOptions { - userId: number; - teamId: number; - recipientId: number; -} - -export const deleteTemplateRecipient = async ({ - userId, - teamId, - recipientId, -}: DeleteTemplateRecipientOptions): Promise => { - const recipientToDelete = await prisma.recipient.findFirst({ - where: { - id: recipientId, - envelope: { - type: EnvelopeType.TEMPLATE, - team: buildTeamWhereQuery({ teamId, userId }), - }, - }, - }); - - if (!recipientToDelete) { - throw new AppError(AppErrorCode.NOT_FOUND, { - message: 'Recipient not found', - }); - } - - const { envelopeWhereInput } = await getEnvelopeWhereInput({ - id: { - type: 'envelopeId', - id: recipientToDelete.envelopeId, - }, - type: EnvelopeType.TEMPLATE, - userId, - teamId, - }); - - if (!recipientToDelete || recipientToDelete.id !== recipientId) { - throw new AppError(AppErrorCode.NOT_FOUND, { - message: 'Recipient not found', - }); - } - - await prisma.recipient.delete({ - where: { - id: recipientId, - envelope: envelopeWhereInput, - }, - }); -}; diff --git a/packages/lib/server-only/recipient/update-document-recipients.ts b/packages/lib/server-only/recipient/update-envelope-recipients.ts similarity index 77% rename from packages/lib/server-only/recipient/update-document-recipients.ts rename to packages/lib/server-only/recipient/update-envelope-recipients.ts index bede48dfe..92c365177 100644 --- a/packages/lib/server-only/recipient/update-document-recipients.ts +++ b/packages/lib/server-only/recipient/update-envelope-recipients.ts @@ -1,5 +1,4 @@ -import { EnvelopeType, RecipientRole } from '@prisma/client'; -import { SendStatus, SigningStatus } from '@prisma/client'; +import { EnvelopeType, RecipientRole, SendStatus, SigningStatus } from '@prisma/client'; import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs'; import type { TRecipientAccessAuthTypes } from '@documenso/lib/types/document-auth'; @@ -16,29 +15,38 @@ import { createRecipientAuthOptions } from '@documenso/lib/utils/document-auth'; import { prisma } from '@documenso/prisma'; import { AppError, AppErrorCode } from '../../errors/app-error'; -import { type EnvelopeIdOptions, mapSecondaryIdToDocumentId } from '../../utils/envelope'; +import { extractLegacyIds } from '../../universal/id'; +import { type EnvelopeIdOptions } from '../../utils/envelope'; import { mapFieldToLegacyField } from '../../utils/fields'; import { canRecipientBeModified } from '../../utils/recipients'; import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id'; -export interface UpdateDocumentRecipientsOptions { +export interface UpdateEnvelopeRecipientsOptions { userId: number; teamId: number; id: EnvelopeIdOptions; - recipients: RecipientData[]; + recipients: { + id: number; + email?: string; + name?: string; + role?: RecipientRole; + signingOrder?: number | null; + accessAuth?: TRecipientAccessAuthTypes[]; + actionAuth?: TRecipientActionAuthTypes[]; + }[]; requestMetadata: ApiRequestMetadata; } -export const updateDocumentRecipients = async ({ +export const updateEnvelopeRecipients = async ({ userId, teamId, id, recipients, requestMetadata, -}: UpdateDocumentRecipientsOptions) => { +}: UpdateEnvelopeRecipientsOptions) => { const { envelopeWhereInput } = await getEnvelopeWhereInput({ id, - type: EnvelopeType.DOCUMENT, + type: null, userId, teamId, }); @@ -62,13 +70,13 @@ export const updateDocumentRecipients = async ({ if (!envelope) { throw new AppError(AppErrorCode.NOT_FOUND, { - message: 'Document not found', + message: 'Envelope not found', }); } if (envelope.completedAt) { throw new AppError(AppErrorCode.INVALID_REQUEST, { - message: 'Document already complete', + message: 'Envelope already complete', }); } @@ -160,24 +168,26 @@ export const updateDocumentRecipients = async ({ }); } - const changes = diffRecipientChanges(originalRecipient, updatedRecipient); - // Handle recipient updated audit log. - if (changes.length > 0) { - await tx.documentAuditLog.create({ - data: createDocumentAuditLogData({ - type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED, - envelopeId: envelope.id, - metadata: requestMetadata, - data: { - recipientEmail: updatedRecipient.email, - recipientName: updatedRecipient.name, - recipientId: updatedRecipient.id, - recipientRole: updatedRecipient.role, - changes, - }, - }), - }); + if (envelope.type === EnvelopeType.DOCUMENT) { + const changes = diffRecipientChanges(originalRecipient, updatedRecipient); + + if (changes.length > 0) { + await tx.documentAuditLog.create({ + data: createDocumentAuditLogData({ + type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED, + envelopeId: envelope.id, + metadata: requestMetadata, + data: { + recipientEmail: updatedRecipient.email, + recipientName: updatedRecipient.name, + recipientId: updatedRecipient.id, + recipientRole: updatedRecipient.role, + changes, + }, + }), + }); + } } return updatedRecipient; @@ -188,19 +198,8 @@ export const updateDocumentRecipients = async ({ return { recipients: updatedRecipients.map((recipient) => ({ ...recipient, - documentId: mapSecondaryIdToDocumentId(envelope.secondaryId), - templateId: null, + ...extractLegacyIds(envelope), fields: recipient.fields.map((field) => mapFieldToLegacyField(field, envelope)), })), }; }; - -type RecipientData = { - id: number; - email?: string; - name?: string; - role?: RecipientRole; - signingOrder?: number | null; - accessAuth?: TRecipientAccessAuthTypes[]; - actionAuth?: TRecipientActionAuthTypes[]; -}; diff --git a/packages/lib/server-only/recipient/update-template-recipients.ts b/packages/lib/server-only/recipient/update-template-recipients.ts deleted file mode 100644 index 45bdff76b..000000000 --- a/packages/lib/server-only/recipient/update-template-recipients.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { EnvelopeType, RecipientRole } from '@prisma/client'; -import { SendStatus, SigningStatus } from '@prisma/client'; - -import type { TRecipientAccessAuthTypes } from '@documenso/lib/types/document-auth'; -import { - type TRecipientActionAuthTypes, - ZRecipientAuthOptionsSchema, -} from '@documenso/lib/types/document-auth'; -import { createRecipientAuthOptions } from '@documenso/lib/utils/document-auth'; -import { prisma } from '@documenso/prisma'; - -import { AppError, AppErrorCode } from '../../errors/app-error'; -import { mapSecondaryIdToTemplateId } from '../../utils/envelope'; -import { mapFieldToLegacyField } from '../../utils/fields'; -import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id'; - -export interface UpdateTemplateRecipientsOptions { - userId: number; - teamId: number; - templateId: number; - recipients: { - id: number; - email?: string; - name?: string; - role?: RecipientRole; - signingOrder?: number | null; - accessAuth?: TRecipientAccessAuthTypes[]; - actionAuth?: TRecipientActionAuthTypes[]; - }[]; -} - -export const updateTemplateRecipients = async ({ - userId, - teamId, - templateId, - recipients, -}: UpdateTemplateRecipientsOptions) => { - const { envelopeWhereInput } = await getEnvelopeWhereInput({ - id: { - type: 'templateId', - id: templateId, - }, - type: EnvelopeType.TEMPLATE, - userId, - teamId, - }); - - const envelope = await prisma.envelope.findFirst({ - where: envelopeWhereInput, - include: { - recipients: true, - team: { - select: { - organisation: { - select: { - organisationClaim: true, - }, - }, - }, - }, - }, - }); - - if (!envelope) { - throw new AppError(AppErrorCode.NOT_FOUND, { - message: 'Template not found', - }); - } - - const recipientsHaveActionAuth = recipients.some( - (recipient) => recipient.actionAuth && recipient.actionAuth.length > 0, - ); - - // Check if user has permission to set the global action auth. - if (recipientsHaveActionAuth && !envelope.team.organisation.organisationClaim.flags.cfr21) { - throw new AppError(AppErrorCode.UNAUTHORIZED, { - message: 'You do not have permission to set the action auth', - }); - } - - const recipientsToUpdate = recipients.map((recipient) => { - const originalRecipient = envelope.recipients.find( - (existingRecipient) => existingRecipient.id === recipient.id, - ); - - if (!originalRecipient) { - throw new AppError(AppErrorCode.NOT_FOUND, { - message: `Recipient with id ${recipient.id} not found`, - }); - } - - return { - originalRecipient, - recipientUpdateData: recipient, - }; - }); - - const updatedRecipients = await prisma.$transaction(async (tx) => { - return await Promise.all( - recipientsToUpdate.map(async ({ originalRecipient, recipientUpdateData }) => { - let authOptions = ZRecipientAuthOptionsSchema.parse(originalRecipient.authOptions); - - if ( - recipientUpdateData.actionAuth !== undefined || - recipientUpdateData.accessAuth !== undefined - ) { - authOptions = createRecipientAuthOptions({ - accessAuth: recipientUpdateData.accessAuth || authOptions.accessAuth, - actionAuth: recipientUpdateData.actionAuth || authOptions.actionAuth, - }); - } - - const mergedRecipient = { - ...originalRecipient, - ...recipientUpdateData, - }; - - const updatedRecipient = await tx.recipient.update({ - where: { - id: originalRecipient.id, - envelopeId: envelope.id, - }, - data: { - name: mergedRecipient.name, - email: mergedRecipient.email, - role: mergedRecipient.role, - signingOrder: mergedRecipient.signingOrder, - envelopeId: envelope.id, - sendStatus: - mergedRecipient.role === RecipientRole.CC ? SendStatus.SENT : SendStatus.NOT_SENT, - signingStatus: - mergedRecipient.role === RecipientRole.CC - ? SigningStatus.SIGNED - : SigningStatus.NOT_SIGNED, - authOptions, - }, - include: { - fields: true, - }, - }); - - // Clear all fields if the recipient role is changed to a type that cannot have fields. - if ( - originalRecipient.role !== updatedRecipient.role && - (updatedRecipient.role === RecipientRole.CC || - updatedRecipient.role === RecipientRole.VIEWER) - ) { - await tx.field.deleteMany({ - where: { - recipientId: updatedRecipient.id, - }, - }); - } - - return updatedRecipient; - }), - ); - }); - - return { - recipients: updatedRecipients.map((recipient) => ({ - ...recipient, - documentId: null, - templateId: mapSecondaryIdToTemplateId(envelope.secondaryId), - fields: recipient.fields.map((field) => mapFieldToLegacyField(field, envelope)), - })), - }; -}; diff --git a/packages/lib/types/envelope.ts b/packages/lib/types/envelope.ts index e0a432821..aeefddba3 100644 --- a/packages/lib/types/envelope.ts +++ b/packages/lib/types/envelope.ts @@ -37,11 +37,8 @@ export const ZEnvelopeSchema = EnvelopeSchema.pick({ userId: true, teamId: true, folderId: true, + templateId: true, }).extend({ - templateId: z - .number() - .nullish() - .describe('The ID of the template that the document was created from, if any.'), documentMeta: DocumentMetaSchema.pick({ signingOrder: true, distributionMethod: true, diff --git a/packages/lib/types/field.ts b/packages/lib/types/field.ts index 133bfc671..23ec25acd 100644 --- a/packages/lib/types/field.ts +++ b/packages/lib/types/field.ts @@ -50,6 +50,11 @@ export const ZFieldSchema = FieldSchema.pick({ templateId: z.number().nullish(), }); +export const ZEnvelopeFieldSchema = ZFieldSchema.omit({ + documentId: true, + templateId: true, +}); + export const ZFieldPageNumberSchema = z .number() .min(1) @@ -69,6 +74,30 @@ export const ZFieldWidthSchema = z.number().min(1).describe('The width of the fi export const ZFieldHeightSchema = z.number().min(1).describe('The height of the field.'); +export const ZClampedFieldPageXSchema = z + .number() + .min(0) + .max(100) + .describe('The percentage based X coordinate where the field will be placed.'); + +export const ZClampedFieldPageYSchema = z + .number() + .min(0) + .max(100) + .describe('The percentage based Y coordinate where the field will be placed.'); + +export const ZClampedFieldWidthSchema = z + .number() + .min(0) + .max(100) + .describe('The percentage based width of the field on the page.'); + +export const ZClampedFieldHeightSchema = z + .number() + .min(0) + .max(100) + .describe('The percentage based height of the field on the page.'); + // --------------------------------------------- const PrismaDecimalSchema = z.preprocess( diff --git a/packages/lib/types/recipient.ts b/packages/lib/types/recipient.ts index 0a09bdf46..ea2a0a912 100644 --- a/packages/lib/types/recipient.ts +++ b/packages/lib/types/recipient.ts @@ -95,3 +95,18 @@ export const ZRecipientManySchema = RecipientSchema.pick({ documentId: z.number().nullish(), templateId: z.number().nullish(), }); + +export const ZEnvelopeRecipientSchema = ZRecipientSchema.omit({ + documentId: true, + templateId: true, +}); + +export const ZEnvelopeRecipientLiteSchema = ZRecipientLiteSchema.omit({ + documentId: true, + templateId: true, +}); + +export const ZEnvelopeRecipientManySchema = ZRecipientManySchema.omit({ + documentId: true, + templateId: true, +}); diff --git a/packages/trpc/server/envelope-router/create-envelope-items.ts b/packages/trpc/server/envelope-router/create-envelope-items.ts index 55ed38daf..239393156 100644 --- a/packages/trpc/server/envelope-router/create-envelope-items.ts +++ b/packages/trpc/server/envelope-router/create-envelope-items.ts @@ -13,11 +13,21 @@ import { } from './create-envelope-items.types'; export const createEnvelopeItemsRoute = authenticatedProcedure + // Todo: Envelopes - Pending direct uploads + // .meta({ + // openapi: { + // method: 'POST', + // path: '/envelope/item/create-many', + // summary: 'Create envelope items', + // description: 'Create multiple envelope items for an envelope', + // tags: ['Envelope Item'], + // }, + // }) .input(ZCreateEnvelopeItemsRequestSchema) .output(ZCreateEnvelopeItemsResponseSchema) .mutation(async ({ input, ctx }) => { const { user, teamId, metadata } = ctx; - const { envelopeId, items } = input; + const { envelopeId, data: items } = input; ctx.logger.info({ input: { diff --git a/packages/trpc/server/envelope-router/create-envelope-items.types.ts b/packages/trpc/server/envelope-router/create-envelope-items.types.ts index 494e562f3..bc44b5948 100644 --- a/packages/trpc/server/envelope-router/create-envelope-items.types.ts +++ b/packages/trpc/server/envelope-router/create-envelope-items.types.ts @@ -7,7 +7,7 @@ import { ZDocumentTitleSchema } from '../document-router/schema'; export const ZCreateEnvelopeItemsRequestSchema = z.object({ envelopeId: z.string(), - items: z + data: z .object({ title: ZDocumentTitleSchema, documentDataId: z.string(), diff --git a/packages/trpc/server/envelope-router/create-envelope.ts b/packages/trpc/server/envelope-router/create-envelope.ts index 7f529362a..03cd8521e 100644 --- a/packages/trpc/server/envelope-router/create-envelope.ts +++ b/packages/trpc/server/envelope-router/create-envelope.ts @@ -9,6 +9,15 @@ import { } from './create-envelope.types'; export const createEnvelopeRoute = authenticatedProcedure + // Todo: Envelopes - Pending direct uploads + // .meta({ + // openapi: { + // method: 'POST', + // path: '/envelope/create', + // summary: 'Create envelope', + // tags: ['Envelope'], + // }, + // }) .input(ZCreateEnvelopeRequestSchema) .output(ZCreateEnvelopeResponseSchema) .mutation(async ({ input, ctx }) => { diff --git a/packages/trpc/server/envelope-router/create-envelope.types.ts b/packages/trpc/server/envelope-router/create-envelope.types.ts index ea0fee260..1b987f4a2 100644 --- a/packages/trpc/server/envelope-router/create-envelope.types.ts +++ b/packages/trpc/server/envelope-router/create-envelope.types.ts @@ -24,16 +24,6 @@ import { } from '../document-router/schema'; import { ZCreateRecipientSchema } from '../recipient-router/schema'; -// 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 ZCreateEnvelopeRequestSchema = z.object({ title: ZDocumentTitleSchema, type: z.nativeEnum(EnvelopeType), diff --git a/packages/trpc/server/envelope-router/delete-envelope-item.ts b/packages/trpc/server/envelope-router/delete-envelope-item.ts index 832322d76..22802c669 100644 --- a/packages/trpc/server/envelope-router/delete-envelope-item.ts +++ b/packages/trpc/server/envelope-router/delete-envelope-item.ts @@ -12,6 +12,15 @@ import { } from './delete-envelope-item.types'; export const deleteEnvelopeItemRoute = authenticatedProcedure + .meta({ + openapi: { + method: 'POST', + path: '/envelope/item/delete', + summary: 'Delete envelope item', + description: 'Delete an envelope item from an envelope', + tags: ['Envelope Item'], + }, + }) .input(ZDeleteEnvelopeItemRequestSchema) .output(ZDeleteEnvelopeItemResponseSchema) .mutation(async ({ input, ctx }) => { diff --git a/packages/trpc/server/envelope-router/delete-envelope.ts b/packages/trpc/server/envelope-router/delete-envelope.ts index 4691c3f5b..95f193e56 100644 --- a/packages/trpc/server/envelope-router/delete-envelope.ts +++ b/packages/trpc/server/envelope-router/delete-envelope.ts @@ -1,8 +1,10 @@ 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 { @@ -11,12 +13,19 @@ import { } from './delete-envelope.types'; export const deleteEnvelopeRoute = authenticatedProcedure - // .meta(deleteEnvelopeMeta) + .meta({ + openapi: { + method: 'POST', + path: '/envelope/delete', + summary: 'Delete envelope', + tags: ['Envelope'], + }, + }) .input(ZDeleteEnvelopeRequestSchema) .output(ZDeleteEnvelopeResponseSchema) .mutation(async ({ input, ctx }) => { const { teamId } = ctx; - const { envelopeId, envelopeType } = input; + const { envelopeId } = input; ctx.logger.info({ input: { @@ -24,7 +33,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, diff --git a/packages/trpc/server/envelope-router/delete-envelope.types.ts b/packages/trpc/server/envelope-router/delete-envelope.types.ts index c2392d379..d0b5c439a 100644 --- a/packages/trpc/server/envelope-router/delete-envelope.types.ts +++ b/packages/trpc/server/envelope-router/delete-envelope.types.ts @@ -1,18 +1,7 @@ -import { EnvelopeType } from '@prisma/client'; import { z } from 'zod'; -// 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(); diff --git a/packages/trpc/server/envelope-router/distribute-envelope.ts b/packages/trpc/server/envelope-router/distribute-envelope.ts index 003727007..7c740b9f4 100644 --- a/packages/trpc/server/envelope-router/distribute-envelope.ts +++ b/packages/trpc/server/envelope-router/distribute-envelope.ts @@ -8,7 +8,15 @@ import { } from './distribute-envelope.types'; export const distributeEnvelopeRoute = authenticatedProcedure - // .meta(distributeEnvelopeMeta) + .meta({ + openapi: { + method: 'POST', + path: '/envelope/distribute', + summary: 'Distribute envelope', + description: 'Send the envelope to recipients based on your distribution method', + tags: ['Envelope'], + }, + }) .input(ZDistributeEnvelopeRequestSchema) .output(ZDistributeEnvelopeResponseSchema) .mutation(async ({ input, ctx }) => { diff --git a/packages/trpc/server/envelope-router/distribute-envelope.types.ts b/packages/trpc/server/envelope-router/distribute-envelope.types.ts index facee7b66..a078be8f5 100644 --- a/packages/trpc/server/envelope-router/distribute-envelope.types.ts +++ b/packages/trpc/server/envelope-router/distribute-envelope.types.ts @@ -2,16 +2,6 @@ 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'], -// }, -// }; - export const ZDistributeEnvelopeRequestSchema = z.object({ envelopeId: z.string().describe('The ID of the envelope to send.'), meta: ZDocumentMetaUpdateSchema.pick({ diff --git a/packages/trpc/server/envelope-router/duplicate-envelope.ts b/packages/trpc/server/envelope-router/duplicate-envelope.ts index 3ed0aca7f..3d37194f3 100644 --- a/packages/trpc/server/envelope-router/duplicate-envelope.ts +++ b/packages/trpc/server/envelope-router/duplicate-envelope.ts @@ -7,6 +7,15 @@ import { } from './duplicate-envelope.types'; export const duplicateEnvelopeRoute = authenticatedProcedure + .meta({ + openapi: { + method: 'POST', + path: '/envelope/duplicate', + summary: 'Duplicate envelope', + description: 'Duplicate an envelope with all its settings', + tags: ['Envelope'], + }, + }) .input(ZDuplicateEnvelopeRequestSchema) .output(ZDuplicateEnvelopeResponseSchema) .mutation(async ({ input, ctx }) => { diff --git a/packages/trpc/server/envelope-router/envelope-fields/create-envelope-fields.ts b/packages/trpc/server/envelope-router/envelope-fields/create-envelope-fields.ts new file mode 100644 index 000000000..fac51bafe --- /dev/null +++ b/packages/trpc/server/envelope-router/envelope-fields/create-envelope-fields.ts @@ -0,0 +1,41 @@ +import { createEnvelopeFields } from '@documenso/lib/server-only/field/create-envelope-fields'; + +import { authenticatedProcedure } from '../../trpc'; +import { + ZCreateEnvelopeFieldsRequestSchema, + ZCreateEnvelopeFieldsResponseSchema, +} from './create-envelope-fields.types'; + +export const createEnvelopeFieldsRoute = authenticatedProcedure + .meta({ + openapi: { + method: 'POST', + path: '/envelope/field/create-many', + summary: 'Create envelope fields', + description: 'Create multiple fields for an envelope', + tags: ['Envelope Fields'], + }, + }) + .input(ZCreateEnvelopeFieldsRequestSchema) + .output(ZCreateEnvelopeFieldsResponseSchema) + .mutation(async ({ input, ctx }) => { + const { user, teamId, metadata } = ctx; + const { envelopeId, data: fields } = input; + + ctx.logger.info({ + input: { + envelopeId, + }, + }); + + return await createEnvelopeFields({ + userId: user.id, + teamId, + id: { + type: 'envelopeId', + id: envelopeId, + }, + fields, + requestMetadata: metadata, + }); + }); diff --git a/packages/trpc/server/envelope-router/envelope-fields/create-envelope-fields.types.ts b/packages/trpc/server/envelope-router/envelope-fields/create-envelope-fields.types.ts new file mode 100644 index 000000000..ff62237fc --- /dev/null +++ b/packages/trpc/server/envelope-router/envelope-fields/create-envelope-fields.types.ts @@ -0,0 +1,40 @@ +import { z } from 'zod'; + +import { + ZClampedFieldHeightSchema, + ZClampedFieldPageXSchema, + ZClampedFieldPageYSchema, + ZClampedFieldWidthSchema, + ZFieldPageNumberSchema, + ZFieldSchema, +} from '@documenso/lib/types/field'; +import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta'; + +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.', + ), + pageNumber: ZFieldPageNumberSchema, + pageX: ZClampedFieldPageXSchema, + pageY: ZClampedFieldPageYSchema, + width: ZClampedFieldWidthSchema, + height: ZClampedFieldHeightSchema, + }), +); + +export const ZCreateEnvelopeFieldsRequestSchema = z.object({ + envelopeId: z.string(), + data: ZCreateFieldSchema.array(), +}); + +export const ZCreateEnvelopeFieldsResponseSchema = z.object({ + fields: z.array(ZFieldSchema), +}); + +export type TCreateEnvelopeFieldsRequest = z.infer; +export type TCreateEnvelopeFieldsResponse = z.infer; diff --git a/packages/trpc/server/envelope-router/envelope-fields/delete-envelope-field.ts b/packages/trpc/server/envelope-router/envelope-fields/delete-envelope-field.ts new file mode 100644 index 000000000..531154d5c --- /dev/null +++ b/packages/trpc/server/envelope-router/envelope-fields/delete-envelope-field.ts @@ -0,0 +1,125 @@ +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, +} from './delete-envelope-field.types'; + +export const deleteEnvelopeFieldRoute = authenticatedProcedure + .meta({ + openapi: { + method: 'POST', + path: '/envelope/field/delete', + summary: 'Delete envelope field', + description: 'Delete an envelope field', + tags: ['Envelope Field'], + }, + }) + .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; + }); + }); diff --git a/packages/trpc/server/envelope-router/envelope-fields/delete-envelope-field.types.ts b/packages/trpc/server/envelope-router/envelope-fields/delete-envelope-field.types.ts new file mode 100644 index 000000000..09b26e51b --- /dev/null +++ b/packages/trpc/server/envelope-router/envelope-fields/delete-envelope-field.types.ts @@ -0,0 +1,10 @@ +import { z } from 'zod'; + +export const ZDeleteEnvelopeFieldRequestSchema = z.object({ + fieldId: z.number(), +}); + +export const ZDeleteEnvelopeFieldResponseSchema = z.void(); + +export type TDeleteEnvelopeFieldRequest = z.infer; +export type TDeleteEnvelopeFieldResponse = z.infer; diff --git a/packages/trpc/server/envelope-router/envelope-fields/get-envelope-field.ts b/packages/trpc/server/envelope-router/envelope-fields/get-envelope-field.ts new file mode 100644 index 000000000..72a01cf11 --- /dev/null +++ b/packages/trpc/server/envelope-router/envelope-fields/get-envelope-field.ts @@ -0,0 +1,36 @@ +import { getFieldById } from '@documenso/lib/server-only/field/get-field-by-id'; + +import { authenticatedProcedure } from '../../trpc'; +import { + ZGetEnvelopeFieldRequestSchema, + ZGetEnvelopeFieldResponseSchema, +} from './get-envelope-field.types'; + +export const getEnvelopeFieldRoute = authenticatedProcedure + .meta({ + openapi: { + method: 'GET', + path: '/envelope/field/{fieldId}', + summary: 'Get envelope field', + description: 'Returns an envelope field given an ID', + tags: ['Envelope Field'], + }, + }) + .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, + }); + }); diff --git a/packages/trpc/server/envelope-router/envelope-fields/get-envelope-field.types.ts b/packages/trpc/server/envelope-router/envelope-fields/get-envelope-field.types.ts new file mode 100644 index 000000000..ab270a202 --- /dev/null +++ b/packages/trpc/server/envelope-router/envelope-fields/get-envelope-field.types.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; + +import { ZEnvelopeFieldSchema } from '@documenso/lib/types/field'; + +export const ZGetEnvelopeFieldRequestSchema = z.object({ + fieldId: z.number(), +}); + +export const ZGetEnvelopeFieldResponseSchema = ZEnvelopeFieldSchema; + +export type TGetEnvelopeFieldRequest = z.infer; +export type TGetEnvelopeFieldResponse = z.infer; diff --git a/packages/trpc/server/envelope-router/envelope-fields/update-envelope-fields.ts b/packages/trpc/server/envelope-router/envelope-fields/update-envelope-fields.ts new file mode 100644 index 000000000..62814959f --- /dev/null +++ b/packages/trpc/server/envelope-router/envelope-fields/update-envelope-fields.ts @@ -0,0 +1,42 @@ +import { updateEnvelopeFields } from '@documenso/lib/server-only/field/update-envelope-fields'; + +import { authenticatedProcedure } from '../../trpc'; +import { + ZUpdateEnvelopeFieldsRequestSchema, + ZUpdateEnvelopeFieldsResponseSchema, +} from './update-envelope-fields.types'; + +export const updateEnvelopeFieldsRoute = authenticatedProcedure + .meta({ + openapi: { + method: 'POST', + path: '/envelope/field/update-many', + summary: 'Update envelope fields', + description: 'Update multiple envelope fields for an envelope', + tags: ['Envelope Field'], + }, + }) + .input(ZUpdateEnvelopeFieldsRequestSchema) + .output(ZUpdateEnvelopeFieldsResponseSchema) + .mutation(async ({ input, ctx }) => { + const { user, teamId } = ctx; + const { envelopeId, data: fields } = input; + + ctx.logger.info({ + input: { + envelopeId, + }, + }); + + return await updateEnvelopeFields({ + userId: user.id, + teamId, + id: { + type: 'envelopeId', + id: envelopeId, + }, + type: null, + fields, + requestMetadata: ctx.metadata, + }); + }); diff --git a/packages/trpc/server/envelope-router/envelope-fields/update-envelope-fields.types.ts b/packages/trpc/server/envelope-router/envelope-fields/update-envelope-fields.types.ts new file mode 100644 index 000000000..b2604727b --- /dev/null +++ b/packages/trpc/server/envelope-router/envelope-fields/update-envelope-fields.types.ts @@ -0,0 +1,40 @@ +import { z } from 'zod'; + +import { + ZClampedFieldHeightSchema, + ZClampedFieldPageXSchema, + ZClampedFieldPageYSchema, + ZClampedFieldWidthSchema, + ZFieldPageNumberSchema, + ZFieldSchema, +} from '@documenso/lib/types/field'; +import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta'; + +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.', + ), + pageNumber: ZFieldPageNumberSchema.optional(), + pageX: ZClampedFieldPageXSchema.optional(), + pageY: ZClampedFieldPageYSchema.optional(), + width: ZClampedFieldWidthSchema.optional(), + height: ZClampedFieldHeightSchema.optional(), + }), +); + +export const ZUpdateEnvelopeFieldsRequestSchema = z.object({ + envelopeId: z.string(), + data: ZUpdateFieldSchema.array(), +}); + +export const ZUpdateEnvelopeFieldsResponseSchema = z.object({ + fields: z.array(ZFieldSchema), +}); + +export type TUpdateEnvelopeFieldsRequest = z.infer; +export type TUpdateEnvelopeFieldsResponse = z.infer; diff --git a/packages/trpc/server/envelope-router/envelope-recipients/create-envelope-recipients.ts b/packages/trpc/server/envelope-router/envelope-recipients/create-envelope-recipients.ts new file mode 100644 index 000000000..450aadbd9 --- /dev/null +++ b/packages/trpc/server/envelope-router/envelope-recipients/create-envelope-recipients.ts @@ -0,0 +1,41 @@ +import { createEnvelopeRecipients } from '@documenso/lib/server-only/recipient/create-envelope-recipients'; + +import { authenticatedProcedure } from '../../trpc'; +import { + ZCreateEnvelopeRecipientsRequestSchema, + ZCreateEnvelopeRecipientsResponseSchema, +} from './create-envelope-recipients.types'; + +export const createEnvelopeRecipientsRoute = authenticatedProcedure + .meta({ + openapi: { + method: 'POST', + path: '/envelope/recipient/create-many', + summary: 'Create envelope recipients', + description: 'Create multiple recipients for an envelope', + tags: ['Envelope Recipients'], + }, + }) + .input(ZCreateEnvelopeRecipientsRequestSchema) + .output(ZCreateEnvelopeRecipientsResponseSchema) + .mutation(async ({ input, ctx }) => { + const { user, teamId, metadata } = ctx; + const { envelopeId, data: recipients } = input; + + ctx.logger.info({ + input: { + envelopeId, + }, + }); + + return await createEnvelopeRecipients({ + userId: user.id, + teamId, + id: { + type: 'envelopeId', + id: envelopeId, + }, + recipients, + requestMetadata: metadata, + }); + }); diff --git a/packages/trpc/server/envelope-router/envelope-recipients/create-envelope-recipients.types.ts b/packages/trpc/server/envelope-router/envelope-recipients/create-envelope-recipients.types.ts new file mode 100644 index 000000000..6bd580881 --- /dev/null +++ b/packages/trpc/server/envelope-router/envelope-recipients/create-envelope-recipients.types.ts @@ -0,0 +1,21 @@ +import { z } from 'zod'; + +import { ZEnvelopeRecipientLiteSchema } from '@documenso/lib/types/recipient'; + +import { ZCreateRecipientSchema } from '../../recipient-router/schema'; + +export const ZCreateEnvelopeRecipientsRequestSchema = z.object({ + envelopeId: z.string(), + data: ZCreateRecipientSchema.array(), +}); + +export const ZCreateEnvelopeRecipientsResponseSchema = z.object({ + recipients: ZEnvelopeRecipientLiteSchema.array(), +}); + +export type TCreateEnvelopeRecipientsRequest = z.infer< + typeof ZCreateEnvelopeRecipientsRequestSchema +>; +export type TCreateEnvelopeRecipientsResponse = z.infer< + typeof ZCreateEnvelopeRecipientsResponseSchema +>; diff --git a/packages/trpc/server/envelope-router/envelope-recipients/delete-envelope-recipient.ts b/packages/trpc/server/envelope-router/envelope-recipients/delete-envelope-recipient.ts new file mode 100644 index 000000000..b5dee5927 --- /dev/null +++ b/packages/trpc/server/envelope-router/envelope-recipients/delete-envelope-recipient.ts @@ -0,0 +1,37 @@ +import { deleteEnvelopeRecipient } from '@documenso/lib/server-only/recipient/delete-envelope-recipient'; + +import { authenticatedProcedure } from '../../trpc'; +import { + ZDeleteEnvelopeRecipientRequestSchema, + ZDeleteEnvelopeRecipientResponseSchema, +} from './delete-envelope-recipient.types'; + +export const deleteEnvelopeRecipientRoute = authenticatedProcedure + .meta({ + openapi: { + method: 'POST', + path: '/envelope/recipient/delete', + summary: 'Delete envelope recipient', + description: 'Delete an envelope recipient', + tags: ['Envelope Recipient'], + }, + }) + .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, + }); + }); diff --git a/packages/trpc/server/envelope-router/envelope-recipients/delete-envelope-recipient.types.ts b/packages/trpc/server/envelope-router/envelope-recipients/delete-envelope-recipient.types.ts new file mode 100644 index 000000000..bf314e07f --- /dev/null +++ b/packages/trpc/server/envelope-router/envelope-recipients/delete-envelope-recipient.types.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; + +export const ZDeleteEnvelopeRecipientRequestSchema = z.object({ + recipientId: z.number(), +}); + +export const ZDeleteEnvelopeRecipientResponseSchema = z.void(); + +export type TDeleteEnvelopeRecipientRequest = z.infer; +export type TDeleteEnvelopeRecipientResponse = z.infer< + typeof ZDeleteEnvelopeRecipientResponseSchema +>; diff --git a/packages/trpc/server/envelope-router/envelope-recipients/get-envelope-recipient.ts b/packages/trpc/server/envelope-router/envelope-recipients/get-envelope-recipient.ts new file mode 100644 index 000000000..bd3d99e7a --- /dev/null +++ b/packages/trpc/server/envelope-router/envelope-recipients/get-envelope-recipient.ts @@ -0,0 +1,52 @@ +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, +} from './get-envelope-recipient.types'; + +export const getEnvelopeRecipientRoute = authenticatedProcedure + .meta({ + openapi: { + method: 'GET', + path: '/envelope/recipient/{recipientId}', + summary: 'Get envelope recipient', + description: 'Returns an envelope recipient given an ID', + tags: ['Envelope Recipient'], + }, + }) + .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; + }); diff --git a/packages/trpc/server/envelope-router/envelope-recipients/get-envelope-recipient.types.ts b/packages/trpc/server/envelope-router/envelope-recipients/get-envelope-recipient.types.ts new file mode 100644 index 000000000..5d0f9392a --- /dev/null +++ b/packages/trpc/server/envelope-router/envelope-recipients/get-envelope-recipient.types.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; + +import { ZEnvelopeRecipientSchema } from '@documenso/lib/types/recipient'; + +export const ZGetEnvelopeRecipientRequestSchema = z.object({ + recipientId: z.number(), +}); + +export const ZGetEnvelopeRecipientResponseSchema = ZEnvelopeRecipientSchema; + +export type TGetEnvelopeRecipientRequest = z.infer; +export type TGetEnvelopeRecipientResponse = z.infer; diff --git a/packages/trpc/server/envelope-router/envelope-recipients/update-envelope-recipients.ts b/packages/trpc/server/envelope-router/envelope-recipients/update-envelope-recipients.ts new file mode 100644 index 000000000..52d3e0741 --- /dev/null +++ b/packages/trpc/server/envelope-router/envelope-recipients/update-envelope-recipients.ts @@ -0,0 +1,41 @@ +import { updateEnvelopeRecipients } from '@documenso/lib/server-only/recipient/update-envelope-recipients'; + +import { authenticatedProcedure } from '../../trpc'; +import { + ZUpdateEnvelopeRecipientsRequestSchema, + ZUpdateEnvelopeRecipientsResponseSchema, +} from './update-envelope-recipients.types'; + +export const updateEnvelopeRecipientsRoute = authenticatedProcedure + .meta({ + openapi: { + method: 'POST', + path: '/envelope/recipient/update-many', + summary: 'Update envelope recipients', + description: 'Update multiple recipients for an envelope', + tags: ['Envelope Recipient'], + }, + }) + .input(ZUpdateEnvelopeRecipientsRequestSchema) + .output(ZUpdateEnvelopeRecipientsResponseSchema) + .mutation(async ({ input, ctx }) => { + const { user, teamId } = ctx; + const { envelopeId, data: recipients } = input; + + ctx.logger.info({ + input: { + envelopeId, + }, + }); + + return await updateEnvelopeRecipients({ + userId: user.id, + teamId, + id: { + type: 'envelopeId', + id: envelopeId, + }, + recipients, + requestMetadata: ctx.metadata, + }); + }); diff --git a/packages/trpc/server/envelope-router/envelope-recipients/update-envelope-recipients.types.ts b/packages/trpc/server/envelope-router/envelope-recipients/update-envelope-recipients.types.ts new file mode 100644 index 000000000..b49f96206 --- /dev/null +++ b/packages/trpc/server/envelope-router/envelope-recipients/update-envelope-recipients.types.ts @@ -0,0 +1,21 @@ +import { z } from 'zod'; + +import { ZRecipientLiteSchema } from '@documenso/lib/types/recipient'; + +import { ZUpdateRecipientSchema } from '../../recipient-router/schema'; + +export const ZUpdateEnvelopeRecipientsRequestSchema = z.object({ + envelopeId: z.string(), + data: ZUpdateRecipientSchema.array(), +}); + +export const ZUpdateEnvelopeRecipientsResponseSchema = z.object({ + recipients: ZRecipientLiteSchema.array(), +}); + +export type TUpdateEnvelopeRecipientsRequest = z.infer< + typeof ZUpdateEnvelopeRecipientsRequestSchema +>; +export type TUpdateEnvelopeRecipientsResponse = z.infer< + typeof ZUpdateEnvelopeRecipientsResponseSchema +>; diff --git a/packages/trpc/server/envelope-router/get-envelope.ts b/packages/trpc/server/envelope-router/get-envelope.ts index 4d85974ab..a8df2f9e1 100644 --- a/packages/trpc/server/envelope-router/get-envelope.ts +++ b/packages/trpc/server/envelope-router/get-envelope.ts @@ -4,7 +4,15 @@ import { authenticatedProcedure } from '../trpc'; import { ZGetEnvelopeRequestSchema, ZGetEnvelopeResponseSchema } from './get-envelope.types'; export const getEnvelopeRoute = authenticatedProcedure - // .meta(getEnvelopeMeta) + .meta({ + openapi: { + method: 'GET', + path: '/envelope/{envelopeId}', + summary: 'Get envelope', + description: 'Returns an envelope given an ID', + tags: ['Envelope'], + }, + }) .input(ZGetEnvelopeRequestSchema) .output(ZGetEnvelopeResponseSchema) .query(async ({ input, ctx }) => { diff --git a/packages/trpc/server/envelope-router/get-envelope.types.ts b/packages/trpc/server/envelope-router/get-envelope.types.ts index 44a27c94d..f266dc702 100644 --- a/packages/trpc/server/envelope-router/get-envelope.types.ts +++ b/packages/trpc/server/envelope-router/get-envelope.types.ts @@ -2,16 +2,6 @@ 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'], -// }, -// }; - export const ZGetEnvelopeRequestSchema = z.object({ envelopeId: z.string(), }); diff --git a/packages/trpc/server/envelope-router/redistribute-envelope.ts b/packages/trpc/server/envelope-router/redistribute-envelope.ts index 2c3af6680..d7dacb4f0 100644 --- a/packages/trpc/server/envelope-router/redistribute-envelope.ts +++ b/packages/trpc/server/envelope-router/redistribute-envelope.ts @@ -7,7 +7,16 @@ import { } from './redistribute-envelope.types'; export const redistributeEnvelopeRoute = authenticatedProcedure - // .meta(redistributeEnvelopeMeta) + .meta({ + 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'], + }, + }) .input(ZRedistributeEnvelopeRequestSchema) .output(ZRedistributeEnvelopeResponseSchema) .mutation(async ({ input, ctx }) => { diff --git a/packages/trpc/server/envelope-router/redistribute-envelope.types.ts b/packages/trpc/server/envelope-router/redistribute-envelope.types.ts index cd8610ab1..9d46ab77b 100644 --- a/packages/trpc/server/envelope-router/redistribute-envelope.types.ts +++ b/packages/trpc/server/envelope-router/redistribute-envelope.types.ts @@ -1,16 +1,5 @@ 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'], -// }, -// }; - export const ZRedistributeEnvelopeRequestSchema = z.object({ envelopeId: z.string(), recipients: z diff --git a/packages/trpc/server/envelope-router/router.ts b/packages/trpc/server/envelope-router/router.ts index 2e650e542..038c404e8 100644 --- a/packages/trpc/server/envelope-router/router.ts +++ b/packages/trpc/server/envelope-router/router.ts @@ -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'; @@ -27,8 +35,6 @@ export const envelopeRouter = router({ duplicate: duplicateEnvelopeRoute, distribute: distributeEnvelopeRoute, redistribute: redistributeEnvelopeRoute, - // share: shareEnvelopeRoute, - item: { getMany: getEnvelopeItemsRoute, getManyByToken: getEnvelopeItemsByTokenRoute, @@ -37,9 +43,17 @@ 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, }, diff --git a/packages/trpc/server/envelope-router/set-envelope-fields.types.ts b/packages/trpc/server/envelope-router/set-envelope-fields.types.ts index ea3fb6269..29ea2035a 100644 --- a/packages/trpc/server/envelope-router/set-envelope-fields.types.ts +++ b/packages/trpc/server/envelope-router/set-envelope-fields.types.ts @@ -1,6 +1,12 @@ import { EnvelopeType, FieldType } from '@prisma/client'; import { z } from 'zod'; +import { + ZClampedFieldHeightSchema, + ZClampedFieldPageXSchema, + ZClampedFieldPageYSchema, + ZClampedFieldWidthSchema, +} from '@documenso/lib/types/field'; import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta'; export const ZSetEnvelopeFieldsRequestSchema = z.object({ @@ -20,28 +26,11 @@ 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: ZClampedFieldPageXSchema, + positionY: ZClampedFieldPageYSchema, + width: ZClampedFieldWidthSchema, + height: ZClampedFieldHeightSchema, + fieldMeta: ZFieldMetaSchema, }), ), }); diff --git a/packages/trpc/server/envelope-router/update-envelope-items.ts b/packages/trpc/server/envelope-router/update-envelope-items.ts index 05a75bd44..b9f61091b 100644 --- a/packages/trpc/server/envelope-router/update-envelope-items.ts +++ b/packages/trpc/server/envelope-router/update-envelope-items.ts @@ -10,6 +10,15 @@ import { } from './update-envelope-items.types'; export const updateEnvelopeItemsRoute = authenticatedProcedure + .meta({ + openapi: { + method: 'POST', + path: '/envelope/item/update-many', + summary: 'Update envelope items', + description: 'Update multiple envelope items for an envelope', + tags: ['Envelope Item'], + }, + }) .input(ZUpdateEnvelopeItemsRequestSchema) .output(ZUpdateEnvelopeItemsResponseSchema) .mutation(async ({ input, ctx }) => { diff --git a/packages/trpc/server/envelope-router/update-envelope.ts b/packages/trpc/server/envelope-router/update-envelope.ts index 8de730be7..f0f2abf99 100644 --- a/packages/trpc/server/envelope-router/update-envelope.ts +++ b/packages/trpc/server/envelope-router/update-envelope.ts @@ -7,7 +7,14 @@ import { } from './update-envelope.types'; export const updateEnvelopeRoute = authenticatedProcedure - // .meta(updateEnvelopeTrpcMeta) + .meta({ + openapi: { + method: 'POST', + path: '/envelope/update', + summary: 'Update envelope', + tags: ['Envelope'], + }, + }) .input(ZUpdateEnvelopeRequestSchema) .output(ZUpdateEnvelopeResponseSchema) .mutation(async ({ input, ctx }) => { diff --git a/packages/trpc/server/envelope-router/update-envelope.types.ts b/packages/trpc/server/envelope-router/update-envelope.types.ts index a6fcc5165..6ca48036c 100644 --- a/packages/trpc/server/envelope-router/update-envelope.types.ts +++ b/packages/trpc/server/envelope-router/update-envelope.types.ts @@ -15,15 +15,6 @@ import { ZDocumentVisibilitySchema, } from '../document-router/schema'; -// 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), diff --git a/packages/trpc/server/field-router/router.ts b/packages/trpc/server/field-router/router.ts index a739f6f24..8f88ffcc1 100644 --- a/packages/trpc/server/field-router/router.ts +++ b/packages/trpc/server/field-router/router.ts @@ -8,8 +8,7 @@ import { removeSignedFieldWithToken } from '@documenso/lib/server-only/field/rem import { setFieldsForDocument } from '@documenso/lib/server-only/field/set-fields-for-document'; import { setFieldsForTemplate } from '@documenso/lib/server-only/field/set-fields-for-template'; import { signFieldWithToken } from '@documenso/lib/server-only/field/sign-field-with-token'; -import { updateDocumentFields } from '@documenso/lib/server-only/field/update-document-fields'; -import { updateTemplateFields } from '@documenso/lib/server-only/field/update-template-fields'; +import { updateEnvelopeFields } from '@documenso/lib/server-only/field/update-envelope-fields'; import { ZGenericSuccessResponse, ZSuccessResponseSchema } from '../document-router/schema'; import { authenticatedProcedure, procedure, router } from '../trpc'; @@ -178,10 +177,14 @@ export const fieldRouter = router({ }, }); - const updatedFields = await updateDocumentFields({ + const updatedFields = await updateEnvelopeFields({ userId: ctx.user.id, teamId, - documentId, + id: { + type: 'documentId', + id: documentId, + }, + type: EnvelopeType.DOCUMENT, fields: [field], requestMetadata: ctx.metadata, }); @@ -214,10 +217,14 @@ export const fieldRouter = router({ }, }); - return await updateDocumentFields({ + return await updateEnvelopeFields({ userId: ctx.user.id, teamId, - documentId, + id: { + type: 'documentId', + id: documentId, + }, + type: EnvelopeType.DOCUMENT, fields, requestMetadata: ctx.metadata, }); @@ -431,11 +438,16 @@ export const fieldRouter = router({ }, }); - const updatedFields = await updateTemplateFields({ + const updatedFields = await updateEnvelopeFields({ userId: ctx.user.id, teamId, - templateId, + id: { + type: 'templateId', + id: templateId, + }, + type: EnvelopeType.TEMPLATE, fields: [field], + requestMetadata: ctx.metadata, }); return updatedFields.fields[0]; @@ -466,11 +478,16 @@ export const fieldRouter = router({ }, }); - return await updateTemplateFields({ + return await updateEnvelopeFields({ userId: ctx.user.id, teamId, - templateId, + id: { + type: 'templateId', + id: templateId, + }, + type: EnvelopeType.TEMPLATE, fields, + requestMetadata: ctx.metadata, }); }), diff --git a/packages/trpc/server/recipient-router/router.ts b/packages/trpc/server/recipient-router/router.ts index b8ed8bc0f..532bb2171 100644 --- a/packages/trpc/server/recipient-router/router.ts +++ b/packages/trpc/server/recipient-router/router.ts @@ -2,15 +2,12 @@ import { EnvelopeType } from '@prisma/client'; import { completeDocumentWithToken } from '@documenso/lib/server-only/document/complete-document-with-token'; import { rejectDocumentWithToken } from '@documenso/lib/server-only/document/reject-document-with-token'; -import { createDocumentRecipients } from '@documenso/lib/server-only/recipient/create-document-recipients'; -import { createTemplateRecipients } from '@documenso/lib/server-only/recipient/create-template-recipients'; -import { deleteDocumentRecipient } from '@documenso/lib/server-only/recipient/delete-document-recipient'; -import { deleteTemplateRecipient } from '@documenso/lib/server-only/recipient/delete-template-recipient'; +import { createEnvelopeRecipients } from '@documenso/lib/server-only/recipient/create-envelope-recipients'; +import { deleteEnvelopeRecipient } from '@documenso/lib/server-only/recipient/delete-envelope-recipient'; import { getRecipientById } from '@documenso/lib/server-only/recipient/get-recipient-by-id'; import { setDocumentRecipients } from '@documenso/lib/server-only/recipient/set-document-recipients'; import { setTemplateRecipients } from '@documenso/lib/server-only/recipient/set-template-recipients'; -import { updateDocumentRecipients } from '@documenso/lib/server-only/recipient/update-document-recipients'; -import { updateTemplateRecipients } from '@documenso/lib/server-only/recipient/update-template-recipients'; +import { updateEnvelopeRecipients } from '@documenso/lib/server-only/recipient/update-envelope-recipients'; import { ZGenericSuccessResponse, ZSuccessResponseSchema } from '../document-router/schema'; import { authenticatedProcedure, procedure, router } from '../trpc'; @@ -108,7 +105,7 @@ export const recipientRouter = router({ }, }); - const createdRecipients = await createDocumentRecipients({ + const createdRecipients = await createEnvelopeRecipients({ userId: ctx.user.id, teamId, id: { @@ -147,7 +144,7 @@ export const recipientRouter = router({ }, }); - return await createDocumentRecipients({ + return await createEnvelopeRecipients({ userId: ctx.user.id, teamId, id: { @@ -184,7 +181,7 @@ export const recipientRouter = router({ }, }); - const updatedRecipients = await updateDocumentRecipients({ + const updatedRecipients = await updateEnvelopeRecipients({ userId: ctx.user.id, teamId, id: { @@ -223,7 +220,7 @@ export const recipientRouter = router({ }, }); - return await updateDocumentRecipients({ + return await updateEnvelopeRecipients({ userId: ctx.user.id, teamId, id: { @@ -259,7 +256,7 @@ export const recipientRouter = router({ }, }); - await deleteDocumentRecipient({ + await deleteEnvelopeRecipient({ userId: ctx.user.id, teamId, recipientId, @@ -363,11 +360,15 @@ export const recipientRouter = router({ }, }); - const createdRecipients = await createTemplateRecipients({ + const createdRecipients = await createEnvelopeRecipients({ userId: ctx.user.id, teamId, - templateId, + id: { + id: templateId, + type: 'templateId', + }, recipients: [recipient], + requestMetadata: ctx.metadata, }); return createdRecipients.recipients[0]; @@ -398,11 +399,15 @@ export const recipientRouter = router({ }, }); - return await createTemplateRecipients({ + return await createEnvelopeRecipients({ userId: ctx.user.id, teamId, - templateId, + id: { + id: templateId, + type: 'templateId', + }, recipients, + requestMetadata: ctx.metadata, }); }), @@ -431,11 +436,15 @@ export const recipientRouter = router({ }, }); - const updatedRecipients = await updateTemplateRecipients({ + const updatedRecipients = await updateEnvelopeRecipients({ userId: ctx.user.id, teamId, - templateId, + id: { + type: 'templateId', + id: templateId, + }, recipients: [recipient], + requestMetadata: ctx.metadata, }); return updatedRecipients.recipients[0]; @@ -466,11 +475,15 @@ export const recipientRouter = router({ }, }); - return await updateTemplateRecipients({ + return await updateEnvelopeRecipients({ userId: ctx.user.id, teamId, - templateId, + id: { + type: 'templateId', + id: templateId, + }, recipients, + requestMetadata: ctx.metadata, }); }), @@ -498,10 +511,11 @@ export const recipientRouter = router({ }, }); - await deleteTemplateRecipient({ + await deleteEnvelopeRecipient({ recipientId, userId: ctx.user.id, teamId, + requestMetadata: ctx.metadata, }); return ZGenericSuccessResponse;