mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 16:23:06 +10:00
feat: add template and field endpoints (#1572)
This commit is contained in:
@ -17,7 +17,6 @@ import { findPasskeys } from '@documenso/lib/server-only/auth/find-passkeys';
|
||||
import { compareSync } from '@documenso/lib/server-only/auth/hash';
|
||||
import { updatePasskey } from '@documenso/lib/server-only/auth/update-passkey';
|
||||
import { createUser } from '@documenso/lib/server-only/user/create-user';
|
||||
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
|
||||
import { authenticatedProcedure, procedure, router } from '../trpc';
|
||||
import {
|
||||
@ -89,7 +88,7 @@ export const authRouter = router({
|
||||
userId: ctx.user.id,
|
||||
verificationResponse,
|
||||
passkeyName: input.passkeyName,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
}),
|
||||
|
||||
@ -132,7 +131,7 @@ export const authRouter = router({
|
||||
await deletePasskey({
|
||||
userId: ctx.user.id,
|
||||
passkeyId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
}),
|
||||
|
||||
@ -158,7 +157,7 @@ export const authRouter = router({
|
||||
userId: ctx.user.id,
|
||||
passkeyId,
|
||||
name,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@ -1,15 +1,37 @@
|
||||
import type { CreateNextContextOptions } from '@trpc/server/adapters/next';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { getServerSession } from '@documenso/lib/next-auth/get-server-session';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
|
||||
export const createTrpcContext = async ({ req, res }: CreateNextContextOptions) => {
|
||||
import type { CreateNextContextOptions } from './adapters/next';
|
||||
|
||||
type CreateTrpcContext = CreateNextContextOptions & {
|
||||
requestSource: 'apiV1' | 'apiV2' | 'app';
|
||||
};
|
||||
|
||||
export const createTrpcContext = async ({ req, res, requestSource }: CreateTrpcContext) => {
|
||||
const { session, user } = await getServerSession({ req, res });
|
||||
|
||||
const metadata: ApiRequestMetadata = {
|
||||
requestMetadata: extractNextApiRequestMetadata(req),
|
||||
source: requestSource,
|
||||
auth: null,
|
||||
};
|
||||
|
||||
const teamId = z.coerce
|
||||
.number()
|
||||
.optional()
|
||||
.catch(() => undefined)
|
||||
.parse(req.headers['x-team-id']);
|
||||
|
||||
if (!session) {
|
||||
return {
|
||||
session: null,
|
||||
user: null,
|
||||
teamId,
|
||||
req,
|
||||
metadata,
|
||||
};
|
||||
}
|
||||
|
||||
@ -17,14 +39,18 @@ export const createTrpcContext = async ({ req, res }: CreateNextContextOptions)
|
||||
return {
|
||||
session: null,
|
||||
user: null,
|
||||
teamId,
|
||||
req,
|
||||
metadata,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
session,
|
||||
user,
|
||||
teamId,
|
||||
req,
|
||||
metadata,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -39,12 +39,10 @@ import {
|
||||
sendDocument,
|
||||
} from '@documenso/lib/server-only/document/send-document';
|
||||
import {
|
||||
ZUpdateDocumentSettingsResponseSchema,
|
||||
updateDocumentSettings,
|
||||
} from '@documenso/lib/server-only/document/update-document-settings';
|
||||
import { updateTitle } from '@documenso/lib/server-only/document/update-title';
|
||||
ZUpdateDocumentResponseSchema,
|
||||
updateDocument,
|
||||
} from '@documenso/lib/server-only/document/update-document';
|
||||
import { symmetricEncrypt } from '@documenso/lib/universal/crypto';
|
||||
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { DocumentStatus } from '@documenso/prisma/client';
|
||||
|
||||
import { authenticatedProcedure, procedure, router } from '../trpc';
|
||||
@ -64,9 +62,8 @@ import {
|
||||
ZSearchDocumentsMutationSchema,
|
||||
ZSendDocumentMutationSchema,
|
||||
ZSetPasswordForDocumentMutationSchema,
|
||||
ZSetSettingsForDocumentMutationSchema,
|
||||
ZSetSigningOrderForDocumentMutationSchema,
|
||||
ZSetTitleForDocumentMutationSchema,
|
||||
ZUpdateDocumentRequestSchema,
|
||||
ZUpdateTypedSignatureSettingsMutationSchema,
|
||||
} from './schema';
|
||||
|
||||
@ -77,9 +74,13 @@ export const documentRouter = router({
|
||||
getDocumentById: authenticatedProcedure
|
||||
.input(ZGetDocumentByIdQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
return await getDocumentById({
|
||||
...input,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
});
|
||||
}),
|
||||
|
||||
@ -104,28 +105,19 @@ export const documentRouter = router({
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/document/find',
|
||||
path: '/document',
|
||||
summary: 'Find documents',
|
||||
description: 'Find documents based on a search criteria',
|
||||
tags: ['Documents'],
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZFindDocumentsQuerySchema)
|
||||
.output(ZFindDocumentsResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { user } = ctx;
|
||||
const { user, teamId } = ctx;
|
||||
|
||||
const {
|
||||
query,
|
||||
teamId,
|
||||
templateId,
|
||||
page,
|
||||
perPage,
|
||||
orderByDirection,
|
||||
orderByColumn,
|
||||
source,
|
||||
status,
|
||||
} = input;
|
||||
const { query, templateId, page, perPage, orderByDirection, orderByColumn, source, status } =
|
||||
input;
|
||||
|
||||
const documents = await findDocuments({
|
||||
userId: user.id,
|
||||
@ -154,34 +146,41 @@ export const documentRouter = router({
|
||||
path: '/document/{documentId}',
|
||||
summary: 'Get document',
|
||||
description: 'Returns a document given an ID',
|
||||
tags: ['Documents'],
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZGetDocumentWithDetailsByIdQuerySchema)
|
||||
.output(ZGetDocumentWithDetailsByIdResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
return await getDocumentWithDetailsById({
|
||||
...input,
|
||||
userId: ctx.user.id,
|
||||
userId: user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Wait until RR7 so we can passthrough documents.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
createDocument: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/create',
|
||||
summary: 'Create document',
|
||||
tags: ['Documents'],
|
||||
},
|
||||
})
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/document/create',
|
||||
// summary: 'Create document',
|
||||
// tags: ['Document'],
|
||||
// },
|
||||
// })
|
||||
.input(ZCreateDocumentMutationSchema)
|
||||
.output(ZCreateDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { title, documentDataId, teamId, timezone } = input;
|
||||
const { teamId } = ctx;
|
||||
const { title, documentDataId, timezone } = input;
|
||||
|
||||
const { remaining } = await getServerLimits({ email: ctx.user.email, teamId });
|
||||
|
||||
@ -199,7 +198,7 @@ export const documentRouter = router({
|
||||
documentDataId,
|
||||
normalizePdf: true,
|
||||
timezone,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
@ -212,38 +211,43 @@ export const documentRouter = router({
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/{documentId}',
|
||||
path: '/document/update',
|
||||
summary: 'Update document',
|
||||
tags: ['Documents'],
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZSetSettingsForDocumentMutationSchema)
|
||||
.output(ZUpdateDocumentSettingsResponseSchema)
|
||||
.input(ZUpdateDocumentRequestSchema)
|
||||
.output(ZUpdateDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { documentId, teamId, data, meta } = input;
|
||||
const { teamId } = ctx;
|
||||
const { documentId, data, meta = {} } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
|
||||
|
||||
if (meta.timezone || meta.dateFormat || meta.redirectUrl) {
|
||||
if (Object.values(meta).length > 0) {
|
||||
await upsertDocumentMeta({
|
||||
documentId,
|
||||
dateFormat: meta.dateFormat,
|
||||
timezone: meta.timezone,
|
||||
redirectUrl: meta.redirectUrl,
|
||||
language: meta.language,
|
||||
userId: ctx.user.id,
|
||||
requestMetadata,
|
||||
teamId,
|
||||
documentId,
|
||||
subject: meta.subject,
|
||||
message: meta.message,
|
||||
timezone: meta.timezone,
|
||||
dateFormat: meta.dateFormat,
|
||||
language: meta.language,
|
||||
typedSignatureEnabled: meta.typedSignatureEnabled,
|
||||
redirectUrl: meta.redirectUrl,
|
||||
distributionMethod: meta.distributionMethod,
|
||||
emailSettings: meta.emailSettings,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}
|
||||
|
||||
return await updateDocumentSettings({
|
||||
return await updateDocument({
|
||||
userId,
|
||||
teamId,
|
||||
documentId,
|
||||
data,
|
||||
requestMetadata,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
@ -253,16 +257,17 @@ export const documentRouter = router({
|
||||
deleteDocument: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/{documentId}/delete',
|
||||
method: 'DELETE',
|
||||
path: '/document/{documentId}',
|
||||
summary: 'Delete document',
|
||||
tags: ['Documents'],
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZDeleteDocumentMutationSchema)
|
||||
.output(z.void())
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { documentId, teamId } = input;
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
@ -270,7 +275,7 @@ export const documentRouter = router({
|
||||
id: documentId,
|
||||
userId,
|
||||
teamId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
@ -281,10 +286,10 @@ export const documentRouter = router({
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/{documentId}/move',
|
||||
path: '/document/move',
|
||||
summary: 'Move document',
|
||||
description: 'Move a document to a team',
|
||||
tags: ['Documents'],
|
||||
description: 'Move a document from your personal account to a team',
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZMoveDocumentToTeamSchema)
|
||||
@ -297,27 +302,7 @@ export const documentRouter = router({
|
||||
documentId,
|
||||
teamId,
|
||||
userId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
// Should probably use `updateDocument`
|
||||
setTitleForDocument: authenticatedProcedure
|
||||
.input(ZSetTitleForDocumentMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { documentId, teamId, title } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
return await updateTitle({
|
||||
title,
|
||||
userId,
|
||||
teamId,
|
||||
documentId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
@ -327,6 +312,7 @@ export const documentRouter = router({
|
||||
setPasswordForDocument: authenticatedProcedure
|
||||
.input(ZSetPasswordForDocumentMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, password } = input;
|
||||
|
||||
const key = DOCUMENSO_ENCRYPTION_KEY;
|
||||
@ -341,10 +327,11 @@ export const documentRouter = router({
|
||||
});
|
||||
|
||||
await upsertDocumentMeta({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
password: securePassword,
|
||||
userId: ctx.user.id,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
@ -354,23 +341,28 @@ export const documentRouter = router({
|
||||
setSigningOrderForDocument: authenticatedProcedure
|
||||
.input(ZSetSigningOrderForDocumentMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, signingOrder } = input;
|
||||
|
||||
return await upsertDocumentMeta({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
signingOrder,
|
||||
userId: ctx.user.id,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @deprecated Remove after deployment.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
updateTypedSignatureSettings: authenticatedProcedure
|
||||
.input(ZUpdateTypedSignatureSettingsMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { documentId, teamId, typedSignatureEnabled } = input;
|
||||
const { teamId } = ctx;
|
||||
const { documentId, typedSignatureEnabled } = input;
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId,
|
||||
@ -386,10 +378,11 @@ export const documentRouter = router({
|
||||
}
|
||||
|
||||
return await upsertDocumentMeta({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
typedSignatureEnabled,
|
||||
userId: ctx.user.id,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
@ -403,27 +396,22 @@ export const documentRouter = router({
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/{documentId}/distribute',
|
||||
path: '/document/distribute',
|
||||
summary: 'Distribute document',
|
||||
description: 'Send the document out to recipients based on your distribution method',
|
||||
tags: ['Documents'],
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZSendDocumentMutationSchema)
|
||||
.output(ZSendDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { documentId, teamId, meta } = input;
|
||||
const { teamId } = ctx;
|
||||
const { documentId, meta = {} } = input;
|
||||
|
||||
if (
|
||||
meta.message ||
|
||||
meta.subject ||
|
||||
meta.timezone ||
|
||||
meta.dateFormat ||
|
||||
meta.redirectUrl ||
|
||||
meta.distributionMethod ||
|
||||
meta.emailSettings
|
||||
) {
|
||||
if (Object.values(meta).length > 0) {
|
||||
await upsertDocumentMeta({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
subject: meta.subject,
|
||||
message: meta.message,
|
||||
@ -431,9 +419,9 @@ export const documentRouter = router({
|
||||
timezone: meta.timezone,
|
||||
redirectUrl: meta.redirectUrl,
|
||||
distributionMethod: meta.distributionMethod,
|
||||
userId: ctx.user.id,
|
||||
emailSettings: meta.emailSettings,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
language: meta.language,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}
|
||||
|
||||
@ -441,31 +429,38 @@ export const documentRouter = router({
|
||||
userId: ctx.user.id,
|
||||
documentId,
|
||||
teamId,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* Todo: Refactor to redistributeDocument.
|
||||
*/
|
||||
resendDocument: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/{documentId}/resend',
|
||||
summary: 'Resend document',
|
||||
path: '/document/redistribute',
|
||||
summary: 'Redistribute document',
|
||||
description:
|
||||
'Resend the document to recipients who have not signed. Will use the distribution method set in the document.',
|
||||
tags: ['Documents'],
|
||||
'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(z.void())
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, recipients } = input;
|
||||
|
||||
return await resendDocument({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
teamId,
|
||||
documentId,
|
||||
recipients,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
@ -476,17 +471,21 @@ export const documentRouter = router({
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/{documentId}/duplicate',
|
||||
path: '/document/duplicate',
|
||||
summary: 'Duplicate document',
|
||||
tags: ['Documents'],
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZDuplicateDocumentMutationSchema)
|
||||
.output(ZDuplicateDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
return await duplicateDocument({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
userId: user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
});
|
||||
}),
|
||||
|
||||
@ -512,6 +511,8 @@ export const documentRouter = router({
|
||||
findDocumentAuditLogs: authenticatedProcedure
|
||||
.input(ZFindDocumentAuditLogsQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
|
||||
const {
|
||||
page,
|
||||
perPage,
|
||||
@ -523,13 +524,14 @@ export const documentRouter = router({
|
||||
} = input;
|
||||
|
||||
return await findDocumentAuditLogs({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
page,
|
||||
perPage,
|
||||
documentId,
|
||||
cursor,
|
||||
filterForRecentActivity,
|
||||
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
}),
|
||||
|
||||
@ -539,7 +541,8 @@ export const documentRouter = router({
|
||||
downloadAuditLogs: authenticatedProcedure
|
||||
.input(ZDownloadAuditLogsMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { documentId, teamId } = input;
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId,
|
||||
@ -570,7 +573,8 @@ export const documentRouter = router({
|
||||
downloadCertificate: authenticatedProcedure
|
||||
.input(ZDownloadCertificateMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { documentId, teamId } = input;
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId,
|
||||
|
||||
@ -15,16 +15,59 @@ import {
|
||||
DocumentStatus,
|
||||
DocumentVisibility,
|
||||
FieldType,
|
||||
RecipientRole,
|
||||
} from '@documenso/prisma/client';
|
||||
|
||||
// Todo: Refactor all to ZDocumentMeta---
|
||||
export const ZDocumentMetaTimezoneSchema = z
|
||||
.string()
|
||||
.describe('The timezone to use for date fields and signing the document.');
|
||||
|
||||
export const ZDocumentMetaDateFormatSchema = z
|
||||
.string()
|
||||
.describe('The date format to use for date fields and signing the document.');
|
||||
|
||||
export const ZDocumentMetaRedirectUrlSchema = z
|
||||
.string()
|
||||
.describe('The URL to which the recipient should be redirected after signing the document.')
|
||||
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
|
||||
message: 'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
||||
});
|
||||
|
||||
export const ZDocumentMetaLanguageSchema = z
|
||||
.enum(SUPPORTED_LANGUAGE_CODES)
|
||||
.describe('The language to use for email communications with recipients.');
|
||||
|
||||
export const ZDocumentMetaSubjectSchema = z
|
||||
.string()
|
||||
.describe('The subject of the email that will be sent to the recipients.');
|
||||
|
||||
export const ZDocumentMetaMessageSchema = z
|
||||
.string()
|
||||
.describe('The message of the email that will be sent to the recipients.');
|
||||
|
||||
export const ZDocumentMetaDistributionMethodSchema = z
|
||||
.nativeEnum(DocumentDistributionMethod)
|
||||
.describe('The distribution method to use when sending the document to the recipients.');
|
||||
|
||||
export const ZDocumentMetaTypedSignatureEnabledSchema = z
|
||||
.boolean()
|
||||
.describe('Whether to allow typed signatures.');
|
||||
|
||||
export const ZFindDocumentsQuerySchema = ZFindSearchParamsSchema.extend({
|
||||
teamId: z.number().min(1).optional(),
|
||||
templateId: z.number().min(1).optional(),
|
||||
source: z.nativeEnum(DocumentSource).optional(),
|
||||
status: z.nativeEnum(DocumentStatus).optional(),
|
||||
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(),
|
||||
orderByColumn: z.enum(['createdAt']).optional(),
|
||||
orderByDirection: z.enum(['asc', 'desc']).default('desc'),
|
||||
orderByDirection: z.enum(['asc', 'desc']).describe('').default('desc'),
|
||||
});
|
||||
|
||||
export const ZFindDocumentAuditLogsQuerySchema = ZFindSearchParamsSchema.extend({
|
||||
@ -36,13 +79,11 @@ export const ZFindDocumentAuditLogsQuerySchema = ZFindSearchParamsSchema.extend(
|
||||
});
|
||||
|
||||
export const ZGetDocumentByIdQuerySchema = z.object({
|
||||
documentId: z.number().min(1),
|
||||
teamId: z.number().min(1).optional(),
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDuplicateDocumentMutationSchema = z.object({
|
||||
documentId: z.number().min(1),
|
||||
teamId: z.number().min(1).optional(),
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export type TGetDocumentByIdQuerySchema = z.infer<typeof ZGetDocumentByIdQuerySchema>;
|
||||
@ -54,8 +95,7 @@ export const ZGetDocumentByTokenQuerySchema = z.object({
|
||||
export type TGetDocumentByTokenQuerySchema = z.infer<typeof ZGetDocumentByTokenQuerySchema>;
|
||||
|
||||
export const ZGetDocumentWithDetailsByIdQuerySchema = z.object({
|
||||
documentId: z.number().min(1),
|
||||
teamId: z.number().min(1).optional(),
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export type TGetDocumentWithDetailsByIdQuerySchema = z.infer<
|
||||
@ -65,64 +105,41 @@ export type TGetDocumentWithDetailsByIdQuerySchema = z.infer<
|
||||
export const ZCreateDocumentMutationSchema = z.object({
|
||||
title: z.string().min(1),
|
||||
documentDataId: z.string().min(1),
|
||||
teamId: z.number().optional(),
|
||||
timezone: z.string().optional(),
|
||||
});
|
||||
|
||||
export type TCreateDocumentMutationSchema = z.infer<typeof ZCreateDocumentMutationSchema>;
|
||||
|
||||
export const ZSetSettingsForDocumentMutationSchema = z.object({
|
||||
export const ZUpdateDocumentRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
teamId: z.number().min(1).optional(),
|
||||
data: z.object({
|
||||
title: z.string().min(1).optional(),
|
||||
externalId: z.string().nullish(),
|
||||
visibility: z.nativeEnum(DocumentVisibility).optional(),
|
||||
globalAccessAuth: ZDocumentAccessAuthTypesSchema.nullable().optional(),
|
||||
globalActionAuth: ZDocumentActionAuthTypesSchema.nullable().optional(),
|
||||
}),
|
||||
meta: z.object({
|
||||
timezone: z.string(),
|
||||
dateFormat: z.string(),
|
||||
redirectUrl: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
|
||||
message:
|
||||
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
||||
}),
|
||||
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
||||
}),
|
||||
data: z
|
||||
.object({
|
||||
title: z.string().describe('The title of the document.').min(1).optional(),
|
||||
externalId: z.string().nullish().describe('The external ID of the document.'),
|
||||
visibility: z
|
||||
.nativeEnum(DocumentVisibility)
|
||||
.describe('The visibility of the document.')
|
||||
.optional(),
|
||||
globalAccessAuth: ZDocumentAccessAuthTypesSchema.nullable().optional(),
|
||||
globalActionAuth: ZDocumentActionAuthTypesSchema.nullable().optional(),
|
||||
})
|
||||
.optional(),
|
||||
meta: z
|
||||
.object({
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
message: ZDocumentMetaMessageSchema.optional(),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type TSetGeneralSettingsForDocumentMutationSchema = z.infer<
|
||||
typeof ZSetSettingsForDocumentMutationSchema
|
||||
>;
|
||||
|
||||
export const ZSetTitleForDocumentMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
teamId: z.number().min(1).optional(),
|
||||
title: z.string().min(1),
|
||||
});
|
||||
|
||||
export type TSetTitleForDocumentMutationSchema = z.infer<typeof ZSetTitleForDocumentMutationSchema>;
|
||||
|
||||
export const ZSetRecipientsForDocumentMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
teamId: z.number().min(1).optional(),
|
||||
recipients: z.array(
|
||||
z.object({
|
||||
id: z.number().nullish(),
|
||||
email: z.string().min(1).email(),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export type TSetRecipientsForDocumentMutationSchema = z.infer<
|
||||
typeof ZSetRecipientsForDocumentMutationSchema
|
||||
>;
|
||||
export type TUpdateDocumentRequestSchema = z.infer<typeof ZUpdateDocumentRequestSchema>;
|
||||
|
||||
export const ZSetFieldsForDocumentMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
@ -145,23 +162,19 @@ export type TSetFieldsForDocumentMutationSchema = z.infer<
|
||||
>;
|
||||
|
||||
export const ZSendDocumentMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
teamId: z.number().optional(),
|
||||
meta: z.object({
|
||||
subject: z.string(),
|
||||
message: z.string(),
|
||||
timezone: z.string().optional(),
|
||||
dateFormat: z.string().optional(),
|
||||
distributionMethod: z.nativeEnum(DocumentDistributionMethod).optional(),
|
||||
redirectUrl: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
|
||||
message:
|
||||
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
||||
}),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
}),
|
||||
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(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const ZSetPasswordForDocumentMutationSchema = z.object({
|
||||
@ -184,7 +197,6 @@ export type TSetSigningOrderForDocumentMutationSchema = z.infer<
|
||||
|
||||
export const ZUpdateTypedSignatureSettingsMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
teamId: z.number().optional(),
|
||||
typedSignatureEnabled: z.boolean(),
|
||||
});
|
||||
|
||||
@ -194,15 +206,16 @@ export type TUpdateTypedSignatureSettingsMutationSchema = z.infer<
|
||||
|
||||
export const ZResendDocumentMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
recipients: z.array(z.number()).min(1),
|
||||
teamId: z.number().min(1).optional(),
|
||||
recipients: z
|
||||
.array(z.number())
|
||||
.min(1)
|
||||
.describe('The IDs of the recipients to redistribute the document to.'),
|
||||
});
|
||||
|
||||
export type TSendDocumentMutationSchema = z.infer<typeof ZSendDocumentMutationSchema>;
|
||||
|
||||
export const ZDeleteDocumentMutationSchema = z.object({
|
||||
documentId: z.number().min(1),
|
||||
teamId: z.number().min(1).optional(),
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export type TDeleteDocumentMutationSchema = z.infer<typeof ZDeleteDocumentMutationSchema>;
|
||||
@ -213,15 +226,13 @@ export const ZSearchDocumentsMutationSchema = z.object({
|
||||
|
||||
export const ZDownloadAuditLogsMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
teamId: z.number().optional(),
|
||||
});
|
||||
|
||||
export const ZDownloadCertificateMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
teamId: z.number().optional(),
|
||||
});
|
||||
|
||||
export const ZMoveDocumentToTeamSchema = z.object({
|
||||
documentId: z.number(),
|
||||
teamId: z.number(),
|
||||
documentId: z.number().describe('The ID of the document to move to a team.'),
|
||||
teamId: z.number().describe('The ID of the team to move the document to.'),
|
||||
});
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
ZCreateDocumentFieldsResponseSchema,
|
||||
createDocumentFields,
|
||||
} from '@documenso/lib/server-only/field/create-document-fields';
|
||||
import {
|
||||
ZCreateTemplateFieldsResponseSchema,
|
||||
createTemplateFields,
|
||||
} from '@documenso/lib/server-only/field/create-template-fields';
|
||||
import { deleteDocumentField } from '@documenso/lib/server-only/field/delete-document-field';
|
||||
import { deleteTemplateField } from '@documenso/lib/server-only/field/delete-template-field';
|
||||
import {
|
||||
ZGetFieldByIdResponseSchema,
|
||||
getFieldById,
|
||||
@ -12,15 +24,37 @@ import {
|
||||
setFieldsForTemplate,
|
||||
} from '@documenso/lib/server-only/field/set-fields-for-template';
|
||||
import { signFieldWithToken } from '@documenso/lib/server-only/field/sign-field-with-token';
|
||||
import {
|
||||
ZUpdateDocumentFieldsResponseSchema,
|
||||
updateDocumentFields,
|
||||
} from '@documenso/lib/server-only/field/update-document-fields';
|
||||
import {
|
||||
ZUpdateTemplateFieldsResponseSchema,
|
||||
updateTemplateFields,
|
||||
} from '@documenso/lib/server-only/field/update-template-fields';
|
||||
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
|
||||
import { authenticatedProcedure, procedure, router } from '../trpc';
|
||||
import {
|
||||
ZAddFieldsMutationSchema,
|
||||
ZAddTemplateFieldsMutationSchema,
|
||||
ZCreateDocumentFieldRequestSchema,
|
||||
ZCreateDocumentFieldResponseSchema,
|
||||
ZCreateDocumentFieldsRequestSchema,
|
||||
ZCreateTemplateFieldRequestSchema,
|
||||
ZCreateTemplateFieldResponseSchema,
|
||||
ZCreateTemplateFieldsRequestSchema,
|
||||
ZDeleteDocumentFieldRequestSchema,
|
||||
ZDeleteTemplateFieldRequestSchema,
|
||||
ZGetFieldQuerySchema,
|
||||
ZRemovedSignedFieldWithTokenMutationSchema,
|
||||
ZSignFieldWithTokenMutationSchema,
|
||||
ZUpdateDocumentFieldRequestSchema,
|
||||
ZUpdateDocumentFieldResponseSchema,
|
||||
ZUpdateDocumentFieldsRequestSchema,
|
||||
ZUpdateTemplateFieldRequestSchema,
|
||||
ZUpdateTemplateFieldResponseSchema,
|
||||
ZUpdateTemplateFieldsRequestSchema,
|
||||
} from './schema';
|
||||
|
||||
export const fieldRouter = router({
|
||||
@ -35,13 +69,14 @@ export const fieldRouter = router({
|
||||
summary: 'Get field',
|
||||
description:
|
||||
'Returns a single field. If you want to retrieve all the fields for a document or template, use the "Get Document" or "Get Template" request.',
|
||||
tags: ['Fields'],
|
||||
tags: ['Document Fields', 'Template Fields'],
|
||||
},
|
||||
})
|
||||
.input(ZGetFieldQuerySchema)
|
||||
.output(ZGetFieldByIdResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { fieldId, teamId } = input;
|
||||
const { teamId } = ctx;
|
||||
const { fieldId } = input;
|
||||
|
||||
return await getFieldById({
|
||||
userId: ctx.user.id,
|
||||
@ -53,23 +88,167 @@ export const fieldRouter = router({
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
addFields: authenticatedProcedure
|
||||
createDocumentField: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/{documentId}/field',
|
||||
summary: 'Set document fields',
|
||||
tags: ['Fields'],
|
||||
path: '/document/field/create',
|
||||
summary: 'Create document field',
|
||||
description: 'Create a single field for a document.',
|
||||
tags: ['Document Fields'],
|
||||
},
|
||||
})
|
||||
.input(ZCreateDocumentFieldRequestSchema)
|
||||
.output(ZCreateDocumentFieldResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, field } = input;
|
||||
|
||||
const createdFields = await createDocumentFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
fields: [field],
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return createdFields.fields[0];
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
createDocumentFields: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/field/create-many',
|
||||
summary: 'Create document fields',
|
||||
description: 'Create multiple fields for a document.',
|
||||
tags: ['Document Fields'],
|
||||
},
|
||||
})
|
||||
.input(ZCreateDocumentFieldsRequestSchema)
|
||||
.output(ZCreateDocumentFieldsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, fields } = input;
|
||||
|
||||
return await createDocumentFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
fields,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
updateDocumentField: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/field/update',
|
||||
summary: 'Update document field',
|
||||
description: 'Update a single field for a document.',
|
||||
tags: ['Document Fields'],
|
||||
},
|
||||
})
|
||||
.input(ZUpdateDocumentFieldRequestSchema)
|
||||
.output(ZUpdateDocumentFieldResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, field } = input;
|
||||
|
||||
const updatedFields = await updateDocumentFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
fields: [field],
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return updatedFields.fields[0];
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
updateDocumentFields: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/field/update-many',
|
||||
summary: 'Update document fields',
|
||||
description: 'Update multiple fields for a document.',
|
||||
tags: ['Document Fields'],
|
||||
},
|
||||
})
|
||||
.input(ZUpdateDocumentFieldsRequestSchema)
|
||||
.output(ZUpdateDocumentFieldsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, fields } = input;
|
||||
|
||||
return await updateDocumentFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
fields,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
deleteDocumentField: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'DELETE',
|
||||
path: '/document/field/{fieldId}',
|
||||
summary: 'Delete document field',
|
||||
tags: ['Document Fields'],
|
||||
},
|
||||
})
|
||||
.input(ZDeleteDocumentFieldRequestSchema)
|
||||
.output(z.void())
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { fieldId } = input;
|
||||
|
||||
await deleteDocumentField({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
fieldId,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
addFields: authenticatedProcedure
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/document/{documentId}/field',
|
||||
// summary: 'Set document fields',
|
||||
// tags: ['Document Fields'],
|
||||
// },
|
||||
// })
|
||||
.input(ZAddFieldsMutationSchema)
|
||||
.output(ZSetFieldsForDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, fields } = input;
|
||||
|
||||
return await setFieldsForDocument({
|
||||
documentId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
fields: fields.map((field) => ({
|
||||
id: field.nativeId,
|
||||
signerEmail: field.signerEmail,
|
||||
@ -81,30 +260,169 @@ export const fieldRouter = router({
|
||||
pageHeight: field.pageHeight,
|
||||
fieldMeta: field.fieldMeta,
|
||||
})),
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
addTemplateFields: authenticatedProcedure
|
||||
createTemplateField: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/{templateId}/field',
|
||||
summary: 'Set template fields',
|
||||
tags: ['Fields'],
|
||||
path: '/template/field/create',
|
||||
summary: 'Create template field',
|
||||
description: 'Create a single field for a template.',
|
||||
tags: ['Template Fields'],
|
||||
},
|
||||
})
|
||||
.input(ZCreateTemplateFieldRequestSchema)
|
||||
.output(ZCreateTemplateFieldResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { templateId, field } = input;
|
||||
|
||||
const createdFields = await createTemplateFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
fields: [field],
|
||||
});
|
||||
|
||||
return createdFields.fields[0];
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
createTemplateFields: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/field/create-many',
|
||||
summary: 'Create template fields',
|
||||
description: 'Create multiple fields for a template.',
|
||||
tags: ['Template Fields'],
|
||||
},
|
||||
})
|
||||
.input(ZCreateTemplateFieldsRequestSchema)
|
||||
.output(ZCreateTemplateFieldsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { templateId, fields } = input;
|
||||
|
||||
return await createTemplateFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
fields,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
updateTemplateField: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/field/update',
|
||||
summary: 'Update template field',
|
||||
description: 'Update a single field for a template.',
|
||||
tags: ['Template Fields'],
|
||||
},
|
||||
})
|
||||
.input(ZUpdateTemplateFieldRequestSchema)
|
||||
.output(ZUpdateTemplateFieldResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { templateId, field } = input;
|
||||
|
||||
const updatedFields = await updateTemplateFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
fields: [field],
|
||||
});
|
||||
|
||||
return updatedFields.fields[0];
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
updateTemplateFields: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/field/update-many',
|
||||
summary: 'Update template fields',
|
||||
description: 'Update multiple fields for a template.',
|
||||
tags: ['Template Fields'],
|
||||
},
|
||||
})
|
||||
.input(ZUpdateTemplateFieldsRequestSchema)
|
||||
.output(ZUpdateTemplateFieldsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { templateId, fields } = input;
|
||||
|
||||
return await updateTemplateFields({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
fields,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
deleteTemplateField: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'DELETE',
|
||||
path: '/template/field/{fieldId}',
|
||||
summary: 'Delete template field',
|
||||
tags: ['Template Fields'],
|
||||
},
|
||||
})
|
||||
.input(ZDeleteTemplateFieldRequestSchema)
|
||||
.output(z.void())
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { fieldId } = input;
|
||||
|
||||
await deleteTemplateField({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
fieldId,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
addTemplateFields: authenticatedProcedure
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/template/{templateId}/field',
|
||||
// summary: 'Set template fields',
|
||||
// tags: ['Template Fields'],
|
||||
// },
|
||||
// })
|
||||
.input(ZAddTemplateFieldsMutationSchema)
|
||||
.output(ZSetFieldsForTemplateResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { templateId, fields } = input;
|
||||
|
||||
return await setFieldsForTemplate({
|
||||
userId: ctx.user.id,
|
||||
templateId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
fields: fields.map((field) => ({
|
||||
id: field.nativeId,
|
||||
signerEmail: field.signerEmail,
|
||||
|
||||
@ -3,6 +3,82 @@ import { z } from 'zod';
|
||||
import { ZRecipientActionAuthSchema } from '@documenso/lib/types/document-auth';
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { FieldType } from '@documenso/prisma/client';
|
||||
import { FieldSchema } from '@documenso/prisma/generated/zod';
|
||||
|
||||
const ZCreateFieldSchema = z.object({
|
||||
recipientId: z.number().describe('The ID of the recipient to create the field for.'),
|
||||
type: FieldSchema.shape.type.describe('The type of the field to create.'),
|
||||
pageNumber: z.number().describe('The page number the field will be on.'),
|
||||
pageX: z.number().describe('The X coordinate of where the field will be placed.'),
|
||||
pageY: z.number().describe('The Y coordinate of where the field will be placed.'),
|
||||
width: z.number().describe('The width of the field.'),
|
||||
height: z.number().describe('The height of the field.'),
|
||||
fieldMeta: ZFieldMetaSchema.optional(),
|
||||
});
|
||||
|
||||
const ZUpdateFieldSchema = z.object({
|
||||
id: z.number().describe('The ID of the field to update.'),
|
||||
type: FieldSchema.shape.type.optional().describe('The type of the field to update.'),
|
||||
pageNumber: z.number().optional().describe('The page number the field will be on.'),
|
||||
pageX: z.number().optional().describe('The X coordinate of where the field will be placed.'),
|
||||
pageY: z.number().optional().describe('The Y coordinate of where the field will be placed.'),
|
||||
width: z.number().optional().describe('The width of the field.'),
|
||||
height: z.number().optional().describe('The height of the field.'),
|
||||
fieldMeta: ZFieldMetaSchema.optional(),
|
||||
});
|
||||
|
||||
export const ZCreateDocumentFieldRequestSchema = z.object({
|
||||
documentId: z.number().min(1),
|
||||
field: ZCreateFieldSchema,
|
||||
});
|
||||
|
||||
export const ZCreateDocumentFieldsRequestSchema = z.object({
|
||||
documentId: z.number().min(1),
|
||||
fields: ZCreateFieldSchema.array(),
|
||||
});
|
||||
|
||||
export const ZUpdateDocumentFieldRequestSchema = z.object({
|
||||
documentId: z.number().min(1),
|
||||
field: ZUpdateFieldSchema,
|
||||
});
|
||||
|
||||
export const ZUpdateDocumentFieldsRequestSchema = z.object({
|
||||
documentId: z.number().min(1),
|
||||
fields: ZUpdateFieldSchema.array(),
|
||||
});
|
||||
|
||||
export const ZDeleteDocumentFieldRequestSchema = z.object({
|
||||
fieldId: z.number().min(1),
|
||||
});
|
||||
|
||||
export const ZCreateTemplateFieldRequestSchema = z.object({
|
||||
templateId: z.number().min(1),
|
||||
field: ZCreateFieldSchema,
|
||||
});
|
||||
|
||||
export const ZCreateDocumentFieldResponseSchema = FieldSchema;
|
||||
export const ZUpdateTemplateFieldResponseSchema = FieldSchema;
|
||||
export const ZUpdateDocumentFieldResponseSchema = FieldSchema;
|
||||
export const ZCreateTemplateFieldResponseSchema = FieldSchema;
|
||||
|
||||
export const ZCreateTemplateFieldsRequestSchema = z.object({
|
||||
templateId: z.number().min(1),
|
||||
fields: ZCreateFieldSchema.array(),
|
||||
});
|
||||
|
||||
export const ZUpdateTemplateFieldRequestSchema = z.object({
|
||||
templateId: z.number().min(1),
|
||||
field: ZUpdateFieldSchema,
|
||||
});
|
||||
|
||||
export const ZUpdateTemplateFieldsRequestSchema = z.object({
|
||||
templateId: z.number().min(1),
|
||||
fields: ZUpdateFieldSchema.array(),
|
||||
});
|
||||
|
||||
export const ZDeleteTemplateFieldRequestSchema = z.object({
|
||||
fieldId: z.number().min(1),
|
||||
});
|
||||
|
||||
export const ZAddFieldsMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
@ -65,7 +141,6 @@ export type TRemovedSignedFieldWithTokenMutationSchema = z.infer<
|
||||
|
||||
export const ZGetFieldQuerySchema = z.object({
|
||||
fieldId: z.number(),
|
||||
teamId: z.number().optional(),
|
||||
});
|
||||
|
||||
export type TGetFieldQuerySchema = z.infer<typeof ZGetFieldQuerySchema>;
|
||||
|
||||
@ -5,8 +5,8 @@ import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { appRouter } from './router';
|
||||
|
||||
export const openApiDocument = generateOpenApiDocument(appRouter, {
|
||||
title: 'Do not use.',
|
||||
title: 'Documenso v2 beta API',
|
||||
description: 'Subject to breaking changes until v2 is fully released.',
|
||||
version: '0.0.0',
|
||||
baseUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/api/beta`,
|
||||
// docsUrl: '', // Todo
|
||||
baseUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/api/v2-beta`,
|
||||
});
|
||||
|
||||
@ -52,7 +52,7 @@ export const profileRouter = router({
|
||||
userId: ctx.user.id,
|
||||
name,
|
||||
signature,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
}),
|
||||
|
||||
@ -96,7 +96,7 @@ export const profileRouter = router({
|
||||
userId: ctx.user.id,
|
||||
password,
|
||||
currentPassword,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
}),
|
||||
|
||||
@ -146,7 +146,7 @@ export const profileRouter = router({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
bytes,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@ -1,26 +1,57 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { completeDocumentWithToken } from '@documenso/lib/server-only/document/complete-document-with-token';
|
||||
import { rejectDocumentWithToken } from '@documenso/lib/server-only/document/reject-document-with-token';
|
||||
import {
|
||||
ZCreateDocumentRecipientsResponseSchema,
|
||||
createDocumentRecipients,
|
||||
} from '@documenso/lib/server-only/recipient/create-document-recipients';
|
||||
import {
|
||||
ZCreateTemplateRecipientsResponseSchema,
|
||||
createTemplateRecipients,
|
||||
} from '@documenso/lib/server-only/recipient/create-template-recipients';
|
||||
import { deleteDocumentRecipient } from '@documenso/lib/server-only/recipient/delete-document-recipient';
|
||||
import { deleteTemplateRecipient } from '@documenso/lib/server-only/recipient/delete-template-recipient';
|
||||
import {
|
||||
ZGetRecipientByIdResponseSchema,
|
||||
getRecipientById,
|
||||
} from '@documenso/lib/server-only/recipient/get-recipient-by-id';
|
||||
import {
|
||||
ZSetRecipientsForDocumentResponseSchema,
|
||||
setRecipientsForDocument,
|
||||
} from '@documenso/lib/server-only/recipient/set-recipients-for-document';
|
||||
ZSetDocumentRecipientsResponseSchema,
|
||||
setDocumentRecipients,
|
||||
} from '@documenso/lib/server-only/recipient/set-document-recipients';
|
||||
import {
|
||||
ZSetRecipientsForTemplateResponseSchema,
|
||||
setRecipientsForTemplate,
|
||||
} from '@documenso/lib/server-only/recipient/set-recipients-for-template';
|
||||
ZSetTemplateRecipientsResponseSchema,
|
||||
setTemplateRecipients,
|
||||
} from '@documenso/lib/server-only/recipient/set-template-recipients';
|
||||
import { updateDocumentRecipients } from '@documenso/lib/server-only/recipient/update-document-recipients';
|
||||
import { updateTemplateRecipients } from '@documenso/lib/server-only/recipient/update-template-recipients';
|
||||
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
|
||||
import { authenticatedProcedure, procedure, router } from '../trpc';
|
||||
import {
|
||||
ZAddSignersMutationSchema,
|
||||
ZAddTemplateSignersMutationSchema,
|
||||
ZCompleteDocumentWithTokenMutationSchema,
|
||||
ZCreateDocumentRecipientRequestSchema,
|
||||
ZCreateDocumentRecipientResponseSchema,
|
||||
ZCreateDocumentRecipientsRequestSchema,
|
||||
ZCreateTemplateRecipientRequestSchema,
|
||||
ZCreateTemplateRecipientResponseSchema,
|
||||
ZCreateTemplateRecipientsRequestSchema,
|
||||
ZDeleteDocumentRecipientRequestSchema,
|
||||
ZDeleteTemplateRecipientRequestSchema,
|
||||
ZGetRecipientQuerySchema,
|
||||
ZRejectDocumentWithTokenMutationSchema,
|
||||
ZSetDocumentRecipientsRequestSchema,
|
||||
ZSetTemplateRecipientsRequestSchema,
|
||||
ZUpdateDocumentRecipientRequestSchema,
|
||||
ZUpdateDocumentRecipientResponseSchema,
|
||||
ZUpdateDocumentRecipientsRequestSchema,
|
||||
ZUpdateDocumentRecipientsResponseSchema,
|
||||
ZUpdateTemplateRecipientRequestSchema,
|
||||
ZUpdateTemplateRecipientResponseSchema,
|
||||
ZUpdateTemplateRecipientsRequestSchema,
|
||||
ZUpdateTemplateRecipientsResponseSchema,
|
||||
} from './schema';
|
||||
|
||||
export const recipientRouter = router({
|
||||
@ -35,13 +66,14 @@ export const recipientRouter = router({
|
||||
summary: 'Get recipient',
|
||||
description:
|
||||
'Returns a single recipient. If you want to retrieve all the recipients for a document or template, use the "Get Document" or "Get Template" request.',
|
||||
tags: ['Recipients'],
|
||||
tags: ['Document Recipients', 'Template Recipients'],
|
||||
},
|
||||
})
|
||||
.input(ZGetRecipientQuerySchema)
|
||||
.output(ZGetRecipientByIdResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { recipientId, teamId } = input;
|
||||
const { teamId } = ctx;
|
||||
const { recipientId } = input;
|
||||
|
||||
return await getRecipientById({
|
||||
userId: ctx.user.id,
|
||||
@ -53,64 +85,349 @@ export const recipientRouter = router({
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
addSigners: authenticatedProcedure
|
||||
createDocumentRecipient: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/{documentId}/recipient/set',
|
||||
summary: 'Set document recipients',
|
||||
tags: ['Recipients'],
|
||||
path: '/document/recipient/create',
|
||||
summary: 'Create document recipient',
|
||||
description: 'Create a single recipient for a document.',
|
||||
tags: ['Document Recipients'],
|
||||
},
|
||||
})
|
||||
.input(ZAddSignersMutationSchema)
|
||||
.output(ZSetRecipientsForDocumentResponseSchema)
|
||||
.input(ZCreateDocumentRecipientRequestSchema)
|
||||
.output(ZCreateDocumentRecipientResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { documentId, teamId, signers } = input;
|
||||
const { teamId } = ctx;
|
||||
const { documentId, recipient } = input;
|
||||
|
||||
return await setRecipientsForDocument({
|
||||
const createdRecipients = await createDocumentRecipients({
|
||||
userId: ctx.user.id,
|
||||
documentId,
|
||||
teamId,
|
||||
recipients: signers.map((signer) => ({
|
||||
id: signer.nativeId,
|
||||
email: signer.email,
|
||||
name: signer.name,
|
||||
role: signer.role,
|
||||
signingOrder: signer.signingOrder,
|
||||
actionAuth: signer.actionAuth,
|
||||
})),
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
documentId,
|
||||
recipients: [recipient],
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return createdRecipients.recipients[0];
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
createDocumentRecipients: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/recipient/create-many',
|
||||
summary: 'Create document recipients',
|
||||
description: 'Create multiple recipients for a document.',
|
||||
tags: ['Document Recipients'],
|
||||
},
|
||||
})
|
||||
.input(ZCreateDocumentRecipientsRequestSchema)
|
||||
.output(ZCreateDocumentRecipientsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, recipients } = input;
|
||||
|
||||
return await createDocumentRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
recipients,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
addTemplateSigners: authenticatedProcedure
|
||||
updateDocumentRecipient: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/{templateId}/recipient/set',
|
||||
summary: 'Set template recipients',
|
||||
tags: ['Recipients'],
|
||||
path: '/document/recipient/update',
|
||||
summary: 'Update document recipient',
|
||||
description: 'Update a single recipient for a document.',
|
||||
tags: ['Document Recipients'],
|
||||
},
|
||||
})
|
||||
.input(ZAddTemplateSignersMutationSchema)
|
||||
.output(ZSetRecipientsForTemplateResponseSchema)
|
||||
.input(ZUpdateDocumentRecipientRequestSchema)
|
||||
.output(ZUpdateDocumentRecipientResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { templateId, signers, teamId } = input;
|
||||
const { teamId } = ctx;
|
||||
const { documentId, recipient } = input;
|
||||
|
||||
return await setRecipientsForTemplate({
|
||||
const updatedRecipients = await updateDocumentRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
recipients: [recipient],
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return updatedRecipients.recipients[0];
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
updateDocumentRecipients: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/recipient/update-many',
|
||||
summary: 'Update document recipients',
|
||||
description: 'Update multiple recipients for a document.',
|
||||
tags: ['Document Recipients'],
|
||||
},
|
||||
})
|
||||
.input(ZUpdateDocumentRecipientsRequestSchema)
|
||||
.output(ZUpdateDocumentRecipientsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, recipients } = input;
|
||||
|
||||
return await updateDocumentRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
recipients,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
deleteDocumentRecipient: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'DELETE',
|
||||
path: '/document/recipient/{recipientId}',
|
||||
summary: 'Delete document recipient',
|
||||
tags: ['Document Recipients'],
|
||||
},
|
||||
})
|
||||
.input(ZDeleteDocumentRecipientRequestSchema)
|
||||
.output(z.void())
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { recipientId } = input;
|
||||
|
||||
await deleteDocumentRecipient({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
recipientId,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setDocumentRecipients: authenticatedProcedure
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/document/recipient/set',
|
||||
// summary: 'Set document recipients',
|
||||
// description:
|
||||
// 'This will replace all recipients attached to the document. If the array contains existing recipients, they will be updated and the original fields will be retained.',
|
||||
// tags: ['Document Recipients'],
|
||||
// },
|
||||
// })
|
||||
.input(ZSetDocumentRecipientsRequestSchema)
|
||||
.output(ZSetDocumentRecipientsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, recipients } = input;
|
||||
|
||||
return await setDocumentRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
recipients: recipients.map((recipient) => ({
|
||||
id: recipient.nativeId,
|
||||
email: recipient.email,
|
||||
name: recipient.name,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
actionAuth: recipient.actionAuth,
|
||||
})),
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
createTemplateRecipient: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/recipient/create',
|
||||
summary: 'Create template recipient',
|
||||
description: 'Create a single recipient for a template.',
|
||||
tags: ['Template Recipients'],
|
||||
},
|
||||
})
|
||||
.input(ZCreateTemplateRecipientRequestSchema)
|
||||
.output(ZCreateTemplateRecipientResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { templateId, recipient } = input;
|
||||
|
||||
const createdRecipients = await createTemplateRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
recipients: signers.map((signer) => ({
|
||||
id: signer.nativeId,
|
||||
email: signer.email,
|
||||
name: signer.name,
|
||||
role: signer.role,
|
||||
signingOrder: signer.signingOrder,
|
||||
actionAuth: signer.actionAuth,
|
||||
recipients: [recipient],
|
||||
});
|
||||
|
||||
return createdRecipients.recipients[0];
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
createTemplateRecipients: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/recipient/create-many',
|
||||
summary: 'Create template recipients',
|
||||
description: 'Create multiple recipients for a template.',
|
||||
tags: ['Template Recipients'],
|
||||
},
|
||||
})
|
||||
.input(ZCreateTemplateRecipientsRequestSchema)
|
||||
.output(ZCreateTemplateRecipientsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { templateId, recipients } = input;
|
||||
|
||||
return await createTemplateRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
recipients,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
updateTemplateRecipient: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/recipient/update',
|
||||
summary: 'Update template recipient',
|
||||
description: 'Update a single recipient for a template.',
|
||||
tags: ['Template Recipients'],
|
||||
},
|
||||
})
|
||||
.input(ZUpdateTemplateRecipientRequestSchema)
|
||||
.output(ZUpdateTemplateRecipientResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { templateId, recipient } = input;
|
||||
|
||||
const updatedRecipients = await updateTemplateRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
recipients: [recipient],
|
||||
});
|
||||
|
||||
return updatedRecipients.recipients[0];
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
updateTemplateRecipients: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/recipient/update-many',
|
||||
summary: 'Update template recipients',
|
||||
description: 'Update multiple recipients for a template.',
|
||||
tags: ['Template Recipients'],
|
||||
},
|
||||
})
|
||||
.input(ZUpdateTemplateRecipientsRequestSchema)
|
||||
.output(ZUpdateTemplateRecipientsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { templateId, recipients } = input;
|
||||
|
||||
return await updateTemplateRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
recipients,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
deleteTemplateRecipient: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'DELETE',
|
||||
path: '/template/recipient/{recipientId}',
|
||||
summary: 'Delete template recipient',
|
||||
tags: ['Template Recipients'],
|
||||
},
|
||||
})
|
||||
.input(ZDeleteTemplateRecipientRequestSchema)
|
||||
.output(z.void())
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { recipientId } = input;
|
||||
|
||||
await deleteTemplateRecipient({
|
||||
recipientId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setTemplateRecipients: authenticatedProcedure
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/template/recipient/set',
|
||||
// summary: 'Set template recipients',
|
||||
// description:
|
||||
// 'This will replace all recipients attached to the template. If the array contains existing recipients, they will be updated and the original fields will be retained.',
|
||||
// tags: ['Template Recipients'],
|
||||
// },
|
||||
// })
|
||||
.input(ZSetTemplateRecipientsRequestSchema)
|
||||
.output(ZSetTemplateRecipientsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { templateId, recipients } = input;
|
||||
|
||||
return await setTemplateRecipients({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
recipients: recipients.map((recipient) => ({
|
||||
id: recipient.nativeId,
|
||||
email: recipient.email,
|
||||
name: recipient.name,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
actionAuth: recipient.actionAuth,
|
||||
})),
|
||||
});
|
||||
}),
|
||||
@ -147,4 +464,32 @@ export const recipientRouter = router({
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* Leaving this here and will remove after deployment.
|
||||
*
|
||||
* @deprecated Remove after deployment.
|
||||
*/
|
||||
addSigners: authenticatedProcedure
|
||||
.input(ZAddSignersMutationSchema)
|
||||
.output(ZSetDocumentRecipientsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, signers } = input;
|
||||
|
||||
return await setDocumentRecipients({
|
||||
userId: ctx.user.id,
|
||||
documentId,
|
||||
teamId,
|
||||
recipients: signers.map((signer) => ({
|
||||
id: signer.nativeId,
|
||||
email: signer.email,
|
||||
name: signer.name,
|
||||
role: signer.role,
|
||||
signingOrder: signer.signingOrder,
|
||||
actionAuth: signer.actionAuth,
|
||||
})),
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@ -1,24 +1,114 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
ZRecipientAccessAuthTypesSchema,
|
||||
ZRecipientActionAuthSchema,
|
||||
ZRecipientActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { RecipientRole } from '@documenso/prisma/client';
|
||||
import { FieldSchema, RecipientSchema } from '@documenso/prisma/generated/zod';
|
||||
|
||||
export const ZGetRecipientQuerySchema = z.object({
|
||||
recipientId: z.number(),
|
||||
teamId: z.number().optional(),
|
||||
});
|
||||
|
||||
export const ZAddSignersMutationSchema = z
|
||||
const ZCreateRecipientSchema = z.object({
|
||||
email: z.string().toLowerCase().email().min(1),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
accessAuth: ZRecipientAccessAuthTypesSchema.optional().nullable(),
|
||||
actionAuth: ZRecipientActionAuthTypesSchema.optional().nullable(),
|
||||
});
|
||||
|
||||
const ZUpdateRecipientSchema = z.object({
|
||||
id: z.number().describe('The ID of the recipient to update.'),
|
||||
email: z.string().toLowerCase().email().min(1).optional(),
|
||||
name: z.string().optional(),
|
||||
role: z.nativeEnum(RecipientRole).optional(),
|
||||
signingOrder: z.number().optional(),
|
||||
accessAuth: ZRecipientAccessAuthTypesSchema.optional().nullable(),
|
||||
actionAuth: ZRecipientActionAuthTypesSchema.optional().nullable(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Use this when returning base recipients from the API.
|
||||
*/
|
||||
export const ZRecipientBaseResponseSchema = RecipientSchema.pick({
|
||||
id: true,
|
||||
documentId: true,
|
||||
templateId: true,
|
||||
email: true,
|
||||
name: true,
|
||||
token: true,
|
||||
documentDeletedAt: true,
|
||||
expired: true,
|
||||
signedAt: true,
|
||||
authOptions: true,
|
||||
signingOrder: true,
|
||||
rejectionReason: true,
|
||||
role: true,
|
||||
readStatus: true,
|
||||
signingStatus: true,
|
||||
sendStatus: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* Use this when returning a full recipient from the API.
|
||||
*/
|
||||
export const ZRecipientResponseSchema = ZRecipientBaseResponseSchema.extend({
|
||||
Field: FieldSchema.array(),
|
||||
});
|
||||
|
||||
export const ZCreateDocumentRecipientRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
recipient: ZCreateRecipientSchema,
|
||||
});
|
||||
|
||||
export const ZCreateDocumentRecipientResponseSchema = ZRecipientBaseResponseSchema;
|
||||
|
||||
export const ZCreateDocumentRecipientsRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
recipients: z.array(ZCreateRecipientSchema).refine((recipients) => {
|
||||
const emails = recipients.map((recipient) => recipient.email.toLowerCase());
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZUpdateDocumentRecipientRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
recipient: ZUpdateRecipientSchema,
|
||||
});
|
||||
|
||||
export const ZUpdateDocumentRecipientResponseSchema = ZRecipientResponseSchema;
|
||||
|
||||
export const ZUpdateDocumentRecipientsRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
recipients: z.array(ZUpdateRecipientSchema).refine((recipients) => {
|
||||
const emails = recipients
|
||||
.filter((recipient) => recipient.email !== undefined)
|
||||
.map((recipient) => recipient.email?.toLowerCase());
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZUpdateDocumentRecipientsResponseSchema = z.object({
|
||||
recipients: z.array(ZRecipientResponseSchema),
|
||||
});
|
||||
|
||||
export const ZDeleteDocumentRecipientRequestSchema = z.object({
|
||||
recipientId: z.number(),
|
||||
});
|
||||
|
||||
export const ZSetDocumentRecipientsRequestSchema = z
|
||||
.object({
|
||||
documentId: z.number(),
|
||||
teamId: z.number().optional(),
|
||||
signers: z.array(
|
||||
recipients: z.array(
|
||||
z.object({
|
||||
nativeId: z.number().optional(),
|
||||
email: z.string().email().min(1),
|
||||
email: z.string().toLowerCase().email().min(1),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
@ -28,7 +118,7 @@ export const ZAddSignersMutationSchema = z
|
||||
})
|
||||
.refine(
|
||||
(schema) => {
|
||||
const emails = schema.signers.map((signer) => signer.email.toLowerCase());
|
||||
const emails = schema.recipients.map((recipient) => recipient.email.toLowerCase());
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
},
|
||||
@ -36,16 +126,59 @@ export const ZAddSignersMutationSchema = z
|
||||
{ message: 'Signers must have unique emails', path: ['signers__root'] },
|
||||
);
|
||||
|
||||
export type TAddSignersMutationSchema = z.infer<typeof ZAddSignersMutationSchema>;
|
||||
export const ZCreateTemplateRecipientRequestSchema = z.object({
|
||||
templateId: z.number(),
|
||||
recipient: ZCreateRecipientSchema,
|
||||
});
|
||||
|
||||
export const ZAddTemplateSignersMutationSchema = z
|
||||
export const ZCreateTemplateRecipientResponseSchema = ZRecipientBaseResponseSchema;
|
||||
|
||||
export const ZCreateTemplateRecipientsRequestSchema = z.object({
|
||||
templateId: z.number(),
|
||||
recipients: z.array(ZCreateRecipientSchema).refine((recipients) => {
|
||||
const emails = recipients.map((recipient) => recipient.email);
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZUpdateTemplateRecipientRequestSchema = z.object({
|
||||
templateId: z.number(),
|
||||
recipient: ZUpdateRecipientSchema,
|
||||
});
|
||||
|
||||
export const ZUpdateTemplateRecipientResponseSchema = ZRecipientResponseSchema;
|
||||
|
||||
export const ZUpdateTemplateRecipientsRequestSchema = z.object({
|
||||
templateId: z.number(),
|
||||
recipients: z.array(ZUpdateRecipientSchema).refine((recipients) => {
|
||||
const emails = recipients
|
||||
.filter((recipient) => recipient.email !== undefined)
|
||||
.map((recipient) => recipient.email);
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZUpdateTemplateRecipientsResponseSchema = z.object({
|
||||
recipients: z.array(ZRecipientResponseSchema).refine((recipients) => {
|
||||
const emails = recipients.map((recipient) => recipient.email);
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZDeleteTemplateRecipientRequestSchema = z.object({
|
||||
recipientId: z.number(),
|
||||
});
|
||||
|
||||
export const ZSetTemplateRecipientsRequestSchema = z
|
||||
.object({
|
||||
teamId: z.number().optional(),
|
||||
templateId: z.number(),
|
||||
signers: z.array(
|
||||
recipients: z.array(
|
||||
z.object({
|
||||
nativeId: z.number().optional(),
|
||||
email: z.string().email().min(1),
|
||||
email: z.string().toLowerCase().email().min(1),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
@ -55,16 +188,14 @@ export const ZAddTemplateSignersMutationSchema = z
|
||||
})
|
||||
.refine(
|
||||
(schema) => {
|
||||
const emails = schema.signers.map((signer) => signer.email.toLowerCase());
|
||||
const emails = schema.recipients.map((recipient) => recipient.email);
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
},
|
||||
// Dirty hack to handle errors when .root is populated for an array type
|
||||
{ message: 'Signers must have unique emails', path: ['signers__root'] },
|
||||
{ message: 'Recipients must have unique emails', path: ['recipients__root'] },
|
||||
);
|
||||
|
||||
export type TAddTemplateSignersMutationSchema = z.infer<typeof ZAddTemplateSignersMutationSchema>;
|
||||
|
||||
export const ZCompleteDocumentWithTokenMutationSchema = z.object({
|
||||
token: z.string(),
|
||||
documentId: z.number(),
|
||||
@ -85,3 +216,32 @@ export const ZRejectDocumentWithTokenMutationSchema = z.object({
|
||||
export type TRejectDocumentWithTokenMutationSchema = z.infer<
|
||||
typeof ZRejectDocumentWithTokenMutationSchema
|
||||
>;
|
||||
|
||||
/**
|
||||
* Legacy schema. Remove after deployment (when addSigners trpc is removed).
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export const ZAddSignersMutationSchema = z
|
||||
.object({
|
||||
documentId: z.number(),
|
||||
signers: z.array(
|
||||
z.object({
|
||||
nativeId: z.number().optional(),
|
||||
email: z.string().toLowerCase().email().min(1),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
actionAuth: ZRecipientActionAuthTypesSchema.optional().nullable(),
|
||||
}),
|
||||
),
|
||||
})
|
||||
.refine(
|
||||
(schema) => {
|
||||
const emails = schema.signers.map((signer) => signer.email.toLowerCase());
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
},
|
||||
// Dirty hack to handle errors when .root is populated for an array type
|
||||
{ message: 'Signers must have unique emails', path: ['signers__root'] },
|
||||
);
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { isValidLanguageCode } from '@documenso/lib/constants/i18n';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import {
|
||||
ZGetDocumentWithDetailsByIdResponseSchema,
|
||||
@ -45,10 +43,9 @@ import {
|
||||
toggleTemplateDirectLink,
|
||||
} from '@documenso/lib/server-only/template/toggle-template-direct-link';
|
||||
import {
|
||||
ZUpdateTemplateSettingsResponseSchema,
|
||||
updateTemplateSettings,
|
||||
} from '@documenso/lib/server-only/template/update-template-settings';
|
||||
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
ZUpdateTemplateResponseSchema,
|
||||
updateTemplate,
|
||||
} from '@documenso/lib/server-only/template/update-template';
|
||||
import type { Document } from '@documenso/prisma/client';
|
||||
|
||||
import { authenticatedProcedure, maybeAuthenticatedProcedure, router } from '../trpc';
|
||||
@ -63,10 +60,8 @@ import {
|
||||
ZFindTemplatesQuerySchema,
|
||||
ZGetTemplateByIdQuerySchema,
|
||||
ZMoveTemplatesToTeamSchema,
|
||||
ZSetSigningOrderForTemplateMutationSchema,
|
||||
ZToggleTemplateDirectLinkMutationSchema,
|
||||
ZUpdateTemplateSettingsMutationSchema,
|
||||
ZUpdateTemplateTypedSignatureSettingsMutationSchema,
|
||||
ZUpdateTemplateRequestSchema,
|
||||
} from './schema';
|
||||
|
||||
export const templateRouter = router({
|
||||
@ -77,7 +72,7 @@ export const templateRouter = router({
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/template/find',
|
||||
path: '/template',
|
||||
summary: 'Find templates',
|
||||
description: 'Find templates based on a search criteria',
|
||||
tags: ['Template'],
|
||||
@ -86,8 +81,11 @@ export const templateRouter = router({
|
||||
.input(ZFindTemplatesQuerySchema)
|
||||
.output(ZFindTemplatesResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
|
||||
return await findTemplates({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
@ -107,7 +105,8 @@ export const templateRouter = router({
|
||||
.input(ZGetTemplateByIdQuerySchema)
|
||||
.output(ZGetTemplateByIdResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { templateId, teamId } = input;
|
||||
const { teamId } = ctx;
|
||||
const { templateId } = input;
|
||||
|
||||
return await getTemplateById({
|
||||
id: templateId,
|
||||
@ -117,22 +116,25 @@ export const templateRouter = router({
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Wait until RR7 so we can passthrough documents.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
createTemplate: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/create',
|
||||
summary: 'Create template',
|
||||
description: 'Create a new template',
|
||||
tags: ['Template'],
|
||||
},
|
||||
})
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/template/create',
|
||||
// summary: 'Create template',
|
||||
// description: 'Create a new template',
|
||||
// tags: ['Template'],
|
||||
// },
|
||||
// })
|
||||
.input(ZCreateTemplateMutationSchema)
|
||||
.output(ZCreateTemplateResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, title, templateDocumentDataId } = input;
|
||||
const { teamId } = ctx;
|
||||
const { title, templateDocumentDataId } = input;
|
||||
|
||||
return await createTemplate({
|
||||
userId: ctx.user.id,
|
||||
@ -149,30 +151,25 @@ export const templateRouter = router({
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/{templateId}',
|
||||
path: '/template/update',
|
||||
summary: 'Update template',
|
||||
tags: ['Template'],
|
||||
},
|
||||
})
|
||||
.input(ZUpdateTemplateSettingsMutationSchema)
|
||||
.output(ZUpdateTemplateSettingsResponseSchema)
|
||||
.input(ZUpdateTemplateRequestSchema)
|
||||
.output(ZUpdateTemplateResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { templateId, teamId, data, meta } = input;
|
||||
const { teamId } = ctx;
|
||||
const { templateId, data, meta } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
|
||||
|
||||
return await updateTemplateSettings({
|
||||
return await updateTemplate({
|
||||
userId,
|
||||
teamId,
|
||||
templateId,
|
||||
data,
|
||||
meta: {
|
||||
...meta,
|
||||
language: isValidLanguageCode(meta?.language) ? meta?.language : undefined,
|
||||
},
|
||||
requestMetadata,
|
||||
meta,
|
||||
});
|
||||
}),
|
||||
|
||||
@ -183,7 +180,7 @@ export const templateRouter = router({
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/{templateId}/duplicate',
|
||||
path: '/template/duplicate',
|
||||
summary: 'Duplicate template',
|
||||
tags: ['Template'],
|
||||
},
|
||||
@ -191,7 +188,8 @@ export const templateRouter = router({
|
||||
.input(ZDuplicateTemplateMutationSchema)
|
||||
.output(ZDuplicateTemplateResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, templateId } = input;
|
||||
const { teamId } = ctx;
|
||||
const { templateId } = input;
|
||||
|
||||
return await duplicateTemplate({
|
||||
userId: ctx.user.id,
|
||||
@ -206,8 +204,8 @@ export const templateRouter = router({
|
||||
deleteTemplate: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/{templateId}/delete',
|
||||
method: 'DELETE',
|
||||
path: '/template/{templateId}',
|
||||
summary: 'Delete template',
|
||||
tags: ['Template'],
|
||||
},
|
||||
@ -215,7 +213,8 @@ export const templateRouter = router({
|
||||
.input(ZDeleteTemplateMutationSchema)
|
||||
.output(z.void())
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { templateId, teamId } = input;
|
||||
const { teamId } = ctx;
|
||||
const { templateId } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
@ -229,7 +228,7 @@ export const templateRouter = router({
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/{templateId}/use',
|
||||
path: '/template/use',
|
||||
summary: 'Use template',
|
||||
description: 'Use the template to create a document',
|
||||
tags: ['Template'],
|
||||
@ -238,7 +237,8 @@ export const templateRouter = router({
|
||||
.input(ZCreateDocumentFromTemplateMutationSchema)
|
||||
.output(ZGetDocumentWithDetailsByIdResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { templateId, teamId, recipients, distributeDocument, customDocumentDataId } = input;
|
||||
const { teamId } = ctx;
|
||||
const { templateId, recipients, distributeDocument, customDocumentDataId } = input;
|
||||
|
||||
const limits = await getServerLimits({ email: ctx.user.email, teamId });
|
||||
|
||||
@ -246,15 +246,13 @@ export const templateRouter = router({
|
||||
throw new Error('You have reached your document limit.');
|
||||
}
|
||||
|
||||
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
|
||||
|
||||
const document: Document = await createDocumentFromTemplate({
|
||||
templateId,
|
||||
teamId,
|
||||
userId: ctx.user.id,
|
||||
recipients,
|
||||
customDocumentDataId,
|
||||
requestMetadata,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
if (distributeDocument) {
|
||||
@ -262,7 +260,7 @@ export const templateRouter = router({
|
||||
documentId: document.id,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
requestMetadata,
|
||||
requestMetadata: ctx.metadata,
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
|
||||
@ -278,18 +276,20 @@ export const templateRouter = router({
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Leaving this endpoint as private for now until there is a use case for it.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
createDocumentFromDirectTemplate: maybeAuthenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/use',
|
||||
summary: 'Use direct template',
|
||||
description: 'Use a direct template to create a document',
|
||||
tags: ['Template'],
|
||||
},
|
||||
})
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/template/direct/use',
|
||||
// summary: 'Use direct template',
|
||||
// description: 'Use a direct template to create a document',
|
||||
// tags: ['Template'],
|
||||
// },
|
||||
// })
|
||||
.input(ZCreateDocumentFromDirectTemplateMutationSchema)
|
||||
.output(ZCreateDocumentFromDirectTemplateResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
@ -302,8 +302,6 @@ export const templateRouter = router({
|
||||
templateUpdatedAt,
|
||||
} = input;
|
||||
|
||||
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
|
||||
|
||||
return await createDocumentFromDirectTemplate({
|
||||
directRecipientName,
|
||||
directRecipientEmail,
|
||||
@ -318,25 +316,7 @@ export const templateRouter = router({
|
||||
email: ctx.user.email,
|
||||
}
|
||||
: undefined,
|
||||
requestMetadata,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setSigningOrderForTemplate: authenticatedProcedure
|
||||
.input(ZSetSigningOrderForTemplateMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { templateId, teamId, signingOrder } = input;
|
||||
|
||||
return await updateTemplateSettings({
|
||||
templateId,
|
||||
teamId,
|
||||
data: {},
|
||||
meta: { signingOrder },
|
||||
userId: ctx.user.id,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
@ -347,7 +327,7 @@ export const templateRouter = router({
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/{templateId}/direct/create',
|
||||
path: '/template/direct/create',
|
||||
summary: 'Create direct link',
|
||||
description: 'Create a direct link for a template',
|
||||
tags: ['Template'],
|
||||
@ -356,7 +336,8 @@ export const templateRouter = router({
|
||||
.input(ZCreateTemplateDirectLinkMutationSchema)
|
||||
.output(ZCreateTemplateDirectLinkResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { templateId, teamId, directRecipientId } = input;
|
||||
const { teamId } = ctx;
|
||||
const { templateId, directRecipientId } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
@ -370,7 +351,7 @@ export const templateRouter = router({
|
||||
});
|
||||
}
|
||||
|
||||
return await createTemplateDirectLink({ userId, templateId, directRecipientId });
|
||||
return await createTemplateDirectLink({ userId, teamId, templateId, directRecipientId });
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -379,8 +360,8 @@ export const templateRouter = router({
|
||||
deleteTemplateDirectLink: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/{templateId}/direct/delete',
|
||||
method: 'DELETE',
|
||||
path: '/template/direct/{templateId}',
|
||||
summary: 'Delete direct link',
|
||||
description: 'Delete a direct link for a template',
|
||||
tags: ['Template'],
|
||||
@ -389,11 +370,12 @@ export const templateRouter = router({
|
||||
.input(ZDeleteTemplateDirectLinkMutationSchema)
|
||||
.output(z.void())
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { templateId } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
await deleteTemplateDirectLink({ userId, templateId });
|
||||
await deleteTemplateDirectLink({ userId, teamId, templateId });
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -412,11 +394,12 @@ export const templateRouter = router({
|
||||
.input(ZToggleTemplateDirectLinkMutationSchema)
|
||||
.output(ZToggleTemplateDirectLinkResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { templateId, enabled } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
return await toggleTemplateDirectLink({ userId, templateId, enabled });
|
||||
return await toggleTemplateDirectLink({ userId, teamId, templateId, enabled });
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -426,7 +409,7 @@ export const templateRouter = router({
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/{templateId}/move',
|
||||
path: '/template/move',
|
||||
summary: 'Move template',
|
||||
description: 'Move a template to a team',
|
||||
tags: ['Template'],
|
||||
@ -444,37 +427,4 @@ export const templateRouter = router({
|
||||
userId,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
updateTemplateTypedSignatureSettings: authenticatedProcedure
|
||||
.input(ZUpdateTemplateTypedSignatureSettingsMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { templateId, teamId, typedSignatureEnabled } = input;
|
||||
|
||||
const template = await getTemplateById({
|
||||
id: templateId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
}).catch(() => null);
|
||||
|
||||
if (!template) {
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: 'Template not found',
|
||||
});
|
||||
}
|
||||
|
||||
return await updateTemplateSettings({
|
||||
templateId,
|
||||
teamId,
|
||||
userId: ctx.user.id,
|
||||
data: {},
|
||||
meta: {
|
||||
typedSignatureEnabled,
|
||||
},
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@ -1,25 +1,27 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
||||
import {
|
||||
DocumentDistributionMethod,
|
||||
DocumentSigningOrder,
|
||||
DocumentVisibility,
|
||||
TemplateType,
|
||||
} from '@documenso/prisma/client';
|
||||
import { DocumentSigningOrder, DocumentVisibility, TemplateType } from '@documenso/prisma/client';
|
||||
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
ZDocumentMetaLanguageSchema,
|
||||
ZDocumentMetaMessageSchema,
|
||||
ZDocumentMetaRedirectUrlSchema,
|
||||
ZDocumentMetaSubjectSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
ZDocumentMetaTypedSignatureEnabledSchema,
|
||||
} from '../document-router/schema';
|
||||
import { ZSignFieldWithTokenMutationSchema } from '../field-router/schema';
|
||||
|
||||
export const ZCreateTemplateMutationSchema = z.object({
|
||||
title: z.string().min(1).trim(),
|
||||
teamId: z.number().optional(),
|
||||
templateDocumentDataId: z.string().min(1),
|
||||
});
|
||||
|
||||
@ -34,119 +36,119 @@ export const ZCreateDocumentFromDirectTemplateMutationSchema = z.object({
|
||||
|
||||
export const ZCreateDocumentFromTemplateMutationSchema = z.object({
|
||||
templateId: z.number(),
|
||||
teamId: z.number().optional(),
|
||||
recipients: z
|
||||
.array(
|
||||
z.object({
|
||||
id: z.number(),
|
||||
id: z.number().describe('The ID of the recipient in the template.'),
|
||||
email: z.string().email(),
|
||||
name: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.describe('The information of the recipients to create the document with.')
|
||||
.refine((recipients) => {
|
||||
const emails = recipients.map((signer) => signer.email);
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
}, 'Recipients must have unique emails'),
|
||||
distributeDocument: z.boolean().optional(),
|
||||
customDocumentDataId: z.string().optional(),
|
||||
distributeDocument: z
|
||||
.boolean()
|
||||
.describe('Whether to create the document as pending and distribute it to recipients.')
|
||||
.optional(),
|
||||
customDocumentDataId: z
|
||||
.string()
|
||||
.describe(
|
||||
'The data ID of an alternative PDF to use when creating the document. If not provided, the PDF attached to the template will be used.',
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const ZDuplicateTemplateMutationSchema = z.object({
|
||||
templateId: z.number(),
|
||||
teamId: z.number().optional(),
|
||||
});
|
||||
|
||||
export const ZCreateTemplateDirectLinkMutationSchema = z.object({
|
||||
templateId: z.number().min(1),
|
||||
teamId: z.number().optional(),
|
||||
directRecipientId: z.number().min(1).optional(),
|
||||
templateId: z.number(),
|
||||
directRecipientId: z
|
||||
.number()
|
||||
.describe(
|
||||
'The of the recipient in the current template to transform into the primary recipient when the template is used.',
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const ZDeleteTemplateDirectLinkMutationSchema = z.object({
|
||||
templateId: z.number().min(1),
|
||||
templateId: z.number(),
|
||||
});
|
||||
|
||||
export const ZToggleTemplateDirectLinkMutationSchema = z.object({
|
||||
templateId: z.number().min(1),
|
||||
templateId: z.number(),
|
||||
enabled: z.boolean(),
|
||||
});
|
||||
|
||||
export const ZDeleteTemplateMutationSchema = z.object({
|
||||
templateId: z.number().min(1),
|
||||
teamId: z.number().optional(),
|
||||
templateId: z.number(),
|
||||
});
|
||||
|
||||
export const MAX_TEMPLATE_PUBLIC_TITLE_LENGTH = 50;
|
||||
export const MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH = 256;
|
||||
|
||||
export const ZUpdateTemplateSettingsMutationSchema = z.object({
|
||||
export const ZUpdateTemplateRequestSchema = z.object({
|
||||
templateId: z.number(),
|
||||
teamId: z.number().min(1).optional(),
|
||||
data: z.object({
|
||||
title: z.string().min(1).optional(),
|
||||
externalId: z.string().nullish(),
|
||||
visibility: z.nativeEnum(DocumentVisibility).optional(),
|
||||
globalAccessAuth: ZDocumentAccessAuthTypesSchema.nullable().optional(),
|
||||
globalActionAuth: ZDocumentActionAuthTypesSchema.nullable().optional(),
|
||||
publicTitle: z.string().trim().min(1).max(MAX_TEMPLATE_PUBLIC_TITLE_LENGTH).optional(),
|
||||
publicDescription: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH)
|
||||
.optional(),
|
||||
type: z.nativeEnum(TemplateType).optional(),
|
||||
language: z
|
||||
.union([z.string(), z.enum(SUPPORTED_LANGUAGE_CODES)])
|
||||
.optional()
|
||||
.default('en'),
|
||||
}),
|
||||
data: z
|
||||
.object({
|
||||
title: z.string().min(1).optional(),
|
||||
externalId: z.string().nullish(),
|
||||
visibility: z.nativeEnum(DocumentVisibility).optional(),
|
||||
globalAccessAuth: ZDocumentAccessAuthTypesSchema.nullable().optional(),
|
||||
globalActionAuth: ZDocumentActionAuthTypesSchema.nullable().optional(),
|
||||
publicTitle: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(MAX_TEMPLATE_PUBLIC_TITLE_LENGTH)
|
||||
.describe(
|
||||
'The title of the template that will be displayed to the public. Only applicable for public templates.',
|
||||
)
|
||||
.optional(),
|
||||
publicDescription: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH)
|
||||
.describe(
|
||||
'The description of the template that will be displayed to the public. Only applicable for public templates.',
|
||||
)
|
||||
.optional(),
|
||||
type: z.nativeEnum(TemplateType).optional(),
|
||||
})
|
||||
.optional(),
|
||||
meta: z
|
||||
.object({
|
||||
subject: z.string(),
|
||||
message: z.string(),
|
||||
timezone: z.string(),
|
||||
dateFormat: z.string(),
|
||||
distributionMethod: z.nativeEnum(DocumentDistributionMethod),
|
||||
emailSettings: ZDocumentEmailSettingsSchema,
|
||||
redirectUrl: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
|
||||
message:
|
||||
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
||||
}),
|
||||
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
||||
typedSignatureEnabled: z.boolean().optional(),
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
message: ZDocumentMetaMessageSchema.optional(),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const ZSetSigningOrderForTemplateMutationSchema = z.object({
|
||||
templateId: z.number(),
|
||||
teamId: z.number().optional(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder),
|
||||
});
|
||||
|
||||
export const ZFindTemplatesQuerySchema = ZFindSearchParamsSchema.extend({
|
||||
teamId: z.number().optional(),
|
||||
type: z.nativeEnum(TemplateType).optional(),
|
||||
type: z.nativeEnum(TemplateType).describe('Filter templates by type.').optional(),
|
||||
});
|
||||
|
||||
export const ZGetTemplateByIdQuerySchema = z.object({
|
||||
templateId: z.number().min(1),
|
||||
teamId: z.number().optional(),
|
||||
});
|
||||
|
||||
export const ZMoveTemplatesToTeamSchema = z.object({
|
||||
templateId: z.number(),
|
||||
teamId: z.number(),
|
||||
});
|
||||
|
||||
export const ZUpdateTemplateTypedSignatureSettingsMutationSchema = z.object({
|
||||
templateId: z.number(),
|
||||
teamId: z.number().optional(),
|
||||
typedSignatureEnabled: z.boolean(),
|
||||
templateId: z.number().describe('The ID of the template to move to.'),
|
||||
teamId: z.number().describe('The ID of the team to move the template to.'),
|
||||
});
|
||||
|
||||
export type TCreateTemplateMutationSchema = z.infer<typeof ZCreateTemplateMutationSchema>;
|
||||
|
||||
@ -5,6 +5,8 @@ import type { OpenApiMeta } from 'trpc-openapi';
|
||||
import { AppError, genericErrorCodeToTrpcErrorCodeMap } from '@documenso/lib/errors/app-error';
|
||||
import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin';
|
||||
import { getApiTokenByToken } from '@documenso/lib/server-only/public-api/get-api-token-by-token';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
|
||||
import type { TrpcContext } from './context';
|
||||
|
||||
@ -62,8 +64,23 @@ export const authenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
|
||||
ctx: {
|
||||
...ctx,
|
||||
user: apiToken.user,
|
||||
teamId: apiToken.teamId || undefined,
|
||||
session: null,
|
||||
source: 'api',
|
||||
metadata: {
|
||||
...ctx.metadata,
|
||||
auditUser: apiToken.team
|
||||
? {
|
||||
id: null,
|
||||
email: null,
|
||||
name: apiToken.team.name,
|
||||
}
|
||||
: {
|
||||
id: apiToken.user.id,
|
||||
email: apiToken.user.email,
|
||||
name: apiToken.user.name,
|
||||
},
|
||||
auth: 'api',
|
||||
} satisfies ApiRequestMetadata,
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -71,7 +88,7 @@ export const authenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
|
||||
if (!ctx.session) {
|
||||
throw new TRPCError({
|
||||
code: 'UNAUTHORIZED',
|
||||
message: 'You must be logged in to perform this action.',
|
||||
message: 'Invalid session or API token.',
|
||||
});
|
||||
}
|
||||
|
||||
@ -80,17 +97,39 @@ export const authenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
|
||||
...ctx,
|
||||
user: ctx.user,
|
||||
session: ctx.session,
|
||||
source: 'app',
|
||||
metadata: {
|
||||
...ctx.metadata,
|
||||
auditUser: {
|
||||
id: ctx.user.id,
|
||||
name: ctx.user.name,
|
||||
email: ctx.user.email,
|
||||
},
|
||||
auth: 'session',
|
||||
} satisfies ApiRequestMetadata,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
export const maybeAuthenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
|
||||
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
|
||||
|
||||
return await next({
|
||||
ctx: {
|
||||
...ctx,
|
||||
user: ctx.user,
|
||||
session: ctx.session,
|
||||
metadata: {
|
||||
...ctx.metadata,
|
||||
auditUser: ctx.user
|
||||
? {
|
||||
id: ctx.user.id,
|
||||
name: ctx.user.name,
|
||||
email: ctx.user.email,
|
||||
}
|
||||
: undefined,
|
||||
requestMetadata,
|
||||
auth: ctx.session ? 'session' : null,
|
||||
} satisfies ApiRequestMetadata,
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -117,6 +156,15 @@ export const adminMiddleware = t.middleware(async ({ ctx, next }) => {
|
||||
...ctx,
|
||||
user: ctx.user,
|
||||
session: ctx.session,
|
||||
metadata: {
|
||||
...ctx.metadata,
|
||||
auditUser: {
|
||||
id: ctx.user.id,
|
||||
name: ctx.user.name,
|
||||
email: ctx.user.email,
|
||||
},
|
||||
auth: 'session',
|
||||
} satisfies ApiRequestMetadata,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@ -2,7 +2,6 @@ import { disableTwoFactorAuthentication } from '@documenso/lib/server-only/2fa/d
|
||||
import { enableTwoFactorAuthentication } from '@documenso/lib/server-only/2fa/enable-2fa';
|
||||
import { setupTwoFactorAuthentication } from '@documenso/lib/server-only/2fa/setup-2fa';
|
||||
import { viewBackupCodes } from '@documenso/lib/server-only/2fa/view-backup-codes';
|
||||
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
|
||||
import { authenticatedProcedure, router } from '../trpc';
|
||||
import {
|
||||
@ -28,7 +27,7 @@ export const twoFactorAuthenticationRouter = router({
|
||||
return await enableTwoFactorAuthentication({
|
||||
user,
|
||||
code,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
}),
|
||||
|
||||
@ -41,7 +40,7 @@ export const twoFactorAuthenticationRouter = router({
|
||||
user,
|
||||
totpCode: input.totpCode,
|
||||
backupCode: input.backupCode,
|
||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
}),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user