feat: add envelopes (#2025)

This PR is handles the changes required to support envelopes. The new
envelope editor/signing page will be hidden during release.

The core changes here is to migrate the documents and templates model to
a centralized envelopes model.

Even though Documents and Templates are removed, from the user
perspective they will still exist as we remap envelopes to documents and
templates.
This commit is contained in:
David Nguyen
2025-10-14 21:56:36 +11:00
committed by GitHub
parent 7b17156e56
commit 7f09ba72f4
447 changed files with 33467 additions and 9622 deletions

View File

@ -1,5 +1,5 @@
import { adminSuperDeleteDocument } from '@documenso/lib/server-only/admin/admin-super-delete-document';
import { sendDeleteEmail } from '@documenso/lib/server-only/document/send-delete-email';
import { superDeleteDocument } from '@documenso/lib/server-only/document/super-delete-document';
import { adminProcedure } from '../trpc';
import {
@ -19,10 +19,10 @@ export const deleteDocumentRoute = adminProcedure
},
});
await sendDeleteEmail({ documentId: id, reason });
await sendDeleteEmail({ envelopeId: id, reason });
await superDeleteDocument({
id,
await adminSuperDeleteDocument({
envelopeId: id,
requestMetadata: ctx.metadata.requestMetadata,
});
});

View File

@ -1,7 +1,7 @@
import { z } from 'zod';
export const ZDeleteDocumentRequestSchema = z.object({
id: z.number().min(1),
id: z.string(),
reason: z.string(),
});

View File

@ -0,0 +1,72 @@
import { EnvelopeType } from '@prisma/client';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import type { FindResultResponse } from '@documenso/lib/types/search-params';
import {
mapSecondaryIdToDocumentId,
unsafeBuildEnvelopeIdQuery,
} from '@documenso/lib/utils/envelope';
import { prisma } from '@documenso/prisma';
import { adminProcedure } from '../trpc';
import {
ZFindDocumentJobsRequestSchema,
ZFindDocumentJobsResponseSchema,
} from './find-document-jobs.types';
export const findDocumentJobsRoute = adminProcedure
.input(ZFindDocumentJobsRequestSchema)
.output(ZFindDocumentJobsResponseSchema)
.query(async ({ input }) => {
const { envelopeId, page = 1, perPage = 5 } = input;
const envelope = await prisma.envelope.findFirst({
where: unsafeBuildEnvelopeIdQuery(
{
type: 'envelopeId',
id: envelopeId,
},
EnvelopeType.DOCUMENT,
),
});
if (!envelope) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Envelope not found',
});
}
const [data, count] = await Promise.all([
prisma.backgroundJob.findMany({
where: {
jobId: 'internal.seal-document',
payload: {
path: ['documentId'],
equals: mapSecondaryIdToDocumentId(envelope.secondaryId),
},
},
skip: Math.max(page - 1, 0) * perPage,
take: perPage,
orderBy: {
submittedAt: 'desc',
},
}),
prisma.backgroundJob.count({
where: {
jobId: 'internal.seal-document',
payload: {
path: ['documentId'],
equals: mapSecondaryIdToDocumentId(envelope.secondaryId),
},
},
}),
]);
return {
data,
count,
currentPage: Math.max(page, 1),
perPage,
totalPages: Math.ceil(count / perPage),
} satisfies FindResultResponse<typeof data>;
});

View File

@ -0,0 +1,26 @@
import { z } from 'zod';
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
import BackgroundJobSchema from '@documenso/prisma/generated/zod/modelSchema/BackgroundJobSchema';
export const ZFindDocumentJobsRequestSchema = ZFindSearchParamsSchema.extend({
envelopeId: z.string(),
});
export const ZFindDocumentJobsResponseSchema = ZFindResultResponse.extend({
data: BackgroundJobSchema.pick({
status: true,
id: true,
retried: true,
maxRetries: true,
jobId: true,
name: true,
version: true,
submittedAt: true,
updatedAt: true,
completedAt: true,
lastRetriedAt: true,
}).array(),
});
export type TFindDocumentJobsResponse = z.infer<typeof ZFindDocumentJobsResponseSchema>;

View File

@ -1,4 +1,5 @@
import { findDocuments } from '@documenso/lib/server-only/admin/get-all-documents';
import { adminFindDocuments } from '@documenso/lib/server-only/admin/admin-find-documents';
import { mapEnvelopesToDocumentMany } from '@documenso/lib/utils/document';
import { adminProcedure } from '../trpc';
import { ZFindDocumentsRequestSchema, ZFindDocumentsResponseSchema } from './find-documents.types';
@ -9,5 +10,10 @@ export const findDocumentsRoute = adminProcedure
.query(async ({ input }) => {
const { query, page, perPage } = input;
return await findDocuments({ query, page, perPage });
const result = await adminFindDocuments({ query, page, perPage });
return {
...result,
data: result.data.map(mapEnvelopesToDocumentMany),
};
});

View File

@ -1,6 +1,9 @@
import { getEntireDocument } from '@documenso/lib/server-only/admin/get-entire-document';
import { sealDocument } from '@documenso/lib/server-only/document/seal-document';
import { EnvelopeType } from '@prisma/client';
import { jobs } from '@documenso/lib/jobs/client';
import { unsafeGetEntireEnvelope } from '@documenso/lib/server-only/admin/get-entire-document';
import { isDocumentCompleted } from '@documenso/lib/utils/document';
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
import { adminProcedure } from '../trpc';
import {
@ -20,9 +23,21 @@ export const resealDocumentRoute = adminProcedure
},
});
const document = await getEntireDocument({ id });
const envelope = await unsafeGetEntireEnvelope({
id: {
type: 'envelopeId',
id,
},
type: EnvelopeType.DOCUMENT,
});
const isResealing = isDocumentCompleted(document.status);
const isResealing = isDocumentCompleted(envelope.status);
await sealDocument({ documentId: id, isResealing });
await jobs.triggerJob({
name: 'internal.seal-document',
payload: {
documentId: mapSecondaryIdToDocumentId(envelope.secondaryId),
isResealing,
},
});
});

View File

@ -1,7 +1,7 @@
import { z } from 'zod';
export const ZResealDocumentRequestSchema = z.object({
id: z.number().min(1),
id: z.string(),
});
export const ZResealDocumentResponseSchema = z.void();

View File

@ -8,6 +8,7 @@ import { deleteUserRoute } from './delete-user';
import { disableUserRoute } from './disable-user';
import { enableUserRoute } from './enable-user';
import { findAdminOrganisationsRoute } from './find-admin-organisations';
import { findDocumentJobsRoute } from './find-document-jobs';
import { findDocumentsRoute } from './find-documents';
import { findSubscriptionClaimsRoute } from './find-subscription-claims';
import { getAdminOrganisationRoute } from './get-admin-organisation';
@ -52,6 +53,7 @@ export const adminRouter = router({
find: findDocumentsRoute,
delete: deleteDocumentRoute,
reseal: resealDocumentRoute,
findJobs: findDocumentJobsRoute,
},
recipient: {
update: updateRecipientRoute,