mirror of
https://github.com/documenso/documenso.git
synced 2026-07-13 14:35:47 +10:00
chore: merge main, resolve biome formatting conflicts
Merge origin/main into feat/external-2fa-codes. Resolve formatting conflicts caused by biome rollout; preserve both feature streams: PR's external 2FA token + signing-session 2FA proof additions plus main's RateLimit/RecipientExpired/signingReminders/date-auto-insert. In complete-document-with-token.ts, drop the duplicate early field-fetching block introduced when main moved that logic later with date auto-insert support; keep the EXTERNAL_TWO_FACTOR_AUTH check using derivedRecipientActionAuth.
This commit is contained in:
@@ -1,6 +1,3 @@
|
||||
import { TsRestHttpError, fetchRequestHandler } from '@ts-rest/serverless/fetch';
|
||||
import { Hono } from 'hono';
|
||||
|
||||
import { ApiContractV1 } from '@documenso/api/v1/contract';
|
||||
import { ApiContractV1Implementation } from '@documenso/api/v1/implementation';
|
||||
import { OpenAPIV1 } from '@documenso/api/v1/openapi';
|
||||
@@ -10,6 +7,8 @@ import { subscribeHandler } from '@documenso/lib/server-only/webhooks/zapier/sub
|
||||
import { unsubscribeHandler } from '@documenso/lib/server-only/webhooks/zapier/unsubscribe';
|
||||
// This is a bit nasty. Todo: Extract
|
||||
import type { HonoEnv } from '@documenso/remix/server/router';
|
||||
import { fetchRequestHandler, TsRestHttpError } from '@ts-rest/serverless/fetch';
|
||||
import { Hono } from 'hono';
|
||||
|
||||
// This is bad, ts-router will be created on each request.
|
||||
// But don't really have a choice here.
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
"types": "./index.ts",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"clean": "rimraf node_modules"
|
||||
},
|
||||
"files": [
|
||||
@@ -26,4 +24,4 @@
|
||||
"ts-pattern": "^5.9.0",
|
||||
"zod": "^3.25.76"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
"include": ["."],
|
||||
"exclude": ["dist", "build", "node_modules"],
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { initContract } from '@ts-rest/core';
|
||||
|
||||
import {
|
||||
ZCreateTemplateV2RequestSchema,
|
||||
ZCreateTemplateV2ResponseSchema,
|
||||
} from '@documenso/trpc/server/template-router/schema';
|
||||
import { initContract } from '@ts-rest/core';
|
||||
|
||||
import {
|
||||
ZAuthorizationHeadersSchema,
|
||||
|
||||
+199
-224
@@ -1,24 +1,20 @@
|
||||
import { DocumentDataType, EnvelopeType, SigningStatus } from '@prisma/client';
|
||||
import { tsr } from '@ts-rest/serverless/fetch';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { DocumentDataType, EnvelopeType, SigningStatus } from '@prisma/client';
|
||||
import { tsr } from '@ts-rest/serverless/fetch';
|
||||
import { match } from 'ts-pattern';
|
||||
import '@documenso/lib/constants/time-zones';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
|
||||
import { updateDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
||||
import { deleteDocument } from '@documenso/lib/server-only/document/delete-document';
|
||||
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
|
||||
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
|
||||
import { updateDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
||||
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
|
||||
import {
|
||||
getEnvelopeById,
|
||||
getEnvelopeWhereInput,
|
||||
} from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
||||
import { getEnvelopeById, getEnvelopeWhereInput } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
||||
import { deleteDocumentField } from '@documenso/lib/server-only/field/delete-document-field';
|
||||
import { updateEnvelopeFields } from '@documenso/lib/server-only/field/update-envelope-fields';
|
||||
import { insertFormValuesInPdf } from '@documenso/lib/server-only/pdf/insert-form-values-in-pdf';
|
||||
@@ -42,16 +38,10 @@ import {
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import { getFileServerSide } from '@documenso/lib/universal/upload/get-file.server';
|
||||
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||
import {
|
||||
getPresignGetUrl,
|
||||
getPresignPostUrl,
|
||||
} from '@documenso/lib/universal/upload/server-actions';
|
||||
import { getPresignGetUrl, getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import {
|
||||
mapSecondaryIdToDocumentId,
|
||||
mapSecondaryIdToTemplateId,
|
||||
} from '@documenso/lib/utils/envelope';
|
||||
import { mapSecondaryIdToDocumentId, mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { ApiContractV1 } from './contract';
|
||||
@@ -67,6 +57,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
perPage,
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
folderId: args.query.folderId,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -77,6 +68,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
externalId: document.externalId,
|
||||
userId: document.userId,
|
||||
teamId: document.teamId,
|
||||
folderId: document.folderId,
|
||||
title: document.title,
|
||||
status: document.status,
|
||||
createdAt: document.createdAt,
|
||||
@@ -164,6 +156,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
externalId: envelope.externalId,
|
||||
userId: envelope.userId,
|
||||
teamId: envelope.teamId,
|
||||
folderId: envelope.folderId,
|
||||
title: envelope.title,
|
||||
status: envelope.status,
|
||||
createdAt: envelope.createdAt,
|
||||
@@ -524,9 +517,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
};
|
||||
}
|
||||
|
||||
const timezone = meta?.timezone
|
||||
? TIME_ZONES.find((tz) => tz === meta?.timezone)
|
||||
: DEFAULT_DOCUMENT_TIME_ZONE;
|
||||
const timezone = meta?.timezone ? TIME_ZONES.find((tz) => tz === meta?.timezone) : DEFAULT_DOCUMENT_TIME_ZONE;
|
||||
|
||||
const isTimeZoneValid = meta?.timezone ? TIME_ZONES.includes(String(timezone)) : true;
|
||||
|
||||
@@ -725,64 +716,176 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
}
|
||||
}),
|
||||
|
||||
createDocumentFromTemplate: authenticatedMiddleware(
|
||||
async (args, user, team, { logger, metadata }) => {
|
||||
const { body, params } = args;
|
||||
createDocumentFromTemplate: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
|
||||
const { body, params } = args;
|
||||
|
||||
logger.info({
|
||||
input: {
|
||||
templateId: params.templateId,
|
||||
logger.info({
|
||||
input: {
|
||||
templateId: params.templateId,
|
||||
},
|
||||
});
|
||||
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId: team.id });
|
||||
|
||||
if (remaining.documents <= 0) {
|
||||
return {
|
||||
status: 400,
|
||||
body: {
|
||||
message: 'You have reached the maximum number of documents allowed for this month',
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId: team.id });
|
||||
const templateId = Number(params.templateId);
|
||||
|
||||
if (remaining.documents <= 0) {
|
||||
return {
|
||||
status: 400,
|
||||
body: {
|
||||
message: 'You have reached the maximum number of documents allowed for this month',
|
||||
},
|
||||
};
|
||||
const fileName = body.title.endsWith('.pdf') ? body.title : `${body.title}.pdf`;
|
||||
|
||||
const template = await getEnvelopeById({
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
});
|
||||
|
||||
if (template.envelopeItems.length !== 1) {
|
||||
throw new Error('API V1 does not support templates with multiple documents');
|
||||
}
|
||||
|
||||
// V1 API request schema uses indices for recipients
|
||||
// So we remap the recipients to attach the IDs
|
||||
const mappedRecipients = body.recipients.map((recipient, index) => {
|
||||
const existingRecipient = template.recipients.at(index);
|
||||
|
||||
if (!existingRecipient) {
|
||||
throw new Error('Recipient not found.');
|
||||
}
|
||||
|
||||
const templateId = Number(params.templateId);
|
||||
return {
|
||||
id: existingRecipient.id,
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
signingOrder: recipient.signingOrder,
|
||||
role: recipient.role, // You probably shouldn't be able to change the role.
|
||||
};
|
||||
});
|
||||
|
||||
const fileName = body.title.endsWith('.pdf') ? body.title : `${body.title}.pdf`;
|
||||
const createdEnvelope = await createDocumentFromTemplate({
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
externalId: body.externalId || null,
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
recipients: mappedRecipients,
|
||||
override: {
|
||||
...body.meta,
|
||||
title: body.title,
|
||||
},
|
||||
attachments: body.attachments,
|
||||
formValues: body.formValues,
|
||||
requestMetadata: metadata,
|
||||
});
|
||||
|
||||
const template = await getEnvelopeById({
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
const envelopeItems = await prisma.envelopeItem.findMany({
|
||||
where: {
|
||||
envelopeId: createdEnvelope.id,
|
||||
},
|
||||
include: {
|
||||
documentData: true,
|
||||
},
|
||||
});
|
||||
|
||||
const firstEnvelopeItemData = envelopeItems[0].documentData;
|
||||
|
||||
if (!firstEnvelopeItemData) {
|
||||
throw new Error('Document data not found.');
|
||||
}
|
||||
|
||||
if (body.formValues) {
|
||||
const pdf = await getFileServerSide(firstEnvelopeItemData);
|
||||
|
||||
const prefilled = await insertFormValuesInPdf({
|
||||
pdf: Buffer.from(pdf),
|
||||
formValues: body.formValues,
|
||||
});
|
||||
|
||||
if (template.envelopeItems.length !== 1) {
|
||||
throw new Error('API V1 does not support templates with multiple documents');
|
||||
}
|
||||
const newDocumentData = await putNormalizedPdfFileServerSide({
|
||||
name: fileName,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(prefilled),
|
||||
});
|
||||
|
||||
// V1 API request schema uses indices for recipients
|
||||
// So we remap the recipients to attach the IDs
|
||||
const mappedRecipients = body.recipients.map((recipient, index) => {
|
||||
const existingRecipient = template.recipients.at(index);
|
||||
await prisma.envelopeItem.update({
|
||||
where: {
|
||||
id: firstEnvelopeItemData.id,
|
||||
},
|
||||
data: {
|
||||
title: body.title || fileName,
|
||||
documentDataId: newDocumentData.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (!existingRecipient) {
|
||||
throw new Error('Recipient not found.');
|
||||
}
|
||||
if (body.authOptions || body.formValues) {
|
||||
await prisma.envelope.update({
|
||||
where: {
|
||||
id: createdEnvelope.id,
|
||||
},
|
||||
data: {
|
||||
formValues: body.formValues,
|
||||
authOptions: body.authOptions,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
id: existingRecipient.id,
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
documentId: mapSecondaryIdToDocumentId(createdEnvelope.secondaryId),
|
||||
recipients: createdEnvelope.recipients.map((recipient) => ({
|
||||
recipientId: recipient.id,
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
token: recipient.token,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
role: recipient.role, // You probably shouldn't be able to change the role.
|
||||
};
|
||||
});
|
||||
|
||||
const createdEnvelope = await createDocumentFromTemplate({
|
||||
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
|
||||
})),
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
generateDocumentFromTemplate: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
|
||||
const { body, params } = args;
|
||||
|
||||
logger.info({
|
||||
input: {
|
||||
templateId: params.templateId,
|
||||
},
|
||||
});
|
||||
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId: team.id });
|
||||
|
||||
if (remaining.documents <= 0) {
|
||||
return {
|
||||
status: 400,
|
||||
body: {
|
||||
message: 'You have reached the maximum number of documents allowed for this month',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const templateId = Number(params.templateId);
|
||||
|
||||
let envelope: Awaited<ReturnType<typeof createDocumentFromTemplate>> | null = null;
|
||||
|
||||
try {
|
||||
envelope = await createDocumentFromTemplate({
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
@@ -790,165 +893,49 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
externalId: body.externalId || null,
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
recipients: mappedRecipients,
|
||||
recipients: body.recipients,
|
||||
prefillFields: body.prefillFields,
|
||||
folderId: body.folderId,
|
||||
override: {
|
||||
...body.meta,
|
||||
title: body.title,
|
||||
...body.meta,
|
||||
},
|
||||
attachments: body.attachments,
|
||||
formValues: body.formValues,
|
||||
requestMetadata: metadata,
|
||||
});
|
||||
} catch (err) {
|
||||
return AppError.toRestAPIError(err);
|
||||
}
|
||||
|
||||
const envelopeItems = await prisma.envelopeItem.findMany({
|
||||
if (body.authOptions) {
|
||||
await prisma.envelope.update({
|
||||
where: {
|
||||
envelopeId: createdEnvelope.id,
|
||||
id: envelope.id,
|
||||
},
|
||||
include: {
|
||||
documentData: true,
|
||||
data: {
|
||||
authOptions: body.authOptions,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const firstEnvelopeItemData = envelopeItems[0].documentData;
|
||||
const legacyDocumentId = mapSecondaryIdToDocumentId(envelope.secondaryId);
|
||||
|
||||
if (!firstEnvelopeItemData) {
|
||||
throw new Error('Document data not found.');
|
||||
}
|
||||
|
||||
if (body.formValues) {
|
||||
const pdf = await getFileServerSide(firstEnvelopeItemData);
|
||||
|
||||
const prefilled = await insertFormValuesInPdf({
|
||||
pdf: Buffer.from(pdf),
|
||||
formValues: body.formValues,
|
||||
});
|
||||
|
||||
const newDocumentData = await putNormalizedPdfFileServerSide({
|
||||
name: fileName,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(prefilled),
|
||||
});
|
||||
|
||||
await prisma.envelopeItem.update({
|
||||
where: {
|
||||
id: firstEnvelopeItemData.id,
|
||||
},
|
||||
data: {
|
||||
title: body.title || fileName,
|
||||
documentDataId: newDocumentData.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (body.authOptions || body.formValues) {
|
||||
await prisma.envelope.update({
|
||||
where: {
|
||||
id: createdEnvelope.id,
|
||||
},
|
||||
data: {
|
||||
formValues: body.formValues,
|
||||
authOptions: body.authOptions,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
documentId: mapSecondaryIdToDocumentId(createdEnvelope.secondaryId),
|
||||
recipients: createdEnvelope.recipients.map((recipient) => ({
|
||||
recipientId: recipient.id,
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
token: recipient.token,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
|
||||
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
|
||||
})),
|
||||
},
|
||||
};
|
||||
},
|
||||
),
|
||||
|
||||
generateDocumentFromTemplate: authenticatedMiddleware(
|
||||
async (args, user, team, { logger, metadata }) => {
|
||||
const { body, params } = args;
|
||||
|
||||
logger.info({
|
||||
input: {
|
||||
templateId: params.templateId,
|
||||
},
|
||||
});
|
||||
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId: team.id });
|
||||
|
||||
if (remaining.documents <= 0) {
|
||||
return {
|
||||
status: 400,
|
||||
body: {
|
||||
message: 'You have reached the maximum number of documents allowed for this month',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const templateId = Number(params.templateId);
|
||||
|
||||
let envelope: Awaited<ReturnType<typeof createDocumentFromTemplate>> | null = null;
|
||||
|
||||
try {
|
||||
envelope = await createDocumentFromTemplate({
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
externalId: body.externalId || null,
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
recipients: body.recipients,
|
||||
prefillFields: body.prefillFields,
|
||||
folderId: body.folderId,
|
||||
override: {
|
||||
title: body.title,
|
||||
...body.meta,
|
||||
},
|
||||
formValues: body.formValues,
|
||||
requestMetadata: metadata,
|
||||
});
|
||||
} catch (err) {
|
||||
return AppError.toRestAPIError(err);
|
||||
}
|
||||
|
||||
if (body.authOptions) {
|
||||
await prisma.envelope.update({
|
||||
where: {
|
||||
id: envelope.id,
|
||||
},
|
||||
data: {
|
||||
authOptions: body.authOptions,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const legacyDocumentId = mapSecondaryIdToDocumentId(envelope.secondaryId);
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
documentId: legacyDocumentId,
|
||||
recipients: envelope.recipients.map((recipient) => ({
|
||||
recipientId: recipient.id,
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
token: recipient.token,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
|
||||
})),
|
||||
},
|
||||
};
|
||||
},
|
||||
),
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
documentId: legacyDocumentId,
|
||||
recipients: envelope.recipients.map((recipient) => ({
|
||||
recipientId: recipient.id,
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
token: recipient.token,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
|
||||
})),
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
sendDocument: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
|
||||
const { id: documentId } = args.params;
|
||||
@@ -1371,22 +1358,13 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
const createdFields = await prisma.$transaction(async (tx) => {
|
||||
return Promise.all(
|
||||
fields.map(async (fieldData) => {
|
||||
const {
|
||||
recipientId,
|
||||
type,
|
||||
pageNumber,
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
pageX,
|
||||
pageY,
|
||||
fieldMeta,
|
||||
} = fieldData;
|
||||
const { recipientId, type, pageNumber, pageWidth, pageHeight, pageX, pageY, fieldMeta } = fieldData;
|
||||
|
||||
if (pageNumber <= 0) {
|
||||
throw new Error('Invalid page number');
|
||||
}
|
||||
|
||||
const recipient = await prisma.recipient.findFirst({
|
||||
const recipient = await tx.recipient.findFirst({
|
||||
where: {
|
||||
id: Number(recipientId),
|
||||
envelopeId: envelope.id,
|
||||
@@ -1401,9 +1379,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
throw new Error('Recipient has already signed the document');
|
||||
}
|
||||
|
||||
const advancedField = ['NUMBER', 'RADIO', 'CHECKBOX', 'DROPDOWN', 'TEXT'].includes(
|
||||
type,
|
||||
);
|
||||
const advancedField = ['NUMBER', 'RADIO', 'CHECKBOX', 'DROPDOWN', 'TEXT'].includes(type);
|
||||
|
||||
if (advancedField && !fieldMeta) {
|
||||
throw new Error(
|
||||
@@ -1507,8 +1483,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
|
||||
updateField: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
|
||||
const { id: documentId, fieldId } = args.params;
|
||||
const { recipientId, type, pageNumber, pageWidth, pageHeight, pageX, pageY, fieldMeta } =
|
||||
args.body;
|
||||
const { recipientId, type, pageNumber, pageWidth, pageHeight, pageX, pageY, fieldMeta } = args.body;
|
||||
|
||||
logger.info({
|
||||
input: {
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import type { Team, User } from '@prisma/client';
|
||||
import type { TsRestRequest } from '@ts-rest/serverless';
|
||||
import type { Logger } from 'pino';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getApiTokenByToken } from '@documenso/lib/server-only/public-api/get-api-token-by-token';
|
||||
import type { BaseApiLog, RootApiLog } from '@documenso/lib/types/api-logs';
|
||||
@@ -9,6 +5,9 @@ import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-reques
|
||||
import { extractRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { logger } from '@documenso/lib/utils/logger';
|
||||
import type { Team, User } from '@prisma/client';
|
||||
import type { TsRestRequest } from '@ts-rest/serverless';
|
||||
import type { Logger } from 'pino';
|
||||
|
||||
type B = {
|
||||
// appRoute: any;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { generateOpenApi } from '@ts-rest/open-api';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { generateOpenApi } from '@ts-rest/open-api';
|
||||
|
||||
import { ApiContractV1 } from './contract';
|
||||
|
||||
|
||||
+36
-48
@@ -1,17 +1,4 @@
|
||||
import { extendZodWithOpenApi } from '@anatine/zod-openapi';
|
||||
import {
|
||||
DocumentDataType,
|
||||
DocumentDistributionMethod,
|
||||
DocumentSigningOrder,
|
||||
FieldType,
|
||||
ReadStatus,
|
||||
RecipientRole,
|
||||
SendStatus,
|
||||
SigningStatus,
|
||||
} from '@prisma/client';
|
||||
import { TemplateType } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
@@ -24,6 +11,19 @@ import {
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { ZEnvelopeAttachmentTypeSchema } from '@documenso/lib/types/envelope-attachment';
|
||||
import { ZFieldMetaPrefillFieldsSchema, ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { zEmail } from '@documenso/lib/utils/zod';
|
||||
import {
|
||||
DocumentDataType,
|
||||
DocumentDistributionMethod,
|
||||
DocumentSigningOrder,
|
||||
FieldType,
|
||||
ReadStatus,
|
||||
RecipientRole,
|
||||
SendStatus,
|
||||
SigningStatus,
|
||||
TemplateType,
|
||||
} from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
extendZodWithOpenApi(z);
|
||||
|
||||
@@ -35,6 +35,7 @@ export const ZNoBodyMutationSchema = null;
|
||||
export const ZGetDocumentsQuerySchema = z.object({
|
||||
page: z.coerce.number().min(1).optional().default(1),
|
||||
perPage: z.coerce.number().min(1).optional().default(10),
|
||||
folderId: z.string().describe('Filter documents by folder ID. When omitted, returns root documents.').optional(),
|
||||
});
|
||||
|
||||
export type TGetDocumentsQuerySchema = z.infer<typeof ZGetDocumentsQuerySchema>;
|
||||
@@ -48,6 +49,7 @@ export const ZSuccessfulDocumentResponseSchema = z.object({
|
||||
externalId: z.string().nullish(),
|
||||
userId: z.number(),
|
||||
teamId: z.number().nullish(),
|
||||
folderId: z.string().nullish(),
|
||||
title: z.string(),
|
||||
status: z.string(),
|
||||
createdAt: z.date(),
|
||||
@@ -78,9 +80,7 @@ export const ZSuccessfulGetDocumentResponseSchema = ZSuccessfulDocumentResponseS
|
||||
),
|
||||
});
|
||||
|
||||
export type TSuccessfulGetDocumentResponseSchema = z.infer<
|
||||
typeof ZSuccessfulGetDocumentResponseSchema
|
||||
>;
|
||||
export type TSuccessfulGetDocumentResponseSchema = z.infer<typeof ZSuccessfulGetDocumentResponseSchema>;
|
||||
|
||||
export type TSuccessfulDocumentResponseSchema = z.infer<typeof ZSuccessfulDocumentResponseSchema>;
|
||||
|
||||
@@ -103,9 +103,7 @@ export const ZResendDocumentForSigningMutationSchema = z.object({
|
||||
recipients: z.array(z.number()),
|
||||
});
|
||||
|
||||
export type TResendDocumentForSigningMutationSchema = z.infer<
|
||||
typeof ZResendDocumentForSigningMutationSchema
|
||||
>;
|
||||
export type TResendDocumentForSigningMutationSchema = z.infer<typeof ZResendDocumentForSigningMutationSchema>;
|
||||
|
||||
export const ZSuccessfulResendDocumentResponseSchema = z.object({
|
||||
message: z.string(),
|
||||
@@ -145,7 +143,7 @@ export const ZCreateDocumentMutationSchema = z.object({
|
||||
recipients: z.array(
|
||||
z.object({
|
||||
name: z.string().min(1),
|
||||
email: z.string().email().min(1),
|
||||
email: zEmail().min(1),
|
||||
role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER),
|
||||
signingOrder: z.number().nullish(),
|
||||
}),
|
||||
@@ -155,16 +153,14 @@ export const ZCreateDocumentMutationSchema = z.object({
|
||||
subject: z.string(),
|
||||
message: z.string(),
|
||||
timezone: z.string().default(DEFAULT_DOCUMENT_TIME_ZONE).openapi({
|
||||
description:
|
||||
'The timezone of the date. Must be one of the options listed in the list below.',
|
||||
description: 'The timezone of the date. Must be one of the options listed in the list below.',
|
||||
enum: TIME_ZONES,
|
||||
}),
|
||||
dateFormat: z
|
||||
.string()
|
||||
.default(DEFAULT_DOCUMENT_DATE_FORMAT)
|
||||
.openapi({
|
||||
description:
|
||||
'The format of the date. Must be one of the options listed in the list below.',
|
||||
description: 'The format of the date. Must be one of the options listed in the list below.',
|
||||
enum: DATE_FORMATS.map((format) => format.value),
|
||||
}),
|
||||
redirectUrl: z.string(),
|
||||
@@ -219,7 +215,7 @@ export const ZCreateDocumentMutationResponseSchema = z.object({
|
||||
z.object({
|
||||
recipientId: z.number(),
|
||||
name: z.string(),
|
||||
email: z.string().email().min(1),
|
||||
email: zEmail().min(1),
|
||||
token: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().nullish(),
|
||||
@@ -229,9 +225,7 @@ export const ZCreateDocumentMutationResponseSchema = z.object({
|
||||
),
|
||||
});
|
||||
|
||||
export type TCreateDocumentMutationResponseSchema = z.infer<
|
||||
typeof ZCreateDocumentMutationResponseSchema
|
||||
>;
|
||||
export type TCreateDocumentMutationResponseSchema = z.infer<typeof ZCreateDocumentMutationResponseSchema>;
|
||||
|
||||
export const ZCreateDocumentFromTemplateMutationSchema = z.object({
|
||||
title: z.string().min(1),
|
||||
@@ -239,7 +233,7 @@ export const ZCreateDocumentFromTemplateMutationSchema = z.object({
|
||||
recipients: z.array(
|
||||
z.object({
|
||||
name: z.string().min(1),
|
||||
email: z.string().email().min(1),
|
||||
email: zEmail().min(1),
|
||||
role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER),
|
||||
signingOrder: z.number().nullish(),
|
||||
}),
|
||||
@@ -283,9 +277,7 @@ export const ZCreateDocumentFromTemplateMutationSchema = z.object({
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type TCreateDocumentFromTemplateMutationSchema = z.infer<
|
||||
typeof ZCreateDocumentFromTemplateMutationSchema
|
||||
>;
|
||||
export type TCreateDocumentFromTemplateMutationSchema = z.infer<typeof ZCreateDocumentFromTemplateMutationSchema>;
|
||||
|
||||
export const ZCreateDocumentFromTemplateMutationResponseSchema = z.object({
|
||||
documentId: z.number(),
|
||||
@@ -294,7 +286,7 @@ export const ZCreateDocumentFromTemplateMutationResponseSchema = z.object({
|
||||
z.object({
|
||||
recipientId: z.number(),
|
||||
name: z.string(),
|
||||
email: z.string().email().min(1),
|
||||
email: zEmail().min(1),
|
||||
token: z.string(),
|
||||
role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER),
|
||||
signingOrder: z.number().nullish(),
|
||||
@@ -321,7 +313,7 @@ export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
|
||||
.array(
|
||||
z.object({
|
||||
id: z.number(),
|
||||
email: z.string().email(),
|
||||
email: zEmail(),
|
||||
name: z.string().optional(),
|
||||
signingOrder: z.number().optional(),
|
||||
}),
|
||||
@@ -370,9 +362,7 @@ export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
|
||||
prefillFields: z.array(ZFieldMetaPrefillFieldsSchema).optional(),
|
||||
});
|
||||
|
||||
export type TGenerateDocumentFromTemplateMutationSchema = z.infer<
|
||||
typeof ZGenerateDocumentFromTemplateMutationSchema
|
||||
>;
|
||||
export type TGenerateDocumentFromTemplateMutationSchema = z.infer<typeof ZGenerateDocumentFromTemplateMutationSchema>;
|
||||
|
||||
export const ZGenerateDocumentFromTemplateMutationResponseSchema = z.object({
|
||||
documentId: z.number(),
|
||||
@@ -381,7 +371,7 @@ export const ZGenerateDocumentFromTemplateMutationResponseSchema = z.object({
|
||||
z.object({
|
||||
recipientId: z.number(),
|
||||
name: z.string(),
|
||||
email: z.string().email().min(1),
|
||||
email: zEmail().min(1),
|
||||
token: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().nullish(),
|
||||
@@ -397,7 +387,7 @@ export type TGenerateDocumentFromTemplateMutationResponseSchema = z.infer<
|
||||
|
||||
export const ZCreateRecipientMutationSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
email: z.string().email().min(1),
|
||||
email: zEmail().min(1),
|
||||
role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER),
|
||||
signingOrder: z.number().nullish(),
|
||||
authOptions: z
|
||||
@@ -432,13 +422,13 @@ export const ZSuccessfulRecipientResponseSchema = z.object({
|
||||
// !: This handles the fact that we have null documentId's for templates
|
||||
// !: while we won't need the default we must add it to satisfy typescript
|
||||
documentId: z.number().nullish().default(-1),
|
||||
email: z.string().email().min(1),
|
||||
email: zEmail().min(1),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().nullish(),
|
||||
token: z.string(),
|
||||
// !: Not used for now
|
||||
// expired: z.string(),
|
||||
expiresAt: z.date().nullish(),
|
||||
expirationNotifiedAt: z.date().nullish(),
|
||||
signedAt: z.date().nullable(),
|
||||
readStatus: z.nativeEnum(ReadStatus),
|
||||
signingStatus: z.nativeEnum(SigningStatus),
|
||||
@@ -463,10 +453,7 @@ const ZCreateFieldSchema = z.object({
|
||||
fieldMeta: ZFieldMetaSchema.openapi({}),
|
||||
});
|
||||
|
||||
export const ZCreateFieldMutationSchema = z.union([
|
||||
ZCreateFieldSchema,
|
||||
z.array(ZCreateFieldSchema).min(1),
|
||||
]);
|
||||
export const ZCreateFieldMutationSchema = z.union([ZCreateFieldSchema, z.array(ZCreateFieldSchema).min(1)]);
|
||||
|
||||
export type TCreateFieldMutationSchema = z.infer<typeof ZCreateFieldMutationSchema>;
|
||||
|
||||
@@ -571,12 +558,13 @@ export const ZRecipientSchema = z.object({
|
||||
id: z.number(),
|
||||
documentId: z.number().nullish(),
|
||||
templateId: z.number().nullish(),
|
||||
email: z.string().email().min(1),
|
||||
email: zEmail().min(1),
|
||||
name: z.string(),
|
||||
token: z.string(),
|
||||
signingOrder: z.number().nullish(),
|
||||
documentDeletedAt: z.date().nullish(),
|
||||
expired: z.date().nullish(),
|
||||
expiresAt: z.date().nullish(),
|
||||
expirationNotifiedAt: z.date().nullish(),
|
||||
signedAt: z.date().nullish(),
|
||||
authOptions: z.unknown(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
|
||||
Reference in New Issue
Block a user