mirror of
https://github.com/documenso/documenso.git
synced 2026-08-23 23:02:22 +10:00
Merge branch 'main' into feature/pdf-placeholder-selection-fields
This commit is contained in:
@@ -12,9 +12,9 @@
|
||||
"@documenso/prisma": "*",
|
||||
"@simplewebauthn/server": "^13.2.2",
|
||||
"@tanstack/react-query": "5.90.10",
|
||||
"@trpc/client": "11.8.1",
|
||||
"@trpc/react-query": "11.8.1",
|
||||
"@trpc/server": "11.8.1",
|
||||
"@trpc/client": "11.17.0",
|
||||
"@trpc/react-query": "11.17.0",
|
||||
"@trpc/server": "11.17.0",
|
||||
"@ts-rest/core": "^3.52.1",
|
||||
"formidable": "^3.5.4",
|
||||
"luxon": "^3.7.2",
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { adminGlobalSearch } from '@documenso/lib/server-only/admin/admin-global-search';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import { ZAdminSearchRequestSchema, ZAdminSearchResponseSchema } from './admin-search.types';
|
||||
|
||||
export const adminSearchRoute = adminProcedure
|
||||
.input(ZAdminSearchRequestSchema)
|
||||
.output(ZAdminSearchResponseSchema)
|
||||
.query(async ({ input }) => {
|
||||
const { query } = input;
|
||||
|
||||
const groups = await adminGlobalSearch({ query });
|
||||
|
||||
return { groups };
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZAdminSearchResultTypeSchema = z.enum([
|
||||
'document',
|
||||
'user',
|
||||
'organisation',
|
||||
'team',
|
||||
'recipient',
|
||||
'subscription',
|
||||
]);
|
||||
|
||||
export const ZAdminSearchResultSchema = z.object({
|
||||
label: z.string(),
|
||||
sublabel: z.string().optional(),
|
||||
path: z.string(),
|
||||
value: z.string(),
|
||||
});
|
||||
|
||||
export const ADMIN_SEARCH_MAX_QUERY_LENGTH = 100;
|
||||
|
||||
export const ZAdminSearchRequestSchema = z.object({
|
||||
query: z.string().trim().min(1).max(ADMIN_SEARCH_MAX_QUERY_LENGTH),
|
||||
});
|
||||
|
||||
export const ZAdminSearchResponseSchema = z.object({
|
||||
groups: z.array(
|
||||
z.object({
|
||||
type: ZAdminSearchResultTypeSchema,
|
||||
results: ZAdminSearchResultSchema.array(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export type TAdminSearchResultType = z.infer<typeof ZAdminSearchResultTypeSchema>;
|
||||
export type TAdminSearchResult = z.infer<typeof ZAdminSearchResultSchema>;
|
||||
export type TAdminSearchRequest = z.infer<typeof ZAdminSearchRequestSchema>;
|
||||
export type TAdminSearchResponse = z.infer<typeof ZAdminSearchResponseSchema>;
|
||||
@@ -1,8 +1,6 @@
|
||||
import { syncMemberCountWithStripeSeatPlan } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { jobs } from '@documenso/lib/jobs/client';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { OrganisationMemberInviteStatus } from '@prisma/client';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
@@ -28,8 +26,6 @@ export const deleteAdminOrganisationMemberRoute = adminProcedure
|
||||
id: organisationId,
|
||||
},
|
||||
include: {
|
||||
subscription: true,
|
||||
organisationClaim: true,
|
||||
teams: {
|
||||
select: {
|
||||
id: true,
|
||||
@@ -41,14 +37,6 @@ export const deleteAdminOrganisationMemberRoute = adminProcedure
|
||||
userId: true,
|
||||
},
|
||||
},
|
||||
invites: {
|
||||
where: {
|
||||
status: OrganisationMemberInviteStatus.PENDING,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -72,18 +60,6 @@ export const deleteAdminOrganisationMemberRoute = adminProcedure
|
||||
});
|
||||
}
|
||||
|
||||
const newMemberCount = organisation.members.length + organisation.invites.length - 1;
|
||||
|
||||
// Removing a member is a reducing operation, so we don't gate it on the
|
||||
// subscription being present. Sync Stripe only when one exists.
|
||||
if (organisation.subscription) {
|
||||
await syncMemberCountWithStripeSeatPlan(
|
||||
organisation.subscription,
|
||||
organisation.organisationClaim,
|
||||
newMemberCount,
|
||||
);
|
||||
}
|
||||
|
||||
const teamIds = organisation.teams.map((team) => team.id);
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
@@ -113,6 +89,13 @@ export const deleteAdminOrganisationMemberRoute = adminProcedure
|
||||
});
|
||||
});
|
||||
|
||||
// A member was removed — queue a seat sync to true the Stripe quantity down
|
||||
// to the new count (no proration, no credit).
|
||||
await jobs.triggerJob({
|
||||
name: 'internal.sync-organisation-seats',
|
||||
payload: { organisationId },
|
||||
});
|
||||
|
||||
await jobs.triggerJob({
|
||||
name: 'send.organisation-member-left.email',
|
||||
payload: {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { router } from '../trpc';
|
||||
import { adminSearchRoute } from './admin-search';
|
||||
import { createAdminOrganisationRoute } from './create-admin-organisation';
|
||||
import { createStripeCustomerRoute } from './create-stripe-customer';
|
||||
import { createSubscriptionClaimRoute } from './create-subscription-claim';
|
||||
@@ -118,5 +119,6 @@ export const adminRouter = router({
|
||||
teamMember: {
|
||||
delete: deleteAdminTeamMemberRoute,
|
||||
},
|
||||
search: adminSearchRoute,
|
||||
updateSiteSetting: updateSiteSettingRoute,
|
||||
});
|
||||
|
||||
@@ -12,8 +12,10 @@ export const createAttachmentRoute = authenticatedProcedure
|
||||
method: 'POST',
|
||||
path: '/document/attachment/create',
|
||||
summary: 'Create attachment',
|
||||
description: 'Create a new attachment for a document',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Create a new attachment for a document',
|
||||
tags: ['Document'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZCreateAttachmentRequestSchema)
|
||||
|
||||
@@ -10,8 +10,10 @@ export const deleteAttachmentRoute = authenticatedProcedure
|
||||
method: 'POST',
|
||||
path: '/document/attachment/delete',
|
||||
summary: 'Delete attachment',
|
||||
description: 'Delete an attachment from a document',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Delete an attachment from a document',
|
||||
tags: ['Document'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZDeleteAttachmentRequestSchema)
|
||||
|
||||
@@ -12,8 +12,10 @@ export const findAttachmentsRoute = authenticatedProcedure
|
||||
method: 'GET',
|
||||
path: '/document/attachment',
|
||||
summary: 'Find attachments',
|
||||
description: 'Find all attachments for a document',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Find all attachments for a document',
|
||||
tags: ['Document'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZFindAttachmentsRequestSchema)
|
||||
|
||||
@@ -10,8 +10,10 @@ export const updateAttachmentRoute = authenticatedProcedure
|
||||
method: 'POST',
|
||||
path: '/document/attachment/update',
|
||||
summary: 'Update attachment',
|
||||
description: 'Update an existing attachment',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Update an existing attachment',
|
||||
tags: ['Document'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZUpdateAttachmentRequestSchema)
|
||||
|
||||
@@ -27,7 +27,7 @@ export const createDocumentTemporaryMeta: TrpcRouteMeta = {
|
||||
path: '/document/create/beta',
|
||||
summary: 'Create document',
|
||||
description:
|
||||
'You will need to upload the PDF to the provided URL returned. Note: Once V2 API is released, this will be removed since we will allow direct uploads, instead of using an upload URL.',
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. You will need to upload the PDF to the provided URL returned. This endpoint will be removed since we will allow direct uploads, instead of using an upload URL.',
|
||||
tags: ['Document'],
|
||||
deprecated: true,
|
||||
},
|
||||
|
||||
@@ -25,8 +25,10 @@ export const createDocumentMeta: TrpcRouteMeta = {
|
||||
path: '/document/create',
|
||||
contentTypes: ['multipart/form-data'],
|
||||
summary: 'Create document',
|
||||
description: 'Create a document using form data.',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide.',
|
||||
tags: ['Document'],
|
||||
deprecated: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,10 @@ export const deleteDocumentMeta: TrpcRouteMeta = {
|
||||
method: 'POST',
|
||||
path: '/document/delete',
|
||||
summary: 'Delete document',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide.',
|
||||
tags: ['Document'],
|
||||
deprecated: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -19,8 +19,10 @@ export const distributeDocumentMeta: TrpcRouteMeta = {
|
||||
method: 'POST',
|
||||
path: '/document/distribute',
|
||||
summary: 'Distribute document',
|
||||
description: 'Send the document out to recipients based on your distribution method',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Send the document out to recipients based on your distribution method',
|
||||
tags: ['Document'],
|
||||
deprecated: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -7,8 +7,10 @@ export const downloadDocumentMeta: TrpcRouteMeta = {
|
||||
method: 'GET',
|
||||
path: '/document/{documentId}/download-beta',
|
||||
summary: 'Download document (beta)',
|
||||
description: 'Get a pre-signed download URL for the original or signed version of a document',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Get a pre-signed download URL for the original or signed version of a document',
|
||||
tags: ['Document'],
|
||||
deprecated: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,10 @@ export const downloadDocumentMeta: TrpcRouteMeta = {
|
||||
method: 'GET',
|
||||
path: '/document/{documentId}/download',
|
||||
summary: 'Download document',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide.',
|
||||
tags: ['Document'],
|
||||
deprecated: true,
|
||||
responseHeaders: z.object({
|
||||
'Content-Type': z.literal('application/pdf'),
|
||||
}),
|
||||
|
||||
@@ -7,7 +7,10 @@ export const duplicateDocumentMeta: TrpcRouteMeta = {
|
||||
method: 'POST',
|
||||
path: '/document/duplicate',
|
||||
summary: 'Duplicate document',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide.',
|
||||
tags: ['Document'],
|
||||
deprecated: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ export const findDocumentsInternalRoute = authenticatedProcedure
|
||||
orderByColumn,
|
||||
source,
|
||||
status,
|
||||
hasExpiredRecipients,
|
||||
period,
|
||||
senderIds,
|
||||
folderId,
|
||||
@@ -49,6 +50,7 @@ export const findDocumentsInternalRoute = authenticatedProcedure
|
||||
period,
|
||||
senderIds,
|
||||
folderId,
|
||||
hasExpiredRecipients,
|
||||
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
||||
}),
|
||||
]);
|
||||
|
||||
@@ -20,6 +20,7 @@ export const ZFindDocumentsInternalResponseSchema = ZFindResultResponse.extend({
|
||||
[ExtendedDocumentStatus.COMPLETED]: z.number(),
|
||||
[ExtendedDocumentStatus.REJECTED]: z.number(),
|
||||
[ExtendedDocumentStatus.CANCELLED]: z.number(),
|
||||
[ExtendedDocumentStatus.EXPIRED]: z.number(),
|
||||
[ExtendedDocumentStatus.INBOX]: z.number(),
|
||||
[ExtendedDocumentStatus.ALL]: z.number(),
|
||||
}),
|
||||
|
||||
@@ -11,7 +11,18 @@ export const findDocumentsRoute = authenticatedProcedure
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
|
||||
const { query, templateId, page, perPage, orderByDirection, orderByColumn, source, status, folderId } = input;
|
||||
const {
|
||||
query,
|
||||
templateId,
|
||||
page,
|
||||
perPage,
|
||||
orderByDirection,
|
||||
orderByColumn,
|
||||
source,
|
||||
status,
|
||||
hasExpiredRecipients,
|
||||
folderId,
|
||||
} = input;
|
||||
|
||||
const documents = await findDocuments({
|
||||
userId: user.id,
|
||||
@@ -20,6 +31,7 @@ export const findDocumentsRoute = authenticatedProcedure
|
||||
query,
|
||||
source,
|
||||
status,
|
||||
hasExpiredRecipients,
|
||||
page,
|
||||
perPage,
|
||||
folderId,
|
||||
|
||||
@@ -10,8 +10,10 @@ export const ZFindDocumentsMeta: TrpcRouteMeta = {
|
||||
method: 'GET',
|
||||
path: '/document',
|
||||
summary: 'Find documents',
|
||||
description: 'Find documents based on a search criteria',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Find documents based on a search criteria',
|
||||
tags: ['Document'],
|
||||
deprecated: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -19,6 +21,11 @@ export const ZFindDocumentsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
templateId: z.number().describe('Filter documents by the template ID used to create it.').optional(),
|
||||
source: z.nativeEnum(DocumentSource).describe('Filter documents by how it was created.').optional(),
|
||||
status: z.nativeEnum(DocumentStatus).describe('Filter documents by the current status').optional(),
|
||||
hasExpiredRecipients: z
|
||||
.enum(['true', 'false'])
|
||||
.describe('Filter for documents that have at least one recipient whose signing link has expired.')
|
||||
.transform((value) => value === 'true')
|
||||
.optional(),
|
||||
folderId: z.string().describe('Filter documents by folder ID').optional(),
|
||||
orderByColumn: z.enum(['createdAt']).optional(),
|
||||
orderByDirection: z.enum(['asc', 'desc']).describe('').default('desc'),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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';
|
||||
import type { Envelope, Prisma } from '@prisma/client';
|
||||
import { DocumentStatus, EnvelopeType, RecipientRole } from '@prisma/client';
|
||||
@@ -106,12 +105,15 @@ export const findInbox = async ({ userId, page = 1, perPage = 10, orderBy }: Fin
|
||||
}),
|
||||
]);
|
||||
|
||||
const maskedData = data.map((document) =>
|
||||
maskRecipientTokensForDocument({
|
||||
document,
|
||||
user,
|
||||
}),
|
||||
);
|
||||
// Not using the maskRecipientTokensForDocument helper here because it needs a
|
||||
// rework due to recipients vs Recipient.
|
||||
const maskedData = data.map((document) => ({
|
||||
...document,
|
||||
recipients: document.recipients.map((recipient) => ({
|
||||
...recipient,
|
||||
token: recipient.email === user.email ? recipient.token : '',
|
||||
})),
|
||||
}));
|
||||
|
||||
return {
|
||||
data: maskedData,
|
||||
|
||||
@@ -8,8 +8,10 @@ export const getDocumentMeta: TrpcRouteMeta = {
|
||||
method: 'GET',
|
||||
path: '/document/{documentId}',
|
||||
summary: 'Get document',
|
||||
description: 'Returns a document given an ID',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Returns a document given an ID',
|
||||
tags: ['Document'],
|
||||
deprecated: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -8,8 +8,10 @@ export const getDocumentsByIdsMeta: TrpcRouteMeta = {
|
||||
method: 'POST',
|
||||
path: '/document/get-many',
|
||||
summary: 'Get multiple documents',
|
||||
description: 'Retrieve multiple documents by their IDs',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Retrieve multiple documents by their IDs',
|
||||
tags: ['Document'],
|
||||
deprecated: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -9,8 +9,9 @@ export const redistributeDocumentMeta: TrpcRouteMeta = {
|
||||
path: '/document/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',
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Redistribute the document to the provided recipients who have not actioned the document. Will use the distribution method set in the document. This also refreshes the signing-link expiration for the targeted unsigned recipients, renewing any expired links.',
|
||||
tags: ['Document'],
|
||||
deprecated: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -13,7 +13,10 @@ export const updateDocumentMeta: TrpcRouteMeta = {
|
||||
method: 'POST',
|
||||
path: '/document/update',
|
||||
summary: 'Update document',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide.',
|
||||
tags: ['Document'],
|
||||
deprecated: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { ZCreateSubscriptionRequestSchema } from './create-subscription.types';
|
||||
export const createSubscriptionRoute = authenticatedProcedure
|
||||
.input(ZCreateSubscriptionRequestSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { organisationId, priceId, isPersonalLayoutMode } = input;
|
||||
const { organisationId, priceId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
@@ -70,9 +70,7 @@ export const createSubscriptionRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
const returnUrl = isPersonalLayoutMode
|
||||
? `${NEXT_PUBLIC_WEBAPP_URL()}/settings/billing-personal`
|
||||
: `${NEXT_PUBLIC_WEBAPP_URL()}/o/${organisation.url}/settings/billing`;
|
||||
const returnUrl = `${NEXT_PUBLIC_WEBAPP_URL()}/o/${organisation.url}/settings/billing`;
|
||||
|
||||
const redirectUrl = await createCheckoutSession({
|
||||
customerId,
|
||||
|
||||
@@ -3,5 +3,4 @@ import { z } from 'zod';
|
||||
export const ZCreateSubscriptionRequestSchema = z.object({
|
||||
organisationId: z.string().describe('The organisation to create the subscription for'),
|
||||
priceId: z.string().describe('The price to create the subscription for'),
|
||||
isPersonalLayoutMode: z.boolean().optional(),
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ import { ZManageSubscriptionRequestSchema } from './manage-subscription.types';
|
||||
export const manageSubscriptionRoute = authenticatedProcedure
|
||||
.input(ZManageSubscriptionRequestSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { organisationId, isPersonalLayoutMode } = input;
|
||||
const { organisationId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
@@ -93,9 +93,7 @@ export const manageSubscriptionRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
const returnUrl = isPersonalLayoutMode
|
||||
? `${NEXT_PUBLIC_WEBAPP_URL()}/settings/billing-personal`
|
||||
: `${NEXT_PUBLIC_WEBAPP_URL()}/o/${organisation.url}/settings/billing`;
|
||||
const returnUrl = `${NEXT_PUBLIC_WEBAPP_URL()}/o/${organisation.url}/settings/billing`;
|
||||
|
||||
const redirectUrl = await getPortalSession({
|
||||
customerId,
|
||||
|
||||
@@ -2,5 +2,4 @@ import { z } from 'zod';
|
||||
|
||||
export const ZManageSubscriptionRequestSchema = z.object({
|
||||
organisationId: z.string().describe('The organisation to manage the subscription for'),
|
||||
isPersonalLayoutMode: z.boolean().optional(),
|
||||
});
|
||||
|
||||
@@ -10,7 +10,19 @@ export const findEnvelopesRoute = authenticatedProcedure
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
|
||||
const { query, type, templateId, page, perPage, orderByDirection, orderByColumn, source, status, folderId } = input;
|
||||
const {
|
||||
query,
|
||||
type,
|
||||
templateId,
|
||||
page,
|
||||
perPage,
|
||||
orderByDirection,
|
||||
orderByColumn,
|
||||
source,
|
||||
status,
|
||||
hasExpiredRecipients,
|
||||
folderId,
|
||||
} = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
@@ -19,6 +31,7 @@ export const findEnvelopesRoute = authenticatedProcedure
|
||||
templateId,
|
||||
source,
|
||||
status,
|
||||
hasExpiredRecipients,
|
||||
folderId,
|
||||
page,
|
||||
perPage,
|
||||
@@ -33,6 +46,7 @@ export const findEnvelopesRoute = authenticatedProcedure
|
||||
query,
|
||||
source,
|
||||
status,
|
||||
hasExpiredRecipients,
|
||||
page,
|
||||
perPage,
|
||||
folderId,
|
||||
|
||||
@@ -20,6 +20,11 @@ export const ZFindEnvelopesRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
templateId: z.number().describe('Filter envelopes by the template ID used to create it.').optional(),
|
||||
source: z.nativeEnum(DocumentSource).describe('Filter envelopes by how it was created.').optional(),
|
||||
status: z.nativeEnum(DocumentStatus).describe('Filter envelopes by the current status.').optional(),
|
||||
hasExpiredRecipients: z
|
||||
.enum(['true', 'false'])
|
||||
.describe('Filter for envelopes that have at least one recipient whose signing link has expired.')
|
||||
.transform((value) => value === 'true')
|
||||
.optional(),
|
||||
folderId: z.string().describe('Filter envelopes by folder ID.').optional(),
|
||||
orderByColumn: z.enum(['createdAt']).optional(),
|
||||
orderByDirection: z.enum(['asc', 'desc']).describe('Sort direction.').default('desc'),
|
||||
|
||||
@@ -10,7 +10,7 @@ export const redistributeEnvelopeMeta: TrpcRouteMeta = {
|
||||
path: '/envelope/redistribute',
|
||||
summary: 'Redistribute envelope',
|
||||
description:
|
||||
'Redistribute the envelope to the provided recipients who have not actioned the envelope. Will use the distribution method set in the envelope',
|
||||
'Redistribute the envelope to the provided recipients who have not actioned the envelope. Will use the distribution method set in the envelope. This also refreshes the signing-link expiration for the targeted unsigned recipients, renewing any expired links.',
|
||||
tags: ['Envelope'],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import { validateFieldAuth } from '@documenso/lib/server-only/document/validate-
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { extractFieldInsertionValues } from '@documenso/lib/utils/envelope-signing';
|
||||
import { assertRecipientNotExpired } from '@documenso/lib/utils/recipients';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, FieldType, RecipientRole, SigningStatus } from '@prisma/client';
|
||||
import { match } from 'ts-pattern';
|
||||
@@ -108,6 +109,12 @@ export const signEnvelopeFieldRoute = procedure
|
||||
});
|
||||
}
|
||||
|
||||
// Both are checked because an assistant may insert values into a field belonging to
|
||||
// another recipient, and neither signing window may have closed. For every other
|
||||
// role these reference the same recipient.
|
||||
assertRecipientNotExpired(recipient);
|
||||
assertRecipientNotExpired(field.recipient);
|
||||
|
||||
if (recipient.signingStatus === SigningStatus.SIGNED || field.recipient.signingStatus === SigningStatus.SIGNED) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: `Recipient ${recipient.id} has already signed`,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
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';
|
||||
@@ -51,8 +52,9 @@ export const fieldRouter = router({
|
||||
path: '/document/field/{fieldId}',
|
||||
summary: 'Get document field',
|
||||
description:
|
||||
'Returns a single field. If you want to retrieve all the fields for a document, use the "Get Document" endpoint.',
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Returns a single field. If you want to retrieve all the fields for a document, use the "Get Document" endpoint.',
|
||||
tags: ['Document Fields'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZGetFieldRequestSchema)
|
||||
@@ -84,8 +86,10 @@ export const fieldRouter = router({
|
||||
method: 'POST',
|
||||
path: '/document/field/create',
|
||||
summary: 'Create document field',
|
||||
description: 'Create a single field for a document.',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Create a single field for a document.',
|
||||
tags: ['Document Fields'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZCreateDocumentFieldRequestSchema)
|
||||
@@ -130,8 +134,10 @@ export const fieldRouter = router({
|
||||
method: 'POST',
|
||||
path: '/document/field/create-many',
|
||||
summary: 'Create document fields',
|
||||
description: 'Create multiple fields for a document.',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Create multiple fields for a document.',
|
||||
tags: ['Document Fields'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZCreateDocumentFieldsRequestSchema)
|
||||
@@ -172,8 +178,10 @@ export const fieldRouter = router({
|
||||
method: 'POST',
|
||||
path: '/document/field/update',
|
||||
summary: 'Update document field',
|
||||
description: 'Update a single field for a document.',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Update a single field for a document.',
|
||||
tags: ['Document Fields'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZUpdateDocumentFieldRequestSchema)
|
||||
@@ -212,8 +220,10 @@ export const fieldRouter = router({
|
||||
method: 'POST',
|
||||
path: '/document/field/update-many',
|
||||
summary: 'Update document fields',
|
||||
description: 'Update multiple fields for a document.',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Update multiple fields for a document.',
|
||||
tags: ['Document Fields'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZUpdateDocumentFieldsRequestSchema)
|
||||
@@ -250,7 +260,10 @@ export const fieldRouter = router({
|
||||
method: 'POST',
|
||||
path: '/document/field/delete',
|
||||
summary: 'Delete document field',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide.',
|
||||
tags: ['Document Fields'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZDeleteDocumentFieldRequestSchema)
|
||||
@@ -323,8 +336,10 @@ export const fieldRouter = router({
|
||||
method: 'POST',
|
||||
path: '/template/field/create',
|
||||
summary: 'Create template field',
|
||||
description: 'Create a single field for a template.',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Create a single field for a template.',
|
||||
tags: ['Template Fields'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZCreateTemplateFieldRequestSchema)
|
||||
@@ -370,8 +385,9 @@ export const fieldRouter = router({
|
||||
path: '/template/field/{fieldId}',
|
||||
summary: 'Get template field',
|
||||
description:
|
||||
'Returns a single field. If you want to retrieve all the fields for a template, use the "Get Template" endpoint.',
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Returns a single field. If you want to retrieve all the fields for a template, use the "Get Template" endpoint.',
|
||||
tags: ['Template Fields'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZGetFieldRequestSchema)
|
||||
@@ -403,8 +419,10 @@ export const fieldRouter = router({
|
||||
method: 'POST',
|
||||
path: '/template/field/create-many',
|
||||
summary: 'Create template fields',
|
||||
description: 'Create multiple fields for a template.',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Create multiple fields for a template.',
|
||||
tags: ['Template Fields'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZCreateTemplateFieldsRequestSchema)
|
||||
@@ -445,8 +463,10 @@ export const fieldRouter = router({
|
||||
method: 'POST',
|
||||
path: '/template/field/update',
|
||||
summary: 'Update template field',
|
||||
description: 'Update a single field for a template.',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Update a single field for a template.',
|
||||
tags: ['Template Fields'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZUpdateTemplateFieldRequestSchema)
|
||||
@@ -485,8 +505,10 @@ export const fieldRouter = router({
|
||||
method: 'POST',
|
||||
path: '/template/field/update-many',
|
||||
summary: 'Update template fields',
|
||||
description: 'Update multiple fields for a template.',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Update multiple fields for a template.',
|
||||
tags: ['Template Fields'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZUpdateTemplateFieldsRequestSchema)
|
||||
@@ -523,7 +545,10 @@ export const fieldRouter = router({
|
||||
method: 'POST',
|
||||
path: '/template/field/delete',
|
||||
summary: 'Delete template field',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide.',
|
||||
tags: ['Template Fields'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZDeleteTemplateFieldRequestSchema)
|
||||
@@ -589,23 +614,37 @@ export const fieldRouter = router({
|
||||
* @private
|
||||
*/
|
||||
signFieldWithToken: procedure.input(ZSignFieldWithTokenMutationSchema).mutation(async ({ input, ctx }) => {
|
||||
const { token, fieldId, value, isBase64, authOptions } = input;
|
||||
try {
|
||||
const { token, fieldId, value, isBase64, authOptions } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
fieldId,
|
||||
},
|
||||
});
|
||||
|
||||
return await signFieldWithToken({
|
||||
token,
|
||||
fieldId,
|
||||
},
|
||||
});
|
||||
value: value ?? '',
|
||||
isBase64,
|
||||
userId: ctx.user?.id,
|
||||
authOptions,
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
} catch (err) {
|
||||
// Log the error for debugging purposes.
|
||||
ctx.logger.error({
|
||||
message: 'Error signing field with token',
|
||||
error: err instanceof AppError ? `[${err.code}]: ${err.message}` : String(err),
|
||||
});
|
||||
|
||||
return await signFieldWithToken({
|
||||
token,
|
||||
fieldId,
|
||||
value: value ?? '',
|
||||
isBase64,
|
||||
userId: ctx.user?.id,
|
||||
authOptions,
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
// Raw console.log incase we're somehow deailing with a funky error object that doesn't serialize well.
|
||||
console.log('Error signing field with token', err);
|
||||
|
||||
// Rethrow the error so that the client receives the appropriate error response.
|
||||
throw err;
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
@@ -614,18 +653,31 @@ export const fieldRouter = router({
|
||||
removeSignedFieldWithToken: procedure
|
||||
.input(ZRemovedSignedFieldWithTokenMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { token, fieldId } = input;
|
||||
try {
|
||||
const { token, fieldId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
fieldId,
|
||||
},
|
||||
});
|
||||
|
||||
return await removeSignedFieldWithToken({
|
||||
token,
|
||||
fieldId,
|
||||
},
|
||||
});
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
} catch (err) {
|
||||
// Log the error for debugging purposes.
|
||||
ctx.logger.error({
|
||||
message: 'Error removing signed field with token',
|
||||
error: err instanceof AppError ? `[${err.code}]: ${err.message}` : String(err),
|
||||
});
|
||||
|
||||
return await removeSignedFieldWithToken({
|
||||
token,
|
||||
fieldId,
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
console.log('Error removing signed field with token', err);
|
||||
|
||||
// Rethrow the error so that the client receives the appropriate error response.
|
||||
throw err;
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { syncMemberCountWithStripeSeatPlan } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity';
|
||||
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getMemberOrganisationRole } from '@documenso/lib/server-only/team/get-member-roles';
|
||||
@@ -32,20 +31,6 @@ export const deleteOrganisationMemberInvitesRoute = authenticatedProcedure
|
||||
userId,
|
||||
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
||||
}),
|
||||
include: {
|
||||
organisationClaim: true,
|
||||
subscription: true,
|
||||
members: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
invites: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!organisation) {
|
||||
@@ -83,22 +68,6 @@ export const deleteOrganisationMemberInvitesRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
const { organisationClaim } = organisation;
|
||||
|
||||
const numberOfCurrentMembers = organisation.members.length;
|
||||
const numberOfCurrentInvites = organisation.invites.length;
|
||||
const totalMemberCountWithInvites = numberOfCurrentMembers + numberOfCurrentInvites - 1;
|
||||
|
||||
// Removing pending invites is a reducing operation, so we don't gate it on
|
||||
// the subscription being present. Sync Stripe only when one exists.
|
||||
if (organisation.subscription) {
|
||||
await syncMemberCountWithStripeSeatPlan(
|
||||
organisation.subscription,
|
||||
organisationClaim,
|
||||
totalMemberCountWithInvites,
|
||||
);
|
||||
}
|
||||
|
||||
await prisma.organisationMemberInvite.deleteMany({
|
||||
where: {
|
||||
id: {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { syncMemberCountWithStripeSeatPlan } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity';
|
||||
import {
|
||||
ORGANISATION_MEMBER_ROLE_HIERARCHY,
|
||||
ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP,
|
||||
} from '@documenso/lib/constants/organisations';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { jobs } from '@documenso/lib/jobs/client';
|
||||
import {
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
isOrganisationRoleWithinUserHierarchy,
|
||||
} from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { OrganisationMemberInviteStatus } from '@documenso/prisma/client';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
@@ -59,8 +58,6 @@ export const deleteOrganisationMembers = async ({
|
||||
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
||||
}),
|
||||
include: {
|
||||
subscription: true,
|
||||
organisationClaim: true,
|
||||
teams: {
|
||||
select: {
|
||||
id: true,
|
||||
@@ -75,14 +72,6 @@ export const deleteOrganisationMembers = async ({
|
||||
},
|
||||
},
|
||||
},
|
||||
invites: {
|
||||
where: {
|
||||
status: OrganisationMemberInviteStatus.PENDING,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -90,8 +79,6 @@ export const deleteOrganisationMembers = async ({
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
const { organisationClaim } = organisation;
|
||||
|
||||
const membersToDelete = organisation.members.filter((member) => organisationMemberIds.includes(member.id));
|
||||
|
||||
const currentUserMember = organisation.members.find((member) => member.userId === userId);
|
||||
@@ -129,15 +116,6 @@ export const deleteOrganisationMembers = async ({
|
||||
}
|
||||
}
|
||||
|
||||
const inviteCount = organisation.invites.length;
|
||||
const newMemberCount = organisation.members.length + inviteCount - membersToDelete.length;
|
||||
|
||||
// Removing members is a reducing operation, so we don't gate it on the
|
||||
// subscription being present. Sync Stripe only when one exists.
|
||||
if (organisation.subscription) {
|
||||
await syncMemberCountWithStripeSeatPlan(organisation.subscription, organisationClaim, newMemberCount);
|
||||
}
|
||||
|
||||
const removedUserIds = membersToDelete.map((member) => member.userId);
|
||||
const teamIds = organisation.teams.map((team) => team.id);
|
||||
|
||||
@@ -184,6 +162,13 @@ export const deleteOrganisationMembers = async ({
|
||||
});
|
||||
});
|
||||
|
||||
// Members were removed — queue a seat sync to true the Stripe quantity down to
|
||||
// the new count (no proration, no credit).
|
||||
await jobs.triggerJob({
|
||||
name: 'internal.sync-organisation-seats',
|
||||
payload: { organisationId },
|
||||
});
|
||||
|
||||
for (const member of membersToDelete) {
|
||||
await jobs.triggerJob({
|
||||
name: 'send.organisation-member-left.email',
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { syncMemberCountWithStripeSeatPlan } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { jobs } from '@documenso/lib/jobs/client';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { OrganisationMemberInviteStatus } from '@documenso/prisma/client';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZLeaveOrganisationRequestSchema, ZLeaveOrganisationResponseSchema } from './leave-organisation.types';
|
||||
@@ -24,26 +22,11 @@ export const leaveOrganisationRoute = authenticatedProcedure
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery({ organisationId, userId }),
|
||||
include: {
|
||||
organisationClaim: true,
|
||||
subscription: true,
|
||||
teams: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
invites: {
|
||||
where: {
|
||||
status: OrganisationMemberInviteStatus.PENDING,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
members: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -59,17 +42,6 @@ export const leaveOrganisationRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
const { organisationClaim } = organisation;
|
||||
|
||||
const inviteCount = organisation.invites.length;
|
||||
const newMemberCount = organisation.members.length + inviteCount - 1;
|
||||
|
||||
// Leaving is a reducing operation, so we don't gate it on the subscription
|
||||
// being present. Sync Stripe only when one exists.
|
||||
if (organisation.subscription) {
|
||||
await syncMemberCountWithStripeSeatPlan(organisation.subscription, organisationClaim, newMemberCount);
|
||||
}
|
||||
|
||||
const teamIds = organisation.teams.map((team) => team.id);
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
@@ -101,6 +73,13 @@ export const leaveOrganisationRoute = authenticatedProcedure
|
||||
});
|
||||
});
|
||||
|
||||
// A member was removed — queue a seat sync to true the Stripe quantity down
|
||||
// to the new count (no proration, no credit).
|
||||
await jobs.triggerJob({
|
||||
name: 'internal.sync-organisation-seats',
|
||||
payload: { organisationId },
|
||||
});
|
||||
|
||||
await jobs.triggerJob({
|
||||
name: 'send.organisation-member-left.email',
|
||||
payload: {
|
||||
|
||||
@@ -124,11 +124,10 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
const isChangingIncludeSenderDetails =
|
||||
includeSenderDetails !== undefined && includeSenderDetails !== currentIncludeSenderDetails;
|
||||
|
||||
if (isPersonalOrganisation && isChangingIncludeSenderDetails) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Personal organisations cannot update the sender details',
|
||||
});
|
||||
}
|
||||
// Personal teams cannot change the sender details — drop the field (no-op)
|
||||
// instead of rejecting the whole update.
|
||||
const derivedIncludeSenderDetails =
|
||||
isPersonalOrganisation && isChangingIncludeSenderDetails ? undefined : includeSenderDetails;
|
||||
|
||||
// Sanitize custom branding CSS at write time so we can store the safe
|
||||
// result and skip per-render sanitisation. Warnings are returned to the
|
||||
@@ -160,7 +159,7 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
documentLanguage,
|
||||
documentTimezone,
|
||||
documentDateFormat,
|
||||
includeSenderDetails,
|
||||
includeSenderDetails: derivedIncludeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
includeAuditLog,
|
||||
typedSignatureEnabled,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { prepareCscRecipientSigning } from '@documenso/ee/server-only/signing/csc/prepare-recipient-signing';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
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 { createEnvelopeRecipients } from '@documenso/lib/server-only/recipient/create-envelope-recipients';
|
||||
@@ -11,7 +12,6 @@ import { isTspEnvelope } from '@documenso/lib/types/signature-level';
|
||||
import { unsafeBuildEnvelopeIdQuery } from '@documenso/lib/utils/envelope';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { ZGenericSuccessResponse, ZSuccessResponseSchema } from '../schema';
|
||||
import { authenticatedProcedure, procedure, router } from '../trpc';
|
||||
import { findRecipientSuggestionsRoute } from './find-recipient-suggestions';
|
||||
@@ -60,8 +60,9 @@ export const recipientRouter = router({
|
||||
path: '/document/recipient/{recipientId}',
|
||||
summary: 'Get document recipient',
|
||||
description:
|
||||
'Returns a single recipient. If you want to retrieve all the recipients for a document, use the "Get Document" endpoint.',
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Returns a single recipient. If you want to retrieve all the recipients for a document, use the "Get Document" endpoint.',
|
||||
tags: ['Document Recipients'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZGetRecipientRequestSchema)
|
||||
@@ -93,8 +94,10 @@ export const recipientRouter = router({
|
||||
method: 'POST',
|
||||
path: '/document/recipient/create',
|
||||
summary: 'Create document recipient',
|
||||
description: 'Create a single recipient for a document.',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Create a single recipient for a document.',
|
||||
tags: ['Document Recipients'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZCreateDocumentRecipientRequestSchema)
|
||||
@@ -132,8 +135,10 @@ export const recipientRouter = router({
|
||||
method: 'POST',
|
||||
path: '/document/recipient/create-many',
|
||||
summary: 'Create document recipients',
|
||||
description: 'Create multiple recipients for a document.',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Create multiple recipients for a document.',
|
||||
tags: ['Document Recipients'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZCreateDocumentRecipientsRequestSchema)
|
||||
@@ -169,8 +174,10 @@ export const recipientRouter = router({
|
||||
method: 'POST',
|
||||
path: '/document/recipient/update',
|
||||
summary: 'Update document recipient',
|
||||
description: 'Update a single recipient for a document.',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Update a single recipient for a document.',
|
||||
tags: ['Document Recipients'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZUpdateDocumentRecipientRequestSchema)
|
||||
@@ -208,8 +215,10 @@ export const recipientRouter = router({
|
||||
method: 'POST',
|
||||
path: '/document/recipient/update-many',
|
||||
summary: 'Update document recipients',
|
||||
description: 'Update multiple recipients for a document.',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Update multiple recipients for a document.',
|
||||
tags: ['Document Recipients'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZUpdateDocumentRecipientsRequestSchema)
|
||||
@@ -245,7 +254,10 @@ export const recipientRouter = router({
|
||||
method: 'POST',
|
||||
path: '/document/recipient/delete',
|
||||
summary: 'Delete document recipient',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide.',
|
||||
tags: ['Document Recipients'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZDeleteDocumentRecipientRequestSchema)
|
||||
@@ -315,8 +327,9 @@ export const recipientRouter = router({
|
||||
path: '/template/recipient/{recipientId}',
|
||||
summary: 'Get template recipient',
|
||||
description:
|
||||
'Returns a single recipient. If you want to retrieve all the recipients for a template, use the "Get Template" endpoint.',
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Returns a single recipient. If you want to retrieve all the recipients for a template, use the "Get Template" endpoint.',
|
||||
tags: ['Template Recipients'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZGetRecipientRequestSchema)
|
||||
@@ -348,8 +361,10 @@ export const recipientRouter = router({
|
||||
method: 'POST',
|
||||
path: '/template/recipient/create',
|
||||
summary: 'Create template recipient',
|
||||
description: 'Create a single recipient for a template.',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Create a single recipient for a template.',
|
||||
tags: ['Template Recipients'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZCreateTemplateRecipientRequestSchema)
|
||||
@@ -387,8 +402,10 @@ export const recipientRouter = router({
|
||||
method: 'POST',
|
||||
path: '/template/recipient/create-many',
|
||||
summary: 'Create template recipients',
|
||||
description: 'Create multiple recipients for a template.',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Create multiple recipients for a template.',
|
||||
tags: ['Template Recipients'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZCreateTemplateRecipientsRequestSchema)
|
||||
@@ -424,8 +441,10 @@ export const recipientRouter = router({
|
||||
method: 'POST',
|
||||
path: '/template/recipient/update',
|
||||
summary: 'Update template recipient',
|
||||
description: 'Update a single recipient for a template.',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Update a single recipient for a template.',
|
||||
tags: ['Template Recipients'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZUpdateTemplateRecipientRequestSchema)
|
||||
@@ -463,8 +482,10 @@ export const recipientRouter = router({
|
||||
method: 'POST',
|
||||
path: '/template/recipient/update-many',
|
||||
summary: 'Update template recipients',
|
||||
description: 'Update multiple recipients for a template.',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Update multiple recipients for a template.',
|
||||
tags: ['Template Recipients'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZUpdateTemplateRecipientsRequestSchema)
|
||||
@@ -500,7 +521,10 @@ export const recipientRouter = router({
|
||||
method: 'POST',
|
||||
path: '/template/recipient/delete',
|
||||
summary: 'Delete template recipient',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide.',
|
||||
tags: ['Template Recipients'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZDeleteTemplateRecipientRequestSchema)
|
||||
@@ -566,47 +590,72 @@ export const recipientRouter = router({
|
||||
.input(ZCompleteDocumentWithTokenMutationSchema)
|
||||
.output(ZCompleteDocumentWithTokenResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { token, documentId, accessAuthOptions, nextSigner, recipientOverride } = input;
|
||||
try {
|
||||
const { token, documentId, accessAuthOptions, nextSigner, recipientOverride } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
// Branch on TSP envelopes before any SES side effects: TSP recipients
|
||||
// can't complete via this route — they go through the CSC sync sign
|
||||
// flow (`enterprise.csc.signEnvelope`). This route returns the redirect URL
|
||||
// for the credential-scope OAuth round-trip.
|
||||
const envelope = await prisma.envelope.findFirstOrThrow({
|
||||
where: {
|
||||
...unsafeBuildEnvelopeIdQuery({ type: 'documentId', id: documentId }, EnvelopeType.DOCUMENT),
|
||||
recipients: { some: { token } },
|
||||
},
|
||||
select: { signatureLevel: true, internalVersion: true },
|
||||
});
|
||||
// Branch on TSP envelopes before any SES side effects: TSP recipients
|
||||
// can't complete via this route — they go through the CSC sync sign
|
||||
// flow (`enterprise.csc.signEnvelope`). This route returns the redirect URL
|
||||
// for the credential-scope OAuth round-trip.
|
||||
const envelope = await prisma.envelope.findFirstOrThrow({
|
||||
where: {
|
||||
...unsafeBuildEnvelopeIdQuery({ type: 'documentId', id: documentId }, EnvelopeType.DOCUMENT),
|
||||
recipients: { some: { token } },
|
||||
},
|
||||
select: { signatureLevel: true, internalVersion: true },
|
||||
});
|
||||
|
||||
if (isTspEnvelope(envelope)) {
|
||||
return await prepareCscRecipientSigning({
|
||||
recipientToken: token,
|
||||
if (isTspEnvelope(envelope)) {
|
||||
return await prepareCscRecipientSigning({
|
||||
recipientToken: token,
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
}
|
||||
|
||||
await completeDocumentWithToken({
|
||||
token,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
accessAuthOptions,
|
||||
nextSigner,
|
||||
recipientOverride,
|
||||
userId: ctx.user?.id,
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
|
||||
return { status: 'SIGNED' as const };
|
||||
} catch (err) {
|
||||
// Resolve retried, stale or concurrent duplicate completion requests
|
||||
// idempotently so the client routes the user to the completed page
|
||||
// instead of surfacing an error for a document that is signed.
|
||||
if (err instanceof AppError && err.code === AppErrorCode.RECIPIENT_ALREADY_SIGNED) {
|
||||
ctx.logger.info({
|
||||
message: 'Recipient attempted to complete a document they have already signed',
|
||||
});
|
||||
|
||||
return { status: 'ALREADY_SIGNED' as const };
|
||||
}
|
||||
|
||||
// Log the error for debugging purposes.
|
||||
ctx.logger.error({
|
||||
message: 'Error completing document with token',
|
||||
error: err instanceof AppError ? `[${err.code}]: ${err.message}` : String(err),
|
||||
});
|
||||
|
||||
// Raw console.log incase we're somehow dealing with a funky error object that doesn't serialize well.
|
||||
console.log('Error completing document with token', err);
|
||||
|
||||
// Rethrow the error so that the client receives the appropriate error response.
|
||||
throw err;
|
||||
}
|
||||
|
||||
await completeDocumentWithToken({
|
||||
token,
|
||||
id: {
|
||||
type: 'documentId',
|
||||
id: documentId,
|
||||
},
|
||||
accessAuthOptions,
|
||||
nextSigner,
|
||||
recipientOverride,
|
||||
userId: ctx.user?.id,
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
|
||||
return { status: 'SIGNED' as const };
|
||||
}),
|
||||
|
||||
/**
|
||||
|
||||
@@ -182,12 +182,16 @@ export type TCompleteDocumentWithTokenMutationSchema = z.infer<typeof ZCompleteD
|
||||
* Discriminated response: SES envelopes return `{ status: 'SIGNED' }` after
|
||||
* the in-place completion; TSP (AES/QES) envelopes return
|
||||
* `{ status: 'REDIRECT', redirectUrl }` pointing at the credential-scope
|
||||
* OAuth authorize endpoint. Frontend callers can branch on `status` —
|
||||
* existing callers ignored the response and remain compatible.
|
||||
* OAuth authorize endpoint. `{ status: 'ALREADY_SIGNED' }` is returned when
|
||||
* the recipient had already signed prior to this request (retries, stale
|
||||
* tabs, concurrent submissions) so callers can notify the user instead of
|
||||
* erroring. Frontend callers can branch on `status` — existing callers
|
||||
* ignored the response and remain compatible.
|
||||
*/
|
||||
export const ZCompleteDocumentWithTokenResponseSchema = z.discriminatedUnion('status', [
|
||||
z.object({ status: z.literal('REDIRECT'), redirectUrl: z.string() }),
|
||||
z.object({ status: z.literal('SIGNED') }),
|
||||
z.object({ status: z.literal('ALREADY_SIGNED') }),
|
||||
]);
|
||||
|
||||
export type TCompleteDocumentWithTokenResponseSchema = z.infer<typeof ZCompleteDocumentWithTokenResponseSchema>;
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { isTokenExpired } from '@documenso/lib/utils/token-verification';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { procedure } from '../trpc';
|
||||
import {
|
||||
ZCompleteTeamEmailVerificationRequestSchema,
|
||||
ZCompleteTeamEmailVerificationResponseSchema,
|
||||
} from './complete-team-email-verification.types';
|
||||
|
||||
/**
|
||||
* Unauthenicated procedure.
|
||||
*/
|
||||
export const completeTeamEmailVerificationRoute = procedure
|
||||
.input(ZCompleteTeamEmailVerificationRequestSchema)
|
||||
.output(ZCompleteTeamEmailVerificationResponseSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
const { token } = input;
|
||||
|
||||
const teamEmailVerification = await prisma.teamEmailVerification.findUnique({
|
||||
where: {
|
||||
token,
|
||||
},
|
||||
include: {
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!teamEmailVerification || isTokenExpired(teamEmailVerification.expiresAt)) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Verification token is invalid or has expired.',
|
||||
});
|
||||
}
|
||||
|
||||
const { team, email, name } = teamEmailVerification;
|
||||
|
||||
if (teamEmailVerification.completed) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Team email verification has already been completed.',
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
const existingTeamEmail = await tx.teamEmail.findFirst({
|
||||
where: {
|
||||
OR: [{ email }, { teamId: team.id }],
|
||||
},
|
||||
});
|
||||
|
||||
if (existingTeamEmail) {
|
||||
throw new AppError(AppErrorCode.ALREADY_EXISTS, {
|
||||
message: 'Email already taken by another team, or this team already has an email.',
|
||||
});
|
||||
}
|
||||
|
||||
await tx.teamEmailVerification.updateMany({
|
||||
where: {
|
||||
teamId: team.id,
|
||||
email,
|
||||
},
|
||||
data: {
|
||||
completed: true,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.teamEmailVerification.deleteMany({
|
||||
where: {
|
||||
teamId: team.id,
|
||||
expiresAt: {
|
||||
lt: new Date(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await tx.teamEmail.create({
|
||||
data: {
|
||||
teamId: team.id,
|
||||
email,
|
||||
name,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCompleteTeamEmailVerificationRequestSchema = z.object({
|
||||
token: z.string().min(1),
|
||||
});
|
||||
|
||||
export const ZCompleteTeamEmailVerificationResponseSchema = z.void();
|
||||
|
||||
export type TCompleteTeamEmailVerificationRequest = z.infer<typeof ZCompleteTeamEmailVerificationRequestSchema>;
|
||||
|
||||
export type TCompleteTeamEmailVerificationResponse = z.infer<typeof ZCompleteTeamEmailVerificationResponseSchema>;
|
||||
@@ -1,11 +1,11 @@
|
||||
import { createTeamEmailVerification } from '@documenso/lib/server-only/team/create-team-email-verification';
|
||||
import { deleteTeamEmail } from '@documenso/lib/server-only/team/delete-team-email';
|
||||
import { deleteTeamEmailVerification } from '@documenso/lib/server-only/team/delete-team-email-verification';
|
||||
import { getTeamEmailByEmail } from '@documenso/lib/server-only/team/get-team-email-by-email';
|
||||
import { resendTeamEmailVerification } from '@documenso/lib/server-only/team/resend-team-email-verification';
|
||||
import { updateTeamEmail } from '@documenso/lib/server-only/team/update-team-email';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { authenticatedProcedure, router } from '../trpc';
|
||||
import { completeTeamEmailVerificationRoute } from './complete-team-email-verification';
|
||||
import { createTeamRoute } from './create-team';
|
||||
import { createTeamGroupsRoute } from './create-team-groups';
|
||||
import { createTeamMembersRoute } from './create-team-members';
|
||||
@@ -58,7 +58,22 @@ export const teamRouter = router({
|
||||
// Todo: Refactor into routes.
|
||||
email: {
|
||||
get: authenticatedProcedure.query(async ({ ctx }) => {
|
||||
return await getTeamEmailByEmail({ email: ctx.user.email });
|
||||
const teamEmail = await prisma.teamEmail.findUnique({
|
||||
where: {
|
||||
email: ctx.user.email,
|
||||
},
|
||||
include: {
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
url: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return teamEmail || null;
|
||||
}),
|
||||
update: authenticatedProcedure.input(ZUpdateTeamEmailMutationSchema).mutation(async ({ input, ctx }) => {
|
||||
ctx.logger.info({
|
||||
@@ -67,7 +82,7 @@ export const teamRouter = router({
|
||||
},
|
||||
});
|
||||
|
||||
return await updateTeamEmail({
|
||||
await updateTeamEmail({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
});
|
||||
@@ -81,7 +96,7 @@ export const teamRouter = router({
|
||||
},
|
||||
});
|
||||
|
||||
return await deleteTeamEmail({
|
||||
await deleteTeamEmail({
|
||||
userId: ctx.user.id,
|
||||
userEmail: ctx.user.email,
|
||||
teamId,
|
||||
@@ -99,7 +114,7 @@ export const teamRouter = router({
|
||||
},
|
||||
});
|
||||
|
||||
return await createTeamEmailVerification({
|
||||
await createTeamEmailVerification({
|
||||
teamId,
|
||||
userId: ctx.user.id,
|
||||
data: {
|
||||
@@ -108,6 +123,7 @@ export const teamRouter = router({
|
||||
},
|
||||
});
|
||||
}),
|
||||
complete: completeTeamEmailVerificationRoute,
|
||||
resend: authenticatedProcedure
|
||||
.input(ZResendTeamEmailVerificationMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
@@ -135,7 +151,7 @@ export const teamRouter = router({
|
||||
},
|
||||
});
|
||||
|
||||
return await deleteTeamEmailVerification({
|
||||
await deleteTeamEmailVerification({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
});
|
||||
|
||||
@@ -124,11 +124,10 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
const isChangingIncludeSenderDetails =
|
||||
includeSenderDetails !== undefined && includeSenderDetails !== currentIncludeSenderDetails;
|
||||
|
||||
if (isPersonalOrganisation && isChangingIncludeSenderDetails) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Personal teams cannot update the sender details',
|
||||
});
|
||||
}
|
||||
// Personal teams cannot change the sender details — drop the field (no-op)
|
||||
// instead of rejecting the whole update.
|
||||
const derivedIncludeSenderDetails =
|
||||
isPersonalOrganisation && isChangingIncludeSenderDetails ? undefined : includeSenderDetails;
|
||||
|
||||
// Sanitize custom branding CSS at write time. `null` means inherit-from-org
|
||||
// for teams, so only run the sanitiser when an explicit string is provided.
|
||||
@@ -163,7 +162,7 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
documentLanguage,
|
||||
documentTimezone,
|
||||
documentDateFormat,
|
||||
includeSenderDetails,
|
||||
includeSenderDetails: derivedIncludeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
includeAuditLog,
|
||||
typedSignatureEnabled,
|
||||
|
||||
@@ -8,8 +8,10 @@ export const getTemplatesByIdsMeta: TrpcRouteMeta = {
|
||||
method: 'POST',
|
||||
path: '/template/get-many',
|
||||
summary: 'Get multiple templates',
|
||||
description: 'Retrieve multiple templates by their IDs',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Retrieve multiple templates by their IDs',
|
||||
tags: ['Template'],
|
||||
deprecated: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -72,8 +72,10 @@ export const templateRouter = router({
|
||||
method: 'GET',
|
||||
path: '/template',
|
||||
summary: 'Find templates',
|
||||
description: 'Find templates based on a search criteria',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Find templates based on a search criteria',
|
||||
tags: ['Template'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZFindTemplatesRequestSchema)
|
||||
@@ -201,7 +203,10 @@ export const templateRouter = router({
|
||||
method: 'GET',
|
||||
path: '/template/{templateId}',
|
||||
summary: 'Get template',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide.',
|
||||
tags: ['Template'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZGetTemplateByIdRequestSchema)
|
||||
@@ -245,8 +250,10 @@ export const templateRouter = router({
|
||||
path: '/template/create',
|
||||
contentTypes: ['multipart/form-data'],
|
||||
summary: 'Create template',
|
||||
description: 'Create a new template',
|
||||
description:
|
||||
'Create a new template. Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide.',
|
||||
tags: ['Template'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZCreateTemplateMutationSchema)
|
||||
@@ -334,8 +341,9 @@ export const templateRouter = router({
|
||||
path: '/template/create/beta',
|
||||
summary: 'Create template',
|
||||
description:
|
||||
'You will need to upload the PDF to the provided URL returned. Note: Once V2 API is released, this will be removed since we will allow direct uploads, instead of using an upload URL.',
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. You will need to upload the PDF to the provided URL returned. Note: Once V2 API is released, this will be removed since we will allow direct uploads, instead of using an upload URL.',
|
||||
tags: ['Template'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZCreateTemplateV2RequestSchema)
|
||||
@@ -418,7 +426,10 @@ export const templateRouter = router({
|
||||
method: 'POST',
|
||||
path: '/template/update',
|
||||
summary: 'Update template',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide.',
|
||||
tags: ['Template'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZUpdateTemplateRequestSchema)
|
||||
@@ -461,7 +472,10 @@ export const templateRouter = router({
|
||||
method: 'POST',
|
||||
path: '/template/duplicate',
|
||||
summary: 'Duplicate template',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide.',
|
||||
tags: ['Template'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZDuplicateTemplateMutationSchema)
|
||||
@@ -497,7 +511,10 @@ export const templateRouter = router({
|
||||
method: 'POST',
|
||||
path: '/template/delete',
|
||||
summary: 'Delete template',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide.',
|
||||
tags: ['Template'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZDeleteTemplateMutationSchema)
|
||||
@@ -534,8 +551,10 @@ export const templateRouter = router({
|
||||
method: 'POST',
|
||||
path: '/template/use',
|
||||
summary: 'Use template',
|
||||
description: 'Use the template to create a document',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Use the template to create a document',
|
||||
tags: ['Template'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZCreateDocumentFromTemplateRequestSchema)
|
||||
@@ -687,8 +706,10 @@ export const templateRouter = router({
|
||||
method: 'POST',
|
||||
path: '/template/direct/create',
|
||||
summary: 'Create direct link',
|
||||
description: 'Create a direct link for a template',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Create a direct link for a template',
|
||||
tags: ['Template'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZCreateTemplateDirectLinkRequestSchema)
|
||||
@@ -743,8 +764,10 @@ export const templateRouter = router({
|
||||
method: 'POST',
|
||||
path: '/template/direct/delete',
|
||||
summary: 'Delete direct link',
|
||||
description: 'Delete a direct link for a template',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Delete a direct link for a template',
|
||||
tags: ['Template'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZDeleteTemplateDirectLinkRequestSchema)
|
||||
@@ -775,8 +798,10 @@ export const templateRouter = router({
|
||||
method: 'POST',
|
||||
path: '/template/direct/toggle',
|
||||
summary: 'Toggle direct link',
|
||||
description: 'Enable or disable a direct link for a template',
|
||||
description:
|
||||
'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Enable or disable a direct link for a template',
|
||||
tags: ['Template'],
|
||||
deprecated: true,
|
||||
},
|
||||
})
|
||||
.input(ZToggleTemplateDirectLinkRequestSchema)
|
||||
|
||||
Reference in New Issue
Block a user