fix: refactor api routes

This commit is contained in:
David Nguyen
2024-12-30 21:01:03 +11:00
parent df33fbf91b
commit 22665543c0
100 changed files with 2268 additions and 2303 deletions

View File

@ -0,0 +1,64 @@
import { z } from 'zod';
import { createDocumentFromDirectTemplate } from '@documenso/lib/server-only/template/create-document-from-direct-template';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { ZSignFieldWithTokenMutationSchema } from '../field-router/schema';
import { maybeAuthenticatedProcedure } from '../trpc';
export const ZCreateDocumentFromTemplateRequestSchema = z.object({
directRecipientName: z.string().optional(),
directRecipientEmail: z.string().email(),
directTemplateToken: z.string().min(1),
directTemplateExternalId: z.string().optional(),
signedFieldValues: z.array(ZSignFieldWithTokenMutationSchema),
templateUpdatedAt: z.date(),
});
export const ZCreateDocumentFromTemplateResponseSchema = z.object({
token: z.string(),
documentId: z.number(),
recipientId: z.number(),
});
export const createDocumentFromTemplateRoute = maybeAuthenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/use',
summary: 'Use direct template',
description: 'Use a direct template to create a document',
tags: ['Template'],
},
})
.input(ZCreateDocumentFromTemplateRequestSchema)
.output(ZCreateDocumentFromTemplateResponseSchema)
.mutation(async ({ input, ctx }) => {
const {
directRecipientName,
directRecipientEmail,
directTemplateToken,
directTemplateExternalId,
signedFieldValues,
templateUpdatedAt,
} = input;
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
return await createDocumentFromDirectTemplate({
directRecipientName,
directRecipientEmail,
directTemplateToken,
directTemplateExternalId,
signedFieldValues,
templateUpdatedAt,
user: ctx.user
? {
id: ctx.user.id,
name: ctx.user.name || undefined,
email: ctx.user.email,
}
: undefined,
requestMetadata,
});
});

View File

@ -0,0 +1,94 @@
import { z } from 'zod';
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
import { AppError } from '@documenso/lib/errors/app-error';
import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import type { Document } from '@documenso/prisma/client';
import {
DocumentDataSchema,
DocumentMetaSchema,
DocumentSchema,
FieldSchema,
RecipientSchema,
} from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZCreateDocumentFromTemplateRequestSchema = z.object({
templateId: z.number(),
teamId: z.number().optional(),
recipients: z
.array(
z.object({
id: z.number(),
email: z.string().email(),
name: z.string().optional(),
}),
)
.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(),
});
export const ZCreateDocumentFromTemplateResponseSchema = DocumentSchema.extend({
documentData: DocumentDataSchema,
documentMeta: DocumentMetaSchema.nullable(),
Recipient: RecipientSchema.array(),
Field: FieldSchema.array(),
});
export const createDocumentFromTemplateRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/use',
summary: 'Use template',
description: 'Use the template to create a document',
tags: ['Template'],
},
})
.input(ZCreateDocumentFromTemplateRequestSchema)
.output(ZCreateDocumentFromTemplateResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId, recipients, distributeDocument } = input;
const limits = await getServerLimits({ email: ctx.user.email, teamId });
if (limits.remaining.documents === 0) {
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,
requestMetadata,
});
if (distributeDocument) {
await sendDocument({
documentId: document.id,
userId: ctx.user.id,
teamId,
requestMetadata,
}).catch((err) => {
console.error(err);
throw new AppError('DOCUMENT_SEND_FAILED');
});
}
return getDocumentWithDetailsById({
documentId: document.id,
userId: ctx.user.id,
teamId,
});
});

View File

@ -0,0 +1,47 @@
import { z } from 'zod';
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { createTemplateDirectLink } from '@documenso/lib/server-only/template/create-template-direct-link';
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
import { TemplateDirectLinkSchema } from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZCreateTemplateDirectLinkRequestSchema = z.object({
templateId: z.number().min(1),
teamId: z.number().optional(),
directRecipientId: z.number().min(1).optional(),
});
export const ZCreateTemplateDirectLinkResponseSchema = TemplateDirectLinkSchema;
export const createTemplateDirectLinkRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/direct/create',
summary: 'Create direct link',
description: 'Create a direct link for a template',
tags: ['Template'],
},
})
.input(ZCreateTemplateDirectLinkRequestSchema)
.output(ZCreateTemplateDirectLinkResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId, directRecipientId } = input;
const userId = ctx.user.id;
const template = await getTemplateById({ id: templateId, teamId, userId: ctx.user.id });
const limits = await getServerLimits({ email: ctx.user.email, teamId: template.teamId });
if (limits.remaining.directTemplates === 0) {
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
message: 'You have reached your direct templates limit.',
});
}
return await createTemplateDirectLink({ userId, templateId, directRecipientId });
});

View File

@ -0,0 +1,37 @@
import { z } from 'zod';
import { createTemplate } from '@documenso/lib/server-only/template/create-template';
import { TemplateSchema } from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZCreateTemplateRequestSchema = z.object({
title: z.string().min(1).trim(),
teamId: z.number().optional(),
templateDocumentDataId: z.string().min(1),
});
export const ZCreateTemplateResponseSchema = TemplateSchema;
export const createTemplateRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/create',
summary: 'Create template',
description: 'Create a new template',
tags: ['Template'],
},
})
.input(ZCreateTemplateRequestSchema)
.output(ZCreateTemplateResponseSchema)
.mutation(async ({ input, ctx }) => {
const { teamId, title, templateDocumentDataId } = input;
return await createTemplate({
userId: ctx.user.id,
teamId,
title,
templateDocumentDataId,
});
});

View File

@ -0,0 +1,32 @@
import { z } from 'zod';
import { deleteTemplateDirectLink } from '@documenso/lib/server-only/template/delete-template-direct-link';
import { TemplateDirectLinkSchema } from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZDeleteTemplateDirectLinkRequestSchema = z.object({
templateId: z.number().min(1),
});
export const ZDeleteTemplateDirectLinkResponseSchema = TemplateDirectLinkSchema;
export const deleteTemplateDirectLinkRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/direct/delete',
summary: 'Delete direct link',
description: 'Delete a direct link for a template',
tags: ['Template'],
},
})
.input(ZDeleteTemplateDirectLinkResponseSchema)
.output(z.void())
.mutation(async ({ input, ctx }) => {
const { templateId } = input;
const userId = ctx.user.id;
await deleteTemplateDirectLink({ userId, templateId });
});

View File

@ -0,0 +1,33 @@
import { z } from 'zod';
import { deleteTemplate } from '@documenso/lib/server-only/template/delete-template';
import { TemplateSchema } from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
import { ZDeleteTemplateMutationSchema } from './schema';
export const ZDeleteTemplateRequestSchema = z.object({
templateId: z.number().min(1),
teamId: z.number().optional(),
});
export const ZDeleteTemplateResponseSchema = TemplateSchema;
export const deleteTemplateRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/delete',
summary: 'Delete template',
tags: ['Template'],
},
})
.input(ZDeleteTemplateMutationSchema)
.output(z.void())
.mutation(async ({ input, ctx }) => {
const { templateId, teamId } = input;
const userId = ctx.user.id;
await deleteTemplate({ userId, id: templateId, teamId });
});

View File

@ -0,0 +1,34 @@
import { z } from 'zod';
import { duplicateTemplate } from '@documenso/lib/server-only/template/duplicate-template';
import { TemplateSchema } from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZDuplicateTemplateRequestSchema = z.object({
templateId: z.number(),
teamId: z.number().optional(),
});
export const ZDuplicateTemplateResponseSchema = TemplateSchema;
export const duplicateTemplateRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/duplicate',
summary: 'Duplicate template',
tags: ['Template'],
},
})
.input(ZDuplicateTemplateRequestSchema)
.output(ZDuplicateTemplateResponseSchema)
.mutation(async ({ input, ctx }) => {
const { teamId, templateId } = input;
return await duplicateTemplate({
userId: ctx.user.id,
teamId,
templateId,
});
});

View File

@ -0,0 +1,62 @@
import { z } from 'zod';
import { findTemplates } from '@documenso/lib/server-only/template/find-templates';
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
import { TemplateType } from '@documenso/prisma/client';
import {
DocumentDataSchema,
FieldSchema,
RecipientSchema,
TeamSchema,
TemplateDirectLinkSchema,
TemplateMetaSchema,
TemplateSchema,
} from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZFindTemplatesRequestSchema = ZFindSearchParamsSchema.extend({
teamId: z.number().optional(),
type: z.nativeEnum(TemplateType).optional(),
});
export const ZFindTemplatesResponseSchema = ZFindResultResponse.extend({
data: TemplateSchema.extend({
templateDocumentData: DocumentDataSchema,
team: TeamSchema.pick({
id: true,
url: true,
}).nullable(),
Field: FieldSchema.array(),
Recipient: RecipientSchema.array(),
templateMeta: TemplateMetaSchema.pick({
signingOrder: true,
distributionMethod: true,
}).nullable(),
directLink: TemplateDirectLinkSchema.pick({
token: true,
enabled: true,
}).nullable(),
}).array(), // Todo: openapi.
});
export type FindTemplateRow = z.infer<typeof ZFindTemplatesResponseSchema>['data'][number];
export const findTemplatesRoute = authenticatedProcedure
.meta({
openapi: {
method: 'GET',
path: '/template/find',
summary: 'Find templates',
description: 'Find templates based on a search criteria',
tags: ['Template'],
},
})
.input(ZFindTemplatesRequestSchema)
.output(ZFindTemplatesResponseSchema)
.query(async ({ input, ctx }) => {
return await findTemplates({
userId: ctx.user.id,
...input,
});
});

View File

@ -0,0 +1,53 @@
import { z } from 'zod';
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
import {
DocumentDataSchema,
FieldSchema,
RecipientSchema,
TemplateDirectLinkSchema,
TemplateMetaSchema,
TemplateSchema,
UserSchema,
} from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZGetTemplateRequestSchema = z.object({
templateId: z.number().min(1),
teamId: z.number().optional(),
});
export const ZGetTemplateResponseSchema = TemplateSchema.extend({
directLink: TemplateDirectLinkSchema.nullable(),
templateDocumentData: DocumentDataSchema,
templateMeta: TemplateMetaSchema.nullable(),
Recipient: RecipientSchema.array(),
Field: FieldSchema.array(),
User: UserSchema.pick({
id: true,
name: true,
email: true,
}),
});
export const getTemplateRoute = authenticatedProcedure
.meta({
openapi: {
method: 'GET',
path: '/template/{templateId}',
summary: 'Get template',
tags: ['Template'],
},
})
.input(ZGetTemplateRequestSchema)
.output(ZGetTemplateResponseSchema)
.query(async ({ input, ctx }) => {
const { templateId, teamId } = input;
return await getTemplateById({
id: templateId,
userId: ctx.user.id,
teamId,
});
});

View File

@ -0,0 +1,36 @@
import { z } from 'zod';
import { moveTemplateToTeam } from '@documenso/lib/server-only/template/move-template-to-team';
import { TemplateSchema } from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZMoveTemplateToTeamRequestSchema = z.object({
templateId: z.number(),
teamId: z.number(),
});
export const ZMoveTemplateToTeamResponseSchema = TemplateSchema;
export const moveTemplateToTeamRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/move',
summary: 'Move template',
description: 'Move a template to a team',
tags: ['Template'],
},
})
.input(ZMoveTemplateToTeamRequestSchema)
.output(ZMoveTemplateToTeamResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId } = input;
const userId = ctx.user.id;
return await moveTemplateToTeam({
templateId,
teamId,
userId,
});
});

View File

@ -1,479 +1,38 @@
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,
getDocumentWithDetailsById,
} from '@documenso/lib/server-only/document/get-document-with-details-by-id';
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
import {
ZCreateDocumentFromDirectTemplateResponseSchema,
createDocumentFromDirectTemplate,
} from '@documenso/lib/server-only/template/create-document-from-direct-template';
import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template';
import {
ZCreateTemplateResponseSchema,
createTemplate,
} from '@documenso/lib/server-only/template/create-template';
import {
ZCreateTemplateDirectLinkResponseSchema,
createTemplateDirectLink,
} from '@documenso/lib/server-only/template/create-template-direct-link';
import { deleteTemplate } from '@documenso/lib/server-only/template/delete-template';
import { deleteTemplateDirectLink } from '@documenso/lib/server-only/template/delete-template-direct-link';
import {
ZDuplicateTemplateResponseSchema,
duplicateTemplate,
} from '@documenso/lib/server-only/template/duplicate-template';
import {
ZFindTemplatesResponseSchema,
findTemplates,
} from '@documenso/lib/server-only/template/find-templates';
import {
ZGetTemplateByIdResponseSchema,
getTemplateById,
} from '@documenso/lib/server-only/template/get-template-by-id';
import {
ZMoveTemplateToTeamResponseSchema,
moveTemplateToTeam,
} from '@documenso/lib/server-only/template/move-template-to-team';
import {
ZToggleTemplateDirectLinkResponseSchema,
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';
import type { Document } from '@documenso/prisma/client';
import { authenticatedProcedure, maybeAuthenticatedProcedure, router } from '../trpc';
import {
ZCreateDocumentFromDirectTemplateMutationSchema,
ZCreateDocumentFromTemplateMutationSchema,
ZCreateTemplateDirectLinkMutationSchema,
ZCreateTemplateMutationSchema,
ZDeleteTemplateDirectLinkMutationSchema,
ZDeleteTemplateMutationSchema,
ZDuplicateTemplateMutationSchema,
ZFindTemplatesQuerySchema,
ZGetTemplateByIdQuerySchema,
ZMoveTemplatesToTeamSchema,
ZSetSigningOrderForTemplateMutationSchema,
ZToggleTemplateDirectLinkMutationSchema,
ZUpdateTemplateSettingsMutationSchema,
ZUpdateTemplateTypedSignatureSettingsMutationSchema,
} from './schema';
import { router } from '../trpc';
import { createDocumentFromTemplateRoute } from './create-document-from-template-route';
import { createTemplateDirectLinkRoute } from './create-template-direct-link-route';
import { createTemplateRoute } from './create-template-route';
import { deleteTemplateDirectLinkRoute } from './delete-template-direct-link-route';
import { deleteTemplateRoute } from './delete-template-route';
import { duplicateTemplateRoute } from './duplicate-template-route';
import { findTemplatesRoute } from './find-templates-route';
import { getTemplateRoute } from './get-template-route';
import { moveTemplateToTeamRoute } from './move-template-to-team-route';
import { setSigningOrderForTemplateRoute } from './set-signing-order-for-template-route';
import { toggleTemplateDirectLinkRoute } from './toggle-template-direct-link-route';
import { updateTemplateRoute } from './update-template-route';
import { updateTemplateTypedSignatureSettingsRoute } from './update-template-typed-signature-settings-route';
export const templateRouter = router({
/**
* @public
* Public endpoints.
*/
findTemplates: authenticatedProcedure
.meta({
openapi: {
method: 'GET',
path: '/template/find',
summary: 'Find templates',
description: 'Find templates based on a search criteria',
tags: ['Template'],
},
})
.input(ZFindTemplatesQuerySchema)
.output(ZFindTemplatesResponseSchema)
.query(async ({ input, ctx }) => {
return await findTemplates({
userId: ctx.user.id,
...input,
});
}),
findTemplates: findTemplatesRoute,
getTemplateById: getTemplateRoute,
createTemplate: createTemplateRoute,
updateTemplate: updateTemplateRoute,
duplicateTemplate: duplicateTemplateRoute,
deleteTemplate: deleteTemplateRoute,
createDocumentFromTemplate: createDocumentFromTemplateRoute,
createDocumentFromDirectTemplate: createDocumentFromTemplateRoute,
createTemplateDirectLink: createTemplateDirectLinkRoute,
deleteTemplateDirectLink: deleteTemplateDirectLinkRoute,
toggleTemplateDirectLink: toggleTemplateDirectLinkRoute,
moveTemplateToTeam: moveTemplateToTeamRoute,
/**
* @public
* Private endpoints.
*/
getTemplateById: authenticatedProcedure
.meta({
openapi: {
method: 'GET',
path: '/template/{templateId}',
summary: 'Get template',
tags: ['Template'],
},
})
.input(ZGetTemplateByIdQuerySchema)
.output(ZGetTemplateByIdResponseSchema)
.query(async ({ input, ctx }) => {
const { templateId, teamId } = input;
return await getTemplateById({
id: templateId,
userId: ctx.user.id,
teamId,
});
}),
/**
* @public
*/
createTemplate: authenticatedProcedure
.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;
return await createTemplate({
userId: ctx.user.id,
teamId,
title,
templateDocumentDataId,
});
}),
/**
* @public
*/
updateTemplate: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}',
summary: 'Update template',
tags: ['Template'],
},
})
.input(ZUpdateTemplateSettingsMutationSchema)
.output(ZUpdateTemplateSettingsResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId, data, meta } = input;
const userId = ctx.user.id;
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
return await updateTemplateSettings({
userId,
teamId,
templateId,
data,
meta: {
...meta,
language: isValidLanguageCode(meta?.language) ? meta?.language : undefined,
},
requestMetadata,
});
}),
/**
* @public
*/
duplicateTemplate: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/duplicate',
summary: 'Duplicate template',
tags: ['Template'],
},
})
.input(ZDuplicateTemplateMutationSchema)
.output(ZDuplicateTemplateResponseSchema)
.mutation(async ({ input, ctx }) => {
const { teamId, templateId } = input;
return await duplicateTemplate({
userId: ctx.user.id,
teamId,
templateId,
});
}),
/**
* @public
*/
deleteTemplate: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/delete',
summary: 'Delete template',
tags: ['Template'],
},
})
.input(ZDeleteTemplateMutationSchema)
.output(z.void())
.mutation(async ({ input, ctx }) => {
const { templateId, teamId } = input;
const userId = ctx.user.id;
await deleteTemplate({ userId, id: templateId, teamId });
}),
/**
* @public
*/
createDocumentFromTemplate: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/use',
summary: 'Use template',
description: 'Use the template to create a document',
tags: ['Template'],
},
})
.input(ZCreateDocumentFromTemplateMutationSchema)
.output(ZGetDocumentWithDetailsByIdResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId, recipients, distributeDocument } = input;
const limits = await getServerLimits({ email: ctx.user.email, teamId });
if (limits.remaining.documents === 0) {
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,
requestMetadata,
});
if (distributeDocument) {
await sendDocument({
documentId: document.id,
userId: ctx.user.id,
teamId,
requestMetadata,
}).catch((err) => {
console.error(err);
throw new AppError('DOCUMENT_SEND_FAILED');
});
}
return getDocumentWithDetailsById({
documentId: document.id,
userId: ctx.user.id,
teamId,
});
}),
/**
* @public
*/
createDocumentFromDirectTemplate: maybeAuthenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/use',
summary: 'Use direct template',
description: 'Use a direct template to create a document',
tags: ['Template'],
},
})
.input(ZCreateDocumentFromDirectTemplateMutationSchema)
.output(ZCreateDocumentFromDirectTemplateResponseSchema)
.mutation(async ({ input, ctx }) => {
const {
directRecipientName,
directRecipientEmail,
directTemplateToken,
directTemplateExternalId,
signedFieldValues,
templateUpdatedAt,
} = input;
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
return await createDocumentFromDirectTemplate({
directRecipientName,
directRecipientEmail,
directTemplateToken,
directTemplateExternalId,
signedFieldValues,
templateUpdatedAt,
user: ctx.user
? {
id: ctx.user.id,
name: ctx.user.name || undefined,
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),
});
}),
/**
* @public
*/
createTemplateDirectLink: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/direct/create',
summary: 'Create direct link',
description: 'Create a direct link for a template',
tags: ['Template'],
},
})
.input(ZCreateTemplateDirectLinkMutationSchema)
.output(ZCreateTemplateDirectLinkResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId, directRecipientId } = input;
const userId = ctx.user.id;
const template = await getTemplateById({ id: templateId, teamId, userId: ctx.user.id });
const limits = await getServerLimits({ email: ctx.user.email, teamId: template.teamId });
if (limits.remaining.directTemplates === 0) {
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
message: 'You have reached your direct templates limit.',
});
}
return await createTemplateDirectLink({ userId, templateId, directRecipientId });
}),
/**
* @public
*/
deleteTemplateDirectLink: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/direct/delete',
summary: 'Delete direct link',
description: 'Delete a direct link for a template',
tags: ['Template'],
},
})
.input(ZDeleteTemplateDirectLinkMutationSchema)
.output(z.void())
.mutation(async ({ input, ctx }) => {
const { templateId } = input;
const userId = ctx.user.id;
await deleteTemplateDirectLink({ userId, templateId });
}),
/**
* @public
*/
toggleTemplateDirectLink: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/direct/toggle',
summary: 'Toggle direct link',
description: 'Enable or disable a direct link for a template',
tags: ['Template'],
},
})
.input(ZToggleTemplateDirectLinkMutationSchema)
.output(ZToggleTemplateDirectLinkResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, enabled } = input;
const userId = ctx.user.id;
return await toggleTemplateDirectLink({ userId, templateId, enabled });
}),
/**
* @public
*/
moveTemplateToTeam: authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/move',
summary: 'Move template',
description: 'Move a template to a team',
tags: ['Template'],
},
})
.input(ZMoveTemplatesToTeamSchema)
.output(ZMoveTemplateToTeamResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId } = input;
const userId = ctx.user.id;
return await moveTemplateToTeam({
templateId,
teamId,
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),
});
}),
setSigningOrderForTemplate: setSigningOrderForTemplateRoute,
updateTemplateTypedSignatureSettings: updateTemplateTypedSignatureSettingsRoute,
});

View File

@ -0,0 +1,28 @@
import { z } from 'zod';
import { updateTemplateSettings } from '@documenso/lib/server-only/template/update-template-settings';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { DocumentSigningOrder } from '@documenso/prisma/client';
import { authenticatedProcedure } from '../trpc';
export const ZSetSigningOrderForTemplateRequestSchema = z.object({
templateId: z.number(),
teamId: z.number().optional(),
signingOrder: z.nativeEnum(DocumentSigningOrder),
});
export const setSigningOrderForTemplateRoute = authenticatedProcedure
.input(ZSetSigningOrderForTemplateRequestSchema)
.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),
});
});

View File

@ -0,0 +1,33 @@
import { z } from 'zod';
import { toggleTemplateDirectLink } from '@documenso/lib/server-only/template/toggle-template-direct-link';
import { TemplateDirectLinkSchema } from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
export const ZToggleTemplateDirectLinkRequestSchema = z.object({
templateId: z.number().min(1),
enabled: z.boolean(),
});
export const ZToggleTemplateDirectLinkResponseSchema = TemplateDirectLinkSchema;
export const toggleTemplateDirectLinkRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}/direct/toggle',
summary: 'Toggle direct link',
description: 'Enable or disable a direct link for a template',
tags: ['Template'],
},
})
.input(ZToggleTemplateDirectLinkRequestSchema)
.output(ZToggleTemplateDirectLinkResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, enabled } = input;
const userId = ctx.user.id;
return await toggleTemplateDirectLink({ userId, templateId, enabled });
});

View File

@ -0,0 +1,91 @@
import { z } from 'zod';
import { SUPPORTED_LANGUAGE_CODES, isValidLanguageCode } from '@documenso/lib/constants/i18n';
import { updateTemplateSettings } from '@documenso/lib/server-only/template/update-template-settings';
import {
ZDocumentAccessAuthTypesSchema,
ZDocumentActionAuthTypesSchema,
} from '@documenso/lib/types/document-auth';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
import { DocumentDistributionMethod, TemplateType } from '@documenso/prisma/client';
import { TemplateSchema } from '@documenso/prisma/generated/zod';
import { authenticatedProcedure } from '../trpc';
import { MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH, MAX_TEMPLATE_PUBLIC_TITLE_LENGTH } from './schema';
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(),
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'),
}),
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(),
})
.optional(),
});
export const ZUpdateTemplateResponseSchema = TemplateSchema;
export const updateTemplateRoute = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/template/{templateId}',
summary: 'Update template',
tags: ['Template'],
},
})
.input(ZUpdateTemplateRequestSchema)
.output(ZUpdateTemplateResponseSchema)
.mutation(async ({ input, ctx }) => {
const { templateId, teamId, data, meta } = input;
const userId = ctx.user.id;
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
return await updateTemplateSettings({
userId,
teamId,
templateId,
data,
meta: {
...meta,
language: isValidLanguageCode(meta?.language) ? meta?.language : undefined,
},
requestMetadata,
});
});

View File

@ -0,0 +1,44 @@
import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
import { updateTemplateSettings } from '@documenso/lib/server-only/template/update-template-settings';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { authenticatedProcedure } from '../trpc';
export const ZUpdateTemplateTypedSignatureSettingsRequestSchema = z.object({
templateId: z.number(),
teamId: z.number().optional(),
typedSignatureEnabled: z.boolean(),
});
export const updateTemplateTypedSignatureSettingsRoute = authenticatedProcedure
.input(ZUpdateTemplateTypedSignatureSettingsRequestSchema)
.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),
});
});