mirror of
https://github.com/documenso/documenso.git
synced 2026-07-14 23:07:13 +10:00
fix: wip
This commit is contained in:
@@ -60,6 +60,15 @@ export const verifyEmbeddingPresignToken = async ({
|
||||
where: {
|
||||
id: tokenId,
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!apiToken) {
|
||||
@@ -69,7 +78,7 @@ export const verifyEmbeddingPresignToken = async ({
|
||||
}
|
||||
|
||||
// This should never happen but we need to narrow types
|
||||
if (!apiToken.userId) {
|
||||
if (!apiToken.userId || !apiToken.user) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'Invalid presign token: API token does not have a user attached',
|
||||
});
|
||||
@@ -119,5 +128,10 @@ export const verifyEmbeddingPresignToken = async ({
|
||||
return {
|
||||
...apiToken,
|
||||
userId,
|
||||
user: {
|
||||
id: apiToken.user.id,
|
||||
name: apiToken.user.name,
|
||||
email: apiToken.user.email,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -97,6 +97,14 @@ export type CreateEnvelopeOptions = {
|
||||
data: string;
|
||||
type?: TEnvelopeAttachmentType;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Whether to bypass adding default recipients.
|
||||
*
|
||||
* Defaults to false.
|
||||
*/
|
||||
bypassDefaultRecipients?: boolean;
|
||||
|
||||
meta?: Partial<Omit<DocumentMeta, 'id'>>;
|
||||
requestMetadata: ApiRequestMetadata;
|
||||
};
|
||||
@@ -110,6 +118,7 @@ export const createEnvelope = async ({
|
||||
meta,
|
||||
requestMetadata,
|
||||
internalVersion,
|
||||
bypassDefaultRecipients = false,
|
||||
}: CreateEnvelopeOptions) => {
|
||||
const {
|
||||
type,
|
||||
@@ -355,9 +364,10 @@ export const createEnvelope = async ({
|
||||
|
||||
const firstEnvelopeItem = envelope.envelopeItems[0];
|
||||
|
||||
const defaultRecipients = settings.defaultRecipients
|
||||
? ZDefaultRecipientsSchema.parse(settings.defaultRecipients)
|
||||
: [];
|
||||
const defaultRecipients =
|
||||
settings.defaultRecipients && !bypassDefaultRecipients
|
||||
? ZDefaultRecipientsSchema.parse(settings.defaultRecipients)
|
||||
: [];
|
||||
|
||||
const mappedDefaultRecipients: CreateEnvelopeRecipientOptions[] = defaultRecipients.map(
|
||||
(recipient) => ({
|
||||
|
||||
@@ -21,14 +21,7 @@ export type SetTemplateRecipientsOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
id: EnvelopeIdOptions;
|
||||
recipients: {
|
||||
id?: number;
|
||||
email: string;
|
||||
name: string;
|
||||
role: RecipientRole;
|
||||
signingOrder?: number | null;
|
||||
actionAuth?: TRecipientActionAuthTypes[];
|
||||
}[];
|
||||
recipients: RecipientData[];
|
||||
};
|
||||
|
||||
export const setTemplateRecipients = async ({
|
||||
@@ -183,7 +176,10 @@ export const setTemplateRecipients = async ({
|
||||
});
|
||||
}
|
||||
|
||||
return upsertedRecipient;
|
||||
return {
|
||||
...upsertedRecipient,
|
||||
clientId: recipient.clientId,
|
||||
};
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -199,7 +195,7 @@ export const setTemplateRecipients = async ({
|
||||
}
|
||||
|
||||
// Filter out recipients that have been removed or have been updated.
|
||||
const filteredRecipients: Recipient[] = existingRecipients.filter((recipient) => {
|
||||
const filteredRecipients: RecipientDataWithClientId[] = existingRecipients.filter((recipient) => {
|
||||
const isRemoved = removedRecipients.find(
|
||||
(removedRecipient) => removedRecipient.id === recipient.id,
|
||||
);
|
||||
@@ -218,3 +214,17 @@ export const setTemplateRecipients = async ({
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
||||
type RecipientData = {
|
||||
id?: number;
|
||||
clientId?: string | null;
|
||||
email: string;
|
||||
name: string;
|
||||
role: RecipientRole;
|
||||
signingOrder?: number | null;
|
||||
actionAuth?: TRecipientActionAuthTypes[];
|
||||
};
|
||||
|
||||
type RecipientDataWithClientId = Recipient & {
|
||||
clientId?: string | null;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZEnvelopeFieldSchema } from '@documenso/lib/types/field';
|
||||
import { ZEnvelopeRecipientLiteSchema } from '@documenso/lib/types/recipient';
|
||||
import { DocumentMetaSchema } from '@documenso/prisma/generated/zod/modelSchema/DocumentMetaSchema';
|
||||
import { EnvelopeItemSchema } from '@documenso/prisma/generated/zod/modelSchema/EnvelopeItemSchema';
|
||||
import { EnvelopeSchema } from '@documenso/prisma/generated/zod/modelSchema/EnvelopeSchema';
|
||||
import { TeamSchema } from '@documenso/prisma/generated/zod/modelSchema/TeamSchema';
|
||||
import { TemplateDirectLinkSchema } from '@documenso/prisma/generated/zod/modelSchema/TemplateDirectLinkSchema';
|
||||
import { ZBaseEmbedDataSchema } from '@documenso/remix/app/types/embed-base-schemas';
|
||||
|
||||
export const ZEnvelopeEditorSettingsSchema = z.object({
|
||||
/**
|
||||
* Generic editor related configurations.
|
||||
*/
|
||||
general: z.object({
|
||||
allowConfigureEnvelopeTitle: z.boolean(),
|
||||
allowUploadAndRecipientStep: z.boolean(),
|
||||
allowAddFieldsStep: z.boolean(),
|
||||
allowPreviewStep: z.boolean(),
|
||||
minimizeLeftSidebar: z.boolean(),
|
||||
}),
|
||||
|
||||
/**
|
||||
* Envelope meta/settings related configuration
|
||||
*
|
||||
* If null, the settings will not be available to be seen/updated.
|
||||
*/
|
||||
settings: z
|
||||
.object({
|
||||
allowConfigureSignatureTypes: z.boolean(),
|
||||
allowConfigureLanguage: z.boolean(),
|
||||
allowConfigureDateFormat: z.boolean(),
|
||||
allowConfigureTimezone: z.boolean(),
|
||||
allowConfigureRedirectUrl: z.boolean(),
|
||||
allowConfigureDistribution: z.boolean(),
|
||||
})
|
||||
.nullable(),
|
||||
|
||||
/**
|
||||
* Action related configurations.
|
||||
*/
|
||||
actions: z.object({
|
||||
allowAttachments: z.boolean(),
|
||||
allowDistributing: z.boolean(),
|
||||
allowDirectLink: z.boolean(),
|
||||
allowDuplication: z.boolean(),
|
||||
allowDownloadPDF: z.boolean(),
|
||||
allowDeletion: z.boolean(),
|
||||
allowReturnToPreviousPage: z.boolean(),
|
||||
}),
|
||||
|
||||
/**
|
||||
* Envelope items related configurations.
|
||||
*
|
||||
* If null, no adjustments to envelope items will be allowed.
|
||||
*/
|
||||
envelopeItems: z
|
||||
.object({
|
||||
allowConfigureTitle: z.boolean(),
|
||||
allowConfigureOrder: z.boolean(),
|
||||
allowUpload: z.boolean(),
|
||||
allowDelete: z.boolean(),
|
||||
})
|
||||
.nullable(),
|
||||
|
||||
/**
|
||||
* Recipient related configurations.
|
||||
*
|
||||
* If null, recipients will not be configurable at all.
|
||||
*/
|
||||
recipients: z
|
||||
.object({
|
||||
allowAIDetection: z.boolean(),
|
||||
allowConfigureSigningOrder: z.boolean(),
|
||||
allowConfigureDictateNextSigner: z.boolean(),
|
||||
allowApproverRole: z.boolean(),
|
||||
allowViewerRole: z.boolean(),
|
||||
allowCCerRole: z.boolean(),
|
||||
allowAssistantRole: z.boolean(),
|
||||
})
|
||||
.nullable(),
|
||||
|
||||
/**
|
||||
* Fields related configurations.
|
||||
*/
|
||||
fields: z.object({
|
||||
allowAIDetection: z.boolean(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type TEnvelopeEditorSettings = z.infer<typeof ZEnvelopeEditorSettingsSchema>;
|
||||
|
||||
export const ZEmbedCreateEnvelopeAuthoringSchema = ZBaseEmbedDataSchema.extend({
|
||||
externalId: z.string().optional(),
|
||||
type: z.nativeEnum(EnvelopeType),
|
||||
features: ZEnvelopeEditorSettingsSchema,
|
||||
});
|
||||
|
||||
export const ZEmbedEditEnvelopeAuthoringSchema = ZBaseEmbedDataSchema.extend({
|
||||
externalId: z.string().optional(),
|
||||
features: ZEnvelopeEditorSettingsSchema,
|
||||
});
|
||||
|
||||
export type TEmbedCreateEnvelopeAuthoring = z.infer<typeof ZEmbedCreateEnvelopeAuthoringSchema>;
|
||||
export type TEmbedEditEnvelopeAuthoring = z.infer<typeof ZEmbedEditEnvelopeAuthoringSchema>;
|
||||
|
||||
/**
|
||||
* A subset of the full envelope response schema used for the envelope editor.
|
||||
*
|
||||
* Internal usage only.
|
||||
*/
|
||||
export const ZEditorEnvelopeSchema = EnvelopeSchema.pick({
|
||||
internalVersion: true,
|
||||
type: true,
|
||||
status: true,
|
||||
source: true,
|
||||
visibility: true,
|
||||
templateType: true,
|
||||
id: true,
|
||||
secondaryId: true,
|
||||
externalId: true,
|
||||
completedAt: true,
|
||||
deletedAt: true,
|
||||
title: true,
|
||||
authOptions: true,
|
||||
publicTitle: true,
|
||||
publicDescription: true,
|
||||
userId: true,
|
||||
teamId: true,
|
||||
folderId: true,
|
||||
}).extend({
|
||||
documentMeta: DocumentMetaSchema.pick({
|
||||
signingOrder: true,
|
||||
distributionMethod: true,
|
||||
id: true,
|
||||
subject: true,
|
||||
message: true,
|
||||
timezone: true,
|
||||
dateFormat: true,
|
||||
redirectUrl: true,
|
||||
typedSignatureEnabled: true,
|
||||
uploadSignatureEnabled: true,
|
||||
drawSignatureEnabled: true,
|
||||
allowDictateNextSigner: true,
|
||||
language: true,
|
||||
emailSettings: true,
|
||||
emailId: true,
|
||||
emailReplyTo: true,
|
||||
}),
|
||||
recipients: ZEnvelopeRecipientLiteSchema.array(),
|
||||
fields: ZEnvelopeFieldSchema.array(),
|
||||
envelopeItems: EnvelopeItemSchema.pick({
|
||||
envelopeId: true,
|
||||
id: true,
|
||||
title: true,
|
||||
order: true,
|
||||
})
|
||||
.extend({
|
||||
data: z.instanceof(Uint8Array).optional(),
|
||||
})
|
||||
.array(),
|
||||
directLink: TemplateDirectLinkSchema.pick({
|
||||
directTemplateRecipientId: true,
|
||||
enabled: true,
|
||||
id: true,
|
||||
token: true,
|
||||
}).nullable(),
|
||||
team: TeamSchema.pick({
|
||||
id: true,
|
||||
url: true,
|
||||
}),
|
||||
user: z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
email: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type TEditorEnvelope = z.infer<typeof ZEditorEnvelopeSchema>;
|
||||
|
||||
export type EnvelopeEditorConfig = TEnvelopeEditorSettings & {
|
||||
embeded?: {
|
||||
presignToken: string;
|
||||
mode: 'create' | 'edit';
|
||||
onCreate?: (envelope: Omit<TEditorEnvelope, 'id'>) => void;
|
||||
onUpdate?: (envelope: TEditorEnvelope) => void;
|
||||
customBrandingLogo?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* The default editor configuration for normal flows.
|
||||
*/
|
||||
export const DEFAULT_EDITOR_CONFIG: EnvelopeEditorConfig = {
|
||||
general: {
|
||||
allowConfigureEnvelopeTitle: true,
|
||||
allowUploadAndRecipientStep: true,
|
||||
allowAddFieldsStep: true,
|
||||
allowPreviewStep: true,
|
||||
minimizeLeftSidebar: false,
|
||||
},
|
||||
settings: {
|
||||
allowConfigureSignatureTypes: true,
|
||||
allowConfigureLanguage: true,
|
||||
allowConfigureDateFormat: true,
|
||||
allowConfigureTimezone: true,
|
||||
allowConfigureRedirectUrl: true,
|
||||
allowConfigureDistribution: true,
|
||||
},
|
||||
actions: {
|
||||
allowAttachments: true,
|
||||
allowDistributing: true,
|
||||
allowDirectLink: true,
|
||||
allowDuplication: true,
|
||||
allowDownloadPDF: true,
|
||||
allowDeletion: true,
|
||||
allowReturnToPreviousPage: true,
|
||||
},
|
||||
envelopeItems: {
|
||||
allowConfigureTitle: true,
|
||||
allowConfigureOrder: true,
|
||||
allowUpload: true,
|
||||
allowDelete: true,
|
||||
},
|
||||
recipients: {
|
||||
allowAIDetection: true,
|
||||
allowConfigureSigningOrder: true,
|
||||
allowConfigureDictateNextSigner: true,
|
||||
|
||||
allowApproverRole: true,
|
||||
allowViewerRole: true,
|
||||
allowCCerRole: true,
|
||||
allowAssistantRole: true,
|
||||
},
|
||||
fields: {
|
||||
allowAIDetection: true,
|
||||
},
|
||||
};
|
||||
@@ -419,4 +419,4 @@ export const ZEnvelopeFieldAndMetaSchema = z.discriminatedUnion('type', [
|
||||
}),
|
||||
]);
|
||||
|
||||
type TEnvelopeFieldAndMeta = z.infer<typeof ZEnvelopeFieldAndMetaSchema>;
|
||||
export type TEnvelopeFieldAndMeta = z.infer<typeof ZEnvelopeFieldAndMetaSchema>;
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
import type { EnvelopeEditorConfig, TEnvelopeEditorSettings } from '../types/envelope-editor';
|
||||
|
||||
export const PRESIGNED_ENVELOPE_ITEM_ID_PREFIX = 'PRESIGNED_';
|
||||
|
||||
/**
|
||||
* Embedded-mode defaults for the editor config.
|
||||
*
|
||||
* - `general`: All true, `minimizeLeftSidebar: true`
|
||||
* - `settings`: All true
|
||||
* - `actions`: `allowAttachments: true`; everything else false (parent app controls lifecycle)
|
||||
* - `envelopeItems`: All true
|
||||
* - `recipients`: All true
|
||||
*/
|
||||
const EMBEDDED_DEFAULTS = {
|
||||
general: {
|
||||
allowConfigureEnvelopeTitle: true,
|
||||
allowUploadAndRecipientStep: true,
|
||||
allowAddFieldsStep: true,
|
||||
allowPreviewStep: true,
|
||||
minimizeLeftSidebar: true,
|
||||
},
|
||||
settings: {
|
||||
allowConfigureSignatureTypes: true,
|
||||
allowConfigureLanguage: true,
|
||||
allowConfigureDateFormat: true,
|
||||
allowConfigureTimezone: true,
|
||||
allowConfigureRedirectUrl: true,
|
||||
allowConfigureDistribution: true,
|
||||
},
|
||||
actions: {
|
||||
allowAttachments: true,
|
||||
allowDistributing: false,
|
||||
allowDirectLink: false,
|
||||
allowDuplication: false,
|
||||
allowDownloadPDF: false,
|
||||
allowDeletion: false,
|
||||
allowReturnToPreviousPage: false,
|
||||
},
|
||||
envelopeItems: {
|
||||
allowConfigureTitle: true,
|
||||
allowConfigureOrder: true,
|
||||
allowUpload: true,
|
||||
allowDelete: true,
|
||||
},
|
||||
recipients: {
|
||||
allowAIDetection: true,
|
||||
allowConfigureSigningOrder: true,
|
||||
allowConfigureDictateNextSigner: true,
|
||||
allowApproverRole: true,
|
||||
allowViewerRole: true,
|
||||
allowCCerRole: true,
|
||||
allowAssistantRole: true,
|
||||
},
|
||||
fields: {
|
||||
allowAIDetection: true,
|
||||
},
|
||||
} as const satisfies TEnvelopeEditorSettings;
|
||||
|
||||
/**
|
||||
* Takes parsed `features` from the embedding hash and an `embeded` config,
|
||||
* and produces a complete `EnvelopeEditorConfig` with sensible embedded-mode defaults.
|
||||
*
|
||||
* Any explicitly provided feature flag overrides the embedded default.
|
||||
*/
|
||||
export function buildEditorConfigFromFeatures(
|
||||
features: TEnvelopeEditorSettings,
|
||||
embeded: EnvelopeEditorConfig['embeded'],
|
||||
): EnvelopeEditorConfig {
|
||||
return {
|
||||
embeded,
|
||||
|
||||
general: {
|
||||
allowConfigureEnvelopeTitle:
|
||||
features.general.allowConfigureEnvelopeTitle ??
|
||||
EMBEDDED_DEFAULTS.general.allowConfigureEnvelopeTitle,
|
||||
allowUploadAndRecipientStep:
|
||||
features.general.allowUploadAndRecipientStep ??
|
||||
EMBEDDED_DEFAULTS.general.allowUploadAndRecipientStep,
|
||||
allowAddFieldsStep:
|
||||
features.general.allowAddFieldsStep ?? EMBEDDED_DEFAULTS.general.allowAddFieldsStep,
|
||||
allowPreviewStep:
|
||||
features.general.allowPreviewStep ?? EMBEDDED_DEFAULTS.general.allowPreviewStep,
|
||||
minimizeLeftSidebar:
|
||||
features.general.minimizeLeftSidebar ?? EMBEDDED_DEFAULTS.general.minimizeLeftSidebar,
|
||||
},
|
||||
|
||||
settings: {
|
||||
allowConfigureSignatureTypes:
|
||||
features.settings?.allowConfigureSignatureTypes ??
|
||||
EMBEDDED_DEFAULTS.settings.allowConfigureSignatureTypes,
|
||||
allowConfigureLanguage:
|
||||
features.settings?.allowConfigureLanguage ??
|
||||
EMBEDDED_DEFAULTS.settings.allowConfigureLanguage,
|
||||
allowConfigureDateFormat:
|
||||
features.settings?.allowConfigureDateFormat ??
|
||||
EMBEDDED_DEFAULTS.settings.allowConfigureDateFormat,
|
||||
allowConfigureTimezone:
|
||||
features.settings?.allowConfigureTimezone ??
|
||||
EMBEDDED_DEFAULTS.settings.allowConfigureTimezone,
|
||||
allowConfigureRedirectUrl:
|
||||
features.settings?.allowConfigureRedirectUrl ??
|
||||
EMBEDDED_DEFAULTS.settings.allowConfigureRedirectUrl,
|
||||
allowConfigureDistribution:
|
||||
features.settings?.allowConfigureDistribution ??
|
||||
EMBEDDED_DEFAULTS.settings.allowConfigureDistribution,
|
||||
},
|
||||
|
||||
actions: {
|
||||
allowAttachments:
|
||||
features.actions.allowAttachments ?? EMBEDDED_DEFAULTS.actions.allowAttachments,
|
||||
allowDistributing:
|
||||
features.actions.allowDistributing ?? EMBEDDED_DEFAULTS.actions.allowDistributing,
|
||||
allowDirectLink:
|
||||
features.actions.allowDirectLink ?? EMBEDDED_DEFAULTS.actions.allowDirectLink,
|
||||
allowDuplication:
|
||||
features.actions.allowDuplication ?? EMBEDDED_DEFAULTS.actions.allowDuplication,
|
||||
allowDownloadPDF:
|
||||
features.actions.allowDownloadPDF ?? EMBEDDED_DEFAULTS.actions.allowDownloadPDF,
|
||||
allowDeletion: features.actions.allowDeletion ?? EMBEDDED_DEFAULTS.actions.allowDeletion,
|
||||
allowReturnToPreviousPage:
|
||||
features.actions.allowReturnToPreviousPage ??
|
||||
EMBEDDED_DEFAULTS.actions.allowReturnToPreviousPage,
|
||||
},
|
||||
|
||||
envelopeItems: {
|
||||
allowConfigureTitle:
|
||||
features.envelopeItems?.allowConfigureTitle ??
|
||||
EMBEDDED_DEFAULTS.envelopeItems.allowConfigureTitle,
|
||||
allowConfigureOrder:
|
||||
features.envelopeItems?.allowConfigureOrder ??
|
||||
EMBEDDED_DEFAULTS.envelopeItems.allowConfigureOrder,
|
||||
allowUpload:
|
||||
features.envelopeItems?.allowUpload ?? EMBEDDED_DEFAULTS.envelopeItems.allowUpload,
|
||||
allowDelete:
|
||||
features.envelopeItems?.allowDelete ?? EMBEDDED_DEFAULTS.envelopeItems.allowDelete,
|
||||
},
|
||||
|
||||
recipients: {
|
||||
allowAIDetection:
|
||||
features.recipients?.allowAIDetection ?? EMBEDDED_DEFAULTS.recipients.allowAIDetection,
|
||||
allowConfigureSigningOrder:
|
||||
features.recipients?.allowConfigureSigningOrder ??
|
||||
EMBEDDED_DEFAULTS.recipients.allowConfigureSigningOrder,
|
||||
allowConfigureDictateNextSigner:
|
||||
features.recipients?.allowConfigureDictateNextSigner ??
|
||||
EMBEDDED_DEFAULTS.recipients.allowConfigureDictateNextSigner,
|
||||
allowApproverRole:
|
||||
features.recipients?.allowApproverRole ?? EMBEDDED_DEFAULTS.recipients.allowApproverRole,
|
||||
allowViewerRole:
|
||||
features.recipients?.allowViewerRole ?? EMBEDDED_DEFAULTS.recipients.allowViewerRole,
|
||||
allowCCerRole:
|
||||
features.recipients?.allowCCerRole ?? EMBEDDED_DEFAULTS.recipients.allowCCerRole,
|
||||
allowAssistantRole:
|
||||
features.recipients?.allowAssistantRole ?? EMBEDDED_DEFAULTS.recipients.allowAssistantRole,
|
||||
},
|
||||
|
||||
fields: {
|
||||
allowAIDetection:
|
||||
features.fields?.allowAIDetection ?? EMBEDDED_DEFAULTS.fields.allowAIDetection,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -32,6 +32,10 @@ export const createEmbeddingEnvelopeRoute = procedure
|
||||
userId,
|
||||
teamId,
|
||||
input,
|
||||
requestMetadata: ctx.metadata,
|
||||
options: {
|
||||
// Default recipients should be added on the frontend automatically for embeds.
|
||||
bypassDefaultRecipients: true,
|
||||
},
|
||||
apiRequestMetadata: ctx.metadata,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -32,7 +32,7 @@ export const createEnvelopeRoute = authenticatedProcedure
|
||||
userId: ctx.user.id,
|
||||
teamId: ctx.teamId,
|
||||
input,
|
||||
requestMetadata: ctx.metadata,
|
||||
apiRequestMetadata: ctx.metadata,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,14 +47,19 @@ type CreateEnvelopeRouteOptions = {
|
||||
*/
|
||||
teamId: number;
|
||||
input: TCreateEnvelopeRequest;
|
||||
requestMetadata: ApiRequestMetadata;
|
||||
apiRequestMetadata: ApiRequestMetadata;
|
||||
|
||||
options?: {
|
||||
bypassDefaultRecipients?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export const createEnvelopeRouteCaller = async ({
|
||||
userId,
|
||||
teamId,
|
||||
input,
|
||||
requestMetadata,
|
||||
apiRequestMetadata,
|
||||
options = {},
|
||||
}: CreateEnvelopeRouteOptions) => {
|
||||
const { payload, files } = input;
|
||||
|
||||
@@ -116,6 +121,7 @@ export const createEnvelopeRouteCaller = async ({
|
||||
flattenForm: type !== EnvelopeType.TEMPLATE,
|
||||
});
|
||||
|
||||
// Todo: Embeds - Does this need to be done on the frontend?
|
||||
const { cleanedPdf, placeholders } = await extractPdfPlaceholders(normalized);
|
||||
|
||||
const { id: documentDataId } = await putPdfFileServerSide({
|
||||
@@ -188,7 +194,8 @@ export const createEnvelopeRouteCaller = async ({
|
||||
},
|
||||
attachments,
|
||||
meta,
|
||||
requestMetadata,
|
||||
requestMetadata: apiRequestMetadata,
|
||||
bypassDefaultRecipients: options.bypassDefaultRecipients,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,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 type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { canEnvelopeItemsBeModified } from '@documenso/lib/utils/envelope';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
@@ -57,50 +58,75 @@ export const deleteEnvelopeItemRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
const result = await prisma.$transaction(async (tx) => {
|
||||
const deletedEnvelopeItem = await tx.envelopeItem.delete({
|
||||
where: {
|
||||
id: envelopeItemId,
|
||||
envelopeId: envelope.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
documentData: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await tx.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.ENVELOPE_ITEM_DELETED,
|
||||
envelopeId: envelope.id,
|
||||
data: {
|
||||
envelopeItemId: deletedEnvelopeItem.id,
|
||||
envelopeItemTitle: deletedEnvelopeItem.title,
|
||||
},
|
||||
user: {
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
},
|
||||
requestMetadata: metadata.requestMetadata,
|
||||
}),
|
||||
});
|
||||
|
||||
return deletedEnvelopeItem;
|
||||
});
|
||||
|
||||
await prisma.documentData.delete({
|
||||
where: {
|
||||
id: result.documentData.id,
|
||||
envelopeItem: {
|
||||
is: null,
|
||||
},
|
||||
},
|
||||
await UNSAFE_deleteEnvelopeItem({
|
||||
envelopeId,
|
||||
envelopeItemId,
|
||||
user,
|
||||
apiRequestMetadata: metadata,
|
||||
});
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
});
|
||||
|
||||
type UnsafeDeleteEnvelopeItemOptions = {
|
||||
envelopeId: string;
|
||||
envelopeItemId: string;
|
||||
user: {
|
||||
id: number;
|
||||
name: string | null;
|
||||
email: string;
|
||||
};
|
||||
apiRequestMetadata: ApiRequestMetadata;
|
||||
};
|
||||
|
||||
export const UNSAFE_deleteEnvelopeItem = async ({
|
||||
envelopeId,
|
||||
envelopeItemId,
|
||||
user,
|
||||
apiRequestMetadata,
|
||||
}: UnsafeDeleteEnvelopeItemOptions) => {
|
||||
const result = await prisma.$transaction(async (tx) => {
|
||||
const deletedEnvelopeItem = await tx.envelopeItem.delete({
|
||||
where: {
|
||||
id: envelopeItemId,
|
||||
envelopeId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
documentData: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await tx.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.ENVELOPE_ITEM_DELETED,
|
||||
envelopeId,
|
||||
data: {
|
||||
envelopeItemId: deletedEnvelopeItem.id,
|
||||
envelopeItemTitle: deletedEnvelopeItem.title,
|
||||
},
|
||||
user: {
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
},
|
||||
requestMetadata: apiRequestMetadata.requestMetadata,
|
||||
}),
|
||||
});
|
||||
|
||||
return deletedEnvelopeItem;
|
||||
});
|
||||
|
||||
await prisma.documentData.delete({
|
||||
where: {
|
||||
id: result.documentData.id,
|
||||
envelopeItem: {
|
||||
is: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -54,6 +54,9 @@ export const updateEnvelopeItemsRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
// Note: This logic is duplicated in many places. If we plan to allow changing title/order
|
||||
// even after the envelope has been sent, make sure to update it everywhere including
|
||||
// embedding routes.
|
||||
if (!canEnvelopeItemsBeModified(envelope, envelope.recipients)) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Envelope item is not editable',
|
||||
@@ -71,30 +74,51 @@ export const updateEnvelopeItemsRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
const updatedEnvelopeItems = await Promise.all(
|
||||
data.map(async ({ envelopeItemId, order, title }) =>
|
||||
prisma.envelopeItem.update({
|
||||
where: {
|
||||
envelopeId: envelope.id,
|
||||
id: envelopeItemId,
|
||||
},
|
||||
data: {
|
||||
order,
|
||||
title,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
order: true,
|
||||
title: true,
|
||||
envelopeId: true,
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
// Todo: Envelope [AUDIT_LOGS]
|
||||
const updatedEnvelopeItems = await UNSAFE_updateEnvelopeItems({
|
||||
envelopeId,
|
||||
data,
|
||||
});
|
||||
|
||||
return {
|
||||
data: updatedEnvelopeItems,
|
||||
};
|
||||
});
|
||||
|
||||
type UnsafeUpdateEnvelopeItemsOptions = {
|
||||
envelopeId: string;
|
||||
data: {
|
||||
envelopeItemId: string;
|
||||
order?: number;
|
||||
title?: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export const UNSAFE_updateEnvelopeItems = async ({
|
||||
envelopeId,
|
||||
data,
|
||||
}: UnsafeUpdateEnvelopeItemsOptions) => {
|
||||
// Todo: Envelope [AUDIT_LOGS]
|
||||
|
||||
const updatedEnvelopeItems = await Promise.all(
|
||||
data.map(async ({ envelopeItemId, order, title }) =>
|
||||
prisma.envelopeItem.update({
|
||||
where: {
|
||||
envelopeId,
|
||||
id: envelopeItemId,
|
||||
},
|
||||
data: {
|
||||
order,
|
||||
title,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
order: true,
|
||||
title: true,
|
||||
envelopeId: true,
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return updatedEnvelopeItems;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user