feat: add trpc openapi (#1535)

This commit is contained in:
David Nguyen
2024-12-14 01:23:35 +09:00
committed by GitHub
parent f73441ee85
commit b4a7f1887d
42 changed files with 1198 additions and 341 deletions

View File

@ -56,6 +56,7 @@
"recharts": "^2.7.2", "recharts": "^2.7.2",
"remeda": "^2.17.3", "remeda": "^2.17.3",
"sharp": "0.32.6", "sharp": "0.32.6",
"trpc-openapi": "^1.2.0",
"ts-pattern": "^5.0.5", "ts-pattern": "^5.0.5",
"ua-parser-js": "^1.0.37", "ua-parser-js": "^1.0.37",
"uqr": "^0.1.2", "uqr": "^0.1.2",

View File

@ -37,10 +37,8 @@ export const DocumentPageViewRecentActivity = ({
{ {
documentId, documentId,
filterForRecentActivity: true, filterForRecentActivity: true,
orderBy: { orderByColumn: 'createdAt',
column: 'createdAt', orderByDirection: 'asc',
direction: 'asc',
},
perPage: 10, perPage: 10,
}, },
{ {

View File

@ -12,8 +12,8 @@ import {
DO_NOT_INVALIDATE_QUERY_ON_MUTATION, DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
SKIP_QUERY_BATCH_META, SKIP_QUERY_BATCH_META,
} from '@documenso/lib/constants/trpc'; } from '@documenso/lib/constants/trpc';
import type { TGetDocumentWithDetailsByIdResponse } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
import { DocumentDistributionMethod, DocumentStatus } from '@documenso/prisma/client'; import { DocumentDistributionMethod, DocumentStatus } from '@documenso/prisma/client';
import type { DocumentWithDetails } from '@documenso/prisma/types/document';
import { trpc } from '@documenso/trpc/react'; import { trpc } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils'; import { cn } from '@documenso/ui/lib/utils';
import { Card, CardContent } from '@documenso/ui/primitives/card'; import { Card, CardContent } from '@documenso/ui/primitives/card';
@ -35,7 +35,7 @@ import { useOptionalCurrentTeam } from '~/providers/team';
export type EditDocumentFormProps = { export type EditDocumentFormProps = {
className?: string; className?: string;
initialDocument: DocumentWithDetails; initialDocument: TGetDocumentWithDetailsByIdResponse;
documentRootPath: string; documentRootPath: string;
isDocumentEnterprise: boolean; isDocumentEnterprise: boolean;
}; };
@ -103,7 +103,7 @@ export const EditDocumentForm = ({
const { mutateAsync: addFields } = trpc.field.addFields.useMutation({ const { mutateAsync: addFields } = trpc.field.addFields.useMutation({
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION, ...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
onSuccess: (newFields) => { onSuccess: ({ fields: newFields }) => {
utils.document.getDocumentWithDetailsById.setData( utils.document.getDocumentWithDetailsById.setData(
{ {
documentId: initialDocument.id, documentId: initialDocument.id,
@ -134,7 +134,7 @@ export const EditDocumentForm = ({
const { mutateAsync: addSigners } = trpc.recipient.addSigners.useMutation({ const { mutateAsync: addSigners } = trpc.recipient.addSigners.useMutation({
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION, ...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
onSuccess: (newRecipients) => { onSuccess: ({ recipients: newRecipients }) => {
utils.document.getDocumentWithDetailsById.setData( utils.document.getDocumentWithDetailsById.setData(
{ {
documentId: initialDocument.id, documentId: initialDocument.id,

View File

@ -9,8 +9,8 @@ import { DateTime } from 'luxon';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params'; import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import type { FindResultResponse } from '@documenso/lib/types/search-params'; import type { TFindDocumentsResponse } from '@documenso/lib/server-only/document/find-documents';
import type { Document, Recipient, Team, User } from '@documenso/prisma/client'; import type { Team } from '@documenso/prisma/client';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table'; import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
import { DataTable } from '@documenso/ui/primitives/data-table'; import { DataTable } from '@documenso/ui/primitives/data-table';
@ -24,13 +24,7 @@ import { DataTableActionDropdown } from './data-table-action-dropdown';
import { DataTableTitle } from './data-table-title'; import { DataTableTitle } from './data-table-title';
export type DocumentsDataTableProps = { export type DocumentsDataTableProps = {
results: FindResultResponse< results: TFindDocumentsResponse;
Document & {
Recipient: Recipient[];
User: Pick<User, 'id' | 'name' | 'email'>;
team: Pick<Team, 'id' | 'url'> | null;
}
>;
showSenderColumn?: boolean; showSenderColumn?: boolean;
team?: Pick<Team, 'id' | 'url'> & { teamEmail?: string }; team?: Pick<Team, 'id' | 'url'> & { teamEmail?: string };
}; };

View File

@ -26,10 +26,8 @@ export const TemplatePageViewRecentActivity = ({
const { data, isLoading, isLoadingError, refetch } = trpc.document.findDocuments.useQuery({ const { data, isLoading, isLoadingError, refetch } = trpc.document.findDocuments.useQuery({
templateId, templateId,
teamId, teamId,
orderBy: { orderByColumn: 'createdAt',
column: 'createdAt', orderByDirection: 'asc',
direction: 'asc',
},
perPage: 5, perPage: 5,
}); });

View File

@ -4,8 +4,10 @@ import { useRouter } from 'next/navigation';
import { Trans, msg } from '@lingui/macro'; import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react'; import { useLingui } from '@lingui/react';
import { match } from 'ts-pattern';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react'; import { trpc } from '@documenso/trpc/react';
import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar'; import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
@ -51,10 +53,20 @@ export const MoveTemplateDialog = ({ templateId, open, onOpenChange }: MoveTempl
}); });
onOpenChange(false); onOpenChange(false);
}, },
onError: (error) => { onError: (err) => {
const error = AppError.parseError(err);
const errorMessage = match(error.code)
.with(
AppErrorCode.NOT_FOUND,
() => msg`Template not found or already associated with a team.`,
)
.with(AppErrorCode.UNAUTHORIZED, () => msg`You are not a member of this team.`)
.otherwise(() => msg`An error occurred while moving the template.`);
toast({ toast({
title: _(msg`Error`), title: _(msg`Error`),
description: error.message || _(msg`An error occurred while moving the template.`), description: _(errorMessage),
variant: 'destructive', variant: 'destructive',
duration: 7500, duration: 7500,
}); });

View File

@ -0,0 +1,47 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { createOpenApiNextHandler } from 'trpc-openapi';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { buildLogger } from '@documenso/lib/utils/logger';
import type { TRPCError } from '@documenso/trpc/server';
import { createTrpcContext } from '@documenso/trpc/server/context';
import { appRouter } from '@documenso/trpc/server/router';
const logger = buildLogger();
export default createOpenApiNextHandler<typeof appRouter>({
router: appRouter,
createContext: async ({ req, res }: { req: NextApiRequest; res: NextApiResponse }) =>
createTrpcContext({ req, res }),
onError: ({ error, path }: { error: TRPCError; path?: string }) => {
// Always log the error for now.
console.error(error.message);
const appError = AppError.parseError(error.cause || error);
const isAppError = error.cause instanceof AppError;
// Only log AppErrors that are explicitly set to 500 or the error code
// is in the errorCodesToAlertOn list.
const isLoggableAppError =
isAppError && (appError.statusCode === 500 || errorCodesToAlertOn.includes(appError.code));
// Only log TRPC errors that are in the `errorCodesToAlertOn` list and is
// not an AppError.
const isLoggableTrpcError = !isAppError && errorCodesToAlertOn.includes(error.code);
if (isLoggableAppError || isLoggableTrpcError) {
logger.error(error, {
method: path,
context: {
source: '/v2/api',
appError: AppError.toJSON(appError),
},
});
}
},
responseMeta: () => {},
});
const errorCodesToAlertOn = [AppErrorCode.UNKNOWN_ERROR, 'INTERNAL_SERVER_ERROR'];

View File

@ -0,0 +1,9 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { openApiDocument } from '@documenso/trpc/server/open-api';
const handler = (_req: NextApiRequest, res: NextApiResponse) => {
res.status(200).send(openApiDocument);
};
export default handler;

View File

@ -45,6 +45,7 @@ export default trpcNext.createNextApiHandler({
logger.error(error, { logger.error(error, {
method: path, method: path,
context: { context: {
source: 'trpc',
appError: AppError.toJSON(appError), appError: AppError.toJSON(appError),
}, },
}); });

314
package-lock.json generated
View File

@ -536,6 +536,7 @@
"recharts": "^2.7.2", "recharts": "^2.7.2",
"remeda": "^2.17.3", "remeda": "^2.17.3",
"sharp": "0.32.6", "sharp": "0.32.6",
"trpc-openapi": "^1.2.0",
"ts-pattern": "^5.0.5", "ts-pattern": "^5.0.5",
"ua-parser-js": "^1.0.37", "ua-parser-js": "^1.0.37",
"uqr": "^0.1.2", "uqr": "^0.1.2",
@ -3494,6 +3495,11 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/@hapi/bourne": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz",
"integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w=="
},
"node_modules/@hapi/hoek": { "node_modules/@hapi/hoek": {
"version": "9.3.0", "version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
@ -12007,6 +12013,18 @@
"node": ">=6.5" "node": ">=6.5"
} }
}, },
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.11.3", "version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
@ -14311,6 +14329,21 @@
} }
} }
}, },
"node_modules/co-body": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/co-body/-/co-body-6.2.0.tgz",
"integrity": "sha512-Kbpv2Yd1NdL1V/V4cwLVxraHDV6K8ayohr2rmH0J87Er8+zJjcTa6dAn9QMPC9CRgU8+aNajKbSf1TzDB1yKPA==",
"dependencies": {
"@hapi/bourne": "^3.0.0",
"inflation": "^2.0.0",
"qs": "^6.5.2",
"raw-body": "^2.3.3",
"type-is": "^1.6.16"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/code-block-writer": { "node_modules/code-block-writer": {
"version": "12.0.0", "version": "12.0.0",
"resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz", "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz",
@ -14612,6 +14645,14 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/consola": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz",
"integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==",
"engines": {
"node": "^14.18.0 || >=16.10.0"
}
},
"node_modules/console-control-strings": { "node_modules/console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
@ -14627,6 +14668,17 @@
"simple-wcswidth": "^1.0.1" "simple-wcswidth": "^1.0.1"
} }
}, },
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"dependencies": {
"safe-buffer": "5.2.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/content-type": { "node_modules/content-type": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
@ -14712,9 +14764,9 @@
} }
}, },
"node_modules/cookie-es": { "node_modules/cookie-es": {
"version": "1.0.0", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.0.0.tgz", "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz",
"integrity": "sha512-mWYvfOLrfEc996hlKcdABeIiPHUPC6DM2QYZdGGOvhOTbA3tjm2eBwqlJpoFdjC89NI4Qt6h0Pu06Mp+1Pj5OQ==" "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="
}, },
"node_modules/copy-anything": { "node_modules/copy-anything": {
"version": "3.0.5", "version": "3.0.5",
@ -14880,6 +14932,14 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/crossws": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.1.tgz",
"integrity": "sha512-HsZgeVYaG+b5zA+9PbIPGq4+J/CJynJuearykPsXx4V/eMhyQ5EDVg3Ak2FBZtVXCiOLu/U7IiwDHTr9MA+IKw==",
"dependencies": {
"uncrypto": "^0.1.3"
}
},
"node_modules/crypto-js": { "node_modules/crypto-js": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
@ -15587,6 +15647,11 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/defu": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="
},
"node_modules/degenerator": { "node_modules/degenerator": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
@ -15693,6 +15758,11 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/destr": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz",
"integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ=="
},
"node_modules/detect-indent": { "node_modules/detect-indent": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
@ -18123,6 +18193,14 @@
} }
} }
}, },
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/from": { "node_modules/from": {
"version": "0.1.7", "version": "0.1.7",
"resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
@ -18793,6 +18871,23 @@
"js-yaml": "bin/js-yaml.js" "js-yaml": "bin/js-yaml.js"
} }
}, },
"node_modules/h3": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/h3/-/h3-1.13.0.tgz",
"integrity": "sha512-vFEAu/yf8UMUcB4s43OaDaigcqpQd14yanmOsn+NcRX3/guSKncyE2rOYhq8RIchgJrPSs/QiIddnTTR1ddiAg==",
"dependencies": {
"cookie-es": "^1.2.2",
"crossws": ">=0.2.0 <0.4.0",
"defu": "^6.1.4",
"destr": "^2.0.3",
"iron-webcrypto": "^1.2.1",
"ohash": "^1.1.4",
"radix3": "^1.1.2",
"ufo": "^1.5.4",
"uncrypto": "^0.1.3",
"unenv": "^1.10.0"
}
},
"node_modules/hard-rejection": { "node_modules/hard-rejection": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
@ -19844,6 +19939,14 @@
"integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
"dev": true "dev": true
}, },
"node_modules/inflation": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/inflation/-/inflation-2.1.0.tgz",
"integrity": "sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==",
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/inflection": { "node_modules/inflection": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/inflection/-/inflection-2.0.1.tgz", "resolved": "https://registry.npmjs.org/inflection/-/inflection-2.0.1.tgz",
@ -20219,6 +20322,14 @@
"node": ">= 10" "node": ">= 10"
} }
}, },
"node_modules/iron-webcrypto": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz",
"integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==",
"funding": {
"url": "https://github.com/sponsors/brc-dd"
}
},
"node_modules/is-alphabetical": { "node_modules/is-alphabetical": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
@ -22035,8 +22146,7 @@
"node_modules/lodash.clonedeep": { "node_modules/lodash.clonedeep": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
"dev": true
}, },
"node_modules/lodash.debounce": { "node_modules/lodash.debounce": {
"version": "4.0.8", "version": "4.0.8",
@ -22863,6 +22973,14 @@
"esbuild": "0.*" "esbuild": "0.*"
} }
}, },
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/memfs": { "node_modules/memfs": {
"version": "3.5.3", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz",
@ -22917,6 +23035,14 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/merge-descriptors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/merge-refs": { "node_modules/merge-refs": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-1.2.2.tgz", "resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-1.2.2.tgz",
@ -22990,6 +23116,14 @@
"uuid": "dist/bin/uuid" "uuid": "dist/bin/uuid"
} }
}, },
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/micro": { "node_modules/micro": {
"version": "10.0.1", "version": "10.0.1",
"resolved": "https://registry.npmjs.org/micro/-/micro-10.0.1.tgz", "resolved": "https://registry.npmjs.org/micro/-/micro-10.0.1.tgz",
@ -23733,6 +23867,17 @@
"node": ">=8.6" "node": ">=8.6"
} }
}, },
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": { "node_modules/mime-db": {
"version": "1.52.0", "version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@ -24196,7 +24341,6 @@
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"dev": true,
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
} }
@ -24665,6 +24809,11 @@
"url": "https://opencollective.com/node-fetch" "url": "https://opencollective.com/node-fetch"
} }
}, },
"node_modules/node-fetch-native": {
"version": "1.6.4",
"resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.4.tgz",
"integrity": "sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ=="
},
"node_modules/node-gyp": { "node_modules/node-gyp": {
"version": "9.4.1", "version": "9.4.1",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz",
@ -24993,6 +25142,38 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0" "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
} }
}, },
"node_modules/node-mocks-http": {
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/node-mocks-http/-/node-mocks-http-1.16.2.tgz",
"integrity": "sha512-2Sh6YItRp1oqewZNlck3LaFp5vbyW2u51HX2p1VLxQ9U/bG90XV8JY9O7Nk+HDd6OOn/oV3nA5Tx5k4Rki0qlg==",
"dependencies": {
"accepts": "^1.3.7",
"content-disposition": "^0.5.3",
"depd": "^1.1.0",
"fresh": "^0.5.2",
"merge-descriptors": "^1.0.1",
"methods": "^1.1.2",
"mime": "^1.3.4",
"parseurl": "^1.3.3",
"range-parser": "^1.2.0",
"type-is": "^1.6.18"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@types/express": "^4.17.21 || ^5.0.0",
"@types/node": "*"
},
"peerDependenciesMeta": {
"@types/express": {
"optional": true
},
"@types/node": {
"optional": true
}
}
},
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.14", "version": "2.0.14",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
@ -25580,6 +25761,11 @@
"integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
"dev": true "dev": true
}, },
"node_modules/ohash": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.4.tgz",
"integrity": "sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g=="
},
"node_modules/oidc-token-hash": { "node_modules/oidc-token-hash": {
"version": "5.0.3", "version": "5.0.3",
"resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz",
@ -25681,6 +25867,11 @@
} }
} }
}, },
"node_modules/openapi-types": {
"version": "12.1.3",
"resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz",
"integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="
},
"node_modules/openapi3-ts": { "node_modules/openapi3-ts": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-2.0.2.tgz", "resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-2.0.2.tgz",
@ -26440,6 +26631,14 @@
"url": "https://ko-fi.com/killymxi" "url": "https://ko-fi.com/killymxi"
} }
}, },
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/partysocket": { "node_modules/partysocket": {
"version": "0.0.17", "version": "0.0.17",
"resolved": "https://registry.npmjs.org/partysocket/-/partysocket-0.0.17.tgz", "resolved": "https://registry.npmjs.org/partysocket/-/partysocket-0.0.17.tgz",
@ -26595,8 +26794,7 @@
"node_modules/pathe": { "node_modules/pathe": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="
"dev": true
}, },
"node_modules/pathval": { "node_modules/pathval": {
"version": "1.1.1", "version": "1.1.1",
@ -27908,6 +28106,11 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/radix3": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz",
"integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="
},
"node_modules/raf-schd": { "node_modules/raf-schd": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
@ -27946,6 +28149,14 @@
"safe-buffer": "^5.1.0" "safe-buffer": "^5.1.0"
} }
}, },
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": { "node_modules/raw-body": {
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz",
@ -32782,6 +32993,32 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/trpc-openapi": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/trpc-openapi/-/trpc-openapi-1.2.0.tgz",
"integrity": "sha512-pfYoCd/3KYXWXvUPZBKJw455OOwngKN/6SIcj7Yit19OMLJ+8yVZkEvGEeg5wUSwfsiTdRsKuvqkRPXVSwV7ew==",
"workspaces": [
".",
"examples/with-nextjs",
"examples/with-express",
"examples/with-interop",
"examples/with-serverless",
"examples/with-fastify",
"examples/with-nuxtjs"
],
"dependencies": {
"co-body": "^6.1.0",
"h3": "^1.6.4",
"lodash.clonedeep": "^4.5.0",
"node-mocks-http": "^1.12.2",
"openapi-types": "^12.1.1",
"zod-to-json-schema": "^3.21.1"
},
"peerDependencies": {
"@trpc/server": "^10.0.0",
"zod": "^3.14.4"
}
},
"node_modules/ts-api-utils": { "node_modules/ts-api-utils": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz",
@ -33547,6 +33784,18 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"dependencies": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/typed-array-buffer": { "node_modules/typed-array-buffer": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz",
@ -33665,10 +33914,9 @@
} }
}, },
"node_modules/ufo": { "node_modules/ufo": {
"version": "1.4.0", "version": "1.5.4",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz",
"integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==", "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ=="
"dev": true
}, },
"node_modules/ulid": { "node_modules/ulid": {
"version": "2.3.0", "version": "2.3.0",
@ -33703,6 +33951,11 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/uncrypto": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz",
"integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="
},
"node_modules/undici": { "node_modules/undici": {
"version": "5.28.2", "version": "5.28.2",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.2.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.2.tgz",
@ -33720,6 +33973,29 @@
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true "dev": true
}, },
"node_modules/unenv": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/unenv/-/unenv-1.10.0.tgz",
"integrity": "sha512-wY5bskBQFL9n3Eca5XnhH6KbUo/tfvkwm9OpcdCvLaeA7piBNbavbOKJySEwQ1V0RH6HvNlSAFRTpvTqgKRQXQ==",
"dependencies": {
"consola": "^3.2.3",
"defu": "^6.1.4",
"mime": "^3.0.0",
"node-fetch-native": "^1.6.4",
"pathe": "^1.1.2"
}
},
"node_modules/unenv/node_modules/mime": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/unified": { "node_modules/unified": {
"version": "10.1.2", "version": "10.1.2",
"resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz",
@ -35409,9 +35685,9 @@
} }
}, },
"node_modules/zod": { "node_modules/zod": {
"version": "3.23.8", "version": "3.24.1",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }
@ -35454,6 +35730,14 @@
"@prisma/debug": "5.22.0" "@prisma/debug": "5.22.0"
} }
}, },
"node_modules/zod-to-json-schema": {
"version": "3.24.1",
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.1.tgz",
"integrity": "sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==",
"peerDependencies": {
"zod": "^3.24.1"
}
},
"node_modules/zwitch": { "node_modules/zwitch": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",

View File

@ -29,7 +29,7 @@ import { setRecipientsForDocument } from '@documenso/lib/server-only/recipient/s
import { updateRecipient } from '@documenso/lib/server-only/recipient/update-recipient'; import { updateRecipient } from '@documenso/lib/server-only/recipient/update-recipient';
import { createTeamMemberInvites } from '@documenso/lib/server-only/team/create-team-member-invites'; import { createTeamMemberInvites } from '@documenso/lib/server-only/team/create-team-member-invites';
import { deleteTeamMembers } from '@documenso/lib/server-only/team/delete-team-members'; import { deleteTeamMembers } from '@documenso/lib/server-only/team/delete-team-members';
import type { CreateDocumentFromTemplateResponse } from '@documenso/lib/server-only/template/create-document-from-template'; import type { TCreateDocumentFromTemplateResponse } from '@documenso/lib/server-only/template/create-document-from-template';
import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template'; import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template';
import { createDocumentFromTemplateLegacy } from '@documenso/lib/server-only/template/create-document-from-template-legacy'; import { createDocumentFromTemplateLegacy } from '@documenso/lib/server-only/template/create-document-from-template-legacy';
import { deleteTemplate } from '@documenso/lib/server-only/template/delete-template'; import { deleteTemplate } from '@documenso/lib/server-only/template/delete-template';
@ -345,7 +345,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
}); });
} }
const recipients = await setRecipientsForDocument({ const { recipients } = await setRecipientsForDocument({
userId: user.id, userId: user.id,
teamId: team?.id, teamId: team?.id,
documentId: document.id, documentId: document.id,
@ -560,7 +560,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
const templateId = Number(params.templateId); const templateId = Number(params.templateId);
let document: CreateDocumentFromTemplateResponse | null = null; let document: TCreateDocumentFromTemplateResponse | null = null;
try { try {
document = await createDocumentFromTemplate({ document = await createDocumentFromTemplate({
@ -630,7 +630,6 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
token: recipient.token, token: recipient.token,
role: recipient.role, role: recipient.role,
signingOrder: recipient.signingOrder, signingOrder: recipient.signingOrder,
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`, signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
})), })),
}, },
@ -786,7 +785,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
} }
try { try {
const newRecipients = await setRecipientsForDocument({ const { recipients: newRecipients } = await setRecipientsForDocument({
documentId: Number(documentId), documentId: Number(documentId),
userId: user.id, userId: user.id,
teamId: team?.id, teamId: team?.id,

View File

@ -1,5 +1,7 @@
'use server'; 'use server';
import type { z } from 'zod';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs'; import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata'; import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
@ -8,6 +10,7 @@ import { prisma } from '@documenso/prisma';
import { DocumentSource, DocumentVisibility, WebhookTriggerEvents } from '@documenso/prisma/client'; import { DocumentSource, DocumentVisibility, WebhookTriggerEvents } from '@documenso/prisma/client';
import type { Team, TeamGlobalSettings } from '@documenso/prisma/client'; import type { Team, TeamGlobalSettings } from '@documenso/prisma/client';
import { TeamMemberRole } from '@documenso/prisma/client'; import { TeamMemberRole } from '@documenso/prisma/client';
import { DocumentSchema } from '@documenso/prisma/generated/zod';
import { ZWebhookDocumentSchema } from '../../types/webhook-payload'; import { ZWebhookDocumentSchema } from '../../types/webhook-payload';
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook'; import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
@ -22,6 +25,10 @@ export type CreateDocumentOptions = {
requestMetadata?: RequestMetadata; requestMetadata?: RequestMetadata;
}; };
export const ZCreateDocumentResponseSchema = DocumentSchema;
export type TCreateDocumentResponse = z.infer<typeof ZCreateDocumentResponseSchema>;
export const createDocument = async ({ export const createDocument = async ({
userId, userId,
title, title,
@ -30,7 +37,7 @@ export const createDocument = async ({
teamId, teamId,
formValues, formValues,
requestMetadata, requestMetadata,
}: CreateDocumentOptions) => { }: CreateDocumentOptions): Promise<TCreateDocumentResponse> => {
const user = await prisma.user.findFirstOrThrow({ const user = await prisma.user.findFirstOrThrow({
where: { where: {
id: userId, id: userId,

View File

@ -1,19 +1,27 @@
import { z } from 'zod';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { DocumentSource, type Prisma } from '@documenso/prisma/client'; import { DocumentSource, type Prisma } from '@documenso/prisma/client';
import { getDocumentWhereInput } from './get-document-by-id'; import { getDocumentWhereInput } from './get-document-by-id';
export interface DuplicateDocumentByIdOptions { export interface DuplicateDocumentOptions {
documentId: number; documentId: number;
userId: number; userId: number;
teamId?: number; teamId?: number;
} }
export const duplicateDocumentById = async ({ export const ZDuplicateDocumentResponseSchema = z.object({
documentId: z.number(),
});
export type TDuplicateDocumentResponse = z.infer<typeof ZDuplicateDocumentResponseSchema>;
export const duplicateDocument = async ({
documentId, documentId,
userId, userId,
teamId, teamId,
}: DuplicateDocumentByIdOptions) => { }: DuplicateDocumentOptions): Promise<TDuplicateDocumentResponse> => {
const documentWhereInput = await getDocumentWhereInput({ const documentWhereInput = await getDocumentWhereInput({
documentId, documentId,
userId, userId,
@ -78,5 +86,7 @@ export const duplicateDocumentById = async ({
const createdDocument = await prisma.document.create(createDocumentArguments); const createdDocument = await prisma.document.create(createDocumentArguments);
return createdDocument.id; return {
documentId: createdDocument.id,
};
}; };

View File

@ -1,5 +1,6 @@
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import type { z } from 'zod';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import type { import type {
@ -11,10 +12,16 @@ import type {
User, User,
} from '@documenso/prisma/client'; } from '@documenso/prisma/client';
import { RecipientRole, SigningStatus, TeamMemberRole } from '@documenso/prisma/client'; import { RecipientRole, SigningStatus, TeamMemberRole } from '@documenso/prisma/client';
import {
DocumentSchema,
RecipientSchema,
TeamSchema,
UserSchema,
} from '@documenso/prisma/generated/zod';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import { DocumentVisibility } from '../../types/document-visibility'; import { DocumentVisibility } from '../../types/document-visibility';
import type { FindResultResponse } from '../../types/search-params'; import { type FindResultResponse, ZFindResultResponse } from '../../types/search-params';
import { maskRecipientTokensForDocument } from '../../utils/mask-recipient-tokens-for-document'; import { maskRecipientTokensForDocument } from '../../utils/mask-recipient-tokens-for-document';
export type PeriodSelectorValue = '' | '7d' | '14d' | '30d'; export type PeriodSelectorValue = '' | '7d' | '14d' | '30d';
@ -36,6 +43,23 @@ export type FindDocumentsOptions = {
query?: string; query?: string;
}; };
export const ZFindDocumentsResponseSchema = ZFindResultResponse.extend({
data: DocumentSchema.extend({
User: UserSchema.pick({
id: true,
name: true,
email: true,
}),
Recipient: RecipientSchema.array(),
team: TeamSchema.pick({
id: true,
url: true,
}).nullable(),
}).array(), // Todo: openapi remap.
});
export type TFindDocumentsResponse = z.infer<typeof ZFindDocumentsResponseSchema>;
export const findDocuments = async ({ export const findDocuments = async ({
userId, userId,
teamId, teamId,
@ -48,7 +72,7 @@ export const findDocuments = async ({
period, period,
senderIds, senderIds,
query, query,
}: FindDocumentsOptions) => { }: FindDocumentsOptions): Promise<TFindDocumentsResponse> => {
const user = await prisma.user.findFirstOrThrow({ const user = await prisma.user.findFirstOrThrow({
where: { where: {
id: userId, id: userId,

View File

@ -1,6 +1,15 @@
import { prisma } from '@documenso/prisma'; import type { z } from 'zod';
import type { DocumentWithDetails } from '@documenso/prisma/types/document';
import { prisma } from '@documenso/prisma';
import {
DocumentDataSchema,
DocumentMetaSchema,
DocumentSchema,
FieldSchema,
RecipientSchema,
} from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { getDocumentWhereInput } from './get-document-by-id'; import { getDocumentWhereInput } from './get-document-by-id';
export type GetDocumentWithDetailsByIdOptions = { export type GetDocumentWithDetailsByIdOptions = {
@ -9,18 +18,29 @@ export type GetDocumentWithDetailsByIdOptions = {
teamId?: number; teamId?: number;
}; };
export const ZGetDocumentWithDetailsByIdResponseSchema = DocumentSchema.extend({
documentData: DocumentDataSchema,
documentMeta: DocumentMetaSchema.nullable(),
Recipient: RecipientSchema.array(),
Field: FieldSchema.array(),
});
export type TGetDocumentWithDetailsByIdResponse = z.infer<
typeof ZGetDocumentWithDetailsByIdResponseSchema
>;
export const getDocumentWithDetailsById = async ({ export const getDocumentWithDetailsById = async ({
documentId, documentId,
userId, userId,
teamId, teamId,
}: GetDocumentWithDetailsByIdOptions): Promise<DocumentWithDetails> => { }: GetDocumentWithDetailsByIdOptions): Promise<TGetDocumentWithDetailsByIdResponse> => {
const documentWhereInput = await getDocumentWhereInput({ const documentWhereInput = await getDocumentWhereInput({
documentId, documentId,
userId, userId,
teamId, teamId,
}); });
return await prisma.document.findFirstOrThrow({ const document = await prisma.document.findFirst({
where: documentWhereInput, where: documentWhereInput,
include: { include: {
documentData: true, documentData: true,
@ -29,4 +49,12 @@ export const getDocumentWithDetailsById = async ({
Field: true, Field: true,
}, },
}); });
if (!document) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Document not found',
});
}
return document;
}; };

View File

@ -1,7 +1,9 @@
import { TRPCError } from '@trpc/server'; import { TRPCError } from '@trpc/server';
import type { z } from 'zod';
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata'; import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { DocumentSchema } from '@documenso/prisma/generated/zod';
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs'; import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
import { createDocumentAuditLogData } from '../../utils/document-audit-logs'; import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
@ -13,12 +15,16 @@ export type MoveDocumentToTeamOptions = {
requestMetadata?: RequestMetadata; requestMetadata?: RequestMetadata;
}; };
export const ZMoveDocumentToTeamResponseSchema = DocumentSchema;
export type TMoveDocumentToTeamResponse = z.infer<typeof ZMoveDocumentToTeamResponseSchema>;
export const moveDocumentToTeam = async ({ export const moveDocumentToTeam = async ({
documentId, documentId,
teamId, teamId,
userId, userId,
requestMetadata, requestMetadata,
}: MoveDocumentToTeamOptions) => { }: MoveDocumentToTeamOptions): Promise<TMoveDocumentToTeamResponse> => {
return await prisma.$transaction(async (tx) => { return await prisma.$transaction(async (tx) => {
const user = await tx.user.findUniqueOrThrow({ const user = await tx.user.findUniqueOrThrow({
where: { id: userId }, where: { id: userId },

View File

@ -38,7 +38,7 @@ export const resendDocument = async ({
recipients, recipients,
teamId, teamId,
requestMetadata, requestMetadata,
}: ResendDocumentOptions) => { }: ResendDocumentOptions): Promise<void> => {
const user = await prisma.user.findFirstOrThrow({ const user = await prisma.user.findFirstOrThrow({
where: { where: {
id: userId, id: userId,

View File

@ -1,3 +1,5 @@
import type { z } from 'zod';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs'; import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata'; import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file'; import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
@ -11,6 +13,11 @@ import {
SigningStatus, SigningStatus,
WebhookTriggerEvents, WebhookTriggerEvents,
} from '@documenso/prisma/client'; } from '@documenso/prisma/client';
import {
DocumentMetaSchema,
DocumentSchema,
RecipientSchema,
} from '@documenso/prisma/generated/zod';
import { jobs } from '../../jobs/client'; import { jobs } from '../../jobs/client';
import { extractDerivedDocumentEmailSettings } from '../../types/document-email'; import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
@ -27,13 +34,20 @@ export type SendDocumentOptions = {
requestMetadata?: RequestMetadata; requestMetadata?: RequestMetadata;
}; };
export const ZSendDocumentResponseSchema = DocumentSchema.extend({
documentMeta: DocumentMetaSchema.nullable(),
Recipient: RecipientSchema.array(),
});
export type TSendDocumentResponse = z.infer<typeof ZSendDocumentResponseSchema>;
export const sendDocument = async ({ export const sendDocument = async ({
documentId, documentId,
userId, userId,
teamId, teamId,
sendEmail, sendEmail,
requestMetadata, requestMetadata,
}: SendDocumentOptions) => { }: SendDocumentOptions): Promise<TSendDocumentResponse> => {
const user = await prisma.user.findFirstOrThrow({ const user = await prisma.user.findFirstOrThrow({
where: { where: {
id: userId, id: userId,
@ -211,6 +225,7 @@ export const sendDocument = async ({
id: documentId, id: documentId,
}, },
include: { include: {
documentMeta: true,
Recipient: true, Recipient: true,
}, },
}); });

View File

@ -1,6 +1,7 @@
'use server'; 'use server';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import type { z } from 'zod';
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs'; import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
@ -10,6 +11,7 @@ import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { DocumentVisibility } from '@documenso/prisma/client'; import { DocumentVisibility } from '@documenso/prisma/client';
import { DocumentStatus, TeamMemberRole } from '@documenso/prisma/client'; import { DocumentStatus, TeamMemberRole } from '@documenso/prisma/client';
import { DocumentSchema } from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error'; import { AppError, AppErrorCode } from '../../errors/app-error';
import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth'; import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth';
@ -29,13 +31,17 @@ export type UpdateDocumentSettingsOptions = {
requestMetadata?: RequestMetadata; requestMetadata?: RequestMetadata;
}; };
export const ZUpdateDocumentSettingsResponseSchema = DocumentSchema;
export type TUpdateDocumentSettingsResponse = z.infer<typeof ZUpdateDocumentSettingsResponseSchema>;
export const updateDocumentSettings = async ({ export const updateDocumentSettings = async ({
userId, userId,
teamId, teamId,
documentId, documentId,
data, data,
requestMetadata, requestMetadata,
}: UpdateDocumentSettingsOptions) => { }: UpdateDocumentSettingsOptions): Promise<TUpdateDocumentSettingsResponse> => {
if (!data.title && !data.globalAccessAuth && !data.globalActionAuth) { if (!data.title && !data.globalAccessAuth && !data.globalActionAuth) {
throw new AppError(AppErrorCode.INVALID_BODY, { throw new AppError(AppErrorCode.INVALID_BODY, {
message: 'Missing data to update', message: 'Missing data to update',

View File

@ -1,4 +1,9 @@
import type { z } from 'zod';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { FieldSchema } from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error';
export type GetFieldByIdOptions = { export type GetFieldByIdOptions = {
userId: number; userId: number;
@ -8,13 +13,17 @@ export type GetFieldByIdOptions = {
templateId?: number; templateId?: number;
}; };
export const ZGetFieldByIdResponseSchema = FieldSchema;
export type TGetFieldByIdResponse = z.infer<typeof ZGetFieldByIdResponseSchema>;
export const getFieldById = async ({ export const getFieldById = async ({
userId, userId,
teamId, teamId,
fieldId, fieldId,
documentId, documentId,
templateId, templateId,
}: GetFieldByIdOptions) => { }: GetFieldByIdOptions): Promise<TGetFieldByIdResponse> => {
const field = await prisma.field.findFirst({ const field = await prisma.field.findFirst({
where: { where: {
id: fieldId, id: fieldId,
@ -45,5 +54,11 @@ export const getFieldById = async ({
}, },
}); });
if (!field) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Field not found',
});
}
return field; return field;
}; };

View File

@ -1,4 +1,5 @@
import { isDeepEqual } from 'remeda'; import { isDeepEqual } from 'remeda';
import { z } from 'zod';
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox'; import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown'; import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
@ -23,6 +24,7 @@ import {
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import type { Field } from '@documenso/prisma/client'; import type { Field } from '@documenso/prisma/client';
import { FieldType } from '@documenso/prisma/client'; import { FieldType } from '@documenso/prisma/client';
import { FieldSchema } from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error'; import { AppError, AppErrorCode } from '../../errors/app-error';
import { canRecipientFieldsBeModified } from '../../utils/recipients'; import { canRecipientFieldsBeModified } from '../../utils/recipients';
@ -34,12 +36,18 @@ export interface SetFieldsForDocumentOptions {
requestMetadata?: RequestMetadata; requestMetadata?: RequestMetadata;
} }
export const ZSetFieldsForDocumentResponseSchema = z.object({
fields: z.array(FieldSchema),
});
export type TSetFieldsForDocumentResponse = z.infer<typeof ZSetFieldsForDocumentResponseSchema>;
export const setFieldsForDocument = async ({ export const setFieldsForDocument = async ({
userId, userId,
documentId, documentId,
fields, fields,
requestMetadata, requestMetadata,
}: SetFieldsForDocumentOptions): Promise<Field[]> => { }: SetFieldsForDocumentOptions): Promise<TSetFieldsForDocumentResponse> => {
const document = await prisma.document.findFirst({ const document = await prisma.document.findFirst({
where: { where: {
id: documentId, id: documentId,
@ -75,11 +83,15 @@ export const setFieldsForDocument = async ({
}); });
if (!document) { if (!document) {
throw new Error('Document not found'); throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Document not found',
});
} }
if (document.completedAt) { if (document.completedAt) {
throw new Error('Document already complete'); throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: 'Document already complete',
});
} }
const existingFields = await prisma.field.findMany({ const existingFields = await prisma.field.findMany({
@ -335,7 +347,9 @@ export const setFieldsForDocument = async ({
return !isRemoved && !isUpdated; return !isRemoved && !isUpdated;
}); });
return [...filteredFields, ...persistedFields]; return {
fields: [...filteredFields, ...persistedFields],
};
}; };
/** /**

View File

@ -1,3 +1,5 @@
import { z } from 'zod';
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox'; import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown'; import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number'; import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
@ -14,6 +16,7 @@ import {
} from '@documenso/lib/types/field-meta'; } from '@documenso/lib/types/field-meta';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { FieldType } from '@documenso/prisma/client'; import { FieldType } from '@documenso/prisma/client';
import { FieldSchema } from '@documenso/prisma/generated/zod';
export type SetFieldsForTemplateOptions = { export type SetFieldsForTemplateOptions = {
userId: number; userId: number;
@ -31,11 +34,17 @@ export type SetFieldsForTemplateOptions = {
}[]; }[];
}; };
export const ZSetFieldsForTemplateResponseSchema = z.object({
fields: z.array(FieldSchema),
});
export type TSetFieldsForTemplateResponse = z.infer<typeof ZSetFieldsForTemplateResponseSchema>;
export const setFieldsForTemplate = async ({ export const setFieldsForTemplate = async ({
userId, userId,
templateId, templateId,
fields, fields,
}: SetFieldsForTemplateOptions) => { }: SetFieldsForTemplateOptions): Promise<TSetFieldsForTemplateResponse> => {
const template = await prisma.template.findFirst({ const template = await prisma.template.findFirst({
where: { where: {
id: templateId, id: templateId,
@ -206,5 +215,7 @@ export const setFieldsForTemplate = async ({
return !isRemoved && !isUpdated; return !isRemoved && !isUpdated;
}); });
return [...filteredFields, ...persistedFields]; return {
fields: [...filteredFields, ...persistedFields],
};
}; };

View File

@ -1,6 +1,7 @@
import { createElement } from 'react'; import { createElement } from 'react';
import { msg } from '@lingui/macro'; import { msg } from '@lingui/macro';
import { z } from 'zod';
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
import { mailer } from '@documenso/email/mailer'; import { mailer } from '@documenso/email/mailer';
@ -21,6 +22,7 @@ import { prisma } from '@documenso/prisma';
import type { Recipient } from '@documenso/prisma/client'; import type { Recipient } from '@documenso/prisma/client';
import { RecipientRole } from '@documenso/prisma/client'; import { RecipientRole } from '@documenso/prisma/client';
import { SendStatus, SigningStatus } from '@documenso/prisma/client'; import { SendStatus, SigningStatus } from '@documenso/prisma/client';
import { RecipientSchema } from '@documenso/prisma/generated/zod';
import { getI18nInstance } from '../../client-only/providers/i18n.server'; import { getI18nInstance } from '../../client-only/providers/i18n.server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app'; import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
@ -39,13 +41,21 @@ export interface SetRecipientsForDocumentOptions {
requestMetadata?: RequestMetadata; requestMetadata?: RequestMetadata;
} }
export const ZSetRecipientsForDocumentResponseSchema = z.object({
recipients: RecipientSchema.array(),
});
export type TSetRecipientsForDocumentResponse = z.infer<
typeof ZSetRecipientsForDocumentResponseSchema
>;
export const setRecipientsForDocument = async ({ export const setRecipientsForDocument = async ({
userId, userId,
teamId, teamId,
documentId, documentId,
recipients, recipients,
requestMetadata, requestMetadata,
}: SetRecipientsForDocumentOptions): Promise<Recipient[]> => { }: SetRecipientsForDocumentOptions): Promise<TSetRecipientsForDocumentResponse> => {
const document = await prisma.document.findFirst({ const document = await prisma.document.findFirst({
where: { where: {
id: documentId, id: documentId,
@ -344,7 +354,9 @@ export const setRecipientsForDocument = async ({
return !isRemoved && !isUpdated; return !isRemoved && !isUpdated;
}); });
return [...filteredRecipients, ...persistedRecipients]; return {
recipients: [...filteredRecipients, ...persistedRecipients],
};
}; };
/** /**

View File

@ -1,3 +1,5 @@
import { z } from 'zod';
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
import { import {
DIRECT_TEMPLATE_RECIPIENT_EMAIL, DIRECT_TEMPLATE_RECIPIENT_EMAIL,
@ -6,6 +8,7 @@ import {
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import type { Recipient } from '@documenso/prisma/client'; import type { Recipient } from '@documenso/prisma/client';
import { RecipientRole } from '@documenso/prisma/client'; import { RecipientRole } from '@documenso/prisma/client';
import { RecipientSchema } from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error'; import { AppError, AppErrorCode } from '../../errors/app-error';
import { import {
@ -29,12 +32,20 @@ export type SetRecipientsForTemplateOptions = {
}[]; }[];
}; };
export const ZSetRecipientsForTemplateResponseSchema = z.object({
recipients: RecipientSchema.array(),
});
export type TSetRecipientsForTemplateResponse = z.infer<
typeof ZSetRecipientsForTemplateResponseSchema
>;
export const setRecipientsForTemplate = async ({ export const setRecipientsForTemplate = async ({
userId, userId,
teamId, teamId,
templateId, templateId,
recipients, recipients,
}: SetRecipientsForTemplateOptions) => { }: SetRecipientsForTemplateOptions): Promise<TSetRecipientsForTemplateResponse> => {
const template = await prisma.template.findFirst({ const template = await prisma.template.findFirst({
where: { where: {
id: templateId, id: templateId,
@ -220,5 +231,7 @@ export const setRecipientsForTemplate = async ({
return !isRemoved && !isUpdated; return !isRemoved && !isUpdated;
}); });
return [...filteredRecipients, ...persistedRecipients]; return {
recipients: [...filteredRecipients, ...persistedRecipients],
};
}; };

View File

@ -3,6 +3,7 @@ import { createElement } from 'react';
import { msg } from '@lingui/macro'; import { msg } from '@lingui/macro';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { z } from 'zod';
import { mailer } from '@documenso/email/mailer'; import { mailer } from '@documenso/email/mailer';
import { DocumentCreatedFromDirectTemplateEmailTemplate } from '@documenso/email/templates/document-created-from-direct-template'; import { DocumentCreatedFromDirectTemplateEmailTemplate } from '@documenso/email/templates/document-created-from-direct-template';
@ -67,6 +68,16 @@ type CreatedDirectRecipientField = {
derivedRecipientActionAuth: TRecipientActionAuthTypes | null; derivedRecipientActionAuth: TRecipientActionAuthTypes | null;
}; };
export const ZCreateDocumentFromDirectTemplateResponseSchema = z.object({
token: z.string(),
documentId: z.number(),
recipientId: z.number(),
});
export type TCreateDocumentFromDirectTemplateResponse = z.infer<
typeof ZCreateDocumentFromDirectTemplateResponseSchema
>;
export const createDocumentFromDirectTemplate = async ({ export const createDocumentFromDirectTemplate = async ({
directRecipientName: initialDirectRecipientName, directRecipientName: initialDirectRecipientName,
directRecipientEmail, directRecipientEmail,
@ -76,7 +87,7 @@ export const createDocumentFromDirectTemplate = async ({
templateUpdatedAt, templateUpdatedAt,
requestMetadata, requestMetadata,
user, user,
}: CreateDocumentFromDirectTemplateOptions) => { }: CreateDocumentFromDirectTemplateOptions): Promise<TCreateDocumentFromDirectTemplateResponse> => {
const template = await prisma.template.findFirst({ const template = await prisma.template.findFirst({
where: { where: {
directLink: { directLink: {

View File

@ -1,3 +1,5 @@
import type { z } from 'zod';
import { nanoid } from '@documenso/lib/universal/id'; import { nanoid } from '@documenso/lib/universal/id';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import type { DocumentDistributionMethod } from '@documenso/prisma/client'; import type { DocumentDistributionMethod } from '@documenso/prisma/client';
@ -11,6 +13,11 @@ import {
SigningStatus, SigningStatus,
WebhookTriggerEvents, WebhookTriggerEvents,
} from '@documenso/prisma/client'; } from '@documenso/prisma/client';
import {
DocumentDataSchema,
DocumentSchema,
RecipientSchema,
} from '@documenso/prisma/generated/zod';
import type { SupportedLanguageCodes } from '../../constants/i18n'; import type { SupportedLanguageCodes } from '../../constants/i18n';
import { AppError, AppErrorCode } from '../../errors/app-error'; import { AppError, AppErrorCode } from '../../errors/app-error';
@ -36,10 +43,6 @@ type FinalRecipient = Pick<
fields: Field[]; fields: Field[];
}; };
export type CreateDocumentFromTemplateResponse = Awaited<
ReturnType<typeof createDocumentFromTemplate>
>;
export type CreateDocumentFromTemplateOptions = { export type CreateDocumentFromTemplateOptions = {
templateId: number; templateId: number;
externalId?: string | null; externalId?: string | null;
@ -72,6 +75,15 @@ export type CreateDocumentFromTemplateOptions = {
requestMetadata?: RequestMetadata; requestMetadata?: RequestMetadata;
}; };
export const ZCreateDocumentFromTemplateResponseSchema = DocumentSchema.extend({
documentData: DocumentDataSchema,
Recipient: RecipientSchema.array(),
});
export type TCreateDocumentFromTemplateResponse = z.infer<
typeof ZCreateDocumentFromTemplateResponseSchema
>;
export const createDocumentFromTemplate = async ({ export const createDocumentFromTemplate = async ({
templateId, templateId,
externalId, externalId,
@ -80,7 +92,7 @@ export const createDocumentFromTemplate = async ({
recipients, recipients,
override, override,
requestMetadata, requestMetadata,
}: CreateDocumentFromTemplateOptions) => { }: CreateDocumentFromTemplateOptions): Promise<TCreateDocumentFromTemplateResponse> => {
const user = await prisma.user.findFirstOrThrow({ const user = await prisma.user.findFirstOrThrow({
where: { where: {
id: userId, id: userId,

View File

@ -1,13 +1,15 @@
'use server'; 'use server';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import type { z } from 'zod';
import { import {
DIRECT_TEMPLATE_RECIPIENT_EMAIL, DIRECT_TEMPLATE_RECIPIENT_EMAIL,
DIRECT_TEMPLATE_RECIPIENT_NAME, DIRECT_TEMPLATE_RECIPIENT_NAME,
} from '@documenso/lib/constants/direct-templates'; } from '@documenso/lib/constants/direct-templates';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import type { Recipient, TemplateDirectLink } from '@documenso/prisma/client'; import type { Recipient } from '@documenso/prisma/client';
import { TemplateDirectLinkSchema } from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error'; import { AppError, AppErrorCode } from '../../errors/app-error';
@ -17,11 +19,17 @@ export type CreateTemplateDirectLinkOptions = {
directRecipientId?: number; directRecipientId?: number;
}; };
export const ZCreateTemplateDirectLinkResponseSchema = TemplateDirectLinkSchema;
export type TCreateTemplateDirectLinkResponse = z.infer<
typeof ZCreateTemplateDirectLinkResponseSchema
>;
export const createTemplateDirectLink = async ({ export const createTemplateDirectLink = async ({
templateId, templateId,
userId, userId,
directRecipientId, directRecipientId,
}: CreateTemplateDirectLinkOptions): Promise<TemplateDirectLink> => { }: CreateTemplateDirectLinkOptions): Promise<TCreateTemplateDirectLinkResponse> => {
const template = await prisma.template.findFirst({ const template = await prisma.template.findFirst({
where: { where: {
id: templateId, id: templateId,

View File

@ -1,4 +1,7 @@
import type { z } from 'zod';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { TemplateSchema } from '@documenso/prisma/generated/zod';
import type { TCreateTemplateMutationSchema } from '@documenso/trpc/server/template-router/schema'; import type { TCreateTemplateMutationSchema } from '@documenso/trpc/server/template-router/schema';
export type CreateTemplateOptions = TCreateTemplateMutationSchema & { export type CreateTemplateOptions = TCreateTemplateMutationSchema & {
@ -6,6 +9,10 @@ export type CreateTemplateOptions = TCreateTemplateMutationSchema & {
teamId?: number; teamId?: number;
}; };
export const ZCreateTemplateResponseSchema = TemplateSchema;
export type TCreateTemplateResponse = z.infer<typeof ZCreateTemplateResponseSchema>;
export const createTemplate = async ({ export const createTemplate = async ({
title, title,
userId, userId,

View File

@ -1,19 +1,25 @@
import { omit } from 'remeda'; import { omit } from 'remeda';
import type { z } from 'zod';
import { nanoid } from '@documenso/lib/universal/id'; import { nanoid } from '@documenso/lib/universal/id';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import type { Prisma } from '@documenso/prisma/client'; import type { Prisma } from '@documenso/prisma/client';
import { TemplateSchema } from '@documenso/prisma/generated/zod';
import type { TDuplicateTemplateMutationSchema } from '@documenso/trpc/server/template-router/schema'; import type { TDuplicateTemplateMutationSchema } from '@documenso/trpc/server/template-router/schema';
export type DuplicateTemplateOptions = TDuplicateTemplateMutationSchema & { export type DuplicateTemplateOptions = TDuplicateTemplateMutationSchema & {
userId: number; userId: number;
}; };
export const ZDuplicateTemplateResponseSchema = TemplateSchema;
export type TDuplicateTemplateResponse = z.infer<typeof ZDuplicateTemplateResponseSchema>;
export const duplicateTemplate = async ({ export const duplicateTemplate = async ({
templateId, templateId,
userId, userId,
teamId, teamId,
}: DuplicateTemplateOptions) => { }: DuplicateTemplateOptions): Promise<TDuplicateTemplateResponse> => {
let templateWhereFilter: Prisma.TemplateWhereUniqueInput = { let templateWhereFilter: Prisma.TemplateWhereUniqueInput = {
id: templateId, id: templateId,
userId, userId,

View File

@ -1,7 +1,18 @@
import type { z } from 'zod';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import type { Prisma, Template } from '@documenso/prisma/client'; import type { Prisma, Template } from '@documenso/prisma/client';
import {
DocumentDataSchema,
FieldSchema,
RecipientSchema,
TeamSchema,
TemplateDirectLinkSchema,
TemplateMetaSchema,
TemplateSchema,
} from '@documenso/prisma/generated/zod';
import type { FindResultResponse } from '../../types/search-params'; import { type FindResultResponse, ZFindResultResponse } from '../../types/search-params';
export type FindTemplatesOptions = { export type FindTemplatesOptions = {
userId: number; userId: number;
@ -11,8 +22,28 @@ export type FindTemplatesOptions = {
perPage?: number; perPage?: number;
}; };
export type FindTemplatesResponse = Awaited<ReturnType<typeof findTemplates>>; export const ZFindTemplatesResponseSchema = ZFindResultResponse.extend({
export type FindTemplateRow = FindTemplatesResponse['data'][number]; 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 TFindTemplatesResponse = z.infer<typeof ZFindTemplatesResponseSchema>;
export type FindTemplateRow = TFindTemplatesResponse['data'][number];
export const findTemplates = async ({ export const findTemplates = async ({
userId, userId,
@ -20,7 +51,7 @@ export const findTemplates = async ({
type, type,
page = 1, page = 1,
perPage = 10, perPage = 10,
}: FindTemplatesOptions) => { }: FindTemplatesOptions): Promise<TFindTemplatesResponse> => {
let whereFilter: Prisma.TemplateWhereInput = { let whereFilter: Prisma.TemplateWhereInput = {
userId, userId,
teamId: null, teamId: null,

View File

@ -1,6 +1,9 @@
import { TRPCError } from '@trpc/server'; import type { z } from 'zod';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { TemplateSchema } from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error';
export type MoveTemplateToTeamOptions = { export type MoveTemplateToTeamOptions = {
templateId: number; templateId: number;
@ -8,11 +11,15 @@ export type MoveTemplateToTeamOptions = {
userId: number; userId: number;
}; };
export const ZMoveTemplateToTeamResponseSchema = TemplateSchema;
export type TMoveTemplateToTeamResponse = z.infer<typeof ZMoveTemplateToTeamResponseSchema>;
export const moveTemplateToTeam = async ({ export const moveTemplateToTeam = async ({
templateId, templateId,
teamId, teamId,
userId, userId,
}: MoveTemplateToTeamOptions) => { }: MoveTemplateToTeamOptions): Promise<TMoveTemplateToTeamResponse> => {
return await prisma.$transaction(async (tx) => { return await prisma.$transaction(async (tx) => {
const template = await tx.template.findFirst({ const template = await tx.template.findFirst({
where: { where: {
@ -23,8 +30,7 @@ export const moveTemplateToTeam = async ({
}); });
if (!template) { if (!template) {
throw new TRPCError({ throw new AppError(AppErrorCode.NOT_FOUND, {
code: 'NOT_FOUND',
message: 'Template not found or already associated with a team.', message: 'Template not found or already associated with a team.',
}); });
} }
@ -41,9 +47,8 @@ export const moveTemplateToTeam = async ({
}); });
if (!team) { if (!team) {
throw new TRPCError({ throw new AppError(AppErrorCode.UNAUTHORIZED, {
code: 'FORBIDDEN', message: 'Team does not exist or you are not a member of this team.',
message: 'You are not a member of this team.',
}); });
} }

View File

@ -1,6 +1,9 @@
'use server'; 'use server';
import type { z } from 'zod';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { TemplateDirectLinkSchema } from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error'; import { AppError, AppErrorCode } from '../../errors/app-error';
@ -10,11 +13,17 @@ export type ToggleTemplateDirectLinkOptions = {
enabled: boolean; enabled: boolean;
}; };
export const ZToggleTemplateDirectLinkResponseSchema = TemplateDirectLinkSchema;
export type TToggleTemplateDirectLinkResponse = z.infer<
typeof ZToggleTemplateDirectLinkResponseSchema
>;
export const toggleTemplateDirectLink = async ({ export const toggleTemplateDirectLink = async ({
templateId, templateId,
userId, userId,
enabled, enabled,
}: ToggleTemplateDirectLinkOptions) => { }: ToggleTemplateDirectLinkOptions): Promise<TToggleTemplateDirectLinkResponse> => {
const template = await prisma.template.findFirst({ const template = await prisma.template.findFirst({
where: { where: {
id: templateId, id: templateId,

View File

@ -1,9 +1,12 @@
'use server'; 'use server';
import type { z } from 'zod';
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata'; import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import type { Template, TemplateMeta } from '@documenso/prisma/client'; import type { Template, TemplateMeta } from '@documenso/prisma/client';
import { TemplateSchema } from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error'; import { AppError, AppErrorCode } from '../../errors/app-error';
import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth'; import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth';
@ -26,13 +29,17 @@ export type UpdateTemplateSettingsOptions = {
requestMetadata?: RequestMetadata; requestMetadata?: RequestMetadata;
}; };
export const ZUpdateTemplateSettingsResponseSchema = TemplateSchema;
export type TUpdateTemplateSettingsResponse = z.infer<typeof ZUpdateTemplateSettingsResponseSchema>;
export const updateTemplateSettings = async ({ export const updateTemplateSettings = async ({
userId, userId,
teamId, teamId,
templateId, templateId,
meta, meta,
data, data,
}: UpdateTemplateSettingsOptions) => { }: UpdateTemplateSettingsOptions): Promise<TUpdateTemplateSettingsResponse> => {
if (Object.values(data).length === 0 && Object.keys(meta ?? {}).length === 0) { if (Object.values(data).length === 0 && Object.keys(meta ?? {}).length === 0) {
throw new AppError(AppErrorCode.INVALID_BODY, { throw new AppError(AppErrorCode.INVALID_BODY, {
message: 'Missing data to update', message: 'Missing data to update',

View File

@ -78,7 +78,9 @@ class HoneybadgerLogger implements Logger {
error(error: Error, options?: LoggerDescriptionOptions): void { error(error: Error, options?: LoggerDescriptionOptions): void {
const { context = {}, level = 'error', method, path } = options || {}; const { context = {}, level = 'error', method, path } = options || {};
const tags = [`level:${level}`]; // const tags = [`level:${level}`];
const tags = [];
let errorMessage = error.message; let errorMessage = error.message;
if (method) { if (method) {

View File

@ -8,19 +8,40 @@ import { DOCUMENSO_ENCRYPTION_KEY } from '@documenso/lib/constants/crypto';
import { AppError } from '@documenso/lib/errors/app-error'; import { AppError } from '@documenso/lib/errors/app-error';
import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt'; import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt';
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta'; import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
import { createDocument } from '@documenso/lib/server-only/document/create-document'; import {
ZCreateDocumentResponseSchema,
createDocument,
} from '@documenso/lib/server-only/document/create-document';
import { deleteDocument } from '@documenso/lib/server-only/document/delete-document'; import { deleteDocument } from '@documenso/lib/server-only/document/delete-document';
import { duplicateDocumentById } from '@documenso/lib/server-only/document/duplicate-document-by-id'; import {
ZDuplicateDocumentResponseSchema,
duplicateDocument,
} from '@documenso/lib/server-only/document/duplicate-document-by-id';
import { findDocumentAuditLogs } from '@documenso/lib/server-only/document/find-document-audit-logs'; import { findDocumentAuditLogs } from '@documenso/lib/server-only/document/find-document-audit-logs';
import { findDocuments } from '@documenso/lib/server-only/document/find-documents'; import {
ZFindDocumentsResponseSchema,
findDocuments,
} from '@documenso/lib/server-only/document/find-documents';
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id'; import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token'; import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id'; import {
import { moveDocumentToTeam } from '@documenso/lib/server-only/document/move-document-to-team'; ZGetDocumentWithDetailsByIdResponseSchema,
getDocumentWithDetailsById,
} from '@documenso/lib/server-only/document/get-document-with-details-by-id';
import {
ZMoveDocumentToTeamResponseSchema,
moveDocumentToTeam,
} from '@documenso/lib/server-only/document/move-document-to-team';
import { resendDocument } from '@documenso/lib/server-only/document/resend-document'; import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
import { searchDocumentsWithKeyword } from '@documenso/lib/server-only/document/search-documents-with-keyword'; import { searchDocumentsWithKeyword } from '@documenso/lib/server-only/document/search-documents-with-keyword';
import { sendDocument } from '@documenso/lib/server-only/document/send-document'; import {
import { updateDocumentSettings } from '@documenso/lib/server-only/document/update-document-settings'; ZSendDocumentResponseSchema,
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'; import { updateTitle } from '@documenso/lib/server-only/document/update-title';
import { symmetricEncrypt } from '@documenso/lib/universal/crypto'; import { symmetricEncrypt } from '@documenso/lib/universal/crypto';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata'; import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
@ -32,12 +53,13 @@ import {
ZDeleteDocumentMutationSchema, ZDeleteDocumentMutationSchema,
ZDownloadAuditLogsMutationSchema, ZDownloadAuditLogsMutationSchema,
ZDownloadCertificateMutationSchema, ZDownloadCertificateMutationSchema,
ZDuplicateDocumentMutationSchema,
ZFindDocumentAuditLogsQuerySchema, ZFindDocumentAuditLogsQuerySchema,
ZFindDocumentsQuerySchema, ZFindDocumentsQuerySchema,
ZGetDocumentByIdQuerySchema, ZGetDocumentByIdQuerySchema,
ZGetDocumentByTokenQuerySchema, ZGetDocumentByTokenQuerySchema,
ZGetDocumentWithDetailsByIdQuerySchema, ZGetDocumentWithDetailsByIdQuerySchema,
ZMoveDocumentsToTeamSchema, ZMoveDocumentToTeamSchema,
ZResendDocumentMutationSchema, ZResendDocumentMutationSchema,
ZSearchDocumentsMutationSchema, ZSearchDocumentsMutationSchema,
ZSendDocumentMutationSchema, ZSendDocumentMutationSchema,
@ -49,7 +71,9 @@ import {
} from './schema'; } from './schema';
export const documentRouter = router({ export const documentRouter = router({
// Internal endpoint for now. /**
* @private
*/
getDocumentById: authenticatedProcedure getDocumentById: authenticatedProcedure
.input(ZGetDocumentByIdQuerySchema) .input(ZGetDocumentByIdQuerySchema)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
@ -59,7 +83,9 @@ export const documentRouter = router({
}); });
}), }),
// Internal endpoint for now. /**
* @private
*/
getDocumentByToken: procedure getDocumentByToken: procedure
.input(ZGetDocumentByTokenQuerySchema) .input(ZGetDocumentByTokenQuerySchema)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
@ -71,6 +97,9 @@ export const documentRouter = router({
}); });
}), }),
/**
* @public
*/
findDocuments: authenticatedProcedure findDocuments: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -82,11 +111,21 @@ export const documentRouter = router({
}, },
}) })
.input(ZFindDocumentsQuerySchema) .input(ZFindDocumentsQuerySchema)
.output(z.unknown()) .output(ZFindDocumentsResponseSchema)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
const { user } = ctx; const { user } = ctx;
const { query, teamId, templateId, page, perPage, orderBy, source, status } = input; const {
query,
teamId,
templateId,
page,
perPage,
orderByDirection,
orderByColumn,
source,
status,
} = input;
const documents = await findDocuments({ const documents = await findDocuments({
userId: user.id, userId: user.id,
@ -97,12 +136,17 @@ export const documentRouter = router({
status, status,
page, page,
perPage, perPage,
orderBy, orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
}); });
return documents; return documents;
}), }),
/**
* @public
*
* Todo: Refactor to getDocumentById.
*/
getDocumentWithDetailsById: authenticatedProcedure getDocumentWithDetailsById: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -114,7 +158,7 @@ export const documentRouter = router({
}, },
}) })
.input(ZGetDocumentWithDetailsByIdQuerySchema) .input(ZGetDocumentWithDetailsByIdQuerySchema)
.output(z.unknown()) .output(ZGetDocumentWithDetailsByIdResponseSchema)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
return await getDocumentWithDetailsById({ return await getDocumentWithDetailsById({
...input, ...input,
@ -122,6 +166,9 @@ export const documentRouter = router({
}); });
}), }),
/**
* @public
*/
createDocument: authenticatedProcedure createDocument: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -132,7 +179,7 @@ export const documentRouter = router({
}, },
}) })
.input(ZCreateDocumentMutationSchema) .input(ZCreateDocumentMutationSchema)
.output(z.unknown()) .output(ZCreateDocumentResponseSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { title, documentDataId, teamId } = input; const { title, documentDataId, teamId } = input;
@ -154,7 +201,11 @@ export const documentRouter = router({
}); });
}), }),
// Todo: Refactor to updateDocument. /**
* @public
*
* Todo: Refactor to updateDocument.
*/
setSettingsForDocument: authenticatedProcedure setSettingsForDocument: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -165,7 +216,7 @@ export const documentRouter = router({
}, },
}) })
.input(ZSetSettingsForDocumentMutationSchema) .input(ZSetSettingsForDocumentMutationSchema)
.output(z.unknown()) .output(ZUpdateDocumentSettingsResponseSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { documentId, teamId, data, meta } = input; const { documentId, teamId, data, meta } = input;
@ -194,6 +245,9 @@ export const documentRouter = router({
}); });
}), }),
/**
* @public
*/
deleteDocument: authenticatedProcedure deleteDocument: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -204,13 +258,13 @@ export const documentRouter = router({
}, },
}) })
.input(ZDeleteDocumentMutationSchema) .input(ZDeleteDocumentMutationSchema)
.output(z.unknown()) .output(z.void())
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { documentId, teamId } = input; const { documentId, teamId } = input;
const userId = ctx.user.id; const userId = ctx.user.id;
return await deleteDocument({ await deleteDocument({
id: documentId, id: documentId,
userId, userId,
teamId, teamId,
@ -218,6 +272,9 @@ export const documentRouter = router({
}); });
}), }),
/**
* @public
*/
moveDocumentToTeam: authenticatedProcedure moveDocumentToTeam: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -228,8 +285,8 @@ export const documentRouter = router({
tags: ['Documents'], tags: ['Documents'],
}, },
}) })
.input(ZMoveDocumentsToTeamSchema) .input(ZMoveDocumentToTeamSchema)
.output(z.unknown()) .output(ZMoveDocumentToTeamResponseSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { documentId, teamId } = input; const { documentId, teamId } = input;
const userId = ctx.user.id; const userId = ctx.user.id;
@ -242,7 +299,9 @@ export const documentRouter = router({
}); });
}), }),
// Internal endpoint for now. /**
* @private
*/
// Should probably use `updateDocument` // Should probably use `updateDocument`
setTitleForDocument: authenticatedProcedure setTitleForDocument: authenticatedProcedure
.input(ZSetTitleForDocumentMutationSchema) .input(ZSetTitleForDocumentMutationSchema)
@ -260,7 +319,9 @@ export const documentRouter = router({
}); });
}), }),
// Internal endpoint for now. /**
* @private
*/
setPasswordForDocument: authenticatedProcedure setPasswordForDocument: authenticatedProcedure
.input(ZSetPasswordForDocumentMutationSchema) .input(ZSetPasswordForDocumentMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@ -285,7 +346,9 @@ export const documentRouter = router({
}); });
}), }),
// Internal endpoint for now. /**
* @private
*/
setSigningOrderForDocument: authenticatedProcedure setSigningOrderForDocument: authenticatedProcedure
.input(ZSetSigningOrderForDocumentMutationSchema) .input(ZSetSigningOrderForDocumentMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@ -299,7 +362,9 @@ export const documentRouter = router({
}); });
}), }),
// Internal endpoint for now. /**
* @private
*/
updateTypedSignatureSettings: authenticatedProcedure updateTypedSignatureSettings: authenticatedProcedure
.input(ZUpdateTypedSignatureSettingsMutationSchema) .input(ZUpdateTypedSignatureSettingsMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@ -326,8 +391,12 @@ export const documentRouter = router({
}); });
}), }),
// Todo: Refactor to distributeDocument. /**
// Todo: Rework before releasing API. * @public
*
* Todo: Refactor to distributeDocument.
* Todo: Rework before releasing API.
*/
sendDocument: authenticatedProcedure sendDocument: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -339,7 +408,7 @@ export const documentRouter = router({
}, },
}) })
.input(ZSendDocumentMutationSchema) .input(ZSendDocumentMutationSchema)
.output(z.unknown()) .output(ZSendDocumentResponseSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { documentId, teamId, meta } = input; const { documentId, teamId, meta } = input;
@ -374,6 +443,9 @@ export const documentRouter = router({
}); });
}), }),
/**
* @public
*/
resendDocument: authenticatedProcedure resendDocument: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -386,7 +458,7 @@ export const documentRouter = router({
}, },
}) })
.input(ZResendDocumentMutationSchema) .input(ZResendDocumentMutationSchema)
.output(z.unknown()) .output(z.void())
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
return await resendDocument({ return await resendDocument({
userId: ctx.user.id, userId: ctx.user.id,
@ -395,6 +467,9 @@ export const documentRouter = router({
}); });
}), }),
/**
* @public
*/
duplicateDocument: authenticatedProcedure duplicateDocument: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -404,16 +479,18 @@ export const documentRouter = router({
tags: ['Documents'], tags: ['Documents'],
}, },
}) })
.input(ZGetDocumentByIdQuerySchema) .input(ZDuplicateDocumentMutationSchema)
.output(z.unknown()) .output(ZDuplicateDocumentResponseSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
return await duplicateDocumentById({ return await duplicateDocument({
userId: ctx.user.id, userId: ctx.user.id,
...input, ...input,
}); });
}), }),
// Internal endpoint for now. /**
* @private
*/
searchDocuments: authenticatedProcedure searchDocuments: authenticatedProcedure
.input(ZSearchDocumentsMutationSchema) .input(ZSearchDocumentsMutationSchema)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
@ -427,11 +504,21 @@ export const documentRouter = router({
return documents; return documents;
}), }),
// Internal endpoint for now. /**
* @private
*/
findDocumentAuditLogs: authenticatedProcedure findDocumentAuditLogs: authenticatedProcedure
.input(ZFindDocumentAuditLogsQuerySchema) .input(ZFindDocumentAuditLogsQuerySchema)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
const { page, perPage, documentId, cursor, filterForRecentActivity, orderBy } = input; const {
page,
perPage,
documentId,
cursor,
filterForRecentActivity,
orderByColumn,
orderByDirection,
} = input;
return await findDocumentAuditLogs({ return await findDocumentAuditLogs({
page, page,
@ -439,12 +526,14 @@ export const documentRouter = router({
documentId, documentId,
cursor, cursor,
filterForRecentActivity, filterForRecentActivity,
orderBy, orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
userId: ctx.user.id, userId: ctx.user.id,
}); });
}), }),
// Internal endpoint for now. /**
* @private
*/
downloadAuditLogs: authenticatedProcedure downloadAuditLogs: authenticatedProcedure
.input(ZDownloadAuditLogsMutationSchema) .input(ZDownloadAuditLogsMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@ -473,7 +562,9 @@ export const documentRouter = router({
}; };
}), }),
// Internal endpoint for now. /**
* @private
*/
downloadCertificate: authenticatedProcedure downloadCertificate: authenticatedProcedure
.input(ZDownloadCertificateMutationSchema) .input(ZDownloadCertificateMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {

View File

@ -23,24 +23,16 @@ export const ZFindDocumentsQuerySchema = ZFindSearchParamsSchema.extend({
templateId: z.number().min(1).optional(), templateId: z.number().min(1).optional(),
source: z.nativeEnum(DocumentSource).optional(), source: z.nativeEnum(DocumentSource).optional(),
status: z.nativeEnum(DocumentStatus).optional(), status: z.nativeEnum(DocumentStatus).optional(),
orderBy: z orderByColumn: z.enum(['createdAt']).optional(),
.object({ orderByDirection: z.enum(['asc', 'desc']).default('desc'),
column: z.enum(['createdAt']),
direction: z.enum(['asc', 'desc']),
})
.optional(),
}); });
export const ZFindDocumentAuditLogsQuerySchema = ZFindSearchParamsSchema.extend({ export const ZFindDocumentAuditLogsQuerySchema = ZFindSearchParamsSchema.extend({
documentId: z.number().min(1), documentId: z.number().min(1),
cursor: z.string().optional(), cursor: z.string().optional(),
filterForRecentActivity: z.boolean().optional(), filterForRecentActivity: z.boolean().optional(),
orderBy: z orderByColumn: z.enum(['createdAt', 'type']).optional(),
.object({ orderByDirection: z.enum(['asc', 'desc']).default('desc'),
column: z.enum(['createdAt', 'type']),
direction: z.enum(['asc', 'desc']),
})
.optional(),
}); });
export const ZGetDocumentByIdQuerySchema = z.object({ export const ZGetDocumentByIdQuerySchema = z.object({
@ -48,6 +40,11 @@ export const ZGetDocumentByIdQuerySchema = z.object({
teamId: z.number().min(1).optional(), teamId: z.number().min(1).optional(),
}); });
export const ZDuplicateDocumentMutationSchema = z.object({
documentId: z.number().min(1),
teamId: z.number().min(1).optional(),
});
export type TGetDocumentByIdQuerySchema = z.infer<typeof ZGetDocumentByIdQuerySchema>; export type TGetDocumentByIdQuerySchema = z.infer<typeof ZGetDocumentByIdQuerySchema>;
export const ZGetDocumentByTokenQuerySchema = z.object({ export const ZGetDocumentByTokenQuerySchema = z.object({
@ -223,7 +220,7 @@ export const ZDownloadCertificateMutationSchema = z.object({
teamId: z.number().optional(), teamId: z.number().optional(),
}); });
export const ZMoveDocumentsToTeamSchema = z.object({ export const ZMoveDocumentToTeamSchema = z.object({
documentId: z.number(), documentId: z.number(),
teamId: z.number(), teamId: z.number(),
}); });

View File

@ -1,9 +1,16 @@
import { z } from 'zod'; import {
ZGetFieldByIdResponseSchema,
import { getFieldById } from '@documenso/lib/server-only/field/get-field-by-id'; getFieldById,
} from '@documenso/lib/server-only/field/get-field-by-id';
import { removeSignedFieldWithToken } from '@documenso/lib/server-only/field/remove-signed-field-with-token'; import { removeSignedFieldWithToken } from '@documenso/lib/server-only/field/remove-signed-field-with-token';
import { setFieldsForDocument } from '@documenso/lib/server-only/field/set-fields-for-document'; import {
import { setFieldsForTemplate } from '@documenso/lib/server-only/field/set-fields-for-template'; ZSetFieldsForDocumentResponseSchema,
setFieldsForDocument,
} from '@documenso/lib/server-only/field/set-fields-for-document';
import {
ZSetFieldsForTemplateResponseSchema,
setFieldsForTemplate,
} from '@documenso/lib/server-only/field/set-fields-for-template';
import { signFieldWithToken } from '@documenso/lib/server-only/field/sign-field-with-token'; import { signFieldWithToken } from '@documenso/lib/server-only/field/sign-field-with-token';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata'; import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
@ -17,6 +24,9 @@ import {
} from './schema'; } from './schema';
export const fieldRouter = router({ export const fieldRouter = router({
/**
* @public
*/
getField: authenticatedProcedure getField: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -28,7 +38,7 @@ export const fieldRouter = router({
}, },
}) })
.input(ZGetFieldQuerySchema) .input(ZGetFieldQuerySchema)
.output(z.unknown()) .output(ZGetFieldByIdResponseSchema)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
const { fieldId, teamId } = input; const { fieldId, teamId } = input;
@ -39,6 +49,9 @@ export const fieldRouter = router({
}); });
}), }),
/**
* @public
*/
addFields: authenticatedProcedure addFields: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -49,7 +62,7 @@ export const fieldRouter = router({
}, },
}) })
.input(ZAddFieldsMutationSchema) .input(ZAddFieldsMutationSchema)
.output(z.unknown()) .output(ZSetFieldsForDocumentResponseSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { documentId, fields } = input; const { documentId, fields } = input;
@ -71,6 +84,9 @@ export const fieldRouter = router({
}); });
}), }),
/**
* @public
*/
addTemplateFields: authenticatedProcedure addTemplateFields: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -81,7 +97,7 @@ export const fieldRouter = router({
}, },
}) })
.input(ZAddTemplateFieldsMutationSchema) .input(ZAddTemplateFieldsMutationSchema)
.output(z.unknown()) .output(ZSetFieldsForTemplateResponseSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { templateId, fields } = input; const { templateId, fields } = input;
@ -102,7 +118,9 @@ export const fieldRouter = router({
}); });
}), }),
// Internal endpoint for now. /**
* @internal
*/
signFieldWithToken: procedure signFieldWithToken: procedure
.input(ZSignFieldWithTokenMutationSchema) .input(ZSignFieldWithTokenMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@ -119,7 +137,9 @@ export const fieldRouter = router({
}); });
}), }),
// Internal endpoint for now. /**
* @internal
*/
removeSignedFieldWithToken: procedure removeSignedFieldWithToken: procedure
.input(ZRemovedSignedFieldWithTokenMutationSchema) .input(ZRemovedSignedFieldWithTokenMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {

View File

@ -0,0 +1,12 @@
import { generateOpenApiDocument } from 'trpc-openapi';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { appRouter } from './router';
export const openApiDocument = generateOpenApiDocument(appRouter, {
title: 'Do not use.',
version: '0.0.0',
baseUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/api/beta`,
// docsUrl: '', // Todo
});

View File

@ -1,9 +1,13 @@
import { z } from 'zod';
import { completeDocumentWithToken } from '@documenso/lib/server-only/document/complete-document-with-token'; 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 { rejectDocumentWithToken } from '@documenso/lib/server-only/document/reject-document-with-token';
import { setRecipientsForDocument } from '@documenso/lib/server-only/recipient/set-recipients-for-document'; import {
import { setRecipientsForTemplate } from '@documenso/lib/server-only/recipient/set-recipients-for-template'; ZSetRecipientsForDocumentResponseSchema,
setRecipientsForDocument,
} from '@documenso/lib/server-only/recipient/set-recipients-for-document';
import {
ZSetRecipientsForTemplateResponseSchema,
setRecipientsForTemplate,
} from '@documenso/lib/server-only/recipient/set-recipients-for-template';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata'; import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { authenticatedProcedure, procedure, router } from '../trpc'; import { authenticatedProcedure, procedure, router } from '../trpc';
@ -15,6 +19,9 @@ import {
} from './schema'; } from './schema';
export const recipientRouter = router({ export const recipientRouter = router({
/**
* @internal
*/
addSigners: authenticatedProcedure addSigners: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -25,7 +32,7 @@ export const recipientRouter = router({
}, },
}) })
.input(ZAddSignersMutationSchema) .input(ZAddSignersMutationSchema)
.output(z.unknown()) .output(ZSetRecipientsForDocumentResponseSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { documentId, teamId, signers } = input; const { documentId, teamId, signers } = input;
@ -45,6 +52,9 @@ export const recipientRouter = router({
}); });
}), }),
/**
* @internal
*/
addTemplateSigners: authenticatedProcedure addTemplateSigners: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -55,7 +65,7 @@ export const recipientRouter = router({
}, },
}) })
.input(ZAddTemplateSignersMutationSchema) .input(ZAddTemplateSignersMutationSchema)
.output(z.unknown()) .output(ZSetRecipientsForTemplateResponseSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { templateId, signers, teamId } = input; const { templateId, signers, teamId } = input;
@ -74,7 +84,9 @@ export const recipientRouter = router({
}); });
}), }),
// Internal endpoint for now. /**
* @internal
*/
completeDocumentWithToken: procedure completeDocumentWithToken: procedure
.input(ZCompleteDocumentWithTokenMutationSchema) .input(ZCompleteDocumentWithTokenMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@ -89,7 +101,9 @@ export const recipientRouter = router({
}); });
}), }),
// Internal endpoint for now. /**
* @internal
*/
rejectDocumentWithToken: procedure rejectDocumentWithToken: procedure
.input(ZRejectDocumentWithTokenMutationSchema) .input(ZRejectDocumentWithTokenMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {

View File

@ -79,16 +79,17 @@ export const teamRouter = router({
return await getTeams({ userId: ctx.user.id }); return await getTeams({ userId: ctx.user.id });
}), }),
// Todo: Public endpoint.
findTeams: authenticatedProcedure findTeams: authenticatedProcedure
.meta({ // .meta({
openapi: { // openapi: {
method: 'GET', // method: 'GET',
path: '/team', // path: '/team',
summary: 'Find teams', // summary: 'Find teams',
description: 'Find your teams based on a search criteria', // description: 'Find your teams based on a search criteria',
tags: ['Teams'], // tags: ['Teams'],
}, // },
}) // })
.input(ZFindTeamsQuerySchema) .input(ZFindTeamsQuerySchema)
.output(z.unknown()) .output(z.unknown())
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
@ -98,30 +99,32 @@ export const teamRouter = router({
}); });
}), }),
// Todo: Public endpoint.
getTeam: authenticatedProcedure getTeam: authenticatedProcedure
.meta({ // .meta({
openapi: { // openapi: {
method: 'GET', // method: 'GET',
path: '/team/{teamId}', // path: '/team/{teamId}',
summary: 'Get team', // summary: 'Get team',
tags: ['Teams'], // tags: ['Teams'],
}, // },
}) // })
.input(ZGetTeamQuerySchema) .input(ZGetTeamQuerySchema)
.output(z.unknown()) .output(z.unknown())
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
return await getTeamById({ teamId: input.teamId, userId: ctx.user.id }); return await getTeamById({ teamId: input.teamId, userId: ctx.user.id });
}), }),
// Todo: Public endpoint.
createTeam: authenticatedProcedure createTeam: authenticatedProcedure
.meta({ // .meta({
openapi: { // openapi: {
method: 'POST', // method: 'POST',
path: '/team/create', // path: '/team/create',
summary: 'Create team', // summary: 'Create team',
tags: ['Teams'], // tags: ['Teams'],
}, // },
}) // })
.input(ZCreateTeamMutationSchema) .input(ZCreateTeamMutationSchema)
.output(z.unknown()) .output(z.unknown())
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@ -131,15 +134,16 @@ export const teamRouter = router({
}); });
}), }),
// Todo: Public endpoint.
updateTeam: authenticatedProcedure updateTeam: authenticatedProcedure
.meta({ // .meta({
openapi: { // openapi: {
method: 'POST', // method: 'POST',
path: '/team/{teamId}', // path: '/team/{teamId}',
summary: 'Update team', // summary: 'Update team',
tags: ['Teams'], // tags: ['Teams'],
}, // },
}) // })
.input(ZUpdateTeamMutationSchema) .input(ZUpdateTeamMutationSchema)
.output(z.unknown()) .output(z.unknown())
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@ -149,15 +153,16 @@ export const teamRouter = router({
}); });
}), }),
// Todo: Public endpoint.
deleteTeam: authenticatedProcedure deleteTeam: authenticatedProcedure
.meta({ // .meta({
openapi: { // openapi: {
method: 'POST', // method: 'POST',
path: '/team/{teamId}/delete', // path: '/team/{teamId}/delete',
summary: 'Delete team', // summary: 'Delete team',
tags: ['Teams'], // tags: ['Teams'],
}, // },
}) // })
.input(ZDeleteTeamMutationSchema) .input(ZDeleteTeamMutationSchema)
.output(z.unknown()) .output(z.unknown())
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@ -167,16 +172,17 @@ export const teamRouter = router({
}); });
}), }),
// Todo: Public endpoint.
leaveTeam: authenticatedProcedure leaveTeam: authenticatedProcedure
.meta({ // .meta({
openapi: { // openapi: {
method: 'POST', // method: 'POST',
path: '/team/{teamId}/leave', // path: '/team/{teamId}/leave',
summary: 'Leave a team', // summary: 'Leave a team',
description: '', // description: '',
tags: ['Teams'], // tags: ['Teams'],
}, // },
}) // })
.input(ZLeaveTeamMutationSchema) .input(ZLeaveTeamMutationSchema)
.output(z.unknown()) .output(z.unknown())
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@ -186,16 +192,17 @@ export const teamRouter = router({
}); });
}), }),
// Todo: Public endpoint.
findTeamMemberInvites: authenticatedProcedure findTeamMemberInvites: authenticatedProcedure
.meta({ // .meta({
openapi: { // openapi: {
method: 'GET', // method: 'GET',
path: '/team/{teamId}/member/invite', // path: '/team/{teamId}/member/invite',
summary: 'Find member invites', // summary: 'Find member invites',
description: 'Returns pending team member invites', // description: 'Returns pending team member invites',
tags: ['Teams'], // tags: ['Teams'],
}, // },
}) // })
.input(ZFindTeamMemberInvitesQuerySchema) .input(ZFindTeamMemberInvitesQuerySchema)
.output(z.unknown()) .output(z.unknown())
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
@ -205,16 +212,17 @@ export const teamRouter = router({
}); });
}), }),
// Todo: Public endpoint.
createTeamMemberInvites: authenticatedProcedure createTeamMemberInvites: authenticatedProcedure
.meta({ // .meta({
openapi: { // openapi: {
method: 'POST', // method: 'POST',
path: '/team/{teamId}/member/invite', // path: '/team/{teamId}/member/invite',
summary: 'Invite members', // summary: 'Invite members',
description: 'Send email invitations to users to join the team', // description: 'Send email invitations to users to join the team',
tags: ['Teams'], // tags: ['Teams'],
}, // },
}) // })
.input(ZCreateTeamMemberInvitesMutationSchema) .input(ZCreateTeamMemberInvitesMutationSchema)
.output(z.unknown()) .output(z.unknown())
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@ -225,16 +233,17 @@ export const teamRouter = router({
}); });
}), }),
// Todo: Public endpoint.
resendTeamMemberInvitation: authenticatedProcedure resendTeamMemberInvitation: authenticatedProcedure
.meta({ // .meta({
openapi: { // openapi: {
method: 'POST', // method: 'POST',
path: '/team/{teamId}/member/invite/{invitationId}/resend', // path: '/team/{teamId}/member/invite/{invitationId}/resend',
summary: 'Resend member invite', // summary: 'Resend member invite',
description: 'Resend an email invitation to a user to join the team', // description: 'Resend an email invitation to a user to join the team',
tags: ['Teams'], // tags: ['Teams'],
}, // },
}) // })
.input(ZResendTeamMemberInvitationMutationSchema) .input(ZResendTeamMemberInvitationMutationSchema)
.output(z.unknown()) .output(z.unknown())
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@ -245,16 +254,17 @@ export const teamRouter = router({
}); });
}), }),
// Todo: Public endpoint.
deleteTeamMemberInvitations: authenticatedProcedure deleteTeamMemberInvitations: authenticatedProcedure
.meta({ // .meta({
openapi: { // openapi: {
method: 'POST', // method: 'POST',
path: '/team/{teamId}/member/invite/delete', // path: '/team/{teamId}/member/invite/delete',
summary: 'Delete member invite', // summary: 'Delete member invite',
description: 'Delete a pending team member invite', // description: 'Delete a pending team member invite',
tags: ['Teams'], // tags: ['Teams'],
}, // },
}) // })
.input(ZDeleteTeamMemberInvitationsMutationSchema) .input(ZDeleteTeamMemberInvitationsMutationSchema)
.output(z.unknown()) .output(z.unknown())
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@ -264,31 +274,33 @@ export const teamRouter = router({
}); });
}), }),
// Todo: Public endpoint.
getTeamMembers: authenticatedProcedure getTeamMembers: authenticatedProcedure
.meta({ // .meta({
openapi: { // openapi: {
method: 'GET', // method: 'GET',
path: '/team/{teamId}/member', // path: '/team/{teamId}/member',
summary: 'Get members', // summary: 'Get members',
tags: ['Teams'], // tags: ['Teams'],
}, // },
}) // })
.input(ZGetTeamMembersQuerySchema) .input(ZGetTeamMembersQuerySchema)
.output(z.unknown()) .output(z.unknown())
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
return await getTeamMembers({ teamId: input.teamId, userId: ctx.user.id }); return await getTeamMembers({ teamId: input.teamId, userId: ctx.user.id });
}), }),
// Todo: Public endpoint.
findTeamMembers: authenticatedProcedure findTeamMembers: authenticatedProcedure
.meta({ // .meta({
openapi: { // openapi: {
method: 'GET', // method: 'GET',
path: '/team/{teamId}/member/find', // path: '/team/{teamId}/member/find',
summary: 'Find members', // summary: 'Find members',
description: 'Find team members based on a search criteria', // description: 'Find team members based on a search criteria',
tags: ['Teams'], // tags: ['Teams'],
}, // },
}) // })
.input(ZFindTeamMembersQuerySchema) .input(ZFindTeamMembersQuerySchema)
.output(z.unknown()) .output(z.unknown())
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
@ -298,15 +310,16 @@ export const teamRouter = router({
}); });
}), }),
// Todo: Public endpoint.
updateTeamMember: authenticatedProcedure updateTeamMember: authenticatedProcedure
.meta({ // .meta({
openapi: { // openapi: {
method: 'POST', // method: 'POST',
path: '/team/{teamId}/member/{teamMemberId}', // path: '/team/{teamId}/member/{teamMemberId}',
summary: 'Update member', // summary: 'Update member',
tags: ['Teams'], // tags: ['Teams'],
}, // },
}) // })
.input(ZUpdateTeamMemberMutationSchema) .input(ZUpdateTeamMemberMutationSchema)
.output(z.unknown()) .output(z.unknown())
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@ -316,16 +329,17 @@ export const teamRouter = router({
}); });
}), }),
// Todo: Public endpoint.
deleteTeamMembers: authenticatedProcedure deleteTeamMembers: authenticatedProcedure
.meta({ // .meta({
openapi: { // openapi: {
method: 'POST', // method: 'POST',
path: '/team/{teamId}/member/delete', // path: '/team/{teamId}/member/delete',
summary: 'Delete members', // summary: 'Delete members',
description: '', // description: '',
tags: ['Teams'], // tags: ['Teams'],
}, // },
}) // })
.input(ZDeleteTeamMembersMutationSchema) .input(ZDeleteTeamMembersMutationSchema)
.output(z.unknown()) .output(z.unknown())
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@ -374,16 +388,17 @@ export const teamRouter = router({
return await getTeamInvitations({ email: ctx.user.email }); return await getTeamInvitations({ email: ctx.user.email });
}), }),
// Todo: Public endpoint.
updateTeamPublicProfile: authenticatedProcedure updateTeamPublicProfile: authenticatedProcedure
.meta({ // .meta({
openapi: { // openapi: {
method: 'POST', // method: 'POST',
path: '/team/{teamId}/profile', // path: '/team/{teamId}/profile',
summary: 'Update a team public profile', // summary: 'Update a team public profile',
description: '', // description: '',
tags: ['Teams'], // tags: ['Teams'],
}, // },
}) // })
.input(ZUpdateTeamPublicProfileMutationSchema) .input(ZUpdateTeamPublicProfileMutationSchema)
.output(z.unknown()) .output(z.unknown())
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {

View File

@ -4,19 +4,50 @@ import { z } from 'zod';
import { getServerLimits } from '@documenso/ee/server-only/limits/server'; import { getServerLimits } from '@documenso/ee/server-only/limits/server';
import { isValidLanguageCode } from '@documenso/lib/constants/i18n'; import { isValidLanguageCode } from '@documenso/lib/constants/i18n';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; 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 { sendDocument } from '@documenso/lib/server-only/document/send-document';
import { createDocumentFromDirectTemplate } from '@documenso/lib/server-only/template/create-document-from-direct-template'; 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 { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template';
import { createTemplate } from '@documenso/lib/server-only/template/create-template'; import {
import { createTemplateDirectLink } from '@documenso/lib/server-only/template/create-template-direct-link'; 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 { deleteTemplate } from '@documenso/lib/server-only/template/delete-template';
import { deleteTemplateDirectLink } from '@documenso/lib/server-only/template/delete-template-direct-link'; import { deleteTemplateDirectLink } from '@documenso/lib/server-only/template/delete-template-direct-link';
import { duplicateTemplate } from '@documenso/lib/server-only/template/duplicate-template'; import {
import { findTemplates } from '@documenso/lib/server-only/template/find-templates'; ZDuplicateTemplateResponseSchema,
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id'; duplicateTemplate,
import { moveTemplateToTeam } from '@documenso/lib/server-only/template/move-template-to-team'; } from '@documenso/lib/server-only/template/duplicate-template';
import { toggleTemplateDirectLink } from '@documenso/lib/server-only/template/toggle-template-direct-link'; import {
import { updateTemplateSettings } from '@documenso/lib/server-only/template/update-template-settings'; 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 { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import type { Document } from '@documenso/prisma/client'; import type { Document } from '@documenso/prisma/client';
@ -39,6 +70,9 @@ import {
} from './schema'; } from './schema';
export const templateRouter = router({ export const templateRouter = router({
/**
* @public
*/
findTemplates: authenticatedProcedure findTemplates: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -50,7 +84,7 @@ export const templateRouter = router({
}, },
}) })
.input(ZFindTemplatesQuerySchema) .input(ZFindTemplatesQuerySchema)
.output(z.unknown()) .output(ZFindTemplatesResponseSchema)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
return await findTemplates({ return await findTemplates({
userId: ctx.user.id, userId: ctx.user.id,
@ -58,6 +92,9 @@ export const templateRouter = router({
}); });
}), }),
/**
* @public
*/
getTemplateById: authenticatedProcedure getTemplateById: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -68,7 +105,7 @@ export const templateRouter = router({
}, },
}) })
.input(ZGetTemplateByIdQuerySchema) .input(ZGetTemplateByIdQuerySchema)
.output(z.unknown()) .output(ZGetTemplateByIdResponseSchema)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
const { templateId, teamId } = input; const { templateId, teamId } = input;
@ -79,6 +116,9 @@ export const templateRouter = router({
}); });
}), }),
/**
* @public
*/
createTemplate: authenticatedProcedure createTemplate: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -90,7 +130,7 @@ export const templateRouter = router({
}, },
}) })
.input(ZCreateTemplateMutationSchema) .input(ZCreateTemplateMutationSchema)
.output(z.unknown()) .output(ZCreateTemplateResponseSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { teamId, title, templateDocumentDataId } = input; const { teamId, title, templateDocumentDataId } = input;
@ -102,6 +142,9 @@ export const templateRouter = router({
}); });
}), }),
/**
* @public
*/
updateTemplate: authenticatedProcedure updateTemplate: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -112,7 +155,7 @@ export const templateRouter = router({
}, },
}) })
.input(ZUpdateTemplateSettingsMutationSchema) .input(ZUpdateTemplateSettingsMutationSchema)
.output(z.unknown()) .output(ZUpdateTemplateSettingsResponseSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { templateId, teamId, data, meta } = input; const { templateId, teamId, data, meta } = input;
@ -133,6 +176,9 @@ export const templateRouter = router({
}); });
}), }),
/**
* @public
*/
duplicateTemplate: authenticatedProcedure duplicateTemplate: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -143,7 +189,7 @@ export const templateRouter = router({
}, },
}) })
.input(ZDuplicateTemplateMutationSchema) .input(ZDuplicateTemplateMutationSchema)
.output(z.unknown()) .output(ZDuplicateTemplateResponseSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { teamId, templateId } = input; const { teamId, templateId } = input;
@ -154,6 +200,9 @@ export const templateRouter = router({
}); });
}), }),
/**
* @public
*/
deleteTemplate: authenticatedProcedure deleteTemplate: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -164,7 +213,7 @@ export const templateRouter = router({
}, },
}) })
.input(ZDeleteTemplateMutationSchema) .input(ZDeleteTemplateMutationSchema)
.output(z.unknown()) .output(z.void())
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { templateId, teamId } = input; const { templateId, teamId } = input;
@ -173,6 +222,9 @@ export const templateRouter = router({
await deleteTemplate({ userId, id: templateId, teamId }); await deleteTemplate({ userId, id: templateId, teamId });
}), }),
/**
* @public
*/
createDocumentFromTemplate: authenticatedProcedure createDocumentFromTemplate: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -184,9 +236,9 @@ export const templateRouter = router({
}, },
}) })
.input(ZCreateDocumentFromTemplateMutationSchema) .input(ZCreateDocumentFromTemplateMutationSchema)
.output(z.unknown()) .output(ZGetDocumentWithDetailsByIdResponseSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { templateId, teamId, recipients } = input; const { templateId, teamId, recipients, distributeDocument } = input;
const limits = await getServerLimits({ email: ctx.user.email, teamId }); const limits = await getServerLimits({ email: ctx.user.email, teamId });
@ -196,7 +248,7 @@ export const templateRouter = router({
const requestMetadata = extractNextApiRequestMetadata(ctx.req); const requestMetadata = extractNextApiRequestMetadata(ctx.req);
let document: Document = await createDocumentFromTemplate({ const document: Document = await createDocumentFromTemplate({
templateId, templateId,
teamId, teamId,
userId: ctx.user.id, userId: ctx.user.id,
@ -204,8 +256,8 @@ export const templateRouter = router({
requestMetadata, requestMetadata,
}); });
if (input.distributeDocument) { if (distributeDocument) {
document = await sendDocument({ await sendDocument({
documentId: document.id, documentId: document.id,
userId: ctx.user.id, userId: ctx.user.id,
teamId, teamId,
@ -217,9 +269,16 @@ export const templateRouter = router({
}); });
} }
return document; return getDocumentWithDetailsById({
documentId: document.id,
userId: ctx.user.id,
teamId,
});
}), }),
/**
* @public
*/
createDocumentFromDirectTemplate: maybeAuthenticatedProcedure createDocumentFromDirectTemplate: maybeAuthenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -231,7 +290,7 @@ export const templateRouter = router({
}, },
}) })
.input(ZCreateDocumentFromDirectTemplateMutationSchema) .input(ZCreateDocumentFromDirectTemplateMutationSchema)
.output(z.unknown()) .output(ZCreateDocumentFromDirectTemplateResponseSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { const {
directRecipientName, directRecipientName,
@ -262,7 +321,9 @@ export const templateRouter = router({
}); });
}), }),
// Internal endpoint for now. /**
* @private
*/
setSigningOrderForTemplate: authenticatedProcedure setSigningOrderForTemplate: authenticatedProcedure
.input(ZSetSigningOrderForTemplateMutationSchema) .input(ZSetSigningOrderForTemplateMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@ -278,6 +339,9 @@ export const templateRouter = router({
}); });
}), }),
/**
* @public
*/
createTemplateDirectLink: authenticatedProcedure createTemplateDirectLink: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -289,7 +353,7 @@ export const templateRouter = router({
}, },
}) })
.input(ZCreateTemplateDirectLinkMutationSchema) .input(ZCreateTemplateDirectLinkMutationSchema)
.output(z.unknown()) .output(ZCreateTemplateDirectLinkResponseSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { templateId, teamId, directRecipientId } = input; const { templateId, teamId, directRecipientId } = input;
@ -308,6 +372,9 @@ export const templateRouter = router({
return await createTemplateDirectLink({ userId, templateId, directRecipientId }); return await createTemplateDirectLink({ userId, templateId, directRecipientId });
}), }),
/**
* @public
*/
deleteTemplateDirectLink: authenticatedProcedure deleteTemplateDirectLink: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -319,7 +386,7 @@ export const templateRouter = router({
}, },
}) })
.input(ZDeleteTemplateDirectLinkMutationSchema) .input(ZDeleteTemplateDirectLinkMutationSchema)
.output(z.unknown()) .output(z.void())
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { templateId } = input; const { templateId } = input;
@ -328,6 +395,9 @@ export const templateRouter = router({
await deleteTemplateDirectLink({ userId, templateId }); await deleteTemplateDirectLink({ userId, templateId });
}), }),
/**
* @public
*/
toggleTemplateDirectLink: authenticatedProcedure toggleTemplateDirectLink: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -339,7 +409,7 @@ export const templateRouter = router({
}, },
}) })
.input(ZToggleTemplateDirectLinkMutationSchema) .input(ZToggleTemplateDirectLinkMutationSchema)
.output(z.unknown()) .output(ZToggleTemplateDirectLinkResponseSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { templateId, enabled } = input; const { templateId, enabled } = input;
@ -348,6 +418,9 @@ export const templateRouter = router({
return await toggleTemplateDirectLink({ userId, templateId, enabled }); return await toggleTemplateDirectLink({ userId, templateId, enabled });
}), }),
/**
* @public
*/
moveTemplateToTeam: authenticatedProcedure moveTemplateToTeam: authenticatedProcedure
.meta({ .meta({
openapi: { openapi: {
@ -359,7 +432,7 @@ export const templateRouter = router({
}, },
}) })
.input(ZMoveTemplatesToTeamSchema) .input(ZMoveTemplatesToTeamSchema)
.output(z.unknown()) .output(ZMoveTemplateToTeamResponseSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { templateId, teamId } = input; const { templateId, teamId } = input;
const userId = ctx.user.id; const userId = ctx.user.id;
@ -371,7 +444,9 @@ export const templateRouter = router({
}); });
}), }),
// Internal endpoint for now. /**
* @internal
*/
updateTemplateTypedSignatureSettings: authenticatedProcedure updateTemplateTypedSignatureSettings: authenticatedProcedure
.input(ZUpdateTemplateTypedSignatureSettingsMutationSchema) .input(ZUpdateTemplateTypedSignatureSettingsMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {

View File

@ -1,43 +1,73 @@
import { TRPCError, initTRPC } from '@trpc/server'; import { TRPCError, initTRPC } from '@trpc/server';
import SuperJSON from 'superjson'; import SuperJSON from 'superjson';
import type { OpenApiMeta } from 'trpc-openapi';
import { AppError, genericErrorCodeToTrpcErrorCodeMap } from '@documenso/lib/errors/app-error'; import { AppError, genericErrorCodeToTrpcErrorCodeMap } from '@documenso/lib/errors/app-error';
import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin'; 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 { TrpcContext } from './context'; import type { TrpcContext } from './context';
const t = initTRPC.context<TrpcContext>().create({ const t = initTRPC
transformer: SuperJSON, .meta<OpenApiMeta>()
errorFormatter(opts) { .context<TrpcContext>()
const { shape, error } = opts; .create({
transformer: SuperJSON,
errorFormatter(opts) {
const { shape, error } = opts;
const originalError = error.cause; const originalError = error.cause;
let data: Record<string, unknown> = shape.data; let data: Record<string, unknown> = shape.data;
if (originalError instanceof AppError) { // Default unknown errors to 400, since if you're throwing an AppError it is expected
data = { // that you already know what you're doing.
...data, if (originalError instanceof AppError) {
appError: AppError.toJSON(originalError), data = {
code: originalError.code, ...data,
httpStatus: appError: AppError.toJSON(originalError),
originalError.statusCode ?? code: originalError.code,
genericErrorCodeToTrpcErrorCodeMap[originalError.code]?.status ?? httpStatus:
500, originalError.statusCode ??
genericErrorCodeToTrpcErrorCodeMap[originalError.code]?.status ??
400,
};
}
return {
...shape,
data,
}; };
} },
});
return {
...shape,
data,
};
},
});
/** /**
* Middlewares * Middlewares
*/ */
export const authenticatedMiddleware = t.middleware(async ({ ctx, next }) => { export const authenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
const authorizationHeader = ctx.req.headers.authorization;
// Taken from `authenticatedMiddleware` in `@documenso/api/v1/middleware/authenticated.ts`.
if (authorizationHeader) {
// Support for both "Authorization: Bearer api_xxx" and "Authorization: api_xxx"
const [token] = (authorizationHeader || '').split('Bearer ').filter((s) => s.length > 0);
if (!token) {
throw new Error('Token was not provided for authenticated middleware');
}
const apiToken = await getApiTokenByToken({ token });
return await next({
ctx: {
...ctx,
user: apiToken.user,
session: null,
source: 'api',
},
});
}
if (!ctx.session) { if (!ctx.session) {
throw new TRPCError({ throw new TRPCError({
code: 'UNAUTHORIZED', code: 'UNAUTHORIZED',
@ -50,6 +80,7 @@ export const authenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
...ctx, ...ctx,
user: ctx.user, user: ctx.user,
session: ctx.session, session: ctx.session,
source: 'app',
}, },
}); });
}); });