Files
documenso/packages/trpc/server/template-router/router.ts
David Nguyen 963ba13aa6 feat: add more template API endpoints (#1198)
## 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 -->
2024-06-27 15:44:16 +10:00

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);
}
}),
});