Files
sign/packages/trpc/server/document-router/router.ts

659 lines
18 KiB
TypeScript
Raw Normal View History

import { TRPCError } from '@trpc/server';
import { DateTime } from 'luxon';
2023-10-15 20:26:32 +11:00
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { DOCUMENSO_ENCRYPTION_KEY } from '@documenso/lib/constants/crypto';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt';
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
2023-12-02 09:38:24 +11:00
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
import { createDocument } from '@documenso/lib/server-only/document/create-document';
import { createDocumentV2 } from '@documenso/lib/server-only/document/create-document-v2';
import { deleteDocument } from '@documenso/lib/server-only/document/delete-document';
import { duplicateDocument } from '@documenso/lib/server-only/document/duplicate-document-by-id';
2024-02-15 18:20:10 +11:00
import { findDocumentAuditLogs } from '@documenso/lib/server-only/document/find-document-audit-logs';
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
fix: update document flow fetch logic (#1039) ## Description **Fixes issues with mismatching state between document steps.** For example, editing a recipient and proceeding to the next step may not display the updated recipient. And going back will display the old recipient instead of the updated values. **This PR also improves mutation and query speeds by adding logic to bypass query invalidation.** ```ts export const trpc = createTRPCReact<AppRouter>({ unstable_overrides: { useMutation: { async onSuccess(opts) { await opts.originalFn(); // This forces mutations to wait for all the queries on the page to reload, and in // this case one of the queries is `searchDocument` for the command overlay, which // on average takes ~500ms. This means that every single mutation must wait for this. await opts.queryClient.invalidateQueries(); }, }, }, }); ``` I've added workarounds to allow us to bypass things such as batching and invalidating queries. But I think we should instead remove this and update all the mutations where a query is required for a more optimised system. ## Example benchmarks Using stg-app vs this preview there's an average 50% speed increase across mutations. **Set signer step:** Average old speed: ~1100ms Average new speed: ~550ms **Set recipient step:** Average old speed: ~1200ms Average new speed: ~600ms **Set fields step:** Average old speed: ~1200ms Average new speed: ~600ms ## Related Issue This will resolve #470 ## Changes Made - Added ability to skip batch queries - Added a state to store the required document data. - Refetch the data between steps if/when required - Optimise mutations and queries ## Checklist - [X] I have tested these changes locally and they work as expected. - [X] I have followed the project's coding style guidelines. --------- Co-authored-by: Lucas Smith <me@lucasjamessmith.me>
2024-03-26 21:12:41 +08:00
import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
import { moveDocumentToTeam } from '@documenso/lib/server-only/document/move-document-to-team';
2023-11-16 07:35:45 +05:30
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
import { searchDocumentsWithKeyword } from '@documenso/lib/server-only/document/search-documents-with-keyword';
2024-10-16 19:03:14 +00:00
import { selfSignDocument } from '@documenso/lib/server-only/document/self-sign-document';
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
import { updateDocument } from '@documenso/lib/server-only/document/update-document';
import { symmetricEncrypt } from '@documenso/lib/universal/crypto';
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
import { DocumentDataType, DocumentStatus } from '@documenso/prisma/client';
import { authenticatedProcedure, procedure, router } from '../trpc';
import {
ZCreateDocumentRequestSchema,
ZCreateDocumentV2RequestSchema,
ZCreateDocumentV2ResponseSchema,
2024-12-10 16:11:20 +09:00
ZDeleteDocumentMutationSchema,
ZDistributeDocumentRequestSchema,
ZDistributeDocumentResponseSchema,
ZDownloadAuditLogsMutationSchema,
ZDownloadCertificateMutationSchema,
ZDuplicateDocumentRequestSchema,
ZDuplicateDocumentResponseSchema,
2024-02-15 18:20:10 +11:00
ZFindDocumentAuditLogsQuerySchema,
ZFindDocumentsRequestSchema,
ZFindDocumentsResponseSchema,
ZGenericSuccessResponse,
ZGetDocumentByIdQuerySchema,
ZGetDocumentByTokenQuerySchema,
ZGetDocumentWithDetailsByIdRequestSchema,
ZGetDocumentWithDetailsByIdResponseSchema,
ZMoveDocumentToTeamResponseSchema,
2024-12-14 01:23:35 +09:00
ZMoveDocumentToTeamSchema,
2023-11-16 07:35:45 +05:30
ZResendDocumentMutationSchema,
ZSearchDocumentsMutationSchema,
ZSelfSignDocumentMutationSchema,
ZSetPasswordForDocumentMutationSchema,
ZSetSigningOrderForDocumentMutationSchema,
ZSuccessResponseSchema,
ZUpdateDocumentRequestSchema,
ZUpdateDocumentResponseSchema,
} from './schema';
export const documentRouter = router({
2024-12-14 01:23:35 +09:00
/**
* @private
*/
getDocumentById: authenticatedProcedure
.input(ZGetDocumentByIdQuerySchema)
.query(async ({ input, ctx }) => {
const { teamId } = ctx;
const { documentId } = input;
2024-12-06 16:01:24 +09:00
return await getDocumentById({
userId: ctx.user.id,
teamId,
documentId,
2024-12-06 16:01:24 +09:00
});
}),
2024-12-14 01:23:35 +09:00
/**
* @private
*/
2024-03-28 13:13:29 +08:00
getDocumentByToken: procedure
.input(ZGetDocumentByTokenQuerySchema)
.query(async ({ input, ctx }) => {
2024-12-06 16:01:24 +09:00
const { token } = input;
2024-12-06 16:01:24 +09:00
return await getDocumentAndSenderByToken({
token,
userId: ctx.user?.id,
});
2024-03-28 13:13:29 +08:00
}),
2024-12-14 01:23:35 +09:00
/**
* @public
*/
2024-12-10 16:11:20 +09:00
findDocuments: authenticatedProcedure
.meta({
openapi: {
method: 'GET',
path: '/document',
2024-12-10 16:11:20 +09:00
summary: 'Find documents',
description: 'Find documents based on a search criteria',
tags: ['Document'],
2024-12-10 16:11:20 +09:00
},
})
.input(ZFindDocumentsRequestSchema)
2024-12-14 01:23:35 +09:00
.output(ZFindDocumentsResponseSchema)
2024-12-10 16:11:20 +09:00
.query(async ({ input, ctx }) => {
const { user, teamId } = ctx;
2024-12-10 16:11:20 +09:00
const { query, templateId, page, perPage, orderByDirection, orderByColumn, source, status } =
input;
2024-12-10 16:11:20 +09:00
const documents = await findDocuments({
userId: user.id,
teamId,
templateId,
query,
2024-12-10 16:11:20 +09:00
source,
status,
page,
perPage,
2024-12-14 01:23:35 +09:00
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
2024-12-10 16:11:20 +09:00
});
return documents;
2024-03-28 13:13:29 +08:00
}),
2024-12-14 01:23:35 +09:00
/**
* @public
*
* Todo: Refactor to getDocumentById.
*/
fix: update document flow fetch logic (#1039) ## Description **Fixes issues with mismatching state between document steps.** For example, editing a recipient and proceeding to the next step may not display the updated recipient. And going back will display the old recipient instead of the updated values. **This PR also improves mutation and query speeds by adding logic to bypass query invalidation.** ```ts export const trpc = createTRPCReact<AppRouter>({ unstable_overrides: { useMutation: { async onSuccess(opts) { await opts.originalFn(); // This forces mutations to wait for all the queries on the page to reload, and in // this case one of the queries is `searchDocument` for the command overlay, which // on average takes ~500ms. This means that every single mutation must wait for this. await opts.queryClient.invalidateQueries(); }, }, }, }); ``` I've added workarounds to allow us to bypass things such as batching and invalidating queries. But I think we should instead remove this and update all the mutations where a query is required for a more optimised system. ## Example benchmarks Using stg-app vs this preview there's an average 50% speed increase across mutations. **Set signer step:** Average old speed: ~1100ms Average new speed: ~550ms **Set recipient step:** Average old speed: ~1200ms Average new speed: ~600ms **Set fields step:** Average old speed: ~1200ms Average new speed: ~600ms ## Related Issue This will resolve #470 ## Changes Made - Added ability to skip batch queries - Added a state to store the required document data. - Refetch the data between steps if/when required - Optimise mutations and queries ## Checklist - [X] I have tested these changes locally and they work as expected. - [X] I have followed the project's coding style guidelines. --------- Co-authored-by: Lucas Smith <me@lucasjamessmith.me>
2024-03-26 21:12:41 +08:00
getDocumentWithDetailsById: authenticatedProcedure
2024-12-10 16:11:20 +09:00
.meta({
openapi: {
method: 'GET',
path: '/document/{documentId}',
summary: 'Get document',
description: 'Returns a document given an ID',
tags: ['Document'],
2024-12-10 16:11:20 +09:00
},
})
.input(ZGetDocumentWithDetailsByIdRequestSchema)
2024-12-14 01:23:35 +09:00
.output(ZGetDocumentWithDetailsByIdResponseSchema)
fix: update document flow fetch logic (#1039) ## Description **Fixes issues with mismatching state between document steps.** For example, editing a recipient and proceeding to the next step may not display the updated recipient. And going back will display the old recipient instead of the updated values. **This PR also improves mutation and query speeds by adding logic to bypass query invalidation.** ```ts export const trpc = createTRPCReact<AppRouter>({ unstable_overrides: { useMutation: { async onSuccess(opts) { await opts.originalFn(); // This forces mutations to wait for all the queries on the page to reload, and in // this case one of the queries is `searchDocument` for the command overlay, which // on average takes ~500ms. This means that every single mutation must wait for this. await opts.queryClient.invalidateQueries(); }, }, }, }); ``` I've added workarounds to allow us to bypass things such as batching and invalidating queries. But I think we should instead remove this and update all the mutations where a query is required for a more optimised system. ## Example benchmarks Using stg-app vs this preview there's an average 50% speed increase across mutations. **Set signer step:** Average old speed: ~1100ms Average new speed: ~550ms **Set recipient step:** Average old speed: ~1200ms Average new speed: ~600ms **Set fields step:** Average old speed: ~1200ms Average new speed: ~600ms ## Related Issue This will resolve #470 ## Changes Made - Added ability to skip batch queries - Added a state to store the required document data. - Refetch the data between steps if/when required - Optimise mutations and queries ## Checklist - [X] I have tested these changes locally and they work as expected. - [X] I have followed the project's coding style guidelines. --------- Co-authored-by: Lucas Smith <me@lucasjamessmith.me>
2024-03-26 21:12:41 +08:00
.query(async ({ input, ctx }) => {
const { teamId, user } = ctx;
const { documentId } = input;
2024-12-06 16:01:24 +09:00
return await getDocumentWithDetailsById({
userId: user.id,
teamId,
documentId,
2024-12-06 16:01:24 +09:00
});
fix: update document flow fetch logic (#1039) ## Description **Fixes issues with mismatching state between document steps.** For example, editing a recipient and proceeding to the next step may not display the updated recipient. And going back will display the old recipient instead of the updated values. **This PR also improves mutation and query speeds by adding logic to bypass query invalidation.** ```ts export const trpc = createTRPCReact<AppRouter>({ unstable_overrides: { useMutation: { async onSuccess(opts) { await opts.originalFn(); // This forces mutations to wait for all the queries on the page to reload, and in // this case one of the queries is `searchDocument` for the command overlay, which // on average takes ~500ms. This means that every single mutation must wait for this. await opts.queryClient.invalidateQueries(); }, }, }, }); ``` I've added workarounds to allow us to bypass things such as batching and invalidating queries. But I think we should instead remove this and update all the mutations where a query is required for a more optimised system. ## Example benchmarks Using stg-app vs this preview there's an average 50% speed increase across mutations. **Set signer step:** Average old speed: ~1100ms Average new speed: ~550ms **Set recipient step:** Average old speed: ~1200ms Average new speed: ~600ms **Set fields step:** Average old speed: ~1200ms Average new speed: ~600ms ## Related Issue This will resolve #470 ## Changes Made - Added ability to skip batch queries - Added a state to store the required document data. - Refetch the data between steps if/when required - Optimise mutations and queries ## Checklist - [X] I have tested these changes locally and they work as expected. - [X] I have followed the project's coding style guidelines. --------- Co-authored-by: Lucas Smith <me@lucasjamessmith.me>
2024-03-26 21:12:41 +08:00
}),
/**
* Temporariy endpoint for V2 Beta until we allow passthrough documents on create.
*
* @public
* @deprecated
*/
createDocumentTemporary: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
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.',
tags: ['Document'],
},
})
.input(ZCreateDocumentV2RequestSchema)
.output(ZCreateDocumentV2ResponseSchema)
.mutation(async ({ input, ctx }) => {
const { teamId } = ctx;
const {
title,
externalId,
visibility,
globalAccessAuth,
globalActionAuth,
recipients,
meta,
} = input;
2023-10-15 20:26:32 +11:00
const { remaining } = await getServerLimits({ email: ctx.user.email, teamId });
2023-10-15 20:26:32 +11:00
if (remaining.documents <= 0) {
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
message: 'You have reached your document limit for this month. Please upgrade your plan.',
statusCode: 400,
});
}
const fileName = title.endsWith('.pdf') ? title : `${title}.pdf`;
2023-10-10 08:25:58 +05:30
const { url, key } = await getPresignPostUrl(fileName, 'application/pdf');
2023-10-10 08:25:58 +05:30
const documentData = await createDocumentData({
data: key,
type: DocumentDataType.S3_PATH,
});
2023-10-10 08:25:58 +05:30
const createdDocument = await createDocumentV2({
userId: ctx.user.id,
teamId,
documentDataId: documentData.id,
normalizePdf: false, // Not normalizing because of presigned URL.
data: {
title,
externalId,
visibility,
globalAccessAuth,
globalActionAuth,
recipients,
},
meta,
requestMetadata: ctx.metadata,
});
return {
document: createdDocument,
uploadUrl: url,
};
2023-10-10 08:25:58 +05:30
}),
2024-12-14 01:23:35 +09:00
/**
* Wait until RR7 so we can passthrough documents.
*
* @private
2024-12-14 01:23:35 +09:00
*/
createDocument: authenticatedProcedure
// .meta({
// openapi: {
// method: 'POST',
// path: '/document/create',
// summary: 'Create document',
// tags: ['Document'],
// },
// })
.input(ZCreateDocumentRequestSchema)
.mutation(async ({ input, ctx }) => {
const { teamId } = ctx;
const { title, documentDataId, timezone } = input;
2024-12-06 16:01:24 +09:00
const { remaining } = await getServerLimits({ email: ctx.user.email, teamId });
2024-12-06 16:01:24 +09:00
if (remaining.documents <= 0) {
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
2024-12-06 16:01:24 +09:00
message: 'You have reached your document limit for this month. Please upgrade your plan.',
statusCode: 400,
});
}
2024-12-06 16:01:24 +09:00
return await createDocument({
userId: ctx.user.id,
teamId,
title,
documentDataId,
2024-12-18 08:14:14 +11:00
normalizePdf: true,
timezone,
requestMetadata: ctx.metadata,
2024-12-06 16:01:24 +09:00
});
}),
2024-12-14 01:23:35 +09:00
/**
* @public
*
* Todo: Refactor to updateDocument.
*/
2024-03-28 13:13:29 +08:00
setSettingsForDocument: authenticatedProcedure
2024-12-10 16:11:20 +09:00
.meta({
openapi: {
method: 'POST',
path: '/document/update',
2024-12-10 16:11:20 +09:00
summary: 'Update document',
tags: ['Document'],
2024-12-10 16:11:20 +09:00
},
})
.input(ZUpdateDocumentRequestSchema)
.output(ZUpdateDocumentResponseSchema)
2024-03-28 13:13:29 +08:00
.mutation(async ({ input, ctx }) => {
const { teamId } = ctx;
const { documentId, data, meta = {} } = input;
2024-12-06 16:01:24 +09:00
const userId = ctx.user.id;
if (Object.values(meta).length > 0) {
2024-12-06 16:01:24 +09:00
await upsertDocumentMeta({
userId: ctx.user.id,
teamId,
2024-02-15 18:20:10 +11:00
documentId,
subject: meta.subject,
message: meta.message,
2024-12-06 16:01:24 +09:00
timezone: meta.timezone,
dateFormat: meta.dateFormat,
2024-12-06 16:01:24 +09:00
language: meta.language,
typedSignatureEnabled: meta.typedSignatureEnabled,
redirectUrl: meta.redirectUrl,
distributionMethod: meta.distributionMethod,
signingOrder: meta.signingOrder,
emailSettings: meta.emailSettings,
requestMetadata: ctx.metadata,
2024-02-15 18:20:10 +11:00
});
}
2024-12-06 16:01:24 +09:00
return await updateDocument({
2024-12-06 16:01:24 +09:00
userId,
teamId,
documentId,
data,
requestMetadata: ctx.metadata,
2024-12-06 16:01:24 +09:00
});
2024-02-15 18:20:10 +11:00
}),
2024-12-14 01:23:35 +09:00
/**
* @public
*/
2024-12-10 16:11:20 +09:00
deleteDocument: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/document/delete',
2024-12-10 16:11:20 +09:00
summary: 'Delete document',
tags: ['Document'],
2024-12-10 16:11:20 +09:00
},
})
.input(ZDeleteDocumentMutationSchema)
.output(ZSuccessResponseSchema)
2024-03-28 13:13:29 +08:00
.mutation(async ({ input, ctx }) => {
const { teamId } = ctx;
const { documentId } = input;
2024-03-28 13:13:29 +08:00
2024-12-10 16:11:20 +09:00
const userId = ctx.user.id;
2024-12-14 01:23:35 +09:00
await deleteDocument({
2024-12-10 16:11:20 +09:00
id: documentId,
userId,
teamId,
requestMetadata: ctx.metadata,
2024-12-10 16:11:20 +09:00
});
return ZGenericSuccessResponse;
2024-03-28 13:13:29 +08:00
}),
2024-12-14 01:23:35 +09:00
/**
* @public
*/
2024-12-10 16:11:20 +09:00
moveDocumentToTeam: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/document/move',
2024-12-10 16:11:20 +09:00
summary: 'Move document',
description: 'Move a document from your personal account to a team',
tags: ['Document'],
2024-12-10 16:11:20 +09:00
},
})
2024-12-14 01:23:35 +09:00
.input(ZMoveDocumentToTeamSchema)
.output(ZMoveDocumentToTeamResponseSchema)
.mutation(async ({ input, ctx }) => {
2024-12-10 16:11:20 +09:00
const { documentId, teamId } = input;
const userId = ctx.user.id;
2024-12-10 16:11:20 +09:00
return await moveDocumentToTeam({
documentId,
teamId,
userId,
requestMetadata: ctx.metadata,
2024-12-06 16:01:24 +09:00
});
}),
2024-12-14 01:23:35 +09:00
/**
* @private
*/
2024-01-17 17:17:08 +11:00
setPasswordForDocument: authenticatedProcedure
.input(ZSetPasswordForDocumentMutationSchema)
.mutation(async ({ input, ctx }) => {
const { teamId } = ctx;
2024-12-06 16:01:24 +09:00
const { documentId, password } = input;
2024-01-17 17:17:08 +11:00
2024-12-06 16:01:24 +09:00
const key = DOCUMENSO_ENCRYPTION_KEY;
2024-12-06 16:01:24 +09:00
if (!key) {
throw new Error('Missing encryption key');
2024-01-17 17:17:08 +11:00
}
2024-12-06 16:01:24 +09:00
const securePassword = symmetricEncrypt({
data: password,
key,
});
await upsertDocumentMeta({
userId: ctx.user.id,
teamId,
2024-12-06 16:01:24 +09:00
documentId,
password: securePassword,
requestMetadata: ctx.metadata,
2024-12-06 16:01:24 +09:00
});
2024-01-17 17:17:08 +11:00
}),
2024-12-14 01:23:35 +09:00
/**
* @private
*
* Todo: Remove and use `updateDocument` endpoint instead.
2024-12-14 01:23:35 +09:00
*/
setSigningOrderForDocument: authenticatedProcedure
.input(ZSetSigningOrderForDocumentMutationSchema)
.mutation(async ({ input, ctx }) => {
const { teamId } = ctx;
2024-12-06 16:01:24 +09:00
const { documentId, signingOrder } = input;
return await upsertDocumentMeta({
userId: ctx.user.id,
teamId,
2024-12-06 16:01:24 +09:00
documentId,
signingOrder,
requestMetadata: ctx.metadata,
2024-12-06 16:01:24 +09:00
});
}),
2024-12-14 01:23:35 +09:00
/**
* @public
*
* Todo: Refactor to distributeDocument.
* Todo: Rework before releasing API.
*/
sendDocument: authenticatedProcedure
2024-12-10 16:11:20 +09:00
.meta({
openapi: {
method: 'POST',
path: '/document/distribute',
2024-12-10 16:11:20 +09:00
summary: 'Distribute document',
description: 'Send the document out to recipients based on your distribution method',
tags: ['Document'],
2024-12-10 16:11:20 +09:00
},
})
.input(ZDistributeDocumentRequestSchema)
.output(ZDistributeDocumentResponseSchema)
.mutation(async ({ input, ctx }) => {
const { teamId } = ctx;
const { documentId, meta = {} } = input;
if (Object.values(meta).length > 0) {
2024-12-06 16:01:24 +09:00
await upsertDocumentMeta({
userId: ctx.user.id,
teamId,
documentId,
2024-12-06 16:01:24 +09:00
subject: meta.subject,
message: meta.message,
dateFormat: meta.dateFormat,
timezone: meta.timezone,
redirectUrl: meta.redirectUrl,
distributionMethod: meta.distributionMethod,
emailSettings: meta.emailSettings,
language: meta.language,
requestMetadata: ctx.metadata,
});
}
2024-12-06 16:01:24 +09:00
return await sendDocument({
userId: ctx.user.id,
documentId,
teamId,
requestMetadata: ctx.metadata,
2024-12-06 16:01:24 +09:00
});
}),
2023-11-08 09:25:44 +00:00
selfSignDocument: authenticatedProcedure
.input(ZSelfSignDocumentMutationSchema)
.mutation(async ({ input, ctx }) => {
try {
2024-11-15 20:25:09 +00:00
const { documentId, teamId } = input;
2024-10-16 19:03:14 +00:00
return await selfSignDocument({
userId: ctx.user.id,
documentId,
2024-11-15 20:25:09 +00:00
teamId,
2025-02-06 11:47:44 +00:00
requestMetadata: ctx.metadata,
});
} catch (err) {
console.error(err);
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'We were unable to self sign this document. Please try again later.',
});
}
}),
2024-12-14 01:23:35 +09:00
/**
* @public
*
* Todo: Refactor to redistributeDocument.
2024-12-14 01:23:35 +09:00
*/
2023-11-16 07:35:45 +05:30
resendDocument: authenticatedProcedure
2024-12-10 16:11:20 +09:00
.meta({
openapi: {
method: 'POST',
path: '/document/redistribute',
summary: 'Redistribute document',
2024-12-10 16:11:20 +09:00
description:
'Redistribute the document to the provided recipients who have not actioned the document. Will use the distribution method set in the document',
tags: ['Document'],
2024-12-10 16:11:20 +09:00
},
})
2023-11-16 07:35:45 +05:30
.input(ZResendDocumentMutationSchema)
.output(ZSuccessResponseSchema)
2023-11-16 07:35:45 +05:30
.mutation(async ({ input, ctx }) => {
const { teamId } = ctx;
const { documentId, recipients } = input;
await resendDocument({
2024-12-06 16:01:24 +09:00
userId: ctx.user.id,
teamId,
documentId,
recipients,
requestMetadata: ctx.metadata,
2024-12-06 16:01:24 +09:00
});
return ZGenericSuccessResponse;
2023-11-16 07:35:45 +05:30
}),
2024-12-14 01:23:35 +09:00
/**
* @public
*/
2023-11-08 09:25:44 +00:00
duplicateDocument: authenticatedProcedure
2024-12-10 16:11:20 +09:00
.meta({
openapi: {
method: 'POST',
path: '/document/duplicate',
2024-12-10 16:11:20 +09:00
summary: 'Duplicate document',
tags: ['Document'],
2024-12-10 16:11:20 +09:00
},
})
.input(ZDuplicateDocumentRequestSchema)
2024-12-14 01:23:35 +09:00
.output(ZDuplicateDocumentResponseSchema)
2023-11-08 09:25:44 +00:00
.mutation(async ({ input, ctx }) => {
const { teamId, user } = ctx;
const { documentId } = input;
2024-12-14 01:23:35 +09:00
return await duplicateDocument({
userId: user.id,
teamId,
documentId,
2024-12-06 16:01:24 +09:00
});
2023-11-08 09:25:44 +00:00
}),
2024-12-14 01:23:35 +09:00
/**
* @private
*/
searchDocuments: authenticatedProcedure
.input(ZSearchDocumentsMutationSchema)
.query(async ({ input, ctx }) => {
const { query } = input;
2024-12-06 16:01:24 +09:00
const documents = await searchDocumentsWithKeyword({
query,
userId: ctx.user.id,
});
2024-12-06 16:01:24 +09:00
return documents;
}),
2024-03-30 14:00:34 +08:00
2024-12-14 01:23:35 +09:00
/**
* @private
*/
2024-12-10 16:11:20 +09:00
findDocumentAuditLogs: authenticatedProcedure
.input(ZFindDocumentAuditLogsQuerySchema)
.query(async ({ input, ctx }) => {
const { teamId } = ctx;
2024-12-14 01:23:35 +09:00
const {
page,
perPage,
documentId,
cursor,
filterForRecentActivity,
orderByColumn,
orderByDirection,
} = input;
2024-12-10 16:11:20 +09:00
return await findDocumentAuditLogs({
userId: ctx.user.id,
teamId,
2024-12-10 16:11:20 +09:00
page,
perPage,
documentId,
cursor,
filterForRecentActivity,
2024-12-14 01:23:35 +09:00
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
2024-12-10 16:11:20 +09:00
});
}),
2024-12-14 01:23:35 +09:00
/**
* @private
*/
downloadAuditLogs: authenticatedProcedure
.input(ZDownloadAuditLogsMutationSchema)
.mutation(async ({ input, ctx }) => {
const { teamId } = ctx;
const { documentId } = input;
2024-12-06 16:01:24 +09:00
const document = await getDocumentById({
2024-12-10 16:11:20 +09:00
documentId,
2024-12-06 16:01:24 +09:00
userId: ctx.user.id,
teamId,
}).catch(() => null);
2024-12-06 16:01:24 +09:00
if (!document || (teamId && document.teamId !== teamId)) {
throw new TRPCError({
2024-12-06 16:01:24 +09:00
code: 'FORBIDDEN',
message: 'You do not have access to this document.',
});
}
2024-12-06 16:01:24 +09:00
const encrypted = encryptSecondaryData({
data: document.id.toString(),
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
});
return {
url: `${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/audit-log?d=${encrypted}`,
};
}),
2024-12-14 01:23:35 +09:00
/**
* @private
*/
downloadCertificate: authenticatedProcedure
.input(ZDownloadCertificateMutationSchema)
.mutation(async ({ input, ctx }) => {
const { teamId } = ctx;
const { documentId } = input;
2024-12-06 16:01:24 +09:00
const document = await getDocumentById({
2024-12-10 16:11:20 +09:00
documentId,
2024-12-06 16:01:24 +09:00
userId: ctx.user.id,
teamId,
});
2024-04-24 19:51:18 +07:00
2024-12-06 16:01:24 +09:00
if (document.status !== DocumentStatus.COMPLETED) {
throw new AppError('DOCUMENT_NOT_COMPLETE');
}
2024-12-06 16:01:24 +09:00
const encrypted = encryptSecondaryData({
data: document.id.toString(),
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
});
2024-12-06 16:01:24 +09:00
return {
url: `${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encrypted}`,
};
}),
});