mirror of
https://github.com/documenso/documenso.git
synced 2025-11-19 19:21:39 +10:00
fix: merge conflicts
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(),
|
||||
});
|
||||
|
||||
|
||||
72
packages/trpc/server/admin-router/find-document-jobs.ts
Normal file
72
packages/trpc/server/admin-router/find-document-jobs.ts
Normal file
@ -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,11 +1,13 @@
|
||||
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 { isValidExpirySettings } from '@documenso/lib/utils/expiry';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
@ -65,28 +67,86 @@ 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: {
|
||||
...meta,
|
||||
expiryAmount,
|
||||
expiryUnit,
|
||||
},
|
||||
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,
|
||||
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,
|
||||
@ -23,16 +22,6 @@ import {
|
||||
ZDocumentExpiryAmountSchema,
|
||||
ZDocumentExpiryUnitSchema,
|
||||
ZDocumentExternalIdSchema,
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
ZDocumentMetaDrawSignatureEnabledSchema,
|
||||
ZDocumentMetaLanguageSchema,
|
||||
ZDocumentMetaMessageSchema,
|
||||
ZDocumentMetaRedirectUrlSchema,
|
||||
ZDocumentMetaSubjectSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
ZDocumentMetaTypedSignatureEnabledSchema,
|
||||
ZDocumentMetaUploadSignatureEnabledSchema,
|
||||
ZDocumentTitleSchema,
|
||||
ZDocumentVisibilitySchema,
|
||||
} from './schema';
|
||||
@ -84,22 +73,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 { isValidExpirySettings } from '@documenso/lib/utils/expiry';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
@ -10,7 +13,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;
|
||||
@ -38,20 +41,30 @@ 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,
|
||||
},
|
||||
],
|
||||
},
|
||||
meta: {
|
||||
expiryAmount,
|
||||
expiryUnit,
|
||||
},
|
||||
normalizePdf: true,
|
||||
userTimezone: timezone,
|
||||
requestMetadata: ctx.metadata,
|
||||
folderId,
|
||||
expiryAmount,
|
||||
expiryUnit,
|
||||
});
|
||||
|
||||
return {
|
||||
id: document.id,
|
||||
legacyDocumentId: mapSecondaryIdToDocumentId(document.secondaryId),
|
||||
};
|
||||
});
|
||||
|
||||
@ -4,8 +4,9 @@ import {
|
||||
ZDocumentExpiryAmountSchema,
|
||||
ZDocumentExpiryUnitSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
ZDocumentTitleSchema,
|
||||
} from './schema';
|
||||
} 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 = {
|
||||
@ -27,7 +28,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,68 +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.');
|
||||
|
||||
export const ZDocumentExpiryAmountSchema = z
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.describe('The amount for expiry duration (e.g., 3 for "3 days").');
|
||||
|
||||
export const ZDocumentExpiryUnitSchema = z
|
||||
.enum(['minutes', 'hours', 'days', 'weeks', 'months'])
|
||||
.describe('The unit for expiry duration (e.g., "days" for "3 days").');
|
||||
|
||||
28
packages/trpc/server/document-router/share-document.ts
Normal file
28
packages/trpc/server/document-router/share-document.ts
Normal file
@ -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 });
|
||||
});
|
||||
16
packages/trpc/server/document-router/share-document.types.ts
Normal file
16
packages/trpc/server/document-router/share-document.types.ts
Normal file
@ -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,6 +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 { updateEnvelope } from '@documenso/lib/server-only/envelope/update-envelope';
|
||||
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
|
||||
import { isValidExpirySettings } from '@documenso/lib/utils/expiry';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
@ -8,13 +8,13 @@ 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 }) => {
|
||||
@ -38,37 +38,23 @@ export const updateDocumentRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
expiryAmount: meta.expiryAmount,
|
||||
expiryUnit: meta.expiryUnit,
|
||||
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,23 +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 {
|
||||
ZDocumentExpiryAmountSchema,
|
||||
ZDocumentExpiryUnitSchema,
|
||||
ZDocumentExternalIdSchema,
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
ZDocumentMetaDrawSignatureEnabledSchema,
|
||||
ZDocumentMetaLanguageSchema,
|
||||
ZDocumentMetaMessageSchema,
|
||||
ZDocumentMetaRedirectUrlSchema,
|
||||
ZDocumentMetaSubjectSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
ZDocumentMetaTypedSignatureEnabledSchema,
|
||||
ZDocumentMetaUploadSignatureEnabledSchema,
|
||||
ZDocumentTitleSchema,
|
||||
ZDocumentVisibilitySchema,
|
||||
} from './schema';
|
||||
@ -47,29 +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(),
|
||||
expiryAmount: ZDocumentExpiryAmountSchema.optional(),
|
||||
expiryUnit: ZDocumentExpiryUnitSchema.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({
|
||||
|
||||
111
packages/trpc/server/envelope-router/create-envelope-items.ts
Normal file
111
packages/trpc/server/envelope-router/create-envelope-items.ts
Normal file
@ -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>;
|
||||
68
packages/trpc/server/envelope-router/create-envelope.ts
Normal file
68
packages/trpc/server/envelope-router/create-envelope.ts
Normal file
@ -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>;
|
||||
64
packages/trpc/server/envelope-router/delete-envelope-item.ts
Normal file
64
packages/trpc/server/envelope-router/delete-envelope-item.ts
Normal file
@ -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>;
|
||||
50
packages/trpc/server/envelope-router/delete-envelope.ts
Normal file
50
packages/trpc/server/envelope-router/delete-envelope.ts
Normal file
@ -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>;
|
||||
55
packages/trpc/server/envelope-router/distribute-envelope.ts
Normal file
55
packages/trpc/server/envelope-router/distribute-envelope.ts
Normal file
@ -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>;
|
||||
34
packages/trpc/server/envelope-router/duplicate-envelope.ts
Normal file
34
packages/trpc/server/envelope-router/duplicate-envelope.ts
Normal file
@ -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>;
|
||||
29
packages/trpc/server/envelope-router/get-envelope.ts
Normal file
29
packages/trpc/server/envelope-router/get-envelope.ts
Normal file
@ -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,
|
||||
});
|
||||
});
|
||||
22
packages/trpc/server/envelope-router/get-envelope.types.ts
Normal file
22
packages/trpc/server/envelope-router/get-envelope.types.ts
Normal file
@ -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>;
|
||||
38
packages/trpc/server/envelope-router/router.ts
Normal file
38
packages/trpc/server/envelope-router/router.ts
Normal file
@ -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,
|
||||
},
|
||||
});
|
||||
66
packages/trpc/server/envelope-router/set-envelope-fields.ts
Normal file
66
packages/trpc/server/envelope-router/set-envelope-fields.ts
Normal file
@ -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>;
|
||||
472
packages/trpc/server/envelope-router/sign-envelope-field.ts
Normal file
472
packages/trpc/server/envelope-router/sign-envelope-field.ts
Normal file
@ -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>;
|
||||
36
packages/trpc/server/envelope-router/update-envelope.ts
Normal file
36
packages/trpc/server/envelope-router/update-envelope.ts
Normal file
@ -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