mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 08:13:56 +10:00
## Description
Update the API endpoint to support more actions for templates
## Changes Made
Add the following endpoints for templates:
- Get template
- Get templates
- Delete template
Get template(s) returns associated recipients and fields.
UI:
- Updated template delete button to have the destructive delete variant
## Testing Performed
Tested endpoints via /api/v1/openapi
Tested deleting templates via UI manually
## Test data
<details>
<summary>Delete template response</summary>
```json
{
"id": 32,
"type": "PRIVATE",
"title": "documenso-supporter-pledge.pdf",
"userId": 3,
"teamId": null,
"templateDocumentDataId": "clxva9b4h0001rrh7v0wdw97h",
"createdAt": "2024-06-26T03:35:45.065Z",
"updatedAt": "2024-06-26T03:35:45.065Z"
}
```
</details>
<details>
<summary>Get template response</summary>
```json
{
"id": 28,
"type": "PRIVATE",
"title": "blank_long.pdf",
"userId": 3,
"teamId": null,
"templateDocumentDataId": "clxu4vyty0003rrr52ue5ee4d",
"createdAt": "2024-06-25T08:17:38.418Z",
"updatedAt": "2024-06-26T03:36:33.890Z",
"templateMeta": {
"id": "clxvaacte0004rrh7s2k910nw",
"subject": "",
"message": "",
"timezone": "Australia/Melbourne",
"dateFormat": "yyyy-MM-dd hh:mm a",
"templateId": 28,
"redirectUrl": ""
},
"directLink": {
"token": "tBJHVFR75sC8m6hPfBTZd",
"enabled": true
},
"templateDocumentData": {
"id": "clxu4vyty0003rrr52ue5ee4d",
"type": "BYTES_64",
"data": "<PDF DATA>"
},
"Field": [
{
"id": 327,
"recipientId": 357,
"type": "SIGNATURE",
"page": 1,
"positionX": "55.8431952662722",
"positionY": "21.39588100686499",
"width": "29.58579881656805",
"height": "6.864988558352403"
},
{
"id": 328,
"recipientId": 357,
"type": "EMAIL",
"page": 1,
"positionX": "28.03254437869823",
"positionY": "72.99771167048056",
"width": "29.58579881656805",
"height": "6.864988558352403"
}
],
"Recipient": [
{
"id": 357,
"email": "direct.link@documenso.com",
"name": "Direct link recipient",
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"role": "SIGNER"
},
{
"id": 359,
"email": "example@documenso.com",
"name": "Example User",
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"role": "SIGNER"
}
]
}
```
</details>
<details>
<summary>Get templates response</summary>
```json
{
"templates": [
{
"id": 33,
"type": "PRIVATE",
"title": "documenso-supporter-pledge.pdf",
"userId": 3,
"teamId": null,
"templateDocumentDataId": "clxva9oaj0003rrh7hwdyg60o",
"createdAt": "2024-06-26T03:36:02.130Z",
"updatedAt": "2024-06-26T03:36:02.130Z",
"directLink": null,
"Field": [],
"Recipient": []
},
{
"id": 28,
"type": "PRIVATE",
"title": "blank_long.pdf",
"userId": 3,
"teamId": null,
"templateDocumentDataId": "clxu4vyty0003rrr52ue5ee4d",
"createdAt": "2024-06-25T08:17:38.418Z",
"updatedAt": "2024-06-26T03:36:33.890Z",
"directLink": {
"token": "tBJHVFR75sC8m6hPfBTZd",
"enabled": true
},
"Field": [
{
"id": 327,
"recipientId": 357,
"type": "SIGNATURE",
"page": 1,
"positionX": "55.8431952662722",
"positionY": "21.39588100686499",
"width": "29.58579881656805",
"height": "6.864988558352403"
},
{
"id": 328,
"recipientId": 357,
"type": "EMAIL",
"page": 1,
"positionX": "28.03254437869823",
"positionY": "72.99771167048056",
"width": "29.58579881656805",
"height": "6.864988558352403"
}
],
"Recipient": [
{
"id": 357,
"email": "direct.link@documenso.com",
"name": "Direct link recipient",
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"role": "SIGNER"
},
{
"id": 359,
"email": "example@documenso.com",
"name": "Example User",
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"role": "SIGNER"
}
]
}
],
"totalPages": 2
}
```
</details>
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Added support for team-based template deletion in the dashboard.
- Enhanced API to manage templates, including fetching and deleting
templates by team ID.
- **Bug Fixes**
- Improved error handling for template operations, ensuring better
feedback when templates are not found.
- **Refactor**
- Updated various components and functions to include `teamId` for more
robust template management.
- **Documentation**
- Expanded schema definitions to detail new structures for template and
team interactions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
277 lines
8.6 KiB
TypeScript
277 lines
8.6 KiB
TypeScript
import { TRPCError } from '@trpc/server';
|
|
|
|
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
|
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
|
import { 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 { createTemplate } from '@documenso/lib/server-only/template/create-template';
|
|
import { 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 { duplicateTemplate } from '@documenso/lib/server-only/template/duplicate-template';
|
|
import { getTemplateWithDetailsById } from '@documenso/lib/server-only/template/get-template-with-details-by-id';
|
|
import { toggleTemplateDirectLink } from '@documenso/lib/server-only/template/toggle-template-direct-link';
|
|
import { 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,
|
|
ZGetTemplateWithDetailsByIdQuerySchema,
|
|
ZToggleTemplateDirectLinkMutationSchema,
|
|
ZUpdateTemplateSettingsMutationSchema,
|
|
} from './schema';
|
|
|
|
export const templateRouter = router({
|
|
createTemplate: authenticatedProcedure
|
|
.input(ZCreateTemplateMutationSchema)
|
|
.mutation(async ({ input, ctx }) => {
|
|
try {
|
|
const { teamId, title, templateDocumentDataId } = input;
|
|
|
|
return await createTemplate({
|
|
userId: ctx.user.id,
|
|
teamId,
|
|
title,
|
|
templateDocumentDataId,
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
|
|
throw new TRPCError({
|
|
code: 'BAD_REQUEST',
|
|
message: 'We were unable to create this template. Please try again later.',
|
|
});
|
|
}
|
|
}),
|
|
|
|
createDocumentFromDirectTemplate: maybeAuthenticatedProcedure
|
|
.input(ZCreateDocumentFromDirectTemplateMutationSchema)
|
|
.mutation(async ({ input, ctx }) => {
|
|
try {
|
|
const { directRecipientEmail, directTemplateToken, signedFieldValues, templateUpdatedAt } =
|
|
input;
|
|
|
|
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
|
|
|
|
return await createDocumentFromDirectTemplate({
|
|
directRecipientEmail,
|
|
directTemplateToken,
|
|
signedFieldValues,
|
|
templateUpdatedAt,
|
|
user: ctx.user
|
|
? {
|
|
id: ctx.user.id,
|
|
name: ctx.user.name || undefined,
|
|
email: ctx.user.email,
|
|
}
|
|
: undefined,
|
|
requestMetadata,
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
|
|
throw AppError.parseErrorToTRPCError(err);
|
|
}
|
|
}),
|
|
|
|
createDocumentFromTemplate: authenticatedProcedure
|
|
.input(ZCreateDocumentFromTemplateMutationSchema)
|
|
.mutation(async ({ input, ctx }) => {
|
|
try {
|
|
const { templateId, teamId } = input;
|
|
|
|
const limits = await getServerLimits({ email: ctx.user.email });
|
|
|
|
if (limits.remaining.documents === 0) {
|
|
throw new Error('You have reached your document limit.');
|
|
}
|
|
|
|
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
|
|
|
|
let document: Document = await createDocumentFromTemplate({
|
|
templateId,
|
|
teamId,
|
|
userId: ctx.user.id,
|
|
recipients: input.recipients,
|
|
requestMetadata,
|
|
});
|
|
|
|
if (input.sendDocument) {
|
|
document = await sendDocument({
|
|
documentId: document.id,
|
|
userId: ctx.user.id,
|
|
teamId,
|
|
requestMetadata,
|
|
}).catch((err) => {
|
|
console.error(err);
|
|
|
|
throw new AppError('DOCUMENT_SEND_FAILED');
|
|
});
|
|
}
|
|
|
|
return document;
|
|
} catch (err) {
|
|
console.error(err);
|
|
|
|
throw AppError.parseErrorToTRPCError(err);
|
|
}
|
|
}),
|
|
|
|
duplicateTemplate: authenticatedProcedure
|
|
.input(ZDuplicateTemplateMutationSchema)
|
|
.mutation(async ({ input, ctx }) => {
|
|
try {
|
|
const { teamId, templateId } = input;
|
|
|
|
return await duplicateTemplate({
|
|
userId: ctx.user.id,
|
|
teamId,
|
|
templateId,
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
|
|
throw new TRPCError({
|
|
code: 'BAD_REQUEST',
|
|
message: 'We were unable to duplicate the template. Please try again later.',
|
|
});
|
|
}
|
|
}),
|
|
|
|
deleteTemplate: authenticatedProcedure
|
|
.input(ZDeleteTemplateMutationSchema)
|
|
.mutation(async ({ input, ctx }) => {
|
|
try {
|
|
const { id, teamId } = input;
|
|
|
|
const userId = ctx.user.id;
|
|
|
|
return await deleteTemplate({ userId, id, teamId });
|
|
} catch (err) {
|
|
console.error(err);
|
|
|
|
throw new TRPCError({
|
|
code: 'BAD_REQUEST',
|
|
message: 'We were unable to delete this template. Please try again later.',
|
|
});
|
|
}
|
|
}),
|
|
|
|
getTemplateWithDetailsById: authenticatedProcedure
|
|
.input(ZGetTemplateWithDetailsByIdQuerySchema)
|
|
.query(async ({ input, ctx }) => {
|
|
try {
|
|
return await getTemplateWithDetailsById({
|
|
id: input.id,
|
|
userId: ctx.user.id,
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
|
|
throw new TRPCError({
|
|
code: 'BAD_REQUEST',
|
|
message: 'We were unable to find this template. Please try again later.',
|
|
});
|
|
}
|
|
}),
|
|
|
|
// Todo: Add API
|
|
updateTemplateSettings: authenticatedProcedure
|
|
.input(ZUpdateTemplateSettingsMutationSchema)
|
|
.mutation(async ({ input, ctx }) => {
|
|
try {
|
|
const { templateId, teamId, data, meta } = input;
|
|
|
|
const userId = ctx.user.id;
|
|
|
|
const requestMetadata = extractNextApiRequestMetadata(ctx.req);
|
|
|
|
return await updateTemplateSettings({
|
|
userId,
|
|
teamId,
|
|
templateId,
|
|
data,
|
|
meta,
|
|
requestMetadata,
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
|
|
throw new TRPCError({
|
|
code: 'BAD_REQUEST',
|
|
message:
|
|
'We were unable to update the settings for this template. Please try again later.',
|
|
});
|
|
}
|
|
}),
|
|
|
|
createTemplateDirectLink: authenticatedProcedure
|
|
.input(ZCreateTemplateDirectLinkMutationSchema)
|
|
.mutation(async ({ input, ctx }) => {
|
|
try {
|
|
const { templateId, directRecipientId } = input;
|
|
|
|
const userId = ctx.user.id;
|
|
|
|
const limits = await getServerLimits({ email: ctx.user.email });
|
|
|
|
if (limits.remaining.directTemplates === 0) {
|
|
throw new AppError(
|
|
AppErrorCode.LIMIT_EXCEEDED,
|
|
'You have reached your direct templates limit.',
|
|
);
|
|
}
|
|
|
|
return await createTemplateDirectLink({ userId, templateId, directRecipientId });
|
|
} catch (err) {
|
|
console.error(err);
|
|
|
|
const error = AppError.parseError(err);
|
|
throw AppError.parseErrorToTRPCError(error);
|
|
}
|
|
}),
|
|
|
|
deleteTemplateDirectLink: authenticatedProcedure
|
|
.input(ZDeleteTemplateDirectLinkMutationSchema)
|
|
.mutation(async ({ input, ctx }) => {
|
|
try {
|
|
const { templateId } = input;
|
|
|
|
const userId = ctx.user.id;
|
|
|
|
return await deleteTemplateDirectLink({ userId, templateId });
|
|
} catch (err) {
|
|
console.error(err);
|
|
|
|
const error = AppError.parseError(err);
|
|
throw AppError.parseErrorToTRPCError(error);
|
|
}
|
|
}),
|
|
|
|
toggleTemplateDirectLink: authenticatedProcedure
|
|
.input(ZToggleTemplateDirectLinkMutationSchema)
|
|
.mutation(async ({ input, ctx }) => {
|
|
try {
|
|
const { templateId, enabled } = input;
|
|
|
|
const userId = ctx.user.id;
|
|
|
|
return await toggleTemplateDirectLink({ userId, templateId, enabled });
|
|
} catch (err) {
|
|
console.error(err);
|
|
|
|
const error = AppError.parseError(err);
|
|
throw AppError.parseErrorToTRPCError(error);
|
|
}
|
|
}),
|
|
});
|