mirror of
https://github.com/documenso/documenso.git
synced 2025-11-12 07:43:16 +10:00
fix: refactor document router (#1990)
This commit is contained in:
@ -92,7 +92,11 @@ export const handleOAuthCallbackUrl = async (options: HandleOAuthCallbackUrlOpti
|
||||
providerAccountId: sub,
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -29,7 +29,13 @@ export const run = async ({
|
||||
id: documentId,
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
documentMeta: true,
|
||||
recipients: true,
|
||||
team: {
|
||||
|
||||
@ -39,7 +39,13 @@ export const run = async ({
|
||||
},
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -51,7 +57,13 @@ export const run = async ({
|
||||
organisationId: payload.organisationId,
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -39,7 +39,13 @@ export const run = async ({
|
||||
},
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -49,6 +55,11 @@ export const run = async ({
|
||||
where: {
|
||||
id: payload.memberUserId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { branding, emailLanguage, senderEmail } = await getEmailContext({
|
||||
|
||||
@ -38,7 +38,13 @@ export const run = async ({
|
||||
id: recipientId,
|
||||
},
|
||||
},
|
||||
user: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
documentMeta: true,
|
||||
},
|
||||
});
|
||||
|
||||
@ -33,7 +33,13 @@ export const run = async ({
|
||||
id: documentId,
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
documentMeta: true,
|
||||
team: {
|
||||
select: {
|
||||
|
||||
@ -15,7 +15,7 @@ import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-reques
|
||||
import { nanoid, prefixedId } from '@documenso/lib/universal/id';
|
||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { TCreateDocumentV2Request } from '@documenso/trpc/server/document-router/schema';
|
||||
import type { TCreateDocumentTemporaryRequest } from '@documenso/trpc/server/document-router/create-document-temporary.types';
|
||||
|
||||
import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth';
|
||||
import type { TDocumentFormValues } from '../../types/document-form-values';
|
||||
@ -45,7 +45,7 @@ export type CreateDocumentOptions = {
|
||||
globalAccessAuth?: TDocumentAccessAuthTypes[];
|
||||
globalActionAuth?: TDocumentActionAuthTypes[];
|
||||
formValues?: TDocumentFormValues;
|
||||
recipients: TCreateDocumentV2Request['recipients'];
|
||||
recipients: TCreateDocumentTemporaryRequest['recipients'];
|
||||
folderId?: string;
|
||||
};
|
||||
meta?: Partial<Omit<TemplateMeta, 'id' | 'templateId'>>;
|
||||
|
||||
@ -49,6 +49,11 @@ export const findDocuments = async ({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
|
||||
let team = null;
|
||||
@ -267,7 +272,7 @@ export const findDocuments = async ({
|
||||
|
||||
const findDocumentsFilter = (
|
||||
status: ExtendedDocumentStatus,
|
||||
user: User,
|
||||
user: Pick<User, 'id' | 'email' | 'name'>,
|
||||
folderId?: string | null,
|
||||
) => {
|
||||
return match<ExtendedDocumentStatus, Prisma.DocumentWhereInput>(status)
|
||||
|
||||
@ -73,7 +73,13 @@ export const getDocumentAndSenderByToken = async ({
|
||||
},
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
documentData: true,
|
||||
documentMeta: true,
|
||||
recipients: {
|
||||
@ -90,9 +96,6 @@ export const getDocumentAndSenderByToken = async ({
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
const { password: _password, ...user } = result.user;
|
||||
|
||||
const recipient = result.recipients[0];
|
||||
|
||||
// Sanity check, should not be possible.
|
||||
@ -120,7 +123,11 @@ export const getDocumentAndSenderByToken = async ({
|
||||
|
||||
return {
|
||||
...result,
|
||||
user,
|
||||
user: {
|
||||
id: result.user.id,
|
||||
email: result.user.email,
|
||||
name: result.user.name,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -7,14 +7,12 @@ export type GetDocumentWithDetailsByIdOptions = {
|
||||
documentId: number;
|
||||
userId: number;
|
||||
teamId: number;
|
||||
folderId?: string;
|
||||
};
|
||||
|
||||
export const getDocumentWithDetailsById = async ({
|
||||
documentId,
|
||||
userId,
|
||||
teamId,
|
||||
folderId,
|
||||
}: GetDocumentWithDetailsByIdOptions) => {
|
||||
const { documentWhereInput } = await getDocumentWhereInput({
|
||||
documentId,
|
||||
@ -25,7 +23,6 @@ export const getDocumentWithDetailsById = async ({
|
||||
const document = await prisma.document.findFirst({
|
||||
where: {
|
||||
...documentWhereInput,
|
||||
folderId,
|
||||
},
|
||||
include: {
|
||||
documentData: true,
|
||||
|
||||
@ -28,13 +28,7 @@ export async function rejectDocumentWithToken({
|
||||
documentId,
|
||||
},
|
||||
include: {
|
||||
document: {
|
||||
include: {
|
||||
user: true,
|
||||
recipients: true,
|
||||
documentMeta: true,
|
||||
},
|
||||
},
|
||||
document: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -33,7 +33,13 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
||||
documentData: true,
|
||||
documentMeta: true,
|
||||
recipients: true,
|
||||
user: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
|
||||
@ -24,7 +24,13 @@ export const sendDeleteEmail = async ({ documentId, reason }: SendDeleteEmailOpt
|
||||
id: documentId,
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
documentMeta: true,
|
||||
},
|
||||
});
|
||||
|
||||
@ -30,7 +30,13 @@ export const superDeleteDocument = async ({ id, requestMetadata }: SuperDeleteDo
|
||||
include: {
|
||||
recipients: true,
|
||||
documentMeta: true,
|
||||
user: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -105,7 +105,13 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
directLink: true,
|
||||
templateDocumentData: true,
|
||||
templateMeta: true,
|
||||
user: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -24,7 +24,14 @@ export const resetPassword = async ({ token, password, requestMetadata }: ResetP
|
||||
token,
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
password: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -12,7 +12,13 @@ export type VerifyEmailProps = {
|
||||
export const verifyEmail = async ({ token }: VerifyEmailProps) => {
|
||||
const verificationToken = await prisma.verificationToken.findFirst({
|
||||
include: {
|
||||
user: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
where: {
|
||||
token,
|
||||
|
||||
@ -4,7 +4,7 @@ import type { DocumentWithRecipients } from '@documenso/prisma/types/document-wi
|
||||
|
||||
export type MaskRecipientTokensForDocumentOptions<T extends DocumentWithRecipients> = {
|
||||
document: T;
|
||||
user?: User;
|
||||
user?: Pick<User, 'id' | 'email' | 'name'>;
|
||||
token?: string;
|
||||
};
|
||||
|
||||
|
||||
@ -0,0 +1,81 @@
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
|
||||
import { createDocumentV2 } from '@documenso/lib/server-only/document/create-document-v2';
|
||||
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZCreateDocumentTemporaryRequestSchema,
|
||||
ZCreateDocumentTemporaryResponseSchema,
|
||||
createDocumentTemporaryMeta,
|
||||
} from './create-document-temporary.types';
|
||||
|
||||
/**
|
||||
* Temporariy endpoint for V2 Beta until we allow passthrough documents on create.
|
||||
*
|
||||
* @public
|
||||
* @deprecated
|
||||
*/
|
||||
export const createDocumentTemporaryRoute = authenticatedProcedure
|
||||
.meta(createDocumentTemporaryMeta)
|
||||
.input(ZCreateDocumentTemporaryRequestSchema)
|
||||
.output(ZCreateDocumentTemporaryResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
|
||||
const {
|
||||
title,
|
||||
externalId,
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
recipients,
|
||||
meta,
|
||||
folderId,
|
||||
} = input;
|
||||
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId });
|
||||
|
||||
if (remaining.documents <= 0) {
|
||||
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
|
||||
message: 'You have reached your document limit for this month. Please upgrade your plan.',
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const fileName = title.endsWith('.pdf') ? title : `${title}.pdf`;
|
||||
|
||||
const { url, key } = await getPresignPostUrl(fileName, 'application/pdf');
|
||||
|
||||
const documentData = await createDocumentData({
|
||||
data: key,
|
||||
type: DocumentDataType.S3_PATH,
|
||||
});
|
||||
|
||||
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,
|
||||
folderId,
|
||||
},
|
||||
meta,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return {
|
||||
document: createdDocument,
|
||||
folder: createdDocument.folder, // Todo: Remove this prior to api-v2 release.
|
||||
uploadUrl: url,
|
||||
};
|
||||
});
|
||||
@ -0,0 +1,120 @@
|
||||
import { DocumentSigningOrder } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentSchema } from '@documenso/lib/types/document';
|
||||
import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';
|
||||
import {
|
||||
ZFieldHeightSchema,
|
||||
ZFieldPageNumberSchema,
|
||||
ZFieldPageXSchema,
|
||||
ZFieldPageYSchema,
|
||||
ZFieldWidthSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
|
||||
import { ZCreateRecipientSchema } from '../recipient-router/schema';
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
import {
|
||||
ZDocumentExternalIdSchema,
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
ZDocumentMetaDrawSignatureEnabledSchema,
|
||||
ZDocumentMetaLanguageSchema,
|
||||
ZDocumentMetaMessageSchema,
|
||||
ZDocumentMetaRedirectUrlSchema,
|
||||
ZDocumentMetaSubjectSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
ZDocumentMetaTypedSignatureEnabledSchema,
|
||||
ZDocumentMetaUploadSignatureEnabledSchema,
|
||||
ZDocumentTitleSchema,
|
||||
ZDocumentVisibilitySchema,
|
||||
} from './schema';
|
||||
|
||||
/**
|
||||
* Temporariy endpoint for V2 Beta until we allow passthrough documents on create.
|
||||
*/
|
||||
export const createDocumentTemporaryMeta: TrpcRouteMeta = {
|
||||
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'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZCreateDocumentTemporaryRequestSchema = z.object({
|
||||
title: ZDocumentTitleSchema,
|
||||
externalId: ZDocumentExternalIdSchema.optional(),
|
||||
visibility: ZDocumentVisibilitySchema.optional(),
|
||||
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional(),
|
||||
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional(),
|
||||
formValues: ZDocumentFormValuesSchema.optional(),
|
||||
folderId: z
|
||||
.string()
|
||||
.describe(
|
||||
'The ID of the folder to create the document in. If not provided, the document will be created in the root folder.',
|
||||
)
|
||||
.optional(),
|
||||
recipients: z
|
||||
.array(
|
||||
ZCreateRecipientSchema.extend({
|
||||
fields: ZFieldAndMetaSchema.and(
|
||||
z.object({
|
||||
pageNumber: ZFieldPageNumberSchema,
|
||||
pageX: ZFieldPageXSchema,
|
||||
pageY: ZFieldPageYSchema,
|
||||
width: ZFieldWidthSchema,
|
||||
height: ZFieldHeightSchema,
|
||||
}),
|
||||
)
|
||||
.array()
|
||||
.optional(),
|
||||
}),
|
||||
)
|
||||
.refine(
|
||||
(recipients) => {
|
||||
const emails = recipients.map((recipient) => recipient.email);
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
},
|
||||
{ message: 'Recipients must have unique emails' },
|
||||
)
|
||||
.optional(),
|
||||
meta: z
|
||||
.object({
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
message: ZDocumentMetaMessageSchema.optional(),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
|
||||
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const ZCreateDocumentTemporaryResponseSchema = z.object({
|
||||
document: ZDocumentSchema,
|
||||
uploadUrl: z
|
||||
.string()
|
||||
.describe(
|
||||
'The URL to upload the document PDF to. Use a PUT request with the file via form-data',
|
||||
),
|
||||
});
|
||||
|
||||
export type TCreateDocumentTemporaryRequest = z.infer<typeof ZCreateDocumentTemporaryRequestSchema>;
|
||||
export type TCreateDocumentTemporaryResponse = z.infer<
|
||||
typeof ZCreateDocumentTemporaryResponseSchema
|
||||
>;
|
||||
47
packages/trpc/server/document-router/create-document.ts
Normal file
47
packages/trpc/server/document-router/create-document.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { createDocument } from '@documenso/lib/server-only/document/create-document';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZCreateDocumentRequestSchema,
|
||||
ZCreateDocumentResponseSchema,
|
||||
} from './create-document.types';
|
||||
|
||||
export const createDocumentRoute = authenticatedProcedure
|
||||
.input(ZCreateDocumentRequestSchema)
|
||||
.output(ZCreateDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
const { title, documentDataId, timezone, folderId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId });
|
||||
|
||||
if (remaining.documents <= 0) {
|
||||
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
|
||||
message: 'You have reached your document limit for this month. Please upgrade your plan.',
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const document = await createDocument({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
title,
|
||||
documentDataId,
|
||||
normalizePdf: true,
|
||||
userTimezone: timezone,
|
||||
requestMetadata: ctx.metadata,
|
||||
folderId,
|
||||
});
|
||||
|
||||
return {
|
||||
id: document.id,
|
||||
};
|
||||
});
|
||||
@ -0,0 +1,27 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentMetaTimezoneSchema, ZDocumentTitleSchema } from './schema';
|
||||
|
||||
// Currently not in use until we allow passthrough documents on create.
|
||||
// export const createDocumentMeta: TrpcRouteMeta = {
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/document/create',
|
||||
// summary: 'Create document',
|
||||
// tags: ['Document'],
|
||||
// },
|
||||
// };
|
||||
|
||||
export const ZCreateDocumentRequestSchema = z.object({
|
||||
title: ZDocumentTitleSchema,
|
||||
documentDataId: z.string().min(1),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
folderId: z.string().describe('The ID of the folder to create the document in').optional(),
|
||||
});
|
||||
|
||||
export const ZCreateDocumentResponseSchema = z.object({
|
||||
id: z.number(),
|
||||
});
|
||||
|
||||
export type TCreateDocumentRequest = z.infer<typeof ZCreateDocumentRequestSchema>;
|
||||
export type TCreateDocumentResponse = z.infer<typeof ZCreateDocumentResponseSchema>;
|
||||
35
packages/trpc/server/document-router/delete-document.ts
Normal file
35
packages/trpc/server/document-router/delete-document.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { deleteDocument } from '@documenso/lib/server-only/document/delete-document';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDeleteDocumentRequestSchema,
|
||||
ZDeleteDocumentResponseSchema,
|
||||
deleteDocumentMeta,
|
||||
} from './delete-document.types';
|
||||
import { ZGenericSuccessResponse } from './schema';
|
||||
|
||||
export const deleteDocumentRoute = authenticatedProcedure
|
||||
.meta(deleteDocumentMeta)
|
||||
.input(ZDeleteDocumentRequestSchema)
|
||||
.output(ZDeleteDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
await deleteDocument({
|
||||
id: documentId,
|
||||
userId,
|
||||
teamId,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
});
|
||||
@ -0,0 +1,22 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
import { ZSuccessResponseSchema } from './schema';
|
||||
|
||||
export const deleteDocumentMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/delete',
|
||||
summary: 'Delete document',
|
||||
tags: ['Document'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZDeleteDocumentRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDeleteDocumentResponseSchema = ZSuccessResponseSchema;
|
||||
|
||||
export type TDeleteDocumentRequest = z.infer<typeof ZDeleteDocumentRequestSchema>;
|
||||
export type TDeleteDocumentResponse = z.infer<typeof ZDeleteDocumentResponseSchema>;
|
||||
50
packages/trpc/server/document-router/distribute-document.ts
Normal file
50
packages/trpc/server/document-router/distribute-document.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDistributeDocumentRequestSchema,
|
||||
ZDistributeDocumentResponseSchema,
|
||||
distributeDocumentMeta,
|
||||
} from './distribute-document.types';
|
||||
|
||||
export const distributeDocumentRoute = authenticatedProcedure
|
||||
.meta(distributeDocumentMeta)
|
||||
.input(ZDistributeDocumentRequestSchema)
|
||||
.output(ZDistributeDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, meta = {} } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
if (Object.values(meta).length > 0) {
|
||||
await upsertDocumentMeta({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
subject: meta.subject,
|
||||
message: meta.message,
|
||||
dateFormat: meta.dateFormat,
|
||||
timezone: meta.timezone,
|
||||
redirectUrl: meta.redirectUrl,
|
||||
distributionMethod: meta.distributionMethod,
|
||||
emailSettings: meta.emailSettings,
|
||||
language: meta.language,
|
||||
emailId: meta.emailId,
|
||||
emailReplyTo: meta.emailReplyTo,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}
|
||||
|
||||
return await sendDocument({
|
||||
userId: ctx.user.id,
|
||||
documentId,
|
||||
teamId,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,48 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentLiteSchema } from '@documenso/lib/types/document';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
ZDocumentMetaLanguageSchema,
|
||||
ZDocumentMetaMessageSchema,
|
||||
ZDocumentMetaRedirectUrlSchema,
|
||||
ZDocumentMetaSubjectSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
} from './schema';
|
||||
|
||||
export const distributeDocumentMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/distribute',
|
||||
summary: 'Distribute document',
|
||||
description: 'Send the document out to recipients based on your distribution method',
|
||||
tags: ['Document'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZDistributeDocumentRequestSchema = z.object({
|
||||
documentId: z.number().describe('The ID of the document to send.'),
|
||||
meta: z
|
||||
.object({
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
message: ZDocumentMetaMessageSchema.optional(),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
emailId: z.string().nullish(),
|
||||
emailReplyTo: z.string().email().nullish(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const ZDistributeDocumentResponseSchema = ZDocumentLiteSchema;
|
||||
|
||||
export type TDistributeDocumentRequest = z.infer<typeof ZDistributeDocumentRequestSchema>;
|
||||
export type TDistributeDocumentResponse = z.infer<typeof ZDistributeDocumentResponseSchema>;
|
||||
@ -0,0 +1,47 @@
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt';
|
||||
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDownloadDocumentAuditLogsRequestSchema,
|
||||
ZDownloadDocumentAuditLogsResponseSchema,
|
||||
} from './download-document-audit-logs.types';
|
||||
|
||||
export const downloadDocumentAuditLogsRoute = authenticatedProcedure
|
||||
.input(ZDownloadDocumentAuditLogsRequestSchema)
|
||||
.output(ZDownloadDocumentAuditLogsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
}).catch(() => null);
|
||||
|
||||
if (!document || (teamId && document.teamId !== teamId)) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You do not have access to this document.',
|
||||
});
|
||||
}
|
||||
|
||||
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}`,
|
||||
};
|
||||
});
|
||||
@ -0,0 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDownloadDocumentAuditLogsRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDownloadDocumentAuditLogsResponseSchema = z.object({
|
||||
url: z.string(),
|
||||
});
|
||||
|
||||
export type TDownloadDocumentAuditLogsRequest = z.infer<
|
||||
typeof ZDownloadDocumentAuditLogsRequestSchema
|
||||
>;
|
||||
export type TDownloadDocumentAuditLogsResponse = z.infer<
|
||||
typeof ZDownloadDocumentAuditLogsResponseSchema
|
||||
>;
|
||||
@ -0,0 +1,46 @@
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt';
|
||||
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDownloadDocumentCertificateRequestSchema,
|
||||
ZDownloadDocumentCertificateResponseSchema,
|
||||
} from './download-document-certificate.types';
|
||||
|
||||
export const downloadDocumentCertificateRoute = authenticatedProcedure
|
||||
.input(ZDownloadDocumentCertificateRequestSchema)
|
||||
.output(ZDownloadDocumentCertificateResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
});
|
||||
|
||||
if (!isDocumentCompleted(document.status)) {
|
||||
throw new AppError('DOCUMENT_NOT_COMPLETE');
|
||||
}
|
||||
|
||||
const encrypted = encryptSecondaryData({
|
||||
data: document.id.toString(),
|
||||
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
|
||||
});
|
||||
|
||||
return {
|
||||
url: `${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encrypted}`,
|
||||
};
|
||||
});
|
||||
@ -0,0 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDownloadDocumentCertificateRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDownloadDocumentCertificateResponseSchema = z.object({
|
||||
url: z.string(),
|
||||
});
|
||||
|
||||
export type TDownloadDocumentCertificateRequest = z.infer<
|
||||
typeof ZDownloadDocumentCertificateRequestSchema
|
||||
>;
|
||||
export type TDownloadDocumentCertificateResponse = z.infer<
|
||||
typeof ZDownloadDocumentCertificateResponseSchema
|
||||
>;
|
||||
@ -6,18 +6,14 @@ import { getPresignGetUrl } from '@documenso/lib/universal/upload/server-actions
|
||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZDownloadDocumentRequestSchema, ZDownloadDocumentResponseSchema } from './schema';
|
||||
import {
|
||||
ZDownloadDocumentRequestSchema,
|
||||
ZDownloadDocumentResponseSchema,
|
||||
downloadDocumentMeta,
|
||||
} from './download-document.types';
|
||||
|
||||
export const downloadDocumentRoute = authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
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',
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.meta(downloadDocumentMeta)
|
||||
.input(ZDownloadDocumentRequestSchema)
|
||||
.output(ZDownloadDocumentResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const downloadDocumentMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
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',
|
||||
tags: ['Document'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZDownloadDocumentRequestSchema = z.object({
|
||||
documentId: z.number().describe('The ID of the document to download.'),
|
||||
version: z
|
||||
.enum(['original', 'signed'])
|
||||
.describe(
|
||||
'The version of the document to download. "signed" returns the completed document with signatures, "original" returns the original uploaded document.',
|
||||
)
|
||||
.default('signed'),
|
||||
});
|
||||
|
||||
export const ZDownloadDocumentResponseSchema = z.object({
|
||||
downloadUrl: z.string().describe('Pre-signed URL for downloading the PDF file'),
|
||||
filename: z.string().describe('The filename of the PDF file'),
|
||||
contentType: z.string().describe('MIME type of the file'),
|
||||
});
|
||||
|
||||
export type TDownloadDocumentRequest = z.infer<typeof ZDownloadDocumentRequestSchema>;
|
||||
export type TDownloadDocumentResponse = z.infer<typeof ZDownloadDocumentResponseSchema>;
|
||||
29
packages/trpc/server/document-router/duplicate-document.ts
Normal file
29
packages/trpc/server/document-router/duplicate-document.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { duplicateDocument } from '@documenso/lib/server-only/document/duplicate-document-by-id';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDuplicateDocumentRequestSchema,
|
||||
ZDuplicateDocumentResponseSchema,
|
||||
duplicateDocumentMeta,
|
||||
} from './duplicate-document.types';
|
||||
|
||||
export const duplicateDocumentRoute = authenticatedProcedure
|
||||
.meta(duplicateDocumentMeta)
|
||||
.input(ZDuplicateDocumentRequestSchema)
|
||||
.output(ZDuplicateDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await duplicateDocument({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,23 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const duplicateDocumentMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/duplicate',
|
||||
summary: 'Duplicate document',
|
||||
tags: ['Document'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZDuplicateDocumentRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDuplicateDocumentResponseSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export type TDuplicateDocumentRequest = z.infer<typeof ZDuplicateDocumentRequestSchema>;
|
||||
export type TDuplicateDocumentResponse = z.infer<typeof ZDuplicateDocumentResponseSchema>;
|
||||
@ -0,0 +1,41 @@
|
||||
import { findDocumentAuditLogs } from '@documenso/lib/server-only/document/find-document-audit-logs';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZFindDocumentAuditLogsRequestSchema,
|
||||
ZFindDocumentAuditLogsResponseSchema,
|
||||
} from './find-document-audit-logs.types';
|
||||
|
||||
export const findDocumentAuditLogsRoute = authenticatedProcedure
|
||||
.input(ZFindDocumentAuditLogsRequestSchema)
|
||||
.output(ZFindDocumentAuditLogsResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
|
||||
const {
|
||||
page,
|
||||
perPage,
|
||||
documentId,
|
||||
cursor,
|
||||
filterForRecentActivity,
|
||||
orderByColumn,
|
||||
orderByDirection,
|
||||
} = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await findDocumentAuditLogs({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
page,
|
||||
perPage,
|
||||
documentId,
|
||||
cursor,
|
||||
filterForRecentActivity,
|
||||
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentAuditLogSchema } from '@documenso/lib/types/document-audit-logs';
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
|
||||
export const ZFindDocumentAuditLogsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
documentId: z.number().min(1),
|
||||
cursor: z.string().optional(),
|
||||
filterForRecentActivity: z.boolean().optional(),
|
||||
orderByColumn: z.enum(['createdAt', 'type']).optional(),
|
||||
orderByDirection: z.enum(['asc', 'desc']).default('desc'),
|
||||
});
|
||||
|
||||
export const ZFindDocumentAuditLogsResponseSchema = ZFindResultResponse.extend({
|
||||
data: ZDocumentAuditLogSchema.array(),
|
||||
nextCursor: z.string().optional(),
|
||||
});
|
||||
|
||||
export type TFindDocumentAuditLogsRequest = z.infer<typeof ZFindDocumentAuditLogsRequestSchema>;
|
||||
export type TFindDocumentAuditLogsResponse = z.infer<typeof ZFindDocumentAuditLogsResponseSchema>;
|
||||
@ -0,0 +1,74 @@
|
||||
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
|
||||
import type { GetStatsInput } from '@documenso/lib/server-only/document/get-stats';
|
||||
import { getStats } from '@documenso/lib/server-only/document/get-stats';
|
||||
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZFindDocumentsInternalRequestSchema,
|
||||
ZFindDocumentsInternalResponseSchema,
|
||||
} from './find-documents-internal.types';
|
||||
|
||||
export const findDocumentsInternalRoute = authenticatedProcedure
|
||||
.input(ZFindDocumentsInternalRequestSchema)
|
||||
.output(ZFindDocumentsInternalResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
|
||||
const {
|
||||
query,
|
||||
templateId,
|
||||
page,
|
||||
perPage,
|
||||
orderByDirection,
|
||||
orderByColumn,
|
||||
source,
|
||||
status,
|
||||
period,
|
||||
senderIds,
|
||||
folderId,
|
||||
} = input;
|
||||
|
||||
const getStatOptions: GetStatsInput = {
|
||||
user,
|
||||
period,
|
||||
search: query,
|
||||
folderId,
|
||||
};
|
||||
|
||||
if (teamId) {
|
||||
const team = await getTeamById({ userId: user.id, teamId });
|
||||
|
||||
getStatOptions.team = {
|
||||
teamId: team.id,
|
||||
teamEmail: team.teamEmail?.email,
|
||||
senderIds,
|
||||
currentTeamMemberRole: team.currentTeamRole,
|
||||
currentUserEmail: user.email,
|
||||
userId: user.id,
|
||||
};
|
||||
}
|
||||
|
||||
const [stats, documents] = await Promise.all([
|
||||
getStats(getStatOptions),
|
||||
findDocuments({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
query,
|
||||
templateId,
|
||||
page,
|
||||
perPage,
|
||||
source,
|
||||
status,
|
||||
period,
|
||||
senderIds,
|
||||
folderId,
|
||||
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
...documents,
|
||||
stats,
|
||||
};
|
||||
});
|
||||
@ -0,0 +1,29 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentManySchema } from '@documenso/lib/types/document';
|
||||
import { ZFindResultResponse } from '@documenso/lib/types/search-params';
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
|
||||
import { ZFindDocumentsRequestSchema } from './find-documents.types';
|
||||
|
||||
export const ZFindDocumentsInternalRequestSchema = ZFindDocumentsRequestSchema.extend({
|
||||
period: z.enum(['7d', '14d', '30d']).optional(),
|
||||
senderIds: z.array(z.number()).optional(),
|
||||
status: z.nativeEnum(ExtendedDocumentStatus).optional(),
|
||||
folderId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ZFindDocumentsInternalResponseSchema = ZFindResultResponse.extend({
|
||||
data: ZDocumentManySchema.array(),
|
||||
stats: z.object({
|
||||
[ExtendedDocumentStatus.DRAFT]: z.number(),
|
||||
[ExtendedDocumentStatus.PENDING]: z.number(),
|
||||
[ExtendedDocumentStatus.COMPLETED]: z.number(),
|
||||
[ExtendedDocumentStatus.REJECTED]: z.number(),
|
||||
[ExtendedDocumentStatus.INBOX]: z.number(),
|
||||
[ExtendedDocumentStatus.ALL]: z.number(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type TFindDocumentsInternalRequest = z.infer<typeof ZFindDocumentsInternalRequestSchema>;
|
||||
export type TFindDocumentsInternalResponse = z.infer<typeof ZFindDocumentsInternalResponseSchema>;
|
||||
43
packages/trpc/server/document-router/find-documents.ts
Normal file
43
packages/trpc/server/document-router/find-documents.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZFindDocumentsMeta,
|
||||
ZFindDocumentsRequestSchema,
|
||||
ZFindDocumentsResponseSchema,
|
||||
} from './find-documents.types';
|
||||
|
||||
export const findDocumentsRoute = authenticatedProcedure
|
||||
.meta(ZFindDocumentsMeta)
|
||||
.input(ZFindDocumentsRequestSchema)
|
||||
.output(ZFindDocumentsResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
|
||||
const {
|
||||
query,
|
||||
templateId,
|
||||
page,
|
||||
perPage,
|
||||
orderByDirection,
|
||||
orderByColumn,
|
||||
source,
|
||||
status,
|
||||
folderId,
|
||||
} = input;
|
||||
|
||||
const documents = await findDocuments({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
query,
|
||||
source,
|
||||
status,
|
||||
page,
|
||||
perPage,
|
||||
folderId,
|
||||
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
||||
});
|
||||
|
||||
return documents;
|
||||
});
|
||||
42
packages/trpc/server/document-router/find-documents.types.ts
Normal file
42
packages/trpc/server/document-router/find-documents.types.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { DocumentSource, DocumentStatus } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentManySchema } from '@documenso/lib/types/document';
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const ZFindDocumentsMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/document',
|
||||
summary: 'Find documents',
|
||||
description: 'Find documents based on a search criteria',
|
||||
tags: ['Document'],
|
||||
},
|
||||
};
|
||||
|
||||
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(),
|
||||
folderId: z.string().describe('Filter documents by folder ID').optional(),
|
||||
orderByColumn: z.enum(['createdAt']).optional(),
|
||||
orderByDirection: z.enum(['asc', 'desc']).describe('').default('desc'),
|
||||
});
|
||||
|
||||
export const ZFindDocumentsResponseSchema = ZFindResultResponse.extend({
|
||||
data: ZDocumentManySchema.array(),
|
||||
});
|
||||
|
||||
export type TFindDocumentsRequest = z.infer<typeof ZFindDocumentsRequestSchema>;
|
||||
export type TFindDocumentsResponse = z.infer<typeof ZFindDocumentsResponseSchema>;
|
||||
@ -0,0 +1,43 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZGetDocumentByTokenRequestSchema,
|
||||
ZGetDocumentByTokenResponseSchema,
|
||||
} from './get-document-by-token.types';
|
||||
|
||||
export const getDocumentByTokenRoute = authenticatedProcedure
|
||||
.input(ZGetDocumentByTokenRequestSchema)
|
||||
.output(ZGetDocumentByTokenResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { token } = input;
|
||||
|
||||
const document = await prisma.document.findFirst({
|
||||
where: {
|
||||
recipients: {
|
||||
some: {
|
||||
token,
|
||||
email: ctx.user.email,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
documentData: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!document) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Document not found',
|
||||
});
|
||||
}
|
||||
|
||||
ctx.logger.info({
|
||||
documentId: document.id,
|
||||
});
|
||||
|
||||
return {
|
||||
documentData: document.documentData,
|
||||
};
|
||||
});
|
||||
@ -0,0 +1,14 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import DocumentDataSchema from '@documenso/prisma/generated/zod/modelSchema/DocumentDataSchema';
|
||||
|
||||
export const ZGetDocumentByTokenRequestSchema = z.object({
|
||||
token: z.string().min(1),
|
||||
});
|
||||
|
||||
export const ZGetDocumentByTokenResponseSchema = z.object({
|
||||
documentData: DocumentDataSchema,
|
||||
});
|
||||
|
||||
export type TGetDocumentByTokenRequest = z.infer<typeof ZGetDocumentByTokenRequestSchema>;
|
||||
export type TGetDocumentByTokenResponse = z.infer<typeof ZGetDocumentByTokenResponseSchema>;
|
||||
29
packages/trpc/server/document-router/get-document.ts
Normal file
29
packages/trpc/server/document-router/get-document.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZGetDocumentRequestSchema,
|
||||
ZGetDocumentResponseSchema,
|
||||
getDocumentMeta,
|
||||
} from './get-document.types';
|
||||
|
||||
export const getDocumentRoute = authenticatedProcedure
|
||||
.meta(getDocumentMeta)
|
||||
.input(ZGetDocumentRequestSchema)
|
||||
.output(ZGetDocumentResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getDocumentWithDetailsById({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
});
|
||||
});
|
||||
24
packages/trpc/server/document-router/get-document.types.ts
Normal file
24
packages/trpc/server/document-router/get-document.types.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentSchema } from '@documenso/lib/types/document';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const getDocumentMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/document/{documentId}',
|
||||
summary: 'Get document',
|
||||
description: 'Returns a document given an ID',
|
||||
tags: ['Document'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZGetDocumentRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZGetDocumentResponseSchema = ZDocumentSchema;
|
||||
|
||||
export type TGetDocumentRequest = z.infer<typeof ZGetDocumentRequestSchema>;
|
||||
export type TGetDocumentResponse = z.infer<typeof ZGetDocumentResponseSchema>;
|
||||
@ -0,0 +1,35 @@
|
||||
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZRedistributeDocumentRequestSchema,
|
||||
ZRedistributeDocumentResponseSchema,
|
||||
redistributeDocumentMeta,
|
||||
} from './redistribute-document.types';
|
||||
import { ZGenericSuccessResponse } from './schema';
|
||||
|
||||
export const redistributeDocumentRoute = authenticatedProcedure
|
||||
.meta(redistributeDocumentMeta)
|
||||
.input(ZRedistributeDocumentRequestSchema)
|
||||
.output(ZRedistributeDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, recipients } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
recipients,
|
||||
},
|
||||
});
|
||||
|
||||
await resendDocument({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
recipients,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
});
|
||||
@ -0,0 +1,28 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
import { ZSuccessResponseSchema } from './schema';
|
||||
|
||||
export const redistributeDocumentMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
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',
|
||||
tags: ['Document'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZRedistributeDocumentRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
recipients: z
|
||||
.array(z.number())
|
||||
.min(1)
|
||||
.describe('The IDs of the recipients to redistribute the document to.'),
|
||||
});
|
||||
|
||||
export const ZRedistributeDocumentResponseSchema = ZSuccessResponseSchema;
|
||||
|
||||
export type TRedistributeDocumentRequest = z.infer<typeof ZRedistributeDocumentRequestSchema>;
|
||||
export type TRedistributeDocumentResponse = z.infer<typeof ZRedistributeDocumentResponseSchema>;
|
||||
@ -1,691 +1,49 @@
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt';
|
||||
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
|
||||
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';
|
||||
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';
|
||||
import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
|
||||
import type { GetStatsInput } from '@documenso/lib/server-only/document/get-stats';
|
||||
import { getStats } from '@documenso/lib/server-only/document/get-stats';
|
||||
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
||||
import { searchDocumentsWithKeyword } from '@documenso/lib/server-only/document/search-documents-with-keyword';
|
||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
||||
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||
|
||||
import { authenticatedProcedure, procedure, router } from '../trpc';
|
||||
import { router } from '../trpc';
|
||||
import { createDocumentRoute } from './create-document';
|
||||
import { createDocumentTemporaryRoute } from './create-document-temporary';
|
||||
import { deleteDocumentRoute } from './delete-document';
|
||||
import { distributeDocumentRoute } from './distribute-document';
|
||||
import { downloadDocumentRoute } from './download-document';
|
||||
import { downloadDocumentAuditLogsRoute } from './download-document-audit-logs';
|
||||
import { downloadDocumentCertificateRoute } from './download-document-certificate';
|
||||
import { duplicateDocumentRoute } from './duplicate-document';
|
||||
import { findDocumentAuditLogsRoute } from './find-document-audit-logs';
|
||||
import { findDocumentsRoute } from './find-documents';
|
||||
import { findDocumentsInternalRoute } from './find-documents-internal';
|
||||
import { findInboxRoute } from './find-inbox';
|
||||
import { getDocumentRoute } from './get-document';
|
||||
import { getDocumentByTokenRoute } from './get-document-by-token';
|
||||
import { getInboxCountRoute } from './get-inbox-count';
|
||||
import {
|
||||
ZCreateDocumentRequestSchema,
|
||||
ZCreateDocumentV2RequestSchema,
|
||||
ZCreateDocumentV2ResponseSchema,
|
||||
ZDeleteDocumentMutationSchema,
|
||||
ZDistributeDocumentRequestSchema,
|
||||
ZDistributeDocumentResponseSchema,
|
||||
ZDownloadAuditLogsMutationSchema,
|
||||
ZDownloadCertificateMutationSchema,
|
||||
ZDuplicateDocumentRequestSchema,
|
||||
ZDuplicateDocumentResponseSchema,
|
||||
ZFindDocumentAuditLogsQuerySchema,
|
||||
ZFindDocumentsInternalRequestSchema,
|
||||
ZFindDocumentsInternalResponseSchema,
|
||||
ZFindDocumentsRequestSchema,
|
||||
ZFindDocumentsResponseSchema,
|
||||
ZGenericSuccessResponse,
|
||||
ZGetDocumentByIdQuerySchema,
|
||||
ZGetDocumentByTokenQuerySchema,
|
||||
ZGetDocumentWithDetailsByIdRequestSchema,
|
||||
ZGetDocumentWithDetailsByIdResponseSchema,
|
||||
ZResendDocumentMutationSchema,
|
||||
ZSearchDocumentsMutationSchema,
|
||||
ZSetSigningOrderForDocumentMutationSchema,
|
||||
ZSuccessResponseSchema,
|
||||
} from './schema';
|
||||
import { redistributeDocumentRoute } from './redistribute-document';
|
||||
import { searchDocumentRoute } from './search-document';
|
||||
import { updateDocumentRoute } from './update-document';
|
||||
|
||||
export const documentRouter = router({
|
||||
inbox: {
|
||||
get: getDocumentRoute,
|
||||
find: findDocumentsRoute,
|
||||
create: createDocumentRoute,
|
||||
update: updateDocumentRoute,
|
||||
delete: deleteDocumentRoute,
|
||||
duplicate: duplicateDocumentRoute,
|
||||
downloadCertificate: downloadDocumentCertificateRoute,
|
||||
distribute: distributeDocumentRoute,
|
||||
redistribute: redistributeDocumentRoute,
|
||||
search: searchDocumentRoute,
|
||||
|
||||
// Temporary v2 beta routes to be removed once V2 is fully released.
|
||||
download: downloadDocumentRoute,
|
||||
createDocumentTemporary: createDocumentTemporaryRoute,
|
||||
|
||||
// Internal document routes for custom frontend requests.
|
||||
getDocumentByToken: getDocumentByTokenRoute,
|
||||
findDocumentsInternal: findDocumentsInternalRoute,
|
||||
|
||||
auditLog: {
|
||||
find: findDocumentAuditLogsRoute,
|
||||
download: downloadDocumentAuditLogsRoute,
|
||||
},
|
||||
inbox: router({
|
||||
find: findInboxRoute,
|
||||
getCount: getInboxCountRoute,
|
||||
},
|
||||
updateDocument: updateDocumentRoute,
|
||||
downloadDocument: downloadDocumentRoute,
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
getDocumentById: authenticatedProcedure
|
||||
.input(ZGetDocumentByIdQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getDocumentById({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
getDocumentByToken: procedure
|
||||
.input(ZGetDocumentByTokenQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { token } = input;
|
||||
|
||||
return await getDocumentAndSenderByToken({
|
||||
token,
|
||||
userId: ctx.user?.id,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
findDocuments: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/document',
|
||||
summary: 'Find documents',
|
||||
description: 'Find documents based on a search criteria',
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZFindDocumentsRequestSchema)
|
||||
.output(ZFindDocumentsResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
|
||||
const {
|
||||
query,
|
||||
templateId,
|
||||
page,
|
||||
perPage,
|
||||
orderByDirection,
|
||||
orderByColumn,
|
||||
source,
|
||||
status,
|
||||
folderId,
|
||||
} = input;
|
||||
|
||||
const documents = await findDocuments({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
query,
|
||||
source,
|
||||
status,
|
||||
page,
|
||||
perPage,
|
||||
folderId,
|
||||
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
||||
});
|
||||
|
||||
return documents;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Internal endpoint for /documents page to additionally return getStats.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
findDocumentsInternal: authenticatedProcedure
|
||||
.input(ZFindDocumentsInternalRequestSchema)
|
||||
.output(ZFindDocumentsInternalResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
|
||||
const {
|
||||
query,
|
||||
templateId,
|
||||
page,
|
||||
perPage,
|
||||
orderByDirection,
|
||||
orderByColumn,
|
||||
source,
|
||||
status,
|
||||
period,
|
||||
senderIds,
|
||||
folderId,
|
||||
} = input;
|
||||
|
||||
const getStatOptions: GetStatsInput = {
|
||||
user,
|
||||
period,
|
||||
search: query,
|
||||
folderId,
|
||||
};
|
||||
|
||||
if (teamId) {
|
||||
const team = await getTeamById({ userId: user.id, teamId });
|
||||
|
||||
getStatOptions.team = {
|
||||
teamId: team.id,
|
||||
teamEmail: team.teamEmail?.email,
|
||||
senderIds,
|
||||
currentTeamMemberRole: team.currentTeamRole,
|
||||
currentUserEmail: user.email,
|
||||
userId: user.id,
|
||||
};
|
||||
}
|
||||
|
||||
const [stats, documents] = await Promise.all([
|
||||
getStats(getStatOptions),
|
||||
findDocuments({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
query,
|
||||
templateId,
|
||||
page,
|
||||
perPage,
|
||||
source,
|
||||
status,
|
||||
period,
|
||||
senderIds,
|
||||
folderId,
|
||||
orderBy: orderByColumn
|
||||
? { column: orderByColumn, direction: orderByDirection }
|
||||
: undefined,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
...documents,
|
||||
stats,
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* Todo: Refactor to getDocumentById.
|
||||
*/
|
||||
getDocumentWithDetailsById: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/document/{documentId}',
|
||||
summary: 'Get document',
|
||||
description: 'Returns a document given an ID',
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZGetDocumentWithDetailsByIdRequestSchema)
|
||||
.output(ZGetDocumentWithDetailsByIdResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
const { documentId, folderId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getDocumentWithDetailsById({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
folderId,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* 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, user } = ctx;
|
||||
|
||||
const {
|
||||
title,
|
||||
externalId,
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
recipients,
|
||||
meta,
|
||||
folderId,
|
||||
} = input;
|
||||
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId });
|
||||
|
||||
if (remaining.documents <= 0) {
|
||||
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
|
||||
message: 'You have reached your document limit for this month. Please upgrade your plan.',
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const fileName = title.endsWith('.pdf') ? title : `${title}.pdf`;
|
||||
|
||||
const { url, key } = await getPresignPostUrl(fileName, 'application/pdf');
|
||||
|
||||
const documentData = await createDocumentData({
|
||||
data: key,
|
||||
type: DocumentDataType.S3_PATH,
|
||||
});
|
||||
|
||||
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,
|
||||
folderId,
|
||||
},
|
||||
meta,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return {
|
||||
document: createdDocument,
|
||||
folder: createdDocument.folder, // Todo: Remove this prior to api-v2 release.
|
||||
uploadUrl: url,
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
* Wait until RR7 so we can passthrough documents.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
createDocument: authenticatedProcedure
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/document/create',
|
||||
// summary: 'Create document',
|
||||
// tags: ['Document'],
|
||||
// },
|
||||
// })
|
||||
.input(ZCreateDocumentRequestSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
const { title, documentDataId, timezone, folderId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId });
|
||||
|
||||
if (remaining.documents <= 0) {
|
||||
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
|
||||
message: 'You have reached your document limit for this month. Please upgrade your plan.',
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return await createDocument({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
title,
|
||||
documentDataId,
|
||||
normalizePdf: true,
|
||||
userTimezone: timezone,
|
||||
requestMetadata: ctx.metadata,
|
||||
folderId,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
deleteDocument: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/delete',
|
||||
summary: 'Delete document',
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZDeleteDocumentMutationSchema)
|
||||
.output(ZSuccessResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
await deleteDocument({
|
||||
id: documentId,
|
||||
userId,
|
||||
teamId,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* Todo: Remove and use `updateDocument` endpoint instead.
|
||||
*/
|
||||
setSigningOrderForDocument: authenticatedProcedure
|
||||
.input(ZSetSigningOrderForDocumentMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, signingOrder } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
signingOrder,
|
||||
},
|
||||
});
|
||||
|
||||
return await upsertDocumentMeta({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
signingOrder,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* Todo: Refactor to distributeDocument.
|
||||
* Todo: Rework before releasing API.
|
||||
*/
|
||||
sendDocument: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/distribute',
|
||||
summary: 'Distribute document',
|
||||
description: 'Send the document out to recipients based on your distribution method',
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZDistributeDocumentRequestSchema)
|
||||
.output(ZDistributeDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, meta = {} } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
if (Object.values(meta).length > 0) {
|
||||
await upsertDocumentMeta({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
subject: meta.subject,
|
||||
message: meta.message,
|
||||
dateFormat: meta.dateFormat,
|
||||
timezone: meta.timezone,
|
||||
redirectUrl: meta.redirectUrl,
|
||||
distributionMethod: meta.distributionMethod,
|
||||
emailSettings: meta.emailSettings,
|
||||
language: meta.language,
|
||||
emailId: meta.emailId,
|
||||
emailReplyTo: meta.emailReplyTo,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}
|
||||
|
||||
return await sendDocument({
|
||||
userId: ctx.user.id,
|
||||
documentId,
|
||||
teamId,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* Todo: Refactor to redistributeDocument.
|
||||
*/
|
||||
resendDocument: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
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',
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZResendDocumentMutationSchema)
|
||||
.output(ZSuccessResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, recipients } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
recipients,
|
||||
},
|
||||
});
|
||||
|
||||
await resendDocument({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
recipients,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
duplicateDocument: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/duplicate',
|
||||
summary: 'Duplicate document',
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZDuplicateDocumentRequestSchema)
|
||||
.output(ZDuplicateDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await duplicateDocument({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
searchDocuments: authenticatedProcedure
|
||||
.input(ZSearchDocumentsMutationSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { query } = input;
|
||||
|
||||
const documents = await searchDocumentsWithKeyword({
|
||||
query,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
|
||||
return documents;
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
findDocumentAuditLogs: authenticatedProcedure
|
||||
.input(ZFindDocumentAuditLogsQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
|
||||
const {
|
||||
page,
|
||||
perPage,
|
||||
documentId,
|
||||
cursor,
|
||||
filterForRecentActivity,
|
||||
orderByColumn,
|
||||
orderByDirection,
|
||||
} = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await findDocumentAuditLogs({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
page,
|
||||
perPage,
|
||||
documentId,
|
||||
cursor,
|
||||
filterForRecentActivity,
|
||||
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
downloadAuditLogs: authenticatedProcedure
|
||||
.input(ZDownloadAuditLogsMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
}).catch(() => null);
|
||||
|
||||
if (!document || (teamId && document.teamId !== teamId)) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You do not have access to this document.',
|
||||
});
|
||||
}
|
||||
|
||||
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}`,
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
downloadCertificate: authenticatedProcedure
|
||||
.input(ZDownloadCertificateMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
});
|
||||
|
||||
if (!isDocumentCompleted(document.status)) {
|
||||
throw new AppError('DOCUMENT_NOT_COMPLETE');
|
||||
}
|
||||
|
||||
const encrypted = encryptSecondaryData({
|
||||
data: document.id.toString(),
|
||||
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
|
||||
});
|
||||
|
||||
return {
|
||||
url: `${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encrypted}`,
|
||||
};
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -1,39 +1,9 @@
|
||||
import {
|
||||
DocumentDistributionMethod,
|
||||
DocumentSigningOrder,
|
||||
DocumentSource,
|
||||
DocumentStatus,
|
||||
DocumentVisibility,
|
||||
FieldType,
|
||||
} from '@prisma/client';
|
||||
import { DocumentDistributionMethod, DocumentVisibility } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { VALID_DATE_FORMAT_VALUES } from '@documenso/lib/constants/date-formats';
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import {
|
||||
ZDocumentLiteSchema,
|
||||
ZDocumentManySchema,
|
||||
ZDocumentSchema,
|
||||
} from '@documenso/lib/types/document';
|
||||
import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';
|
||||
import {
|
||||
ZFieldHeightSchema,
|
||||
ZFieldPageNumberSchema,
|
||||
ZFieldPageXSchema,
|
||||
ZFieldPageYSchema,
|
||||
ZFieldWidthSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
|
||||
import { ZCreateRecipientSchema } from '../recipient-router/schema';
|
||||
|
||||
/**
|
||||
* Required for empty responses since we currently can't 201 requests for our openapi setup.
|
||||
@ -116,258 +86,3 @@ export const ZDocumentMetaDrawSignatureEnabledSchema = z
|
||||
export const ZDocumentMetaUploadSignatureEnabledSchema = z
|
||||
.boolean()
|
||||
.describe('Whether to allow recipients to sign using an uploaded signature.');
|
||||
|
||||
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(),
|
||||
folderId: z.string().describe('Filter documents by folder ID').optional(),
|
||||
orderByColumn: z.enum(['createdAt']).optional(),
|
||||
orderByDirection: z.enum(['asc', 'desc']).describe('').default('desc'),
|
||||
});
|
||||
|
||||
export const ZFindDocumentsResponseSchema = ZFindResultResponse.extend({
|
||||
data: ZDocumentManySchema.array(),
|
||||
});
|
||||
|
||||
export type TFindDocumentsResponse = z.infer<typeof ZFindDocumentsResponseSchema>;
|
||||
|
||||
export const ZFindDocumentsInternalRequestSchema = ZFindDocumentsRequestSchema.extend({
|
||||
period: z.enum(['7d', '14d', '30d']).optional(),
|
||||
senderIds: z.array(z.number()).optional(),
|
||||
status: z.nativeEnum(ExtendedDocumentStatus).optional(),
|
||||
folderId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ZFindDocumentsInternalResponseSchema = ZFindResultResponse.extend({
|
||||
data: ZDocumentManySchema.array(),
|
||||
stats: z.object({
|
||||
[ExtendedDocumentStatus.DRAFT]: z.number(),
|
||||
[ExtendedDocumentStatus.PENDING]: z.number(),
|
||||
[ExtendedDocumentStatus.COMPLETED]: z.number(),
|
||||
[ExtendedDocumentStatus.REJECTED]: z.number(),
|
||||
[ExtendedDocumentStatus.INBOX]: z.number(),
|
||||
[ExtendedDocumentStatus.ALL]: z.number(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type TFindDocumentsInternalResponse = z.infer<typeof ZFindDocumentsInternalResponseSchema>;
|
||||
|
||||
export const ZFindDocumentAuditLogsQuerySchema = ZFindSearchParamsSchema.extend({
|
||||
documentId: z.number().min(1),
|
||||
cursor: z.string().optional(),
|
||||
filterForRecentActivity: z.boolean().optional(),
|
||||
orderByColumn: z.enum(['createdAt', 'type']).optional(),
|
||||
orderByDirection: z.enum(['asc', 'desc']).default('desc'),
|
||||
});
|
||||
|
||||
export const ZGetDocumentByIdQuerySchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDuplicateDocumentRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDuplicateDocumentResponseSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZGetDocumentByTokenQuerySchema = z.object({
|
||||
token: z.string().min(1),
|
||||
});
|
||||
|
||||
export type TGetDocumentByTokenQuerySchema = z.infer<typeof ZGetDocumentByTokenQuerySchema>;
|
||||
|
||||
export const ZGetDocumentWithDetailsByIdRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
folderId: z.string().describe('Filter documents by folder ID').optional(),
|
||||
});
|
||||
|
||||
export const ZGetDocumentWithDetailsByIdResponseSchema = ZDocumentSchema;
|
||||
|
||||
export const ZCreateDocumentRequestSchema = z.object({
|
||||
title: ZDocumentTitleSchema,
|
||||
documentDataId: z.string().min(1),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
folderId: z.string().describe('The ID of the folder to create the document in').optional(),
|
||||
});
|
||||
|
||||
export const ZCreateDocumentV2RequestSchema = z.object({
|
||||
title: ZDocumentTitleSchema,
|
||||
externalId: ZDocumentExternalIdSchema.optional(),
|
||||
visibility: ZDocumentVisibilitySchema.optional(),
|
||||
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional(),
|
||||
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional(),
|
||||
formValues: ZDocumentFormValuesSchema.optional(),
|
||||
folderId: z
|
||||
.string()
|
||||
.describe(
|
||||
'The ID of the folder to create the document in. If not provided, the document will be created in the root folder.',
|
||||
)
|
||||
.optional(),
|
||||
recipients: z
|
||||
.array(
|
||||
ZCreateRecipientSchema.extend({
|
||||
fields: ZFieldAndMetaSchema.and(
|
||||
z.object({
|
||||
pageNumber: ZFieldPageNumberSchema,
|
||||
pageX: ZFieldPageXSchema,
|
||||
pageY: ZFieldPageYSchema,
|
||||
width: ZFieldWidthSchema,
|
||||
height: ZFieldHeightSchema,
|
||||
}),
|
||||
)
|
||||
.array()
|
||||
.optional(),
|
||||
}),
|
||||
)
|
||||
.refine(
|
||||
(recipients) => {
|
||||
const emails = recipients.map((recipient) => recipient.email);
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
},
|
||||
{ message: 'Recipients must have unique emails' },
|
||||
)
|
||||
.optional(),
|
||||
meta: z
|
||||
.object({
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
message: ZDocumentMetaMessageSchema.optional(),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
|
||||
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type TCreateDocumentV2Request = z.infer<typeof ZCreateDocumentV2RequestSchema>;
|
||||
|
||||
export const ZCreateDocumentV2ResponseSchema = z.object({
|
||||
document: ZDocumentSchema,
|
||||
uploadUrl: z
|
||||
.string()
|
||||
.describe(
|
||||
'The URL to upload the document PDF to. Use a PUT request with the file via form-data',
|
||||
),
|
||||
});
|
||||
|
||||
export const ZSetFieldsForDocumentMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
fields: z.array(
|
||||
z.object({
|
||||
id: z.number().nullish(),
|
||||
type: z.nativeEnum(FieldType),
|
||||
signerEmail: z.string().min(1),
|
||||
pageNumber: z.number().min(1),
|
||||
pageX: z.number().min(0),
|
||||
pageY: z.number().min(0),
|
||||
pageWidth: z.number().min(0),
|
||||
pageHeight: z.number().min(0),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export type TSetFieldsForDocumentMutationSchema = z.infer<
|
||||
typeof ZSetFieldsForDocumentMutationSchema
|
||||
>;
|
||||
|
||||
export const ZDistributeDocumentRequestSchema = z.object({
|
||||
documentId: z.number().describe('The ID of the document to send.'),
|
||||
meta: z
|
||||
.object({
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
message: ZDocumentMetaMessageSchema.optional(),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
emailId: z.string().nullish(),
|
||||
emailReplyTo: z.string().email().nullish(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const ZDistributeDocumentResponseSchema = ZDocumentLiteSchema;
|
||||
|
||||
export const ZSetPasswordForDocumentMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
password: z.string(),
|
||||
});
|
||||
|
||||
export type TSetPasswordForDocumentMutationSchema = z.infer<
|
||||
typeof ZSetPasswordForDocumentMutationSchema
|
||||
>;
|
||||
|
||||
export const ZSetSigningOrderForDocumentMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder),
|
||||
});
|
||||
|
||||
export type TSetSigningOrderForDocumentMutationSchema = z.infer<
|
||||
typeof ZSetSigningOrderForDocumentMutationSchema
|
||||
>;
|
||||
|
||||
export const ZResendDocumentMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
recipients: z
|
||||
.array(z.number())
|
||||
.min(1)
|
||||
.describe('The IDs of the recipients to redistribute the document to.'),
|
||||
});
|
||||
|
||||
export const ZDeleteDocumentMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export type TDeleteDocumentMutationSchema = z.infer<typeof ZDeleteDocumentMutationSchema>;
|
||||
|
||||
export const ZSearchDocumentsMutationSchema = z.object({
|
||||
query: z.string(),
|
||||
});
|
||||
|
||||
export const ZDownloadAuditLogsMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDownloadCertificateMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDownloadDocumentRequestSchema = z.object({
|
||||
documentId: z.number().describe('The ID of the document to download.'),
|
||||
version: z
|
||||
.enum(['original', 'signed'])
|
||||
.describe(
|
||||
'The version of the document to download. "signed" returns the completed document with signatures, "original" returns the original uploaded document.',
|
||||
)
|
||||
.default('signed'),
|
||||
});
|
||||
|
||||
export const ZDownloadDocumentResponseSchema = z.object({
|
||||
downloadUrl: z.string().describe('Pre-signed URL for downloading the PDF file'),
|
||||
filename: z.string().describe('The filename of the PDF file'),
|
||||
contentType: z.string().describe('MIME type of the file'),
|
||||
});
|
||||
|
||||
export type TDownloadDocumentRequest = z.infer<typeof ZDownloadDocumentRequestSchema>;
|
||||
export type TDownloadDocumentResponse = z.infer<typeof ZDownloadDocumentResponseSchema>;
|
||||
|
||||
21
packages/trpc/server/document-router/search-document.ts
Normal file
21
packages/trpc/server/document-router/search-document.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { searchDocumentsWithKeyword } from '@documenso/lib/server-only/document/search-documents-with-keyword';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZSearchDocumentRequestSchema,
|
||||
ZSearchDocumentResponseSchema,
|
||||
} from './search-document.types';
|
||||
|
||||
export const searchDocumentRoute = authenticatedProcedure
|
||||
.input(ZSearchDocumentRequestSchema)
|
||||
.output(ZSearchDocumentResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { query } = input;
|
||||
|
||||
const documents = await searchDocumentsWithKeyword({
|
||||
query,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
|
||||
return documents;
|
||||
});
|
||||
@ -0,0 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZSearchDocumentRequestSchema = z.object({
|
||||
query: z.string(),
|
||||
});
|
||||
|
||||
export const ZSearchDocumentResponseSchema = z
|
||||
.object({
|
||||
title: z.string(),
|
||||
path: z.string(),
|
||||
value: z.string(),
|
||||
})
|
||||
.array();
|
||||
|
||||
export type TSearchDocumentRequest = z.infer<typeof ZSearchDocumentRequestSchema>;
|
||||
export type TSearchDocumentResponse = z.infer<typeof ZSearchDocumentResponseSchema>;
|
||||
@ -40,7 +40,13 @@ export const createOrganisationGroupRoute = authenticatedProcedure
|
||||
groups: true,
|
||||
members: {
|
||||
include: {
|
||||
user: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user