mirror of
https://github.com/documenso/documenso.git
synced 2026-07-26 01:45:08 +10:00
feat: add envelopes (#2025)
This PR is handles the changes required to support envelopes. The new envelope editor/signing page will be hidden during release. The core changes here is to migrate the documents and templates model to a centralized envelopes model. Even though Documents and Templates are removed, from the user perspective they will still exist as we remap envelopes to documents and templates.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { adminSuperDeleteDocument } from '@documenso/lib/server-only/admin/admin-super-delete-document';
|
||||
import { sendDeleteEmail } from '@documenso/lib/server-only/document/send-delete-email';
|
||||
import { superDeleteDocument } from '@documenso/lib/server-only/document/super-delete-document';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
@@ -19,10 +19,10 @@ export const deleteDocumentRoute = adminProcedure
|
||||
},
|
||||
});
|
||||
|
||||
await sendDeleteEmail({ documentId: id, reason });
|
||||
await sendDeleteEmail({ envelopeId: id, reason });
|
||||
|
||||
await superDeleteDocument({
|
||||
id,
|
||||
await adminSuperDeleteDocument({
|
||||
envelopeId: id,
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDeleteDocumentRequestSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
id: z.string(),
|
||||
reason: z.string(),
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { FindResultResponse } from '@documenso/lib/types/search-params';
|
||||
import {
|
||||
mapSecondaryIdToDocumentId,
|
||||
unsafeBuildEnvelopeIdQuery,
|
||||
} from '@documenso/lib/utils/envelope';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZFindDocumentJobsRequestSchema,
|
||||
ZFindDocumentJobsResponseSchema,
|
||||
} from './find-document-jobs.types';
|
||||
|
||||
export const findDocumentJobsRoute = adminProcedure
|
||||
.input(ZFindDocumentJobsRequestSchema)
|
||||
.output(ZFindDocumentJobsResponseSchema)
|
||||
.query(async ({ input }) => {
|
||||
const { envelopeId, page = 1, perPage = 5 } = input;
|
||||
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
where: unsafeBuildEnvelopeIdQuery(
|
||||
{
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
EnvelopeType.DOCUMENT,
|
||||
),
|
||||
});
|
||||
|
||||
if (!envelope) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Envelope not found',
|
||||
});
|
||||
}
|
||||
|
||||
const [data, count] = await Promise.all([
|
||||
prisma.backgroundJob.findMany({
|
||||
where: {
|
||||
jobId: 'internal.seal-document',
|
||||
payload: {
|
||||
path: ['documentId'],
|
||||
equals: mapSecondaryIdToDocumentId(envelope.secondaryId),
|
||||
},
|
||||
},
|
||||
skip: Math.max(page - 1, 0) * perPage,
|
||||
take: perPage,
|
||||
orderBy: {
|
||||
submittedAt: 'desc',
|
||||
},
|
||||
}),
|
||||
prisma.backgroundJob.count({
|
||||
where: {
|
||||
jobId: 'internal.seal-document',
|
||||
payload: {
|
||||
path: ['documentId'],
|
||||
equals: mapSecondaryIdToDocumentId(envelope.secondaryId),
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
data,
|
||||
count,
|
||||
currentPage: Math.max(page, 1),
|
||||
perPage,
|
||||
totalPages: Math.ceil(count / perPage),
|
||||
} satisfies FindResultResponse<typeof data>;
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import BackgroundJobSchema from '@documenso/prisma/generated/zod/modelSchema/BackgroundJobSchema';
|
||||
|
||||
export const ZFindDocumentJobsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
envelopeId: z.string(),
|
||||
});
|
||||
|
||||
export const ZFindDocumentJobsResponseSchema = ZFindResultResponse.extend({
|
||||
data: BackgroundJobSchema.pick({
|
||||
status: true,
|
||||
id: true,
|
||||
retried: true,
|
||||
maxRetries: true,
|
||||
jobId: true,
|
||||
name: true,
|
||||
version: true,
|
||||
submittedAt: true,
|
||||
updatedAt: true,
|
||||
completedAt: true,
|
||||
lastRetriedAt: true,
|
||||
}).array(),
|
||||
});
|
||||
|
||||
export type TFindDocumentJobsResponse = z.infer<typeof ZFindDocumentJobsResponseSchema>;
|
||||
@@ -1,4 +1,5 @@
|
||||
import { findDocuments } from '@documenso/lib/server-only/admin/get-all-documents';
|
||||
import { adminFindDocuments } from '@documenso/lib/server-only/admin/admin-find-documents';
|
||||
import { mapEnvelopesToDocumentMany } from '@documenso/lib/utils/document';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import { ZFindDocumentsRequestSchema, ZFindDocumentsResponseSchema } from './find-documents.types';
|
||||
@@ -9,5 +10,10 @@ export const findDocumentsRoute = adminProcedure
|
||||
.query(async ({ input }) => {
|
||||
const { query, page, perPage } = input;
|
||||
|
||||
return await findDocuments({ query, page, perPage });
|
||||
const result = await adminFindDocuments({ query, page, perPage });
|
||||
|
||||
return {
|
||||
...result,
|
||||
data: result.data.map(mapEnvelopesToDocumentMany),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { getEntireDocument } from '@documenso/lib/server-only/admin/get-entire-document';
|
||||
import { sealDocument } from '@documenso/lib/server-only/document/seal-document';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { jobs } from '@documenso/lib/jobs/client';
|
||||
import { unsafeGetEntireEnvelope } from '@documenso/lib/server-only/admin/get-entire-document';
|
||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
@@ -20,9 +23,21 @@ export const resealDocumentRoute = adminProcedure
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getEntireDocument({ id });
|
||||
const envelope = await unsafeGetEntireEnvelope({
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id,
|
||||
},
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
});
|
||||
|
||||
const isResealing = isDocumentCompleted(document.status);
|
||||
const isResealing = isDocumentCompleted(envelope.status);
|
||||
|
||||
await sealDocument({ documentId: id, isResealing });
|
||||
await jobs.triggerJob({
|
||||
name: 'internal.seal-document',
|
||||
payload: {
|
||||
documentId: mapSecondaryIdToDocumentId(envelope.secondaryId),
|
||||
isResealing,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZResealDocumentRequestSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
id: z.string(),
|
||||
});
|
||||
|
||||
export const ZResealDocumentResponseSchema = z.void();
|
||||
|
||||
@@ -8,6 +8,7 @@ import { deleteUserRoute } from './delete-user';
|
||||
import { disableUserRoute } from './disable-user';
|
||||
import { enableUserRoute } from './enable-user';
|
||||
import { findAdminOrganisationsRoute } from './find-admin-organisations';
|
||||
import { findDocumentJobsRoute } from './find-document-jobs';
|
||||
import { findDocumentsRoute } from './find-documents';
|
||||
import { findSubscriptionClaimsRoute } from './find-subscription-claims';
|
||||
import { getAdminOrganisationRoute } from './get-admin-organisation';
|
||||
@@ -52,6 +53,7 @@ export const adminRouter = router({
|
||||
find: findDocumentsRoute,
|
||||
delete: deleteDocumentRoute,
|
||||
reseal: resealDocumentRoute,
|
||||
findJobs: findDocumentJobsRoute,
|
||||
},
|
||||
recipient: {
|
||||
update: updateRecipientRoute,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
@@ -23,8 +24,9 @@ export const accessAuthRequest2FAEmailRoute = procedure
|
||||
const user = ctx.user;
|
||||
|
||||
// Get document and recipient by token
|
||||
const document = await prisma.document.findFirst({
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
where: {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
recipients: {
|
||||
some: {
|
||||
token,
|
||||
@@ -40,17 +42,17 @@ export const accessAuthRequest2FAEmailRoute = procedure
|
||||
},
|
||||
});
|
||||
|
||||
if (!document) {
|
||||
if (!envelope) {
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: 'Document not found',
|
||||
});
|
||||
}
|
||||
|
||||
const [recipient] = document.recipients;
|
||||
const [recipient] = envelope.recipients;
|
||||
|
||||
const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({
|
||||
documentAuth: document.authOptions,
|
||||
documentAuth: envelope.authOptions,
|
||||
recipientAuth: recipient.authOptions,
|
||||
});
|
||||
|
||||
@@ -72,7 +74,7 @@ export const accessAuthRequest2FAEmailRoute = procedure
|
||||
|
||||
await send2FATokenEmail({
|
||||
token,
|
||||
documentId: document.id,
|
||||
envelopeId: envelope.id,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
import { DocumentDataType, EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
|
||||
import { createDocumentV2 } from '@documenso/lib/server-only/document/create-document-v2';
|
||||
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
|
||||
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
||||
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
@@ -55,27 +57,82 @@ export const createDocumentTemporaryRoute = authenticatedProcedure
|
||||
type: DocumentDataType.S3_PATH,
|
||||
});
|
||||
|
||||
const createdDocument = await createDocumentV2({
|
||||
const createdEnvelope = await createEnvelope({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentDataId: documentData.id,
|
||||
normalizePdf: false, // Not normalizing because of presigned URL.
|
||||
internalVersion: 1,
|
||||
data: {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title,
|
||||
externalId,
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
recipients,
|
||||
recipients: (recipients || []).map((recipient) => ({
|
||||
...recipient,
|
||||
fields: (recipient.fields || []).map((field) => ({
|
||||
...field,
|
||||
page: field.pageNumber,
|
||||
positionX: field.pageX,
|
||||
positionY: field.pageY,
|
||||
documentDataId: documentData.id,
|
||||
})),
|
||||
})),
|
||||
folderId,
|
||||
envelopeItems: [
|
||||
{
|
||||
documentDataId: documentData.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
meta,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
const envelopeItems = await prisma.envelopeItem.findMany({
|
||||
where: {
|
||||
envelopeId: createdEnvelope.id,
|
||||
},
|
||||
include: {
|
||||
documentData: true,
|
||||
},
|
||||
});
|
||||
|
||||
const legacyDocumentId = mapSecondaryIdToDocumentId(createdEnvelope.secondaryId);
|
||||
|
||||
const firstDocumentData = envelopeItems[0].documentData;
|
||||
|
||||
if (!firstDocumentData) {
|
||||
throw new Error('Document data not found');
|
||||
}
|
||||
|
||||
return {
|
||||
document: createdDocument,
|
||||
folder: createdDocument.folder, // Todo: Remove this prior to api-v2 release.
|
||||
document: {
|
||||
...createdEnvelope,
|
||||
envelopeId: createdEnvelope.id,
|
||||
documentDataId: firstDocumentData.id,
|
||||
documentData: {
|
||||
...firstDocumentData,
|
||||
envelopeItemId: envelopeItems[0].id,
|
||||
},
|
||||
documentMeta: {
|
||||
...createdEnvelope.documentMeta,
|
||||
documentId: legacyDocumentId,
|
||||
},
|
||||
id: legacyDocumentId,
|
||||
fields: createdEnvelope.fields.map((field) => ({
|
||||
...field,
|
||||
documentId: legacyDocumentId,
|
||||
templateId: null,
|
||||
})),
|
||||
recipients: createdEnvelope.recipients.map((recipient) => ({
|
||||
...recipient,
|
||||
documentId: legacyDocumentId,
|
||||
templateId: null,
|
||||
})),
|
||||
},
|
||||
folder: createdEnvelope.folder, // Todo: Remove this prior to api-v2 release.
|
||||
uploadUrl: url,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { DocumentSigningOrder } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentSchema } from '@documenso/lib/types/document';
|
||||
@@ -6,8 +5,8 @@ import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';
|
||||
import { ZDocumentMetaCreateSchema } from '@documenso/lib/types/document-meta';
|
||||
import {
|
||||
ZFieldHeightSchema,
|
||||
ZFieldPageNumberSchema,
|
||||
@@ -21,16 +20,6 @@ import { ZCreateRecipientSchema } from '../recipient-router/schema';
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
import {
|
||||
ZDocumentExternalIdSchema,
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
ZDocumentMetaDrawSignatureEnabledSchema,
|
||||
ZDocumentMetaLanguageSchema,
|
||||
ZDocumentMetaMessageSchema,
|
||||
ZDocumentMetaRedirectUrlSchema,
|
||||
ZDocumentMetaSubjectSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
ZDocumentMetaTypedSignatureEnabledSchema,
|
||||
ZDocumentMetaUploadSignatureEnabledSchema,
|
||||
ZDocumentTitleSchema,
|
||||
ZDocumentVisibilitySchema,
|
||||
} from './schema';
|
||||
@@ -80,22 +69,7 @@ export const ZCreateDocumentTemporaryRequestSchema = z.object({
|
||||
)
|
||||
|
||||
.optional(),
|
||||
meta: z
|
||||
.object({
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
message: ZDocumentMetaMessageSchema.optional(),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
|
||||
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
meta: ZDocumentMetaCreateSchema.optional(),
|
||||
});
|
||||
|
||||
export const ZCreateDocumentTemporaryResponseSchema = z.object({
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { createDocument } from '@documenso/lib/server-only/document/create-document';
|
||||
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
|
||||
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
@@ -9,7 +12,7 @@ import {
|
||||
} from './create-document.types';
|
||||
|
||||
export const createDocumentRoute = authenticatedProcedure
|
||||
.input(ZCreateDocumentRequestSchema)
|
||||
.input(ZCreateDocumentRequestSchema) // Note: Before releasing this to public, update the response schema to be correct.
|
||||
.output(ZCreateDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
@@ -30,18 +33,26 @@ export const createDocumentRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
const document = await createDocument({
|
||||
const document = await createEnvelope({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
title,
|
||||
documentDataId,
|
||||
internalVersion: 1,
|
||||
data: {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title,
|
||||
userTimezone: timezone,
|
||||
folderId,
|
||||
envelopeItems: [
|
||||
{
|
||||
documentDataId,
|
||||
},
|
||||
],
|
||||
},
|
||||
normalizePdf: true,
|
||||
userTimezone: timezone,
|
||||
requestMetadata: ctx.metadata,
|
||||
folderId,
|
||||
});
|
||||
|
||||
return {
|
||||
id: document.id,
|
||||
legacyDocumentId: mapSecondaryIdToDocumentId(document.secondaryId),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentMetaTimezoneSchema, ZDocumentTitleSchema } from './schema';
|
||||
import { ZDocumentMetaTimezoneSchema } from '@documenso/lib/types/document-meta';
|
||||
|
||||
import { ZDocumentTitleSchema } from './schema';
|
||||
|
||||
// Currently not in use until we allow passthrough documents on create.
|
||||
// export const createDocumentMeta: TrpcRouteMeta = {
|
||||
@@ -20,7 +22,7 @@ export const ZCreateDocumentRequestSchema = z.object({
|
||||
});
|
||||
|
||||
export const ZCreateDocumentResponseSchema = z.object({
|
||||
id: z.number(),
|
||||
legacyDocumentId: z.number(),
|
||||
});
|
||||
|
||||
export type TCreateDocumentRequest = z.infer<typeof ZCreateDocumentRequestSchema>;
|
||||
|
||||
@@ -25,7 +25,10 @@ export const deleteDocumentRoute = authenticatedProcedure
|
||||
const userId = ctx.user.id;
|
||||
|
||||
await deleteDocument({
|
||||
id: documentId,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
userId,
|
||||
teamId,
|
||||
requestMetadata: ctx.metadata,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
||||
import { updateDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||
import { mapEnvelopeToDocumentLite } from '@documenso/lib/utils/document';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
@@ -23,10 +24,13 @@ export const distributeDocumentRoute = authenticatedProcedure
|
||||
});
|
||||
|
||||
if (Object.values(meta).length > 0) {
|
||||
await upsertDocumentMeta({
|
||||
await updateDocumentMeta({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
subject: meta.subject,
|
||||
message: meta.message,
|
||||
dateFormat: meta.dateFormat,
|
||||
@@ -41,10 +45,15 @@ export const distributeDocumentRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
return await sendDocument({
|
||||
const envelope = await sendDocument({
|
||||
userId: ctx.user.id,
|
||||
documentId,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
teamId,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return mapEnvelopeToDocumentLite(envelope);
|
||||
});
|
||||
|
||||
@@ -2,8 +2,6 @@ import { z } from 'zod';
|
||||
|
||||
import { ZDocumentLiteSchema } from '@documenso/lib/types/document';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
@@ -12,7 +10,9 @@ import {
|
||||
ZDocumentMetaRedirectUrlSchema,
|
||||
ZDocumentMetaSubjectSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
} from './schema';
|
||||
} from '@documenso/lib/types/document-meta';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const distributeDocumentMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt';
|
||||
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
||||
import { getEnvelopeById } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
||||
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
@@ -24,20 +26,24 @@ export const downloadDocumentAuditLogsRoute = authenticatedProcedure
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId,
|
||||
const envelope = await getEnvelopeById({
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
}).catch(() => null);
|
||||
|
||||
if (!document || (teamId && document.teamId !== teamId)) {
|
||||
if (!envelope) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You do not have access to this document.',
|
||||
});
|
||||
}
|
||||
|
||||
const encrypted = encryptSecondaryData({
|
||||
data: document.id.toString(),
|
||||
data: mapSecondaryIdToDocumentId(envelope.secondaryId).toString(),
|
||||
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt';
|
||||
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
||||
import { getEnvelopeById } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
@@ -25,18 +27,22 @@ export const downloadDocumentCertificateRoute = authenticatedProcedure
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId,
|
||||
const envelope = await getEnvelopeById({
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
});
|
||||
|
||||
if (!isDocumentCompleted(document.status)) {
|
||||
if (!isDocumentCompleted(envelope.status)) {
|
||||
throw new AppError('DOCUMENT_NOT_COMPLETE');
|
||||
}
|
||||
|
||||
const encrypted = encryptSecondaryData({
|
||||
data: document.id.toString(),
|
||||
data: mapSecondaryIdToDocumentId(envelope.secondaryId).toString(),
|
||||
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
import type { DocumentData } from '@prisma/client';
|
||||
import { DocumentDataType, EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
||||
import { getEnvelopeById } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
||||
import { getPresignGetUrl } from '@documenso/lib/universal/upload/server-actions';
|
||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||
|
||||
@@ -27,45 +28,51 @@ export const downloadDocumentRoute = authenticatedProcedure
|
||||
},
|
||||
});
|
||||
|
||||
const envelope = await getEnvelopeById({
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
userId: user.id,
|
||||
teamId,
|
||||
});
|
||||
|
||||
// This error is done AFTER the get envelope so we can test access controls without S3.
|
||||
if (process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Document downloads are only available when S3 storage is configured.',
|
||||
});
|
||||
}
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId,
|
||||
userId: user.id,
|
||||
teamId,
|
||||
});
|
||||
const documentData: DocumentData | undefined = envelope.envelopeItems[0]?.documentData;
|
||||
|
||||
if (!document.documentData) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Document data not found',
|
||||
if (envelope.envelopeItems.length !== 1 || !documentData) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message:
|
||||
'This endpoint only supports documents with a single item. Use envelopes API instead.',
|
||||
});
|
||||
}
|
||||
|
||||
if (document.documentData.type !== DocumentDataType.S3_PATH) {
|
||||
if (documentData.type !== DocumentDataType.S3_PATH) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Document is not stored in S3 and cannot be downloaded via URL.',
|
||||
});
|
||||
}
|
||||
|
||||
if (version === 'signed' && !isDocumentCompleted(document.status)) {
|
||||
if (version === 'signed' && !isDocumentCompleted(envelope.status)) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Document is not completed yet.',
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const documentData =
|
||||
version === 'original'
|
||||
? document.documentData.initialData || document.documentData.data
|
||||
: document.documentData.data;
|
||||
const data =
|
||||
version === 'original' ? documentData.initialData || documentData.data : documentData.data;
|
||||
|
||||
const { url } = await getPresignGetUrl(documentData);
|
||||
const { url } = await getPresignGetUrl(data);
|
||||
|
||||
const baseTitle = document.title.replace(/\.pdf$/, '');
|
||||
const baseTitle = envelope.title.replace(/\.pdf$/, '');
|
||||
const suffix = version === 'signed' ? '_signed.pdf' : '.pdf';
|
||||
const filename = `${baseTitle}${suffix}`;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { duplicateDocument } from '@documenso/lib/server-only/document/duplicate-document-by-id';
|
||||
import { duplicateEnvelope } from '@documenso/lib/server-only/envelope/duplicate-envelope';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
@@ -21,9 +21,17 @@ export const duplicateDocumentRoute = authenticatedProcedure
|
||||
},
|
||||
});
|
||||
|
||||
return await duplicateDocument({
|
||||
const duplicatedEnvelope = await duplicateEnvelope({
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
userId: user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
});
|
||||
|
||||
return {
|
||||
id: duplicatedEnvelope.id,
|
||||
documentId: duplicatedEnvelope.legacyId.id,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -16,7 +16,8 @@ export const ZDuplicateDocumentRequestSchema = z.object({
|
||||
});
|
||||
|
||||
export const ZDuplicateDocumentResponseSchema = z.object({
|
||||
documentId: z.number(),
|
||||
id: z.string().describe('The envelope ID'),
|
||||
documentId: z.number().describe('The legacy document ID'),
|
||||
});
|
||||
|
||||
export type TDuplicateDocumentRequest = z.infer<typeof ZDuplicateDocumentRequestSchema>;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { findDocuments } from '@documenso/lib/server-only/document/find-document
|
||||
import type { GetStatsInput } from '@documenso/lib/server-only/document/get-stats';
|
||||
import { getStats } from '@documenso/lib/server-only/document/get-stats';
|
||||
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
||||
import { mapEnvelopesToDocumentMany } from '@documenso/lib/utils/document';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
@@ -69,6 +70,7 @@ export const findDocumentsInternalRoute = authenticatedProcedure
|
||||
|
||||
return {
|
||||
...documents,
|
||||
data: documents.data.map((envelope) => mapEnvelopesToDocumentMany(envelope)),
|
||||
stats,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
|
||||
import { mapEnvelopesToDocumentMany } from '@documenso/lib/utils/document';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
@@ -39,5 +40,8 @@ export const findDocumentsRoute = authenticatedProcedure
|
||||
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
||||
});
|
||||
|
||||
return documents;
|
||||
return {
|
||||
...documents,
|
||||
data: documents.data.map((envelope) => mapEnvelopesToDocumentMany(envelope)),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { Document, Prisma } from '@prisma/client';
|
||||
import { DocumentStatus, RecipientRole } from '@prisma/client';
|
||||
import type { Envelope, Prisma } from '@prisma/client';
|
||||
import { DocumentStatus, EnvelopeType, RecipientRole } from '@prisma/client';
|
||||
|
||||
import type { FindResultResponse } from '@documenso/lib/types/search-params';
|
||||
import { mapEnvelopesToDocumentMany } from '@documenso/lib/utils/document';
|
||||
import { maskRecipientTokensForDocument } from '@documenso/lib/utils/mask-recipient-tokens-for-document';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
@@ -16,11 +17,16 @@ export const findInboxRoute = authenticatedProcedure
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
return await findInbox({
|
||||
const envelopes = await findInbox({
|
||||
userId,
|
||||
page,
|
||||
perPage,
|
||||
});
|
||||
|
||||
return {
|
||||
...envelopes,
|
||||
data: envelopes.data.map(mapEnvelopesToDocumentMany),
|
||||
};
|
||||
});
|
||||
|
||||
export type FindInboxOptions = {
|
||||
@@ -28,7 +34,7 @@ export type FindInboxOptions = {
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
orderBy?: {
|
||||
column: keyof Omit<Document, 'document'>;
|
||||
column: keyof Omit<Envelope, 'envelope'>;
|
||||
direction: 'asc' | 'desc';
|
||||
};
|
||||
};
|
||||
@@ -38,12 +44,17 @@ export const findInbox = async ({ userId, page = 1, perPage = 10, orderBy }: Fin
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
},
|
||||
});
|
||||
|
||||
const orderByColumn = orderBy?.column ?? 'createdAt';
|
||||
const orderByDirection = orderBy?.direction ?? 'desc';
|
||||
|
||||
const whereClause: Prisma.DocumentWhereInput = {
|
||||
const whereClause: Prisma.EnvelopeWhereInput = {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
status: {
|
||||
not: DocumentStatus.DRAFT,
|
||||
},
|
||||
@@ -59,7 +70,7 @@ export const findInbox = async ({ userId, page = 1, perPage = 10, orderBy }: Fin
|
||||
};
|
||||
|
||||
const [data, count] = await Promise.all([
|
||||
prisma.document.findMany({
|
||||
prisma.envelope.findMany({
|
||||
where: whereClause,
|
||||
skip: Math.max(page - 1, 0) * perPage,
|
||||
take: perPage,
|
||||
@@ -83,7 +94,7 @@ export const findInbox = async ({ userId, page = 1, perPage = 10, orderBy }: Fin
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.document.count({
|
||||
prisma.envelope.count({
|
||||
where: whereClause,
|
||||
}),
|
||||
]);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
@@ -13,8 +15,9 @@ export const getDocumentByTokenRoute = authenticatedProcedure
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { token } = input;
|
||||
|
||||
const document = await prisma.document.findFirst({
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
where: {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
recipients: {
|
||||
some: {
|
||||
token,
|
||||
@@ -23,21 +26,33 @@ export const getDocumentByTokenRoute = authenticatedProcedure
|
||||
},
|
||||
},
|
||||
include: {
|
||||
documentData: true,
|
||||
envelopeItems: {
|
||||
include: {
|
||||
documentData: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!document) {
|
||||
const firstDocumentData = envelope?.envelopeItems[0].documentData;
|
||||
|
||||
if (!envelope || !firstDocumentData) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Document not found',
|
||||
});
|
||||
}
|
||||
|
||||
if (envelope.envelopeItems.length !== 1) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'This endpoint does not support multiple items',
|
||||
});
|
||||
}
|
||||
|
||||
ctx.logger.info({
|
||||
documentId: document.id,
|
||||
documentId: envelope.id,
|
||||
});
|
||||
|
||||
return {
|
||||
documentData: document.documentData,
|
||||
documentData: firstDocumentData,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -24,6 +24,9 @@ export const getDocumentRoute = authenticatedProcedure
|
||||
return await getDocumentWithDetailsById({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DocumentStatus, RecipientRole } from '@prisma/client';
|
||||
import { DocumentStatus, EnvelopeType, RecipientRole } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
@@ -20,7 +20,8 @@ export const getInboxCountRoute = authenticatedProcedure
|
||||
role: {
|
||||
not: RecipientRole.CC,
|
||||
},
|
||||
document: {
|
||||
envelope: {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
status: {
|
||||
not: DocumentStatus.DRAFT,
|
||||
},
|
||||
|
||||
@@ -26,7 +26,10 @@ export const redistributeDocumentRoute = authenticatedProcedure
|
||||
await resendDocument({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
recipients,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ import { getDocumentByTokenRoute } from './get-document-by-token';
|
||||
import { getInboxCountRoute } from './get-inbox-count';
|
||||
import { redistributeDocumentRoute } from './redistribute-document';
|
||||
import { searchDocumentRoute } from './search-document';
|
||||
import { shareDocumentRoute } from './share-document';
|
||||
import { updateDocumentRoute } from './update-document';
|
||||
|
||||
export const documentRouter = router({
|
||||
@@ -30,6 +31,7 @@ export const documentRouter = router({
|
||||
distribute: distributeDocumentRoute,
|
||||
redistribute: redistributeDocumentRoute,
|
||||
search: searchDocumentRoute,
|
||||
share: shareDocumentRoute,
|
||||
|
||||
// Temporary v2 beta routes to be removed once V2 is fully released.
|
||||
download: downloadDocumentRoute,
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { DocumentDistributionMethod, DocumentVisibility } from '@prisma/client';
|
||||
import { DocumentVisibility } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { VALID_DATE_FORMAT_VALUES } from '@documenso/lib/constants/date-formats';
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
||||
|
||||
/**
|
||||
* Required for empty responses since we currently can't 201 requests for our openapi setup.
|
||||
*
|
||||
@@ -34,58 +30,3 @@ export const ZDocumentExternalIdSchema = z
|
||||
export const ZDocumentVisibilitySchema = z
|
||||
.nativeEnum(DocumentVisibility)
|
||||
.describe('The visibility of the document.');
|
||||
|
||||
export const ZDocumentMetaTimezoneSchema = z
|
||||
.string()
|
||||
.describe(
|
||||
'The timezone to use for date fields and signing the document. Example Etc/UTC, Australia/Melbourne',
|
||||
);
|
||||
// Cooked.
|
||||
// .refine((value) => TIME_ZONES.includes(value), {
|
||||
// message: 'Invalid timezone. Please provide a valid timezone',
|
||||
// });
|
||||
|
||||
export type TDocumentMetaTimezone = z.infer<typeof ZDocumentMetaTimezoneSchema>;
|
||||
|
||||
export const ZDocumentMetaDateFormatSchema = z
|
||||
.enum(VALID_DATE_FORMAT_VALUES)
|
||||
.describe('The date format to use for date fields and signing the document.');
|
||||
|
||||
export type TDocumentMetaDateFormat = z.infer<typeof ZDocumentMetaDateFormatSchema>;
|
||||
|
||||
export const ZDocumentMetaRedirectUrlSchema = z
|
||||
.string()
|
||||
.describe('The URL to which the recipient should be redirected after signing the document.')
|
||||
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
|
||||
message: 'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
||||
});
|
||||
|
||||
export const ZDocumentMetaLanguageSchema = z
|
||||
.enum(SUPPORTED_LANGUAGE_CODES)
|
||||
.describe('The language to use for email communications with recipients.');
|
||||
|
||||
export const ZDocumentMetaSubjectSchema = z
|
||||
.string()
|
||||
.max(254)
|
||||
.describe('The subject of the email that will be sent to the recipients.');
|
||||
|
||||
export const ZDocumentMetaMessageSchema = z
|
||||
.string()
|
||||
.max(5000)
|
||||
.describe('The message of the email that will be sent to the recipients.');
|
||||
|
||||
export const ZDocumentMetaDistributionMethodSchema = z
|
||||
.nativeEnum(DocumentDistributionMethod)
|
||||
.describe('The distribution method to use when sending the document to the recipients.');
|
||||
|
||||
export const ZDocumentMetaTypedSignatureEnabledSchema = z
|
||||
.boolean()
|
||||
.describe('Whether to allow recipients to sign using a typed signature.');
|
||||
|
||||
export const ZDocumentMetaDrawSignatureEnabledSchema = z
|
||||
.boolean()
|
||||
.describe('Whether to allow recipients to sign using a draw signature.');
|
||||
|
||||
export const ZDocumentMetaUploadSignatureEnabledSchema = z
|
||||
.boolean()
|
||||
.describe('Whether to allow recipients to sign using an uploaded signature.');
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { createOrGetShareLink } from '@documenso/lib/server-only/share/create-or-get-share-link';
|
||||
|
||||
import { procedure } from '../trpc';
|
||||
import { ZShareDocumentRequestSchema, ZShareDocumentResponseSchema } from './share-document.types';
|
||||
|
||||
// Note: This is an unauthenticated route.
|
||||
export const shareDocumentRoute = procedure
|
||||
.input(ZShareDocumentRequestSchema)
|
||||
.output(ZShareDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { documentId, token } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
if (token) {
|
||||
return await createOrGetShareLink({ documentId, token });
|
||||
}
|
||||
|
||||
if (!ctx.user?.id) {
|
||||
throw new Error('You must either provide a token or be logged in to create a sharing link.');
|
||||
}
|
||||
|
||||
return await createOrGetShareLink({ documentId, userId: ctx.user.id });
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import DocumentShareLinkSchema from '@documenso/prisma/generated/zod/modelSchema/DocumentShareLinkSchema';
|
||||
|
||||
export const ZShareDocumentRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
token: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ZShareDocumentResponseSchema = DocumentShareLinkSchema.pick({
|
||||
slug: true,
|
||||
email: true,
|
||||
});
|
||||
|
||||
export type TShareDocumentRequest = z.infer<typeof ZShareDocumentRequestSchema>;
|
||||
export type TShareDocumentResponse = z.infer<typeof ZShareDocumentResponseSchema>;
|
||||
@@ -1,18 +1,18 @@
|
||||
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
||||
import { updateDocument } from '@documenso/lib/server-only/document/update-document';
|
||||
import { updateEnvelope } from '@documenso/lib/server-only/envelope/update-envelope';
|
||||
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZUpdateDocumentRequestSchema,
|
||||
ZUpdateDocumentResponseSchema,
|
||||
} from './update-document.types';
|
||||
import { updateDocumentMeta } from './update-document.types';
|
||||
import { updateDocumentMeta as updateDocumentTrpcMeta } from './update-document.types';
|
||||
|
||||
/**
|
||||
* Public route.
|
||||
*/
|
||||
export const updateDocumentRoute = authenticatedProcedure
|
||||
.meta(updateDocumentMeta)
|
||||
.meta(updateDocumentTrpcMeta)
|
||||
.input(ZUpdateDocumentRequestSchema)
|
||||
.output(ZUpdateDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
@@ -27,35 +27,23 @@ export const updateDocumentRoute = authenticatedProcedure
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
if (Object.values(meta).length > 0) {
|
||||
await upsertDocumentMeta({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
subject: meta.subject,
|
||||
message: meta.message,
|
||||
timezone: meta.timezone,
|
||||
dateFormat: meta.dateFormat,
|
||||
language: meta.language,
|
||||
typedSignatureEnabled: meta.typedSignatureEnabled,
|
||||
uploadSignatureEnabled: meta.uploadSignatureEnabled,
|
||||
drawSignatureEnabled: meta.drawSignatureEnabled,
|
||||
redirectUrl: meta.redirectUrl,
|
||||
distributionMethod: meta.distributionMethod,
|
||||
signingOrder: meta.signingOrder,
|
||||
allowDictateNextSigner: meta.allowDictateNextSigner,
|
||||
emailId: meta.emailId,
|
||||
emailReplyTo: meta.emailReplyTo,
|
||||
emailSettings: meta.emailSettings,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}
|
||||
|
||||
return await updateDocument({
|
||||
const envelope = await updateEnvelope({
|
||||
userId,
|
||||
teamId,
|
||||
documentId,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
data,
|
||||
meta,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
const mappedDocument = {
|
||||
...envelope,
|
||||
id: mapSecondaryIdToDocumentId(envelope.secondaryId),
|
||||
envelopeId: envelope.id,
|
||||
};
|
||||
|
||||
return mappedDocument;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { DocumentSigningOrder } from '@prisma/client';
|
||||
// import type { OpenApiMeta } from 'trpc-to-openapi';
|
||||
import { z } from 'zod';
|
||||
|
||||
@@ -7,21 +6,11 @@ import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { ZDocumentMetaUpdateSchema } from '@documenso/lib/types/document-meta';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
import {
|
||||
ZDocumentExternalIdSchema,
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
ZDocumentMetaDrawSignatureEnabledSchema,
|
||||
ZDocumentMetaLanguageSchema,
|
||||
ZDocumentMetaMessageSchema,
|
||||
ZDocumentMetaRedirectUrlSchema,
|
||||
ZDocumentMetaSubjectSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
ZDocumentMetaTypedSignatureEnabledSchema,
|
||||
ZDocumentMetaUploadSignatureEnabledSchema,
|
||||
ZDocumentTitleSchema,
|
||||
ZDocumentVisibilitySchema,
|
||||
} from './schema';
|
||||
@@ -45,27 +34,10 @@ export const ZUpdateDocumentRequestSchema = z.object({
|
||||
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional(),
|
||||
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional(),
|
||||
useLegacyFieldInsertion: z.boolean().optional(),
|
||||
folderId: z.string().nullish(),
|
||||
})
|
||||
.optional(),
|
||||
meta: z
|
||||
.object({
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
message: ZDocumentMetaMessageSchema.optional(),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
allowDictateNextSigner: z.boolean().optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
|
||||
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
|
||||
emailId: z.string().nullish(),
|
||||
emailReplyTo: z.string().email().nullish(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
meta: ZDocumentMetaUpdateSchema.optional(),
|
||||
});
|
||||
|
||||
export const ZUpdateDocumentResponseSchema = ZDocumentLiteSchema;
|
||||
|
||||
@@ -68,7 +68,7 @@ export const applyMultiSignSignatureRoute = procedure
|
||||
|
||||
const signatureFields = await prisma.field.findMany({
|
||||
where: {
|
||||
documentId: envelope.document.id,
|
||||
envelopeId: envelope.document.id,
|
||||
recipientId: envelope.recipient.id,
|
||||
type: FieldType.SIGNATURE,
|
||||
inserted: false,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { createDocumentV2 } from '@documenso/lib/server-only/document/create-document-v2';
|
||||
import { verifyEmbeddingPresignToken } from '@documenso/lib/server-only/embedding-presign/verify-embedding-presign-token';
|
||||
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
|
||||
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
|
||||
|
||||
import { procedure } from '../trpc';
|
||||
import {
|
||||
@@ -8,6 +11,7 @@ import {
|
||||
ZCreateEmbeddingDocumentResponseSchema,
|
||||
} from './create-embedding-document.types';
|
||||
|
||||
// Todo: Envelopes - This only supports V1 documents/templates.
|
||||
export const createEmbeddingDocumentRoute = procedure
|
||||
.input(ZCreateEmbeddingDocumentRequestSchema)
|
||||
.output(ZCreateEmbeddingDocumentResponseSchema)
|
||||
@@ -29,27 +33,44 @@ export const createEmbeddingDocumentRoute = procedure
|
||||
|
||||
const { title, documentDataId, externalId, recipients, meta } = input;
|
||||
|
||||
const document = await createDocumentV2({
|
||||
const envelope = await createEnvelope({
|
||||
internalVersion: 1,
|
||||
data: {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title,
|
||||
externalId,
|
||||
recipients,
|
||||
recipients: (recipients || []).map((recipient) => ({
|
||||
...recipient,
|
||||
fields: (recipient.fields || []).map((field) => ({
|
||||
...field,
|
||||
page: field.pageNumber,
|
||||
positionX: field.pageX,
|
||||
positionY: field.pageY,
|
||||
documentDataId,
|
||||
})),
|
||||
})),
|
||||
envelopeItems: [
|
||||
{
|
||||
documentDataId,
|
||||
},
|
||||
],
|
||||
},
|
||||
meta,
|
||||
documentDataId,
|
||||
userId: apiToken.userId,
|
||||
teamId: apiToken.teamId ?? undefined,
|
||||
requestMetadata: metadata,
|
||||
});
|
||||
|
||||
if (!document.id) {
|
||||
if (!envelope.id) {
|
||||
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||
message: 'Failed to create document: missing document ID',
|
||||
});
|
||||
}
|
||||
|
||||
const legacyDocumentId = mapSecondaryIdToDocumentId(envelope.secondaryId);
|
||||
|
||||
return {
|
||||
documentId: document.id,
|
||||
documentId: legacyDocumentId,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
|
||||
@@ -2,17 +2,6 @@ import { z } from 'zod';
|
||||
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import {
|
||||
ZFieldHeightSchema,
|
||||
ZFieldPageNumberSchema,
|
||||
ZFieldPageXSchema,
|
||||
ZFieldPageYSchema,
|
||||
ZFieldWidthSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { DocumentSigningOrder } from '@documenso/prisma/generated/types';
|
||||
|
||||
import {
|
||||
ZDocumentExternalIdSchema,
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
ZDocumentMetaDrawSignatureEnabledSchema,
|
||||
@@ -23,8 +12,18 @@ import {
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
ZDocumentMetaTypedSignatureEnabledSchema,
|
||||
ZDocumentMetaUploadSignatureEnabledSchema,
|
||||
ZDocumentTitleSchema,
|
||||
} from '../document-router/schema';
|
||||
} from '@documenso/lib/types/document-meta';
|
||||
import {
|
||||
ZFieldHeightSchema,
|
||||
ZFieldPageNumberSchema,
|
||||
ZFieldPageXSchema,
|
||||
ZFieldPageYSchema,
|
||||
ZFieldWidthSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { DocumentSigningOrder } from '@documenso/prisma/generated/types';
|
||||
|
||||
import { ZDocumentExternalIdSchema, ZDocumentTitleSchema } from '../document-router/schema';
|
||||
import { ZCreateRecipientSchema } from '../recipient-router/schema';
|
||||
|
||||
export const ZCreateEmbeddingDocumentRequestSchema = z.object({
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { verifyEmbeddingPresignToken } from '@documenso/lib/server-only/embedding-presign/verify-embedding-presign-token';
|
||||
import { createTemplate } from '@documenso/lib/server-only/template/create-template';
|
||||
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
|
||||
import { mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { procedure } from '../trpc';
|
||||
@@ -9,10 +12,11 @@ import {
|
||||
ZCreateEmbeddingTemplateResponseSchema,
|
||||
} from './create-embedding-template.types';
|
||||
|
||||
// Todo: Envelopes - This only supports V1 documents/templates.
|
||||
export const createEmbeddingTemplateRoute = procedure
|
||||
.input(ZCreateEmbeddingTemplateRequestSchema)
|
||||
.output(ZCreateEmbeddingTemplateResponseSchema)
|
||||
.mutation(async ({ input, ctx: { req } }) => {
|
||||
.mutation(async ({ input, ctx: { req, metadata } }) => {
|
||||
try {
|
||||
const authorizationHeader = req.headers.get('authorization');
|
||||
|
||||
@@ -31,20 +35,30 @@ export const createEmbeddingTemplateRoute = procedure
|
||||
const { title, documentDataId, recipients, meta } = input;
|
||||
|
||||
// First create the template
|
||||
const template = await createTemplate({
|
||||
const template = await createEnvelope({
|
||||
internalVersion: 1,
|
||||
userId: apiToken.userId,
|
||||
data: {
|
||||
title,
|
||||
},
|
||||
templateDocumentDataId: documentDataId,
|
||||
teamId: apiToken.teamId ?? undefined,
|
||||
data: {
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title,
|
||||
envelopeItems: [
|
||||
{
|
||||
documentDataId,
|
||||
},
|
||||
],
|
||||
},
|
||||
meta,
|
||||
requestMetadata: metadata,
|
||||
});
|
||||
|
||||
const firstEnvelopeItem = template.envelopeItems[0];
|
||||
|
||||
await Promise.all(
|
||||
recipients.map(async (recipient) => {
|
||||
const createdRecipient = await prisma.recipient.create({
|
||||
data: {
|
||||
templateId: template.id,
|
||||
envelopeId: template.id,
|
||||
email: recipient.email,
|
||||
name: recipient.name || '',
|
||||
role: recipient.role || 'SIGNER',
|
||||
@@ -57,6 +71,8 @@ export const createEmbeddingTemplateRoute = procedure
|
||||
|
||||
const createdFields = await prisma.field.createMany({
|
||||
data: fields.map((field) => ({
|
||||
envelopeId: template.id,
|
||||
envelopeItemId: firstEnvelopeItem.id,
|
||||
recipientId: createdRecipient.id,
|
||||
type: field.type,
|
||||
page: field.pageNumber,
|
||||
@@ -66,7 +82,6 @@ export const createEmbeddingTemplateRoute = procedure
|
||||
height: field.height,
|
||||
customText: '',
|
||||
inserted: false,
|
||||
templateId: template.id,
|
||||
})),
|
||||
});
|
||||
|
||||
@@ -77,37 +92,6 @@ export const createEmbeddingTemplateRoute = procedure
|
||||
}),
|
||||
);
|
||||
|
||||
// Update the template meta if needed
|
||||
if (meta) {
|
||||
const upsertMetaData = {
|
||||
subject: meta.subject,
|
||||
message: meta.message,
|
||||
timezone: meta.timezone,
|
||||
dateFormat: meta.dateFormat,
|
||||
distributionMethod: meta.distributionMethod,
|
||||
signingOrder: meta.signingOrder,
|
||||
redirectUrl: meta.redirectUrl,
|
||||
language: meta.language,
|
||||
typedSignatureEnabled: meta.typedSignatureEnabled,
|
||||
drawSignatureEnabled: meta.drawSignatureEnabled,
|
||||
uploadSignatureEnabled: meta.uploadSignatureEnabled,
|
||||
emailSettings: meta.emailSettings,
|
||||
};
|
||||
|
||||
await prisma.templateMeta.upsert({
|
||||
where: {
|
||||
templateId: template.id,
|
||||
},
|
||||
create: {
|
||||
templateId: template.id,
|
||||
...upsertMetaData,
|
||||
},
|
||||
update: {
|
||||
...upsertMetaData,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (!template.id) {
|
||||
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||
message: 'Failed to create template: missing template ID',
|
||||
@@ -115,7 +99,7 @@ export const createEmbeddingTemplateRoute = procedure
|
||||
}
|
||||
|
||||
return {
|
||||
templateId: template.id,
|
||||
templateId: mapSecondaryIdToTemplateId(template.secondaryId),
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
|
||||
@@ -2,15 +2,6 @@ import { DocumentSigningOrder, FieldType, RecipientRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import {
|
||||
ZFieldHeightSchema,
|
||||
ZFieldPageNumberSchema,
|
||||
ZFieldPageXSchema,
|
||||
ZFieldPageYSchema,
|
||||
ZFieldWidthSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
@@ -22,8 +13,17 @@ import {
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
ZDocumentMetaTypedSignatureEnabledSchema,
|
||||
ZDocumentMetaUploadSignatureEnabledSchema,
|
||||
ZDocumentTitleSchema,
|
||||
} from '../document-router/schema';
|
||||
} from '@documenso/lib/types/document-meta';
|
||||
import {
|
||||
ZFieldHeightSchema,
|
||||
ZFieldPageNumberSchema,
|
||||
ZFieldPageXSchema,
|
||||
ZFieldPageYSchema,
|
||||
ZFieldWidthSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
|
||||
import { ZDocumentTitleSchema } from '../document-router/schema';
|
||||
|
||||
const ZFieldSchema = z.object({
|
||||
type: z.nativeEnum(FieldType),
|
||||
|
||||
@@ -42,7 +42,13 @@ export const getMultiSignDocumentRoute = procedure
|
||||
// Transform fields to match our schema
|
||||
const transformedFields = fields.map((field) => ({
|
||||
...field,
|
||||
recipient,
|
||||
recipient: {
|
||||
...recipient,
|
||||
documentId: document.id,
|
||||
templateId: null,
|
||||
},
|
||||
documentId: document.id,
|
||||
templateId: null,
|
||||
}));
|
||||
|
||||
return {
|
||||
|
||||
@@ -25,9 +25,7 @@ export const ZGetMultiSignDocumentResponseSchema = ZDocumentLiteSchema.extend({
|
||||
subject: true,
|
||||
message: true,
|
||||
timezone: true,
|
||||
password: true,
|
||||
dateFormat: true,
|
||||
documentId: true,
|
||||
redirectUrl: true,
|
||||
typedSignatureEnabled: true,
|
||||
uploadSignatureEnabled: true,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
||||
import { updateDocument } from '@documenso/lib/server-only/document/update-document';
|
||||
import { verifyEmbeddingPresignToken } from '@documenso/lib/server-only/embedding-presign/verify-embedding-presign-token';
|
||||
import { updateEnvelope } from '@documenso/lib/server-only/envelope/update-envelope';
|
||||
import { setFieldsForDocument } from '@documenso/lib/server-only/field/set-fields-for-document';
|
||||
import { setDocumentRecipients } from '@documenso/lib/server-only/recipient/set-document-recipients';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
@@ -39,24 +38,18 @@ export const updateEmbeddingDocumentRoute = procedure
|
||||
|
||||
const { documentId, title, externalId, recipients, meta } = input;
|
||||
|
||||
if (meta && Object.values(meta).length > 0) {
|
||||
await upsertDocumentMeta({
|
||||
documentId: documentId,
|
||||
userId: apiToken.userId,
|
||||
teamId: apiToken.teamId ?? undefined,
|
||||
...meta,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}
|
||||
|
||||
await updateDocument({
|
||||
await updateEnvelope({
|
||||
userId: apiToken.userId,
|
||||
teamId: apiToken.teamId ?? undefined,
|
||||
documentId: documentId,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
data: {
|
||||
title,
|
||||
externalId,
|
||||
},
|
||||
meta,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
@@ -68,7 +61,10 @@ export const updateEmbeddingDocumentRoute = procedure
|
||||
const { recipients: updatedRecipients } = await setDocumentRecipients({
|
||||
userId: apiToken.userId,
|
||||
teamId: apiToken.teamId ?? undefined,
|
||||
documentId: documentId,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
recipients: recipientsWithClientId.map((recipient) => ({
|
||||
id: recipient.id,
|
||||
clientId: recipient.clientId,
|
||||
@@ -100,7 +96,10 @@ export const updateEmbeddingDocumentRoute = procedure
|
||||
await setFieldsForDocument({
|
||||
userId: apiToken.userId,
|
||||
teamId: apiToken.teamId ?? undefined,
|
||||
documentId,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
fields: fields.map((field) => ({
|
||||
...field,
|
||||
pageWidth: field.width,
|
||||
|
||||
@@ -2,17 +2,6 @@ import { z } from 'zod';
|
||||
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import {
|
||||
ZFieldHeightSchema,
|
||||
ZFieldPageNumberSchema,
|
||||
ZFieldPageXSchema,
|
||||
ZFieldPageYSchema,
|
||||
ZFieldWidthSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { DocumentSigningOrder, RecipientRole } from '@documenso/prisma/generated/types';
|
||||
|
||||
import {
|
||||
ZDocumentExternalIdSchema,
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
ZDocumentMetaDrawSignatureEnabledSchema,
|
||||
@@ -23,8 +12,18 @@ import {
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
ZDocumentMetaTypedSignatureEnabledSchema,
|
||||
ZDocumentMetaUploadSignatureEnabledSchema,
|
||||
ZDocumentTitleSchema,
|
||||
} from '../document-router/schema';
|
||||
} from '@documenso/lib/types/document-meta';
|
||||
import {
|
||||
ZFieldHeightSchema,
|
||||
ZFieldPageNumberSchema,
|
||||
ZFieldPageXSchema,
|
||||
ZFieldPageYSchema,
|
||||
ZFieldWidthSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { DocumentSigningOrder, RecipientRole } from '@documenso/prisma/generated/types';
|
||||
|
||||
import { ZDocumentExternalIdSchema, ZDocumentTitleSchema } from '../document-router/schema';
|
||||
|
||||
export const ZUpdateEmbeddingDocumentRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
@@ -37,6 +36,8 @@ export const ZUpdateEmbeddingDocumentRequestSchema = z.object({
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
// We have an any cast so any changes here you need to update it in the embeding document edit page
|
||||
// Search: "map<any>" to find it
|
||||
fields: ZFieldAndMetaSchema.and(
|
||||
z.object({
|
||||
id: z.number().optional(),
|
||||
@@ -45,6 +46,7 @@ export const ZUpdateEmbeddingDocumentRequestSchema = z.object({
|
||||
pageY: ZFieldPageYSchema,
|
||||
width: ZFieldWidthSchema,
|
||||
height: ZFieldHeightSchema,
|
||||
envelopeItemId: z.string(),
|
||||
}),
|
||||
)
|
||||
.array()
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { verifyEmbeddingPresignToken } from '@documenso/lib/server-only/embedding-presign/verify-embedding-presign-token';
|
||||
import { updateEnvelope } from '@documenso/lib/server-only/envelope/update-envelope';
|
||||
import { setFieldsForTemplate } from '@documenso/lib/server-only/field/set-fields-for-template';
|
||||
import { setTemplateRecipients } from '@documenso/lib/server-only/recipient/set-template-recipients';
|
||||
import { updateTemplate } from '@documenso/lib/server-only/template/update-template';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
|
||||
import { procedure } from '../trpc';
|
||||
@@ -38,15 +38,19 @@ export const updateEmbeddingTemplateRoute = procedure
|
||||
|
||||
const { templateId, title, externalId, recipients, meta } = input;
|
||||
|
||||
await updateTemplate({
|
||||
templateId,
|
||||
await updateEnvelope({
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
userId: apiToken.userId,
|
||||
teamId: apiToken.teamId ?? undefined,
|
||||
teamId: apiToken.teamId,
|
||||
data: {
|
||||
title,
|
||||
externalId,
|
||||
},
|
||||
meta,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
const recipientsWithClientId = recipients.map((recipient) => ({
|
||||
@@ -57,7 +61,10 @@ export const updateEmbeddingTemplateRoute = procedure
|
||||
const { recipients: updatedRecipients } = await setTemplateRecipients({
|
||||
userId: apiToken.userId,
|
||||
teamId: apiToken.teamId ?? undefined,
|
||||
templateId,
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
recipients: recipientsWithClientId.map((recipient) => ({
|
||||
id: recipient.id,
|
||||
email: recipient.email,
|
||||
@@ -87,7 +94,10 @@ export const updateEmbeddingTemplateRoute = procedure
|
||||
await setFieldsForTemplate({
|
||||
userId: apiToken.userId,
|
||||
teamId: apiToken.teamId ?? undefined,
|
||||
templateId,
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
fields: fields.map((field) => ({
|
||||
...field,
|
||||
pageWidth: field.width,
|
||||
|
||||
@@ -2,15 +2,6 @@ import { DocumentSigningOrder, FieldType, RecipientRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import {
|
||||
ZFieldHeightSchema,
|
||||
ZFieldPageNumberSchema,
|
||||
ZFieldPageXSchema,
|
||||
ZFieldPageYSchema,
|
||||
ZFieldWidthSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
@@ -22,8 +13,17 @@ import {
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
ZDocumentMetaTypedSignatureEnabledSchema,
|
||||
ZDocumentMetaUploadSignatureEnabledSchema,
|
||||
ZDocumentTitleSchema,
|
||||
} from '../document-router/schema';
|
||||
} from '@documenso/lib/types/document-meta';
|
||||
import {
|
||||
ZFieldHeightSchema,
|
||||
ZFieldPageNumberSchema,
|
||||
ZFieldPageXSchema,
|
||||
ZFieldPageYSchema,
|
||||
ZFieldWidthSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
|
||||
import { ZDocumentTitleSchema } from '../document-router/schema';
|
||||
|
||||
const ZFieldSchema = z.object({
|
||||
id: z.number().optional(),
|
||||
@@ -34,6 +34,7 @@ const ZFieldSchema = z.object({
|
||||
width: ZFieldWidthSchema,
|
||||
height: ZFieldHeightSchema,
|
||||
fieldMeta: ZFieldMetaSchema.optional(),
|
||||
envelopeItemId: z.string(),
|
||||
});
|
||||
|
||||
export const ZUpdateEmbeddingTemplateRequestSchema = z.object({
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getEnvelopeWhereInput } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
||||
import { prefixedId } from '@documenso/lib/universal/id';
|
||||
import { canEnvelopeItemsBeModified } from '@documenso/lib/utils/envelope';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZCreateEnvelopeItemsRequestSchema,
|
||||
ZCreateEnvelopeItemsResponseSchema,
|
||||
} from './create-envelope-items.types';
|
||||
|
||||
export const createEnvelopeItemsRoute = authenticatedProcedure
|
||||
.input(ZCreateEnvelopeItemsRequestSchema)
|
||||
.output(ZCreateEnvelopeItemsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
const { envelopeId, items } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
envelopeId,
|
||||
},
|
||||
});
|
||||
|
||||
// Todo: Envelopes - What to do about "normalizing"?
|
||||
const { envelopeWhereInput } = await getEnvelopeWhereInput({
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
type: null,
|
||||
userId: user.id,
|
||||
teamId,
|
||||
});
|
||||
|
||||
const envelope = await prisma.envelope.findUnique({
|
||||
where: envelopeWhereInput,
|
||||
include: {
|
||||
recipients: true,
|
||||
envelopeItems: {
|
||||
orderBy: {
|
||||
order: 'asc',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!envelope) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Envelope not found',
|
||||
});
|
||||
}
|
||||
|
||||
if (!canEnvelopeItemsBeModified(envelope, envelope.recipients)) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Envelope item is not editable',
|
||||
});
|
||||
}
|
||||
|
||||
// Todo: Envelopes - Limit amount of items that can be created.
|
||||
|
||||
const foundDocumentData = await prisma.documentData.findMany({
|
||||
where: {
|
||||
id: {
|
||||
in: items.map((item) => item.documentDataId),
|
||||
},
|
||||
},
|
||||
select: {
|
||||
envelopeItem: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Check that all the document data was found.
|
||||
if (foundDocumentData.length !== items.length) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Document data not found',
|
||||
});
|
||||
}
|
||||
|
||||
// Check that it doesn't already have an envelope item.
|
||||
if (foundDocumentData.some((documentData) => documentData.envelopeItem?.id)) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Document data not found',
|
||||
});
|
||||
}
|
||||
|
||||
const currentHighestOrderValue =
|
||||
envelope.envelopeItems[envelope.envelopeItems.length - 1]?.order ?? 1;
|
||||
|
||||
const result = await prisma.envelopeItem.createManyAndReturn({
|
||||
data: items.map((item, index) => ({
|
||||
id: prefixedId('envelope_item'),
|
||||
envelopeId,
|
||||
title: item.title,
|
||||
documentDataId: item.documentDataId,
|
||||
order: currentHighestOrderValue + 1,
|
||||
})),
|
||||
include: {
|
||||
documentData: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
createdEnvelopeItems: result,
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import DocumentDataSchema from '@documenso/prisma/generated/zod/modelSchema/DocumentDataSchema';
|
||||
import EnvelopeItemSchema from '@documenso/prisma/generated/zod/modelSchema/EnvelopeItemSchema';
|
||||
|
||||
import { ZDocumentTitleSchema } from '../document-router/schema';
|
||||
|
||||
export const ZCreateEnvelopeItemsRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
items: z
|
||||
.object({
|
||||
title: ZDocumentTitleSchema,
|
||||
documentDataId: z.string(),
|
||||
})
|
||||
.array(),
|
||||
});
|
||||
|
||||
export const ZCreateEnvelopeItemsResponseSchema = z.object({
|
||||
createdEnvelopeItems: EnvelopeItemSchema.pick({
|
||||
id: true,
|
||||
title: true,
|
||||
documentDataId: true,
|
||||
envelopeId: true,
|
||||
order: true,
|
||||
})
|
||||
.extend({
|
||||
documentData: DocumentDataSchema.pick({
|
||||
type: true,
|
||||
id: true,
|
||||
data: true,
|
||||
initialData: true,
|
||||
}),
|
||||
})
|
||||
.array(),
|
||||
});
|
||||
|
||||
export type TCreateEnvelopeItemsRequest = z.infer<typeof ZCreateEnvelopeItemsRequestSchema>;
|
||||
export type TCreateEnvelopeItemsResponse = z.infer<typeof ZCreateEnvelopeItemsResponseSchema>;
|
||||
@@ -0,0 +1,68 @@
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZCreateEnvelopeRequestSchema,
|
||||
ZCreateEnvelopeResponseSchema,
|
||||
} from './create-envelope.types';
|
||||
|
||||
export const createEnvelopeRoute = authenticatedProcedure
|
||||
.input(ZCreateEnvelopeRequestSchema) // Note: Before releasing this to public, update the response schema to be correct.
|
||||
.output(ZCreateEnvelopeResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
const {
|
||||
title,
|
||||
type,
|
||||
externalId,
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
recipients,
|
||||
folderId,
|
||||
items,
|
||||
meta,
|
||||
} = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
|
||||
// Todo: Envelopes - Put the claims for number of items into this.
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId });
|
||||
|
||||
if (remaining.documents <= 0) {
|
||||
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
|
||||
message: 'You have reached your document limit for this month. Please upgrade your plan.',
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const envelope = await createEnvelope({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
internalVersion: 2,
|
||||
data: {
|
||||
type,
|
||||
title,
|
||||
externalId,
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
recipients,
|
||||
folderId,
|
||||
envelopeItems: items,
|
||||
},
|
||||
meta,
|
||||
normalizePdf: true,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return {
|
||||
id: envelope.id,
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';
|
||||
import { ZDocumentMetaCreateSchema } from '@documenso/lib/types/document-meta';
|
||||
import {
|
||||
ZFieldHeightSchema,
|
||||
ZFieldPageNumberSchema,
|
||||
ZFieldPageXSchema,
|
||||
ZFieldPageYSchema,
|
||||
ZFieldWidthSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
|
||||
import {
|
||||
ZDocumentExternalIdSchema,
|
||||
ZDocumentTitleSchema,
|
||||
ZDocumentVisibilitySchema,
|
||||
} 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),
|
||||
externalId: ZDocumentExternalIdSchema.optional(),
|
||||
visibility: ZDocumentVisibilitySchema.optional(),
|
||||
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional(),
|
||||
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional(),
|
||||
formValues: ZDocumentFormValuesSchema.optional(),
|
||||
items: z
|
||||
.object({
|
||||
title: ZDocumentTitleSchema.optional(),
|
||||
documentDataId: z.string(),
|
||||
})
|
||||
.array(),
|
||||
folderId: z
|
||||
.string()
|
||||
.describe(
|
||||
'The ID of the folder to create the document in. If not provided, the document will be created in the root folder.',
|
||||
)
|
||||
.optional(),
|
||||
recipients: z
|
||||
.array(
|
||||
ZCreateRecipientSchema.extend({
|
||||
fields: ZFieldAndMetaSchema.and(
|
||||
z.object({
|
||||
documentDataId: z
|
||||
.string()
|
||||
.describe(
|
||||
'The ID of the document data to create the field on. If empty, the first document data will be used.',
|
||||
),
|
||||
page: ZFieldPageNumberSchema,
|
||||
positionX: ZFieldPageXSchema,
|
||||
positionY: ZFieldPageYSchema,
|
||||
width: ZFieldWidthSchema,
|
||||
height: ZFieldHeightSchema,
|
||||
}),
|
||||
)
|
||||
.array()
|
||||
.optional(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
meta: ZDocumentMetaCreateSchema.optional(),
|
||||
});
|
||||
|
||||
export const ZCreateEnvelopeResponseSchema = z.object({
|
||||
id: z.string(),
|
||||
});
|
||||
|
||||
export type TCreateEnvelopeRequest = z.infer<typeof ZCreateEnvelopeRequestSchema>;
|
||||
export type TCreateEnvelopeResponse = z.infer<typeof ZCreateEnvelopeResponseSchema>;
|
||||
@@ -0,0 +1,64 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getEnvelopeWhereInput } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
||||
import { canEnvelopeItemsBeModified } from '@documenso/lib/utils/envelope';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDeleteEnvelopeItemRequestSchema,
|
||||
ZDeleteEnvelopeItemResponseSchema,
|
||||
} from './delete-envelope-item.types';
|
||||
|
||||
export const deleteEnvelopeItemRoute = authenticatedProcedure
|
||||
.input(ZDeleteEnvelopeItemRequestSchema)
|
||||
.output(ZDeleteEnvelopeItemResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
const { envelopeId, envelopeItemId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
envelopeId,
|
||||
envelopeItemId,
|
||||
},
|
||||
});
|
||||
|
||||
const { envelopeWhereInput } = await getEnvelopeWhereInput({
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
type: null,
|
||||
userId: user.id,
|
||||
teamId,
|
||||
});
|
||||
|
||||
const envelope = await prisma.envelope.findUnique({
|
||||
where: envelopeWhereInput,
|
||||
include: {
|
||||
recipients: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!envelope) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Envelope not found',
|
||||
});
|
||||
}
|
||||
|
||||
if (!canEnvelopeItemsBeModified(envelope, envelope.recipients)) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Envelope item is not editable',
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.envelopeItem.delete({
|
||||
where: {
|
||||
id: envelopeItemId,
|
||||
envelopeId: envelope.id,
|
||||
},
|
||||
});
|
||||
|
||||
// Todo: Envelopes - Audit logs?
|
||||
// Todo: Envelopes - Delete the document data as well?
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDeleteEnvelopeItemRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
envelopeItemId: z.string(),
|
||||
});
|
||||
|
||||
export const ZDeleteEnvelopeItemResponseSchema = z.void();
|
||||
|
||||
export type TDeleteEnvelopeItemRequest = z.infer<typeof ZDeleteEnvelopeItemRequestSchema>;
|
||||
export type TDeleteEnvelopeItemResponse = z.infer<typeof ZDeleteEnvelopeItemResponseSchema>;
|
||||
@@ -0,0 +1,50 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { deleteDocument } from '@documenso/lib/server-only/document/delete-document';
|
||||
import { deleteTemplate } from '@documenso/lib/server-only/template/delete-template';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDeleteEnvelopeRequestSchema,
|
||||
ZDeleteEnvelopeResponseSchema,
|
||||
} from './delete-envelope.types';
|
||||
|
||||
export const deleteEnvelopeRoute = authenticatedProcedure
|
||||
// .meta(deleteEnvelopeMeta)
|
||||
.input(ZDeleteEnvelopeRequestSchema)
|
||||
.output(ZDeleteEnvelopeResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { envelopeId, envelopeType } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
envelopeId,
|
||||
},
|
||||
});
|
||||
|
||||
await match(envelopeType)
|
||||
.with(EnvelopeType.DOCUMENT, async () =>
|
||||
deleteDocument({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
requestMetadata: ctx.metadata,
|
||||
}),
|
||||
)
|
||||
.with(EnvelopeType.TEMPLATE, async () =>
|
||||
deleteTemplate({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.exhaustive();
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
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();
|
||||
|
||||
export type TDeleteEnvelopeRequest = z.infer<typeof ZDeleteEnvelopeRequestSchema>;
|
||||
export type TDeleteEnvelopeResponse = z.infer<typeof ZDeleteEnvelopeResponseSchema>;
|
||||
@@ -0,0 +1,55 @@
|
||||
import { updateDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDistributeEnvelopeRequestSchema,
|
||||
ZDistributeEnvelopeResponseSchema,
|
||||
} from './distribute-envelope.types';
|
||||
|
||||
export const distributeEnvelopeRoute = authenticatedProcedure
|
||||
// .meta(distributeEnvelopeMeta)
|
||||
.input(ZDistributeEnvelopeRequestSchema)
|
||||
.output(ZDistributeEnvelopeResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { envelopeId, meta = {} } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
envelopeId,
|
||||
},
|
||||
});
|
||||
|
||||
if (Object.values(meta).length > 0) {
|
||||
await updateDocumentMeta({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
subject: meta.subject,
|
||||
message: meta.message,
|
||||
dateFormat: meta.dateFormat,
|
||||
timezone: meta.timezone,
|
||||
redirectUrl: meta.redirectUrl,
|
||||
distributionMethod: meta.distributionMethod,
|
||||
emailSettings: meta.emailSettings,
|
||||
language: meta.language,
|
||||
emailId: meta.emailId,
|
||||
emailReplyTo: meta.emailReplyTo,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}
|
||||
|
||||
await sendDocument({
|
||||
userId: ctx.user.id,
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
teamId,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
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({
|
||||
subject: true,
|
||||
message: true,
|
||||
timezone: true,
|
||||
dateFormat: true,
|
||||
distributionMethod: true,
|
||||
redirectUrl: true,
|
||||
language: true,
|
||||
emailId: true,
|
||||
emailReplyTo: true,
|
||||
emailSettings: true,
|
||||
}).optional(),
|
||||
});
|
||||
|
||||
export const ZDistributeEnvelopeResponseSchema = z.void();
|
||||
|
||||
export type TDistributeEnvelopeRequest = z.infer<typeof ZDistributeEnvelopeRequestSchema>;
|
||||
export type TDistributeEnvelopeResponse = z.infer<typeof ZDistributeEnvelopeResponseSchema>;
|
||||
@@ -0,0 +1,34 @@
|
||||
import { duplicateEnvelope } from '@documenso/lib/server-only/envelope/duplicate-envelope';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDuplicateEnvelopeRequestSchema,
|
||||
ZDuplicateEnvelopeResponseSchema,
|
||||
} from './duplicate-envelope.types';
|
||||
|
||||
export const duplicateEnvelopeRoute = authenticatedProcedure
|
||||
.input(ZDuplicateEnvelopeRequestSchema)
|
||||
.output(ZDuplicateEnvelopeResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { envelopeId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
envelopeId,
|
||||
},
|
||||
});
|
||||
|
||||
const duplicatedEnvelope = await duplicateEnvelope({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
duplicatedEnvelopeId: duplicatedEnvelope.id,
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDuplicateEnvelopeRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
});
|
||||
|
||||
export const ZDuplicateEnvelopeResponseSchema = z.object({
|
||||
duplicatedEnvelopeId: z.string(),
|
||||
});
|
||||
|
||||
export type TDuplicateEnvelopeRequest = z.infer<typeof ZDuplicateEnvelopeRequestSchema>;
|
||||
export type TDuplicateEnvelopeResponse = z.infer<typeof ZDuplicateEnvelopeResponseSchema>;
|
||||
@@ -0,0 +1,29 @@
|
||||
import { getEnvelopeById } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZGetEnvelopeRequestSchema, ZGetEnvelopeResponseSchema } from './get-envelope.types';
|
||||
|
||||
export const getEnvelopeRoute = authenticatedProcedure
|
||||
// .meta(getEnvelopeMeta)
|
||||
.input(ZGetEnvelopeRequestSchema)
|
||||
.output(ZGetEnvelopeResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
const { envelopeId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
envelopeId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getEnvelopeById({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
type: null,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
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(),
|
||||
});
|
||||
|
||||
export const ZGetEnvelopeResponseSchema = ZEnvelopeSchema;
|
||||
|
||||
export type TGetEnvelopeRequest = z.infer<typeof ZGetEnvelopeRequestSchema>;
|
||||
export type TGetEnvelopeResponse = z.infer<typeof ZGetEnvelopeResponseSchema>;
|
||||
@@ -0,0 +1,34 @@
|
||||
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZRedistributeEnvelopeRequestSchema,
|
||||
ZRedistributeEnvelopeResponseSchema,
|
||||
} from './redistribute-envelope.types';
|
||||
|
||||
export const redistributeEnvelopeRoute = authenticatedProcedure
|
||||
// .meta(redistributeEnvelopeMeta)
|
||||
.input(ZRedistributeEnvelopeRequestSchema)
|
||||
.output(ZRedistributeEnvelopeResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { envelopeId, recipients } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
envelopeId,
|
||||
recipients,
|
||||
},
|
||||
});
|
||||
|
||||
await resendDocument({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
recipients,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
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
|
||||
.array(z.number())
|
||||
.min(1)
|
||||
.describe('The IDs of the recipients to redistribute the envelope to.'),
|
||||
});
|
||||
|
||||
export const ZRedistributeEnvelopeResponseSchema = z.void();
|
||||
|
||||
export type TRedistributeEnvelopeRequest = z.infer<typeof ZRedistributeEnvelopeRequestSchema>;
|
||||
export type TRedistributeEnvelopeResponse = z.infer<typeof ZRedistributeEnvelopeResponseSchema>;
|
||||
@@ -0,0 +1,38 @@
|
||||
import { router } from '../trpc';
|
||||
import { createEnvelopeRoute } from './create-envelope';
|
||||
import { createEnvelopeItemsRoute } from './create-envelope-items';
|
||||
import { deleteEnvelopeRoute } from './delete-envelope';
|
||||
import { deleteEnvelopeItemRoute } from './delete-envelope-item';
|
||||
import { distributeEnvelopeRoute } from './distribute-envelope';
|
||||
import { duplicateEnvelopeRoute } from './duplicate-envelope';
|
||||
import { getEnvelopeRoute } from './get-envelope';
|
||||
import { redistributeEnvelopeRoute } from './redistribute-envelope';
|
||||
import { setEnvelopeFieldsRoute } from './set-envelope-fields';
|
||||
import { setEnvelopeRecipientsRoute } from './set-envelope-recipients';
|
||||
import { signEnvelopeFieldRoute } from './sign-envelope-field';
|
||||
import { updateEnvelopeRoute } from './update-envelope';
|
||||
import { updateEnvelopeItemsRoute } from './update-envelope-items';
|
||||
|
||||
export const envelopeRouter = router({
|
||||
get: getEnvelopeRoute,
|
||||
create: createEnvelopeRoute,
|
||||
update: updateEnvelopeRoute,
|
||||
delete: deleteEnvelopeRoute,
|
||||
duplicate: duplicateEnvelopeRoute,
|
||||
distribute: distributeEnvelopeRoute,
|
||||
redistribute: redistributeEnvelopeRoute,
|
||||
// share: shareEnvelopeRoute,
|
||||
|
||||
item: {
|
||||
createMany: createEnvelopeItemsRoute,
|
||||
updateMany: updateEnvelopeItemsRoute,
|
||||
delete: deleteEnvelopeItemRoute,
|
||||
},
|
||||
recipient: {
|
||||
set: setEnvelopeRecipientsRoute,
|
||||
},
|
||||
field: {
|
||||
set: setEnvelopeFieldsRoute,
|
||||
sign: signEnvelopeFieldRoute,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
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 { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZSetEnvelopeFieldsRequestSchema,
|
||||
ZSetEnvelopeFieldsResponseSchema,
|
||||
} from './set-envelope-fields.types';
|
||||
|
||||
// Note: This is intended to always be an internal route.
|
||||
export const setEnvelopeFieldsRoute = authenticatedProcedure
|
||||
.input(ZSetEnvelopeFieldsRequestSchema)
|
||||
.output(ZSetEnvelopeFieldsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { envelopeId, envelopeType, fields } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
envelopeId,
|
||||
},
|
||||
});
|
||||
|
||||
await match(envelopeType)
|
||||
.with(EnvelopeType.DOCUMENT, async () =>
|
||||
setFieldsForDocument({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
fields: fields.map((field) => ({
|
||||
...field,
|
||||
pageNumber: field.page,
|
||||
pageX: field.positionX,
|
||||
pageY: field.positionY,
|
||||
pageWidth: field.width,
|
||||
pageHeight: field.height,
|
||||
})),
|
||||
requestMetadata: ctx.metadata,
|
||||
}),
|
||||
)
|
||||
.with(EnvelopeType.TEMPLATE, async () =>
|
||||
setFieldsForTemplate({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
fields: fields.map((field) => ({
|
||||
...field,
|
||||
pageNumber: field.page,
|
||||
pageX: field.positionX,
|
||||
pageY: field.positionY,
|
||||
pageWidth: field.width,
|
||||
pageHeight: field.height,
|
||||
})),
|
||||
}),
|
||||
)
|
||||
.exhaustive();
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { EnvelopeType, FieldType } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
|
||||
export const ZSetEnvelopeFieldsRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
envelopeType: z.nativeEnum(EnvelopeType),
|
||||
fields: z.array(
|
||||
z.object({
|
||||
id: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe('The id of the field. If not provided, a new field will be created.'),
|
||||
envelopeItemId: z.string().describe('The id of the envelope item to put the field on'),
|
||||
recipientId: z.number(),
|
||||
type: z.nativeEnum(FieldType),
|
||||
page: z
|
||||
.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?
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export const ZSetEnvelopeFieldsResponseSchema = z.void();
|
||||
|
||||
export type TSetEnvelopeFieldsRequest = z.infer<typeof ZSetEnvelopeFieldsRequestSchema>;
|
||||
export type TSetEnvelopeFieldsResponse = z.infer<typeof ZSetEnvelopeFieldsResponseSchema>;
|
||||
@@ -0,0 +1,51 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { setDocumentRecipients } from '@documenso/lib/server-only/recipient/set-document-recipients';
|
||||
import { setTemplateRecipients } from '@documenso/lib/server-only/recipient/set-template-recipients';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZSetEnvelopeRecipientsRequestSchema,
|
||||
ZSetEnvelopeRecipientsResponseSchema,
|
||||
} from './set-envelope-recipients.types';
|
||||
|
||||
export const setEnvelopeRecipientsRoute = authenticatedProcedure
|
||||
.input(ZSetEnvelopeRecipientsRequestSchema)
|
||||
.output(ZSetEnvelopeRecipientsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { envelopeId, envelopeType, recipients } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
envelopeId,
|
||||
},
|
||||
});
|
||||
|
||||
return await match(envelopeType)
|
||||
.with(EnvelopeType.DOCUMENT, async () =>
|
||||
setDocumentRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
recipients,
|
||||
requestMetadata: ctx.metadata,
|
||||
}),
|
||||
)
|
||||
.with(EnvelopeType.TEMPLATE, async () =>
|
||||
setTemplateRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
recipients,
|
||||
}),
|
||||
)
|
||||
.exhaustive();
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { EnvelopeType, RecipientRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZRecipientActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
|
||||
import { ZRecipientLiteSchema } from '@documenso/lib/types/recipient';
|
||||
|
||||
export const ZSetEnvelopeRecipientsRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
envelopeType: z.nativeEnum(EnvelopeType),
|
||||
recipients: z.array(
|
||||
z.object({
|
||||
id: z.number().optional(),
|
||||
email: z.string().toLowerCase().email().min(1).max(254),
|
||||
name: z.string().max(255),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
actionAuth: z.array(ZRecipientActionAuthTypesSchema).optional().default([]),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export const ZSetEnvelopeRecipientsResponseSchema = z.object({
|
||||
recipients: ZRecipientLiteSchema.omit({
|
||||
documentId: true,
|
||||
templateId: true,
|
||||
}).array(),
|
||||
});
|
||||
|
||||
export type TSetEnvelopeRecipientsRequest = z.infer<typeof ZSetEnvelopeRecipientsRequestSchema>;
|
||||
export type TSetEnvelopeRecipientsResponse = z.infer<typeof ZSetEnvelopeRecipientsResponseSchema>;
|
||||
@@ -0,0 +1,472 @@
|
||||
import { DocumentStatus, FieldType, RecipientRole, SigningStatus } from '@prisma/client';
|
||||
import { DateTime } from 'luxon';
|
||||
import { P, match } from 'ts-pattern';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
|
||||
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
|
||||
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
|
||||
import { validateRadioField } from '@documenso/lib/advanced-fields-validation/validate-radio';
|
||||
import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text';
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { validateFieldAuth } from '@documenso/lib/server-only/document/validate-field-auth';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import {
|
||||
ZCheckboxFieldMeta,
|
||||
ZDropdownFieldMeta,
|
||||
ZNumberFieldMeta,
|
||||
ZRadioFieldMeta,
|
||||
ZTextFieldMeta,
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { procedure } from '../trpc';
|
||||
import {
|
||||
ZSignEnvelopeFieldRequestSchema,
|
||||
ZSignEnvelopeFieldResponseSchema,
|
||||
} from './sign-envelope-field.types';
|
||||
|
||||
// Note that this is an unauthenticated public procedure route.
|
||||
export const signEnvelopeFieldRoute = procedure
|
||||
.input(ZSignEnvelopeFieldRequestSchema)
|
||||
.output(ZSignEnvelopeFieldResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { user, metadata } = ctx;
|
||||
const { token, fieldId, fieldValue, authOptions } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
fieldId,
|
||||
},
|
||||
});
|
||||
|
||||
const recipient = await prisma.recipient.findFirst({
|
||||
where: {
|
||||
token,
|
||||
},
|
||||
});
|
||||
|
||||
if (!recipient) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
const field = await prisma.field.findFirstOrThrow({
|
||||
where: {
|
||||
id: fieldId,
|
||||
recipient: {
|
||||
...(recipient.role !== RecipientRole.ASSISTANT
|
||||
? {
|
||||
id: recipient.id,
|
||||
}
|
||||
: {
|
||||
signingStatus: {
|
||||
not: SigningStatus.SIGNED,
|
||||
},
|
||||
signingOrder: {
|
||||
gte: recipient.signingOrder ?? 0,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
include: {
|
||||
envelope: {
|
||||
include: {
|
||||
recipients: true,
|
||||
documentMeta: true,
|
||||
},
|
||||
},
|
||||
recipient: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { envelope } = field;
|
||||
const { documentMeta } = envelope;
|
||||
|
||||
if (!envelope || !recipient) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: `Document not found for field ${field.id}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (envelope.internalVersion !== 2) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: `Envelope ${envelope.id} is not a version 2 envelope`,
|
||||
});
|
||||
}
|
||||
|
||||
if (fieldValue.type !== field.type) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Selected values do not match the field values',
|
||||
});
|
||||
}
|
||||
|
||||
if (envelope.deletedAt) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: `Document ${envelope.id} has been deleted`,
|
||||
});
|
||||
}
|
||||
|
||||
if (envelope.status !== DocumentStatus.PENDING) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: `Document ${envelope.id} must be pending for signing`,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
recipient.signingStatus === SigningStatus.SIGNED ||
|
||||
field.recipient.signingStatus === SigningStatus.SIGNED
|
||||
) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: `Recipient ${recipient.id} has already signed`,
|
||||
});
|
||||
}
|
||||
|
||||
// Todo: Envelopes - Need to auto insert read only fields during sealing.
|
||||
if (field.fieldMeta?.readOnly) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: `Field ${fieldId} is read only`,
|
||||
});
|
||||
}
|
||||
|
||||
// Unreachable code based on the above query but we need to satisfy TypeScript
|
||||
if (field.recipientId === null) {
|
||||
throw new Error(`Field ${fieldId} has no recipientId`);
|
||||
}
|
||||
|
||||
let signatureImageAsBase64: string | null = null;
|
||||
let typedSignature: string | null = null;
|
||||
|
||||
const insertionValues: { customText: string; inserted: boolean } = match(fieldValue)
|
||||
.with({ type: FieldType.EMAIL }, (fieldValue) => {
|
||||
const parsedEmailValue = z.string().email().nullable().safeParse(fieldValue.value);
|
||||
|
||||
if (!parsedEmailValue.success) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Invalid email',
|
||||
});
|
||||
}
|
||||
|
||||
if (parsedEmailValue.data === null) {
|
||||
return {
|
||||
customText: '',
|
||||
inserted: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
customText: parsedEmailValue.data,
|
||||
inserted: true,
|
||||
};
|
||||
})
|
||||
.with({ type: P.union(FieldType.NAME, FieldType.INITIALS) }, (fieldValue) => {
|
||||
const parsedGenericStringValue = z.string().min(1).nullable().safeParse(fieldValue.value);
|
||||
|
||||
if (!parsedGenericStringValue.success) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Value is required',
|
||||
});
|
||||
}
|
||||
|
||||
if (parsedGenericStringValue.data === null) {
|
||||
return {
|
||||
customText: '',
|
||||
inserted: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
customText: parsedGenericStringValue.data,
|
||||
inserted: true,
|
||||
};
|
||||
})
|
||||
.with({ type: FieldType.DATE }, (fieldValue) => {
|
||||
if (!fieldValue.value) {
|
||||
return {
|
||||
customText: '',
|
||||
inserted: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
customText: DateTime.now()
|
||||
.setZone(documentMeta.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE)
|
||||
.toFormat(documentMeta.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT),
|
||||
inserted: true,
|
||||
};
|
||||
})
|
||||
.with({ type: FieldType.NUMBER }, (fieldValue) => {
|
||||
if (!fieldValue.value) {
|
||||
return {
|
||||
customText: '',
|
||||
inserted: false,
|
||||
};
|
||||
}
|
||||
|
||||
const numberFieldParsedMeta = ZNumberFieldMeta.parse(field.fieldMeta);
|
||||
const errors = validateNumberField(
|
||||
fieldValue.value.toString(),
|
||||
numberFieldParsedMeta,
|
||||
true,
|
||||
);
|
||||
|
||||
// Todo
|
||||
if (errors.length > 0) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Invalid number',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
customText: fieldValue.value.toString(),
|
||||
inserted: true,
|
||||
};
|
||||
})
|
||||
.with({ type: FieldType.TEXT }, (fieldValue) => {
|
||||
if (fieldValue.value === null) {
|
||||
return {
|
||||
customText: '',
|
||||
inserted: false,
|
||||
};
|
||||
}
|
||||
|
||||
const parsedTextFieldMeta = ZTextFieldMeta.parse(field.fieldMeta);
|
||||
const errors = validateTextField(fieldValue.value, parsedTextFieldMeta, true);
|
||||
|
||||
// Todo
|
||||
if (errors.length > 0) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Invalid email',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
customText: fieldValue.value,
|
||||
inserted: true,
|
||||
};
|
||||
})
|
||||
.with({ type: FieldType.RADIO }, (fieldValue) => {
|
||||
if (fieldValue.value === null) {
|
||||
return {
|
||||
customText: '',
|
||||
inserted: false,
|
||||
};
|
||||
}
|
||||
|
||||
const parsedRadioFieldParsedMeta = ZRadioFieldMeta.parse(field.fieldMeta);
|
||||
const errors = validateRadioField(fieldValue.value, parsedRadioFieldParsedMeta, true);
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new Error(errors.join(', '));
|
||||
}
|
||||
|
||||
// Todo
|
||||
if (errors.length > 0) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Invalid radio value',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
customText: fieldValue.value,
|
||||
inserted: true,
|
||||
};
|
||||
})
|
||||
.with({ type: FieldType.CHECKBOX }, (fieldValue) => {
|
||||
if (fieldValue.value === null) {
|
||||
return {
|
||||
customText: '',
|
||||
inserted: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Todo: Envelopes - This won't work.
|
||||
|
||||
const parsedCheckboxFieldParsedMeta = ZCheckboxFieldMeta.parse(field.fieldMeta);
|
||||
|
||||
const checkboxFieldValues = parsedCheckboxFieldParsedMeta.values || [];
|
||||
|
||||
const { value } = fieldValue;
|
||||
|
||||
const selectedValues = checkboxFieldValues.filter(({ id }) => value.some((v) => v === id));
|
||||
|
||||
if (selectedValues.length !== value.length) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Selected values do not match the checkbox field values',
|
||||
});
|
||||
}
|
||||
|
||||
const errors = validateCheckboxField(
|
||||
selectedValues.map(({ value }) => value),
|
||||
parsedCheckboxFieldParsedMeta,
|
||||
true,
|
||||
);
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Invalid checkbox value:' + errors.join(', '),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
customText: JSON.stringify(fieldValue.value),
|
||||
inserted: true,
|
||||
};
|
||||
})
|
||||
.with({ type: FieldType.DROPDOWN }, (fieldValue) => {
|
||||
if (fieldValue.value === null) {
|
||||
return {
|
||||
customText: '',
|
||||
inserted: false,
|
||||
};
|
||||
}
|
||||
|
||||
const parsedDropdownFieldMeta = ZDropdownFieldMeta.parse(field.fieldMeta);
|
||||
const errors = validateDropdownField(fieldValue.value, parsedDropdownFieldMeta, true);
|
||||
|
||||
// Todo
|
||||
if (errors.length > 0) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Invalid dropdown value',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
customText: fieldValue.value,
|
||||
inserted: true,
|
||||
};
|
||||
})
|
||||
.with({ type: FieldType.SIGNATURE }, (fieldValue) => {
|
||||
const { value, isBase64 } = fieldValue;
|
||||
|
||||
if (!value) {
|
||||
return {
|
||||
customText: '',
|
||||
inserted: false,
|
||||
};
|
||||
}
|
||||
|
||||
signatureImageAsBase64 = isBase64 ? value : null;
|
||||
typedSignature = !isBase64 ? value : null;
|
||||
|
||||
if (documentMeta.typedSignatureEnabled === false && typedSignature) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Typed signatures are not allowed. Please draw your signature',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
customText: '',
|
||||
inserted: true,
|
||||
};
|
||||
})
|
||||
.exhaustive();
|
||||
|
||||
const derivedRecipientActionAuth = await validateFieldAuth({
|
||||
documentAuthOptions: envelope.authOptions,
|
||||
recipient,
|
||||
field,
|
||||
userId: user?.id,
|
||||
authOptions,
|
||||
});
|
||||
|
||||
const assistant = recipient.role === RecipientRole.ASSISTANT ? recipient : undefined;
|
||||
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
const updatedField = await tx.field.update({
|
||||
where: {
|
||||
id: field.id,
|
||||
},
|
||||
data: {
|
||||
customText: insertionValues.customText,
|
||||
inserted: insertionValues.inserted,
|
||||
},
|
||||
include: {
|
||||
signature: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (field.type === FieldType.SIGNATURE) {
|
||||
const signature = await tx.signature.upsert({
|
||||
where: {
|
||||
fieldId: field.id,
|
||||
},
|
||||
create: {
|
||||
fieldId: field.id,
|
||||
recipientId: field.recipientId,
|
||||
signatureImageAsBase64: signatureImageAsBase64,
|
||||
typedSignature: typedSignature,
|
||||
},
|
||||
update: {
|
||||
signatureImageAsBase64: signatureImageAsBase64,
|
||||
typedSignature: typedSignature,
|
||||
},
|
||||
});
|
||||
|
||||
// Dirty but I don't want to deal with type information
|
||||
Object.assign(updatedField, {
|
||||
signature,
|
||||
});
|
||||
}
|
||||
|
||||
await tx.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
type:
|
||||
assistant && field.recipientId !== assistant.id
|
||||
? DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_PREFILLED
|
||||
: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_INSERTED,
|
||||
envelopeId: envelope.id,
|
||||
user: {
|
||||
email: assistant?.email ?? recipient.email,
|
||||
name: assistant?.name ?? recipient.name,
|
||||
},
|
||||
requestMetadata: metadata.requestMetadata,
|
||||
data: {
|
||||
recipientEmail: recipient.email,
|
||||
recipientId: recipient.id,
|
||||
recipientName: recipient.name,
|
||||
recipientRole: recipient.role,
|
||||
fieldId: updatedField.secondaryId,
|
||||
field: match(updatedField.type)
|
||||
.with(FieldType.SIGNATURE, FieldType.FREE_SIGNATURE, (type) => ({
|
||||
type,
|
||||
data: signatureImageAsBase64 || typedSignature || '',
|
||||
}))
|
||||
.with(
|
||||
FieldType.DATE,
|
||||
FieldType.EMAIL,
|
||||
FieldType.NAME,
|
||||
FieldType.TEXT,
|
||||
FieldType.INITIALS,
|
||||
(type) => ({
|
||||
type,
|
||||
data: updatedField.customText,
|
||||
}),
|
||||
)
|
||||
.with(
|
||||
FieldType.NUMBER,
|
||||
FieldType.RADIO,
|
||||
FieldType.CHECKBOX,
|
||||
FieldType.DROPDOWN,
|
||||
(type) => ({
|
||||
type,
|
||||
data: updatedField.customText,
|
||||
}),
|
||||
)
|
||||
.exhaustive(),
|
||||
fieldSecurity: derivedRecipientActionAuth
|
||||
? {
|
||||
type: derivedRecipientActionAuth,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
return {
|
||||
signedField: updatedField,
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZRecipientActionAuthSchema } from '@documenso/lib/types/document-auth';
|
||||
import { ZFieldSchema } from '@documenso/lib/types/field';
|
||||
import { FieldType } from '@documenso/prisma/client';
|
||||
import SignatureSchema from '@documenso/prisma/generated/zod/modelSchema/SignatureSchema';
|
||||
|
||||
export const ZSignEnvelopeFieldValue = z.discriminatedUnion('type', [
|
||||
z.object({
|
||||
type: z.literal(FieldType.CHECKBOX),
|
||||
value: z.array(z.number()),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.RADIO),
|
||||
value: z.string().nullable(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.NUMBER),
|
||||
value: z.number().nullable(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.EMAIL),
|
||||
value: z.string().nullable(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.NAME),
|
||||
value: z.string().nullable(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.INITIALS),
|
||||
value: z.string().nullable(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.TEXT),
|
||||
value: z.string().nullable(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.DROPDOWN),
|
||||
value: z.string().nullable(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.DATE),
|
||||
value: z.boolean(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.SIGNATURE),
|
||||
value: z.string().nullable(),
|
||||
isBase64: z.boolean(),
|
||||
}),
|
||||
]);
|
||||
|
||||
export const ZSignEnvelopeFieldRequestSchema = z.object({
|
||||
token: z.string(),
|
||||
fieldId: z.number(),
|
||||
fieldValue: ZSignEnvelopeFieldValue,
|
||||
authOptions: ZRecipientActionAuthSchema.optional(),
|
||||
});
|
||||
|
||||
export const ZSignEnvelopeFieldResponseSchema = z.object({
|
||||
signedField: ZFieldSchema.omit({
|
||||
templateId: true,
|
||||
documentId: true,
|
||||
}).extend({
|
||||
signature: SignatureSchema.nullish(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type TSignEnvelopeFieldValue = z.infer<typeof ZSignEnvelopeFieldValue>;
|
||||
export type TSignEnvelopeFieldRequest = z.infer<typeof ZSignEnvelopeFieldRequestSchema>;
|
||||
export type TSignEnvelopeFieldResponse = z.infer<typeof ZSignEnvelopeFieldResponseSchema>;
|
||||
@@ -0,0 +1,99 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getEnvelopeWhereInput } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
||||
import { canEnvelopeItemsBeModified } from '@documenso/lib/utils/envelope';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZUpdateEnvelopeItemsRequestSchema,
|
||||
ZUpdateEnvelopeItemsResponseSchema,
|
||||
} from './update-envelope-items.types';
|
||||
|
||||
export const updateEnvelopeItemsRoute = authenticatedProcedure
|
||||
.input(ZUpdateEnvelopeItemsRequestSchema)
|
||||
.output(ZUpdateEnvelopeItemsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
const { envelopeId, data } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
envelopeId,
|
||||
},
|
||||
});
|
||||
|
||||
const { envelopeWhereInput } = await getEnvelopeWhereInput({
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
type: null,
|
||||
userId: user.id,
|
||||
teamId,
|
||||
});
|
||||
|
||||
const envelope = await prisma.envelope.findUnique({
|
||||
where: envelopeWhereInput,
|
||||
include: {
|
||||
recipients: true,
|
||||
envelopeItems: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!envelope) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Envelope not found',
|
||||
});
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Envelope items are required',
|
||||
});
|
||||
}
|
||||
|
||||
if (!canEnvelopeItemsBeModified(envelope, envelope.recipients)) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Envelope item is not editable',
|
||||
});
|
||||
}
|
||||
|
||||
// Check that the items belong to the envelope.
|
||||
const itemsBelongToEnvelope = data.every((item) =>
|
||||
envelope.envelopeItems.some(({ id }) => item.envelopeItemId === id),
|
||||
);
|
||||
|
||||
if (!itemsBelongToEnvelope) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'One or more envelope items to update do not exist',
|
||||
});
|
||||
}
|
||||
|
||||
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?
|
||||
// Todo: Envelopes - Delete the document data as well?
|
||||
|
||||
return {
|
||||
updatedEnvelopeItems,
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import EnvelopeItemSchema from '@documenso/prisma/generated/zod/modelSchema/EnvelopeItemSchema';
|
||||
|
||||
import { ZDocumentTitleSchema } from '../document-router/schema';
|
||||
|
||||
export const ZUpdateEnvelopeItemsRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
data: z
|
||||
.object({
|
||||
envelopeItemId: z.string(),
|
||||
order: z.number().int().min(1).optional(),
|
||||
title: ZDocumentTitleSchema.optional(),
|
||||
})
|
||||
.array()
|
||||
.min(1),
|
||||
});
|
||||
|
||||
export const ZUpdateEnvelopeItemsResponseSchema = z.object({
|
||||
updatedEnvelopeItems: EnvelopeItemSchema.pick({
|
||||
id: true,
|
||||
order: true,
|
||||
title: true,
|
||||
envelopeId: true,
|
||||
}).array(),
|
||||
});
|
||||
|
||||
export type TUpdateEnvelopeItemsRequest = z.infer<typeof ZUpdateEnvelopeItemsRequestSchema>;
|
||||
export type TUpdateEnvelopeItemsResponse = z.infer<typeof ZUpdateEnvelopeItemsResponseSchema>;
|
||||
@@ -0,0 +1,36 @@
|
||||
import { updateEnvelope } from '@documenso/lib/server-only/envelope/update-envelope';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZUpdateEnvelopeRequestSchema,
|
||||
ZUpdateEnvelopeResponseSchema,
|
||||
} from './update-envelope.types';
|
||||
|
||||
export const updateEnvelopeRoute = authenticatedProcedure
|
||||
// .meta(updateEnvelopeTrpcMeta)
|
||||
.input(ZUpdateEnvelopeRequestSchema)
|
||||
.output(ZUpdateEnvelopeResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { envelopeId, data, meta = {} } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
envelopeId,
|
||||
},
|
||||
});
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
return await updateEnvelope({
|
||||
userId,
|
||||
teamId,
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
data,
|
||||
meta,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
// import type { OpenApiMeta } from 'trpc-to-openapi';
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { ZDocumentMetaUpdateSchema } from '@documenso/lib/types/document-meta';
|
||||
import { ZEnvelopeLiteSchema } from '@documenso/lib/types/envelope';
|
||||
|
||||
import {
|
||||
ZDocumentExternalIdSchema,
|
||||
ZDocumentTitleSchema,
|
||||
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),
|
||||
data: z
|
||||
.object({
|
||||
title: ZDocumentTitleSchema.optional(),
|
||||
externalId: ZDocumentExternalIdSchema.nullish(),
|
||||
visibility: ZDocumentVisibilitySchema.optional(),
|
||||
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional(),
|
||||
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional(),
|
||||
folderId: z.string().nullish(),
|
||||
})
|
||||
.optional(),
|
||||
meta: ZDocumentMetaUpdateSchema.optional(),
|
||||
});
|
||||
|
||||
export const ZUpdateEnvelopeResponseSchema = ZEnvelopeLiteSchema;
|
||||
|
||||
export type TUpdateEnvelopeRequest = z.infer<typeof ZUpdateEnvelopeRequestSchema>;
|
||||
export type TUpdateEnvelopeResponse = z.infer<typeof ZUpdateEnvelopeResponseSchema>;
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createDocumentFields } from '@documenso/lib/server-only/field/create-document-fields';
|
||||
import { createTemplateFields } from '@documenso/lib/server-only/field/create-template-fields';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { createEnvelopeFields } from '@documenso/lib/server-only/field/create-envelope-fields';
|
||||
import { deleteDocumentField } from '@documenso/lib/server-only/field/delete-document-field';
|
||||
import { deleteTemplateField } from '@documenso/lib/server-only/field/delete-template-field';
|
||||
import { getFieldById } from '@documenso/lib/server-only/field/get-field-by-id';
|
||||
@@ -72,6 +73,7 @@ export const fieldRouter = router({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
fieldId,
|
||||
envelopeType: EnvelopeType.DOCUMENT,
|
||||
});
|
||||
}),
|
||||
|
||||
@@ -100,10 +102,13 @@ export const fieldRouter = router({
|
||||
},
|
||||
});
|
||||
|
||||
const createdFields = await createDocumentFields({
|
||||
const createdFields = await createEnvelopeFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
fields: [field],
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
@@ -136,10 +141,13 @@ export const fieldRouter = router({
|
||||
},
|
||||
});
|
||||
|
||||
return await createDocumentFields({
|
||||
return await createEnvelopeFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
fields,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
@@ -251,10 +259,8 @@ export const fieldRouter = router({
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* Todo: Refactor to setFieldsForDocument function.
|
||||
*/
|
||||
addFields: authenticatedProcedure
|
||||
setFieldsForDocument: authenticatedProcedure
|
||||
.input(ZSetDocumentFieldsRequestSchema)
|
||||
.output(ZSetDocumentFieldsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
@@ -268,13 +274,16 @@ export const fieldRouter = router({
|
||||
});
|
||||
|
||||
return await setFieldsForDocument({
|
||||
documentId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
fields: fields.map((field) => ({
|
||||
id: field.nativeId,
|
||||
signerEmail: field.signerEmail,
|
||||
id: field.id,
|
||||
recipientId: field.recipientId,
|
||||
envelopeItemId: field.envelopeItemId,
|
||||
type: field.type,
|
||||
pageNumber: field.pageNumber,
|
||||
pageX: field.pageX,
|
||||
@@ -312,11 +321,15 @@ export const fieldRouter = router({
|
||||
},
|
||||
});
|
||||
|
||||
const createdFields = await createTemplateFields({
|
||||
const createdFields = await createEnvelopeFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
fields: [field],
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return createdFields.fields[0];
|
||||
@@ -352,6 +365,7 @@ export const fieldRouter = router({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
fieldId,
|
||||
envelopeType: EnvelopeType.TEMPLATE,
|
||||
});
|
||||
}),
|
||||
|
||||
@@ -380,11 +394,15 @@ export const fieldRouter = router({
|
||||
},
|
||||
});
|
||||
|
||||
return await createTemplateFields({
|
||||
return await createEnvelopeFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
fields,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
@@ -491,10 +509,8 @@ export const fieldRouter = router({
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* Todo: Refactor to setFieldsForTemplate.
|
||||
*/
|
||||
addTemplateFields: authenticatedProcedure
|
||||
setFieldsForTemplate: authenticatedProcedure
|
||||
.input(ZSetFieldsForTemplateRequestSchema)
|
||||
.output(ZSetFieldsForTemplateResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
@@ -508,13 +524,16 @@ export const fieldRouter = router({
|
||||
});
|
||||
|
||||
return await setFieldsForTemplate({
|
||||
templateId,
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
fields: fields.map((field) => ({
|
||||
id: field.nativeId,
|
||||
signerEmail: field.signerEmail,
|
||||
id: field.id,
|
||||
recipientId: field.recipientId,
|
||||
envelopeItemId: field.envelopeItemId,
|
||||
type: field.type,
|
||||
pageNumber: field.pageNumber,
|
||||
pageX: field.pageX,
|
||||
|
||||
@@ -110,11 +110,10 @@ export const ZSetDocumentFieldsRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
fields: z.array(
|
||||
z.object({
|
||||
formId: z.string().min(1),
|
||||
nativeId: z.number().optional(),
|
||||
id: z.number().optional(),
|
||||
type: z.nativeEnum(FieldType),
|
||||
signerEmail: z.string().min(1),
|
||||
recipientId: z.number().min(1),
|
||||
envelopeItemId: z.string(),
|
||||
pageNumber: z.number().min(1),
|
||||
pageX: z.number().min(0),
|
||||
pageY: z.number().min(0),
|
||||
@@ -133,11 +132,10 @@ export const ZSetFieldsForTemplateRequestSchema = z.object({
|
||||
templateId: z.number(),
|
||||
fields: z.array(
|
||||
z.object({
|
||||
formId: z.string().min(1),
|
||||
nativeId: z.number().optional(),
|
||||
id: z.number().optional(),
|
||||
type: z.nativeEnum(FieldType),
|
||||
signerEmail: z.string().min(1),
|
||||
recipientId: z.number().min(1),
|
||||
envelopeItemId: z.string(),
|
||||
pageNumber: z.number().min(1),
|
||||
pageX: z.number().min(0),
|
||||
pageY: z.number().min(0),
|
||||
|
||||
@@ -4,13 +4,10 @@ import { deleteFolder } from '@documenso/lib/server-only/folder/delete-folder';
|
||||
import { findFolders } from '@documenso/lib/server-only/folder/find-folders';
|
||||
import { getFolderBreadcrumbs } from '@documenso/lib/server-only/folder/get-folder-breadcrumbs';
|
||||
import { getFolderById } from '@documenso/lib/server-only/folder/get-folder-by-id';
|
||||
import { moveDocumentToFolder } from '@documenso/lib/server-only/folder/move-document-to-folder';
|
||||
import { moveFolder } from '@documenso/lib/server-only/folder/move-folder';
|
||||
import { moveTemplateToFolder } from '@documenso/lib/server-only/folder/move-template-to-folder';
|
||||
import { pinFolder } from '@documenso/lib/server-only/folder/pin-folder';
|
||||
import { unpinFolder } from '@documenso/lib/server-only/folder/unpin-folder';
|
||||
import { updateFolder } from '@documenso/lib/server-only/folder/update-folder';
|
||||
import { FolderType } from '@documenso/lib/types/folder-type';
|
||||
|
||||
import { authenticatedProcedure, router } from '../trpc';
|
||||
import {
|
||||
@@ -21,9 +18,7 @@ import {
|
||||
ZGenericSuccessResponse,
|
||||
ZGetFoldersResponseSchema,
|
||||
ZGetFoldersSchema,
|
||||
ZMoveDocumentToFolderSchema,
|
||||
ZMoveFolderSchema,
|
||||
ZMoveTemplateToFolderSchema,
|
||||
ZPinFolderSchema,
|
||||
ZSuccessResponseSchema,
|
||||
ZUnpinFolderSchema,
|
||||
@@ -266,95 +261,6 @@ export const folderRouter = router({
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
moveDocumentToFolder: authenticatedProcedure
|
||||
.input(ZMoveDocumentToFolderSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
const { documentId, folderId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
|
||||
if (folderId !== null) {
|
||||
try {
|
||||
await getFolderById({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
folderId,
|
||||
type: FolderType.DOCUMENT,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Folder not found',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const result = await moveDocumentToFolder({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
folderId,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return {
|
||||
...result,
|
||||
type: FolderType.DOCUMENT,
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
moveTemplateToFolder: authenticatedProcedure
|
||||
.input(ZMoveTemplateToFolderSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
const { templateId, folderId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
templateId,
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
|
||||
if (folderId !== null) {
|
||||
try {
|
||||
await getFolderById({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
folderId,
|
||||
type: FolderType.TEMPLATE,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Folder not found',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const result = await moveTemplateToFolder({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
folderId,
|
||||
});
|
||||
|
||||
return {
|
||||
...result,
|
||||
type: FolderType.TEMPLATE,
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
|
||||
@@ -77,18 +77,6 @@ export const ZMoveFolderSchema = z.object({
|
||||
type: ZFolderTypeSchema.optional(),
|
||||
});
|
||||
|
||||
export const ZMoveDocumentToFolderSchema = z.object({
|
||||
documentId: z.number(),
|
||||
folderId: z.string().nullable().optional(),
|
||||
type: z.enum(['DOCUMENT']).optional(),
|
||||
});
|
||||
|
||||
export const ZMoveTemplateToFolderSchema = z.object({
|
||||
templateId: z.number(),
|
||||
folderId: z.string().nullable().optional(),
|
||||
type: z.enum(['TEMPLATE']).optional(),
|
||||
});
|
||||
|
||||
export const ZPinFolderSchema = z.object({
|
||||
folderId: z.string(),
|
||||
type: ZFolderTypeSchema.optional(),
|
||||
|
||||
@@ -2,12 +2,11 @@ import { z } from 'zod';
|
||||
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
|
||||
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
} from '../document-router/schema';
|
||||
} from '@documenso/lib/types/document-meta';
|
||||
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
|
||||
|
||||
export const ZUpdateOrganisationSettingsRequestSchema = z.object({
|
||||
organisationId: z.string(),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
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';
|
||||
@@ -77,6 +79,7 @@ export const recipientRouter = router({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
recipientId,
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
});
|
||||
}),
|
||||
|
||||
@@ -108,7 +111,10 @@ export const recipientRouter = router({
|
||||
const createdRecipients = await createDocumentRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
recipients: [recipient],
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
@@ -144,7 +150,10 @@ export const recipientRouter = router({
|
||||
return await createDocumentRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
recipients,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
@@ -178,7 +187,10 @@ export const recipientRouter = router({
|
||||
const updatedRecipients = await updateDocumentRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
recipients: [recipient],
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
@@ -214,7 +226,10 @@ export const recipientRouter = router({
|
||||
return await updateDocumentRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
recipients,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
@@ -273,9 +288,12 @@ export const recipientRouter = router({
|
||||
return await setDocumentRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
recipients: recipients.map((recipient) => ({
|
||||
id: recipient.nativeId,
|
||||
id: recipient.id,
|
||||
email: recipient.email,
|
||||
name: recipient.name,
|
||||
role: recipient.role,
|
||||
@@ -316,6 +334,7 @@ export const recipientRouter = router({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
recipientId,
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
});
|
||||
}),
|
||||
|
||||
@@ -507,9 +526,12 @@ export const recipientRouter = router({
|
||||
return await setTemplateRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
recipients: recipients.map((recipient) => ({
|
||||
id: recipient.nativeId,
|
||||
id: recipient.id,
|
||||
email: recipient.email,
|
||||
name: recipient.name,
|
||||
role: recipient.role,
|
||||
@@ -533,9 +555,12 @@ export const recipientRouter = router({
|
||||
},
|
||||
});
|
||||
|
||||
return await completeDocumentWithToken({
|
||||
await completeDocumentWithToken({
|
||||
token,
|
||||
documentId,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
authOptions,
|
||||
accessAuthOptions,
|
||||
nextSigner,
|
||||
@@ -560,7 +585,10 @@ export const recipientRouter = router({
|
||||
|
||||
return await rejectDocumentWithToken({
|
||||
token,
|
||||
documentId,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
reason,
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
|
||||
@@ -82,7 +82,7 @@ export const ZSetDocumentRecipientsRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
recipients: z.array(
|
||||
z.object({
|
||||
nativeId: z.number().optional(),
|
||||
id: z.number().optional(),
|
||||
email: z.string().toLowerCase().email().min(1).max(254),
|
||||
name: z.string().max(255),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
@@ -136,7 +136,7 @@ export const ZSetTemplateRecipientsRequestSchema = z.object({
|
||||
templateId: z.number(),
|
||||
recipients: z.array(
|
||||
z.object({
|
||||
nativeId: z.number().optional(),
|
||||
id: z.number().optional(),
|
||||
email: z
|
||||
.string()
|
||||
.toLowerCase()
|
||||
|
||||
@@ -4,12 +4,12 @@ import { authRouter } from './auth-router/router';
|
||||
import { documentRouter } from './document-router/router';
|
||||
import { embeddingPresignRouter } from './embedding-router/_router';
|
||||
import { enterpriseRouter } from './enterprise-router/router';
|
||||
import { envelopeRouter } from './envelope-router/router';
|
||||
import { fieldRouter } from './field-router/router';
|
||||
import { folderRouter } from './folder-router/router';
|
||||
import { organisationRouter } from './organisation-router/router';
|
||||
import { profileRouter } from './profile-router/router';
|
||||
import { recipientRouter } from './recipient-router/router';
|
||||
import { shareLinkRouter } from './share-link-router/router';
|
||||
import { teamRouter } from './team-router/router';
|
||||
import { templateRouter } from './template-router/router';
|
||||
import { router } from './trpc';
|
||||
@@ -17,6 +17,7 @@ import { webhookRouter } from './webhook-router/router';
|
||||
|
||||
export const appRouter = router({
|
||||
enterprise: enterpriseRouter,
|
||||
envelope: envelopeRouter,
|
||||
auth: authRouter,
|
||||
profile: profileRouter,
|
||||
document: documentRouter,
|
||||
@@ -25,7 +26,6 @@ export const appRouter = router({
|
||||
recipient: recipientRouter,
|
||||
admin: adminRouter,
|
||||
organisation: organisationRouter,
|
||||
shareLink: shareLinkRouter,
|
||||
apiToken: apiTokenRouter,
|
||||
team: teamRouter,
|
||||
template: templateRouter,
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { buildTeamWhereQuery } from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { procedure } from '../trpc';
|
||||
import {
|
||||
ZGetDocumentInternalUrlForQRCodeInput,
|
||||
ZGetDocumentInternalUrlForQRCodeOutput,
|
||||
} from './get-document-internal-url-for-qr-code.types';
|
||||
|
||||
export const getDocumentInternalUrlForQRCodeRoute = procedure
|
||||
.input(ZGetDocumentInternalUrlForQRCodeInput)
|
||||
.output(ZGetDocumentInternalUrlForQRCodeOutput)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!ctx.user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const document = await prisma.document.findFirst({
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
id: documentId,
|
||||
userId: ctx.user.id,
|
||||
},
|
||||
{
|
||||
id: documentId,
|
||||
team: buildTeamWhereQuery({ teamId: undefined, userId: ctx.user.id }),
|
||||
},
|
||||
],
|
||||
},
|
||||
include: {
|
||||
team: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!document) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `${NEXT_PUBLIC_WEBAPP_URL()}/t/${document.team.url}/documents/${document.id}`;
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZGetDocumentInternalUrlForQRCodeInput = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export type TGetDocumentInternalUrlForQRCodeInput = z.infer<
|
||||
typeof ZGetDocumentInternalUrlForQRCodeInput
|
||||
>;
|
||||
|
||||
export const ZGetDocumentInternalUrlForQRCodeOutput = z.string().nullable();
|
||||
|
||||
export type TGetDocumentInternalUrlForQRCodeOutput = z.infer<
|
||||
typeof ZGetDocumentInternalUrlForQRCodeOutput
|
||||
>;
|
||||
@@ -1,33 +0,0 @@
|
||||
import { createOrGetShareLink } from '@documenso/lib/server-only/share/create-or-get-share-link';
|
||||
|
||||
import { procedure, router } from '../trpc';
|
||||
import { getDocumentInternalUrlForQRCodeRoute } from './get-document-internal-url-for-qr-code';
|
||||
import { ZCreateOrGetShareLinkMutationSchema } from './schema';
|
||||
|
||||
export const shareLinkRouter = router({
|
||||
createOrGetShareLink: procedure
|
||||
.input(ZCreateOrGetShareLinkMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { documentId, token } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
if (token) {
|
||||
return await createOrGetShareLink({ documentId, token });
|
||||
}
|
||||
|
||||
if (!ctx.user?.id) {
|
||||
throw new Error(
|
||||
'You must either provide a token or be logged in to create a sharing link.',
|
||||
);
|
||||
}
|
||||
|
||||
return await createOrGetShareLink({ documentId, userId: ctx.user.id });
|
||||
}),
|
||||
|
||||
getDocumentInternalUrlForQRCode: getDocumentInternalUrlForQRCodeRoute,
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCreateOrGetShareLinkMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
token: z.string().optional(),
|
||||
});
|
||||
|
||||
export type TCreateOrGetShareLinkMutationSchema = z.infer<
|
||||
typeof ZCreateOrGetShareLinkMutationSchema
|
||||
>;
|
||||
@@ -2,12 +2,11 @@ import { z } from 'zod';
|
||||
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
|
||||
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
} from '../document-router/schema';
|
||||
} from '@documenso/lib/types/document-meta';
|
||||
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
|
||||
|
||||
/**
|
||||
* Null = Inherit from organisation.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Document } from '@prisma/client';
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
import type { Envelope } from '@prisma/client';
|
||||
import { DocumentDataType, EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
@@ -7,24 +7,25 @@ import { jobs } from '@documenso/lib/jobs/client';
|
||||
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
|
||||
import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
|
||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
|
||||
import { duplicateEnvelope } from '@documenso/lib/server-only/envelope/duplicate-envelope';
|
||||
import { updateEnvelope } from '@documenso/lib/server-only/envelope/update-envelope';
|
||||
import {
|
||||
ZCreateDocumentFromDirectTemplateResponseSchema,
|
||||
createDocumentFromDirectTemplate,
|
||||
} from '@documenso/lib/server-only/template/create-document-from-direct-template';
|
||||
import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template';
|
||||
import {
|
||||
ZCreateTemplateResponseSchema,
|
||||
createTemplate,
|
||||
} from '@documenso/lib/server-only/template/create-template';
|
||||
import { createTemplateDirectLink } from '@documenso/lib/server-only/template/create-template-direct-link';
|
||||
import { deleteTemplate } from '@documenso/lib/server-only/template/delete-template';
|
||||
import { deleteTemplateDirectLink } from '@documenso/lib/server-only/template/delete-template-direct-link';
|
||||
import { duplicateTemplate } from '@documenso/lib/server-only/template/duplicate-template';
|
||||
import { findTemplates } from '@documenso/lib/server-only/template/find-templates';
|
||||
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
|
||||
import { toggleTemplateDirectLink } from '@documenso/lib/server-only/template/toggle-template-direct-link';
|
||||
import { updateTemplate } from '@documenso/lib/server-only/template/update-template';
|
||||
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
||||
import { mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope';
|
||||
import { mapFieldToLegacyField } from '@documenso/lib/utils/fields';
|
||||
import { mapRecipientToLegacyRecipient } from '@documenso/lib/utils/recipients';
|
||||
import { mapEnvelopeToTemplateLite } from '@documenso/lib/utils/templates';
|
||||
|
||||
import { ZGenericSuccessResponse, ZSuccessResponseSchema } from '../document-router/schema';
|
||||
import { authenticatedProcedure, maybeAuthenticatedProcedure, router } from '../trpc';
|
||||
@@ -36,6 +37,7 @@ import {
|
||||
ZCreateTemplateDirectLinkRequestSchema,
|
||||
ZCreateTemplateDirectLinkResponseSchema,
|
||||
ZCreateTemplateMutationSchema,
|
||||
ZCreateTemplateResponseSchema,
|
||||
ZCreateTemplateV2RequestSchema,
|
||||
ZCreateTemplateV2ResponseSchema,
|
||||
ZDeleteTemplateDirectLinkRequestSchema,
|
||||
@@ -77,11 +79,44 @@ export const templateRouter = router({
|
||||
},
|
||||
});
|
||||
|
||||
return await findTemplates({
|
||||
const result = await findTemplates({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
...input,
|
||||
});
|
||||
|
||||
// Remapping for backwards compatibility.
|
||||
return {
|
||||
...result,
|
||||
data: result.data.map((envelope) => {
|
||||
const legacyTemplateId = mapSecondaryIdToTemplateId(envelope.secondaryId);
|
||||
|
||||
return {
|
||||
id: legacyTemplateId,
|
||||
envelopeId: envelope.id,
|
||||
type: envelope.templateType,
|
||||
visibility: envelope.visibility,
|
||||
externalId: envelope.externalId,
|
||||
title: envelope.title,
|
||||
userId: envelope.userId,
|
||||
teamId: envelope.teamId,
|
||||
authOptions: envelope.authOptions,
|
||||
createdAt: envelope.createdAt,
|
||||
updatedAt: envelope.updatedAt,
|
||||
publicTitle: envelope.publicTitle,
|
||||
publicDescription: envelope.publicDescription,
|
||||
folderId: envelope.folderId,
|
||||
useLegacyFieldInsertion: envelope.useLegacyFieldInsertion,
|
||||
team: envelope.team,
|
||||
fields: envelope.fields.map((field) => mapFieldToLegacyField(field, envelope)),
|
||||
recipients: envelope.recipients.map((recipient) =>
|
||||
mapRecipientToLegacyRecipient(recipient, envelope),
|
||||
),
|
||||
templateMeta: envelope.documentMeta,
|
||||
directLink: envelope.directLink,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
@@ -109,7 +144,10 @@ export const templateRouter = router({
|
||||
});
|
||||
|
||||
return await getTemplateById({
|
||||
id: templateId,
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
});
|
||||
@@ -121,7 +159,7 @@ export const templateRouter = router({
|
||||
* @private
|
||||
*/
|
||||
createTemplate: authenticatedProcedure
|
||||
// .meta({
|
||||
// .meta({ // Note before releasing this to public, update the response schema to be correct.
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/template/create',
|
||||
@@ -142,15 +180,26 @@ export const templateRouter = router({
|
||||
},
|
||||
});
|
||||
|
||||
return await createTemplate({
|
||||
const envelope = await createEnvelope({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
templateDocumentDataId,
|
||||
internalVersion: 1,
|
||||
data: {
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title,
|
||||
folderId,
|
||||
envelopeItems: [
|
||||
{
|
||||
documentDataId: templateDocumentDataId,
|
||||
},
|
||||
],
|
||||
},
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return {
|
||||
legacyTemplateId: mapSecondaryIdToTemplateId(envelope.secondaryId),
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
@@ -197,26 +246,38 @@ export const templateRouter = router({
|
||||
type: DocumentDataType.S3_PATH,
|
||||
});
|
||||
|
||||
const createdTemplate = await createTemplate({
|
||||
const createdTemplate = await createEnvelope({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
templateDocumentDataId: templateDocumentData.id,
|
||||
internalVersion: 1,
|
||||
data: {
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title,
|
||||
envelopeItems: [
|
||||
{
|
||||
documentDataId: templateDocumentData.id,
|
||||
},
|
||||
],
|
||||
folderId,
|
||||
externalId,
|
||||
externalId: externalId ?? undefined,
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
templateType: type,
|
||||
publicTitle,
|
||||
publicDescription,
|
||||
type,
|
||||
},
|
||||
meta,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
const legacyTemplateId = mapSecondaryIdToTemplateId(createdTemplate.secondaryId);
|
||||
|
||||
const fullTemplate = await getTemplateById({
|
||||
id: createdTemplate.id,
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: legacyTemplateId,
|
||||
},
|
||||
userId: user.id,
|
||||
teamId,
|
||||
});
|
||||
@@ -252,13 +313,22 @@ export const templateRouter = router({
|
||||
},
|
||||
});
|
||||
|
||||
return await updateTemplate({
|
||||
const envelope = await updateEnvelope({
|
||||
userId,
|
||||
teamId,
|
||||
templateId,
|
||||
data,
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
data: {
|
||||
...data,
|
||||
templateType: data?.type, // Backwards compatibility.
|
||||
},
|
||||
meta,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return mapEnvelopeToTemplateLite(envelope);
|
||||
}),
|
||||
|
||||
/**
|
||||
@@ -285,11 +355,16 @@ export const templateRouter = router({
|
||||
},
|
||||
});
|
||||
|
||||
return await duplicateTemplate({
|
||||
const duplicatedEnvelope = await duplicateEnvelope({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
});
|
||||
|
||||
return mapEnvelopeToTemplateLite(duplicatedEnvelope.envelope);
|
||||
}),
|
||||
|
||||
/**
|
||||
@@ -317,7 +392,14 @@ export const templateRouter = router({
|
||||
},
|
||||
});
|
||||
|
||||
await deleteTemplate({ userId, id: templateId, teamId });
|
||||
await deleteTemplate({
|
||||
userId,
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
teamId,
|
||||
});
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
}),
|
||||
@@ -360,12 +442,25 @@ export const templateRouter = router({
|
||||
throw new Error('You have reached your document limit.');
|
||||
}
|
||||
|
||||
const document: Document = await createDocumentFromTemplate({
|
||||
templateId,
|
||||
// Backwards compatibility mapping since we need the envelopeItemId for the custom document data.
|
||||
const customDocumentData = customDocumentDataId
|
||||
? [
|
||||
{
|
||||
documentDataId: customDocumentDataId,
|
||||
envelopeItemId: undefined,
|
||||
},
|
||||
]
|
||||
: input.customDocumentData || [];
|
||||
|
||||
const envelope: Envelope = await createDocumentFromTemplate({
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
teamId,
|
||||
userId: ctx.user.id,
|
||||
recipients,
|
||||
customDocumentDataId,
|
||||
customDocumentData,
|
||||
requestMetadata: ctx.metadata,
|
||||
folderId,
|
||||
prefillFields,
|
||||
@@ -373,7 +468,10 @@ export const templateRouter = router({
|
||||
|
||||
if (distributeDocument) {
|
||||
await sendDocument({
|
||||
documentId: document.id,
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelope.id,
|
||||
},
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
requestMetadata: ctx.metadata,
|
||||
@@ -385,7 +483,10 @@ export const templateRouter = router({
|
||||
}
|
||||
|
||||
return getDocumentWithDetailsById({
|
||||
documentId: document.id,
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
id: envelope.id,
|
||||
},
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
});
|
||||
@@ -470,7 +571,14 @@ export const templateRouter = router({
|
||||
},
|
||||
});
|
||||
|
||||
const template = await getTemplateById({ id: templateId, teamId, userId: ctx.user.id });
|
||||
const template = await getTemplateById({
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
teamId,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
|
||||
const limits = await getServerLimits({ userId: ctx.user.id, teamId: template.teamId });
|
||||
|
||||
@@ -480,7 +588,15 @@ export const templateRouter = router({
|
||||
});
|
||||
}
|
||||
|
||||
return await createTemplateDirectLink({ userId, teamId, templateId, directRecipientId });
|
||||
return await createTemplateDirectLink({
|
||||
userId,
|
||||
teamId,
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
directRecipientId,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
@@ -569,7 +685,10 @@ export const templateRouter = router({
|
||||
}
|
||||
|
||||
const template = await getTemplateById({
|
||||
id: templateId,
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
teamId,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
@@ -7,15 +7,6 @@ import {
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { ZFieldMetaPrefillFieldsSchema } from '@documenso/lib/types/field-meta';
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import {
|
||||
ZTemplateLiteSchema,
|
||||
ZTemplateManySchema,
|
||||
ZTemplateSchema,
|
||||
} from '@documenso/lib/types/template';
|
||||
import { TemplateDirectLinkSchema } from '@documenso/prisma/generated/zod/modelSchema/TemplateDirectLinkSchema';
|
||||
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
@@ -27,7 +18,16 @@ import {
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
ZDocumentMetaTypedSignatureEnabledSchema,
|
||||
ZDocumentMetaUploadSignatureEnabledSchema,
|
||||
} from '../document-router/schema';
|
||||
} from '@documenso/lib/types/document-meta';
|
||||
import { ZFieldMetaPrefillFieldsSchema } from '@documenso/lib/types/field-meta';
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import {
|
||||
ZTemplateLiteSchema,
|
||||
ZTemplateManySchema,
|
||||
ZTemplateSchema,
|
||||
} from '@documenso/lib/types/template';
|
||||
import { LegacyTemplateDirectLinkSchema } from '@documenso/prisma/types/template-legacy-schema';
|
||||
|
||||
import { ZSignFieldWithTokenMutationSchema } from '../field-router/schema';
|
||||
|
||||
export const MAX_TEMPLATE_PUBLIC_TITLE_LENGTH = 50;
|
||||
@@ -109,9 +109,21 @@ export const ZCreateDocumentFromTemplateRequestSchema = z.object({
|
||||
customDocumentDataId: z
|
||||
.string()
|
||||
.describe(
|
||||
'The data ID of an alternative PDF to use when creating the document. If not provided, the PDF attached to the template will be used.',
|
||||
'[DEPRECATED] - Use customDocumentData instead. The data ID of an alternative PDF to use when creating the document. If not provided, the PDF attached to the template will be used.',
|
||||
)
|
||||
.optional(),
|
||||
customDocumentData: z
|
||||
.array(
|
||||
z.object({
|
||||
documentDataId: z.string(),
|
||||
envelopeItemId: z.string(),
|
||||
}),
|
||||
)
|
||||
.describe(
|
||||
'The data IDs of alternative PDFs to use when creating the document. If not provided, the PDF attached to the template will be used.',
|
||||
)
|
||||
.optional(),
|
||||
|
||||
folderId: z
|
||||
.string()
|
||||
.describe(
|
||||
@@ -144,13 +156,14 @@ export const ZCreateTemplateDirectLinkRequestSchema = z.object({
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const GenericDirectLinkResponseSchema = TemplateDirectLinkSchema.pick({
|
||||
const GenericDirectLinkResponseSchema = LegacyTemplateDirectLinkSchema.pick({
|
||||
id: true,
|
||||
templateId: true,
|
||||
token: true,
|
||||
createdAt: true,
|
||||
enabled: true,
|
||||
directTemplateRecipientId: true,
|
||||
envelopeId: true,
|
||||
templateId: true,
|
||||
});
|
||||
|
||||
export const ZCreateTemplateDirectLinkResponseSchema = GenericDirectLinkResponseSchema;
|
||||
@@ -194,6 +207,10 @@ export const ZCreateTemplateV2ResponseSchema = z.object({
|
||||
uploadUrl: z.string().min(1),
|
||||
});
|
||||
|
||||
export const ZCreateTemplateResponseSchema = z.object({
|
||||
legacyTemplateId: z.number(),
|
||||
});
|
||||
|
||||
export const ZUpdateTemplateRequestSchema = z.object({
|
||||
templateId: z.number(),
|
||||
data: z
|
||||
@@ -207,6 +224,7 @@ export const ZUpdateTemplateRequestSchema = z.object({
|
||||
publicDescription: ZTemplatePublicDescriptionSchema.optional(),
|
||||
type: z.nativeEnum(TemplateType).optional(),
|
||||
useLegacyFieldInsertion: z.boolean().optional(),
|
||||
folderId: z.string().nullish(),
|
||||
})
|
||||
.optional(),
|
||||
meta: ZTemplateMetaUpsertSchema.optional(),
|
||||
|
||||
Reference in New Issue
Block a user